Changeset - a30d48dab8b8
[Not reviewed]
Merge longmem
4 61 44
Hasan Yavuz Ă–ZDERYA - 7 years ago 2018-09-17 15:25:05
hy@ozderya.net
Merge with default
109 files changed with 6483 insertions and 1713 deletions:
0 comments (0 inline, 0 general)
.hgtags
Show inline comments
 
cdddeef73834c62a8ba5f3ac4c000cd4cb059e06 v0.1
 
73524cc41d6c6f4a664b39a74ae202be21b58eaf v0.2
 
1332b87e543fd947b57d45ccf62e9a0886e09cb5 v0.3
 
92b7092ddffa87776c7886dc071113416f772860 v0.4
 
11a1ee6b27d1cd655193cfdc032aab76ad7a743c v0.5
 
878ad8de497561098a63a0d21d36b103cea0719c v0.5.1
 
e545d9bf398aa5728e0ae0236f6c014d9fe55320 v0.6
 
b4d0a38444d31872633e474d89ffc15cd0fe42f0 v0.7
 
27b0354ca2c5ea7b3870156417ce7e04e799bbf7 v0.7.1
 
fd5f1eb480ec372b49df58b497458de05c30057c v0.8.0
 
9c9a11cd15fd094e2b2b65dc51805fd8fd1d2460 v0.8.1
 
4cf9a1ee1f107a38e03dbe17c4f2882c43d827c9 v0.9.0
 
ef003f7af8f37f760c22dae776f5ff8e1b526deb v0.9.1
 
7ce85527f3f2f6d19afc0401d2ac723f6c072481 v0.10.0
CMakeLists.txt
Show inline comments
 
#
 
# Copyright © 2017 Hasan Yavuz Özderya
 
# Copyright © 2018 Hasan Yavuz Özderya
 
#
 
# This file is part of serialplot.
 
#
 
# serialplot is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# serialplot is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
#
 

	
 
cmake_minimum_required(VERSION 3.2.2)
 

	
 
project(serialplot)
 

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

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

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

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

	
 
# Find the QtWidgets library
 
find_package(Qt5Widgets)
 

	
 
# If set, cmake will download Qwt over SVN, build and use it as a static library.
 
set(BUILD_QWT true CACHE BOOL "Download and build Qwt automatically.")
 
if (BUILD_QWT)
 
  include(BuildQwt)
 
else (BUILD_QWT)
 
    find_package(Qwt 6.1 REQUIRED)
 
endif (BUILD_QWT)
 

	
 
# If set, cmake will download QtColorWidgets over git, build and use it as a static library.
 
set(BUILD_QTCOLORWIDGETS true CACHE BOOL "Download and build QtColorWidgets library automatically.")
 
if (BUILD_QTCOLORWIDGETS)
 
  include(BuildQColorWidgets)
 
else ()
 
  find_package(QtColorWidgets REQUIRED)
 
endif ()
 

	
 
set(BUILD_LEDWIDGET true CACHE BOOL "Download and build LedWidget automatically.")
 
if (BUILD_LEDWIDGET)
 
  include(BuildLedWidget)
 
else (BUILD_LEDWIDGET)
 
  include(FindLedWidget)
 
endif (BUILD_LEDWIDGET)
 

	
 
# includes
 
include_directories("./src"
 
  ${QWT_INCLUDE_DIR}
 
  ${QTCOLORWIDGETS_INCLUDE_DIRS}
 
  ${LEDWIDGET_INCLUDE_DIR}
 
  )
 

	
 
# flags
 
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${QTCOLORWIDGETS_FLAGS}")
 

	
 
# wrap UI and resource files
 
qt5_wrap_ui(UI_FILES
 
  src/mainwindow.ui
 
  src/portcontrol.ui
 
  src/about_dialog.ui
 
  src/snapshotview.ui
 
  src/commandpanel.ui
 
  src/commandwidget.ui
 
  src/dataformatpanel.ui
 
  src/plotcontrolpanel.ui
 
  src/recordpanel.ui
 
  src/numberformatbox.ui
 
  src/endiannessbox.ui
 
  src/binarystreamreadersettings.ui
 
  src/asciireadersettings.ui
 
  src/framedreadersettings.ui
 
  src/demoreadersettings.ui
 
  src/updatecheckdialog.ui
 
  )
 

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

	
 
add_executable(${PROGRAM_NAME} WIN32
 
  src/main.cpp
 
  src/mainwindow.cpp
 
  src/portcontrol.cpp
 
  src/plot.cpp
 
  src/zoomer.cpp
 
  src/scrollzoomer.cpp
 
  src/scrollbar.cpp
 
  src/hidabletabwidget.cpp
 
  src/framebuffer.cpp
 
  src/scalepicker.cpp
 
  src/scalezoomer.cpp
 
  src/portlist.cpp
 
  src/snapshot.cpp
 
  src/snapshotview.cpp
 
  src/snapshotmanager.cpp
 
  src/plotsnapshotoverlay.cpp
 
  src/commandpanel.cpp
 
  src/commandwidget.cpp
 
  src/commandedit.cpp
 
  src/dataformatpanel.cpp
 
  src/plotcontrolpanel.cpp
 
  src/recordpanel.cpp
 
  src/datarecorder.cpp
 
  src/tooltipfilter.cpp
 
  src/sneakylineedit.cpp
 
  src/channelmanager.cpp
 
  src/stream.cpp
 
  src/streamchannel.cpp
 
  src/channelinfomodel.cpp
 
  src/ringbuffer.cpp
 
  src/ringbuffer.cpp
 
  src/indexbuffer.cpp
 
  src/readonlybuffer.cpp
 
  src/framebufferseries.cpp
 
  src/numberformatbox.cpp
 
  src/endiannessbox.cpp
 
  src/abstractreader.cpp
 
  src/binarystreamreader.cpp
 
  src/binarystreamreadersettings.cpp
 
  src/asciireader.cpp
 
  src/asciireadersettings.cpp
 
  src/demoreader.cpp
 
  src/demoreadersettings.cpp
 
  src/framedreader.cpp
 
  src/framedreadersettings.cpp
 
  src/plotmanager.cpp
 
  src/plotmenu.cpp
 
  src/barplot.cpp
 
  src/barchart.cpp
 
  src/barscaledraw.cpp
 
  src/numberformat.cpp
 
  src/updatechecker.cpp
 
  src/versionnumber.cpp
 
  src/updatecheckdialog.cpp
 
  src/samplepack.cpp
 
  src/source.cpp
 
  src/sink.cpp
 
  src/samplecounter.cpp
 
  misc/windows_icon.rc
 
  ${UI_FILES}
 
  ${RES_FILES}
 
  )
 

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

	
 
if (BUILD_QWT)
 
  add_dependencies(${PROGRAM_NAME} QWT)
 
endif ()
 

	
 
if (BUILD_QTCOLORWIDGETS)
 
  add_dependencies(${PROGRAM_NAME} QCW)
 
endif ()
 

	
 
if (BUILD_LEDWIDGET)
 
  add_dependencies(${PROGRAM_NAME} LEDW)
 
endif (BUILD_LEDWIDGET)
 

	
 

	
 
# set compiler flags
 
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
 

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

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

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

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

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

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

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

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

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

	
 
if (UNIX)
 
  add_custom_target(uninstall
 
    COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
 
endif (UNIX)
 

	
 
# testing
 
set(ENABLE_TESTS false CACHE BOOL "Build tests.")
 
if (ENABLE_TESTS)
 
  enable_testing()
 
  add_subdirectory(tests)
 
endif ()
 

	
 
# 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 ${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.")
 

	
 
if (NOT QWT_USE_STATIC)
 
  set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}, libqwt6-qt5 (>= 6.1.1)")
 
endif (NOT QWT_USE_STATIC)
 

	
 
if (UNIX)
 
  set(CPACK_PACKAGE_EXECUTABLES "${PROGRAM_NAME}")
 
elseif (WIN32)
 
  set(CPACK_PACKAGE_EXECUTABLES "${PROGRAM_NAME};${PROGRAM_DISPLAY_NAME}")
 
  set(CPACK_PACKAGE_INSTALL_DIRECTORY "${PROGRAM_NAME}")
 
  set(CPACK_CREATE_DESKTOP_LINKS "${PROGRAM_NAME}")
 
  set(CPACK_NSIS_MODIFY_PATH "ON")
 
  set(CPACK_PACKAGE_ICON "${CMAKE_SOURCE_DIR}/misc/serialplot.bmp")
 
  string(REPLACE "/" "\\\\" CPACK_PACKAGE_ICON ${CPACK_PACKAGE_ICON})
 
  set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/COPYING")
 
  set(CPACK_NSIS_MENU_LINKS
 
    "https://bitbucket.org/hyOzd/serialplot" "SerialPlot source code on bitbucket.org")
 
  set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL true)
 
endif (UNIX)
 

	
 
if (UNIX)
 
  # set debian package name
 
  string(TOLOWER "${CPACK_PACKAGE_NAME}" CPACK_PACKAGE_NAME_LOWERCASE)
 
  find_program(DPKG_PROGRAM dpkg DOC "dpkg program of Debian-based systems")
 
  if(DPKG_PROGRAM)
 
    execute_process(
 
      COMMAND ${DPKG_PROGRAM} --print-architecture
 
      OUTPUT_VARIABLE CPACK_DEBIAN_PACKAGE_ARCHITECTURE
 
      OUTPUT_STRIP_TRAILING_WHITESPACE)
 
    set(CPACK_PACKAGE_FILE_NAME
 
      "${CPACK_PACKAGE_NAME_LOWERCASE}_${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}")
 
  else(DPKG_PROGRAM)
 
    set(CPACK_PACKAGE_FILE_NAME
 
      "${CPACK_PACKAGE_NAME_LOWERCASE}_${PROJECT_VERSION}-${PROJECT_VERSION_REVISION}_${CMAKE_SYSTEM_NAME}")
 
  endif(DPKG_PROGRAM)
 
endif (UNIX)
 

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

	
 
include(ExternalProject)
 

	
 
ExternalProject_Add(LEDW
 
  PREFIX ledw
 
  HG_REPOSITORY https://bitbucket.org/hyOzd/ledwidget
 
  UPDATE_COMMAND ""
 
  INSTALL_COMMAND "")
 

	
 
ExternalProject_Get_Property(LEDW binary_dir source_dir)
 
set(LEDWIDGET_INCLUDE_DIR ${source_dir}/src)
 
set(LEDWIDGET_LIBRARY ${binary_dir}/libledwidget.a)
cmake/modules/BuildLinuxAppImage.cmake
Show inline comments
 
# Based on: https://github.com/mhoeher/opentodolist
 
#
 
# Note: we extract linuxdeployqt appimage so that it can run in docker, that's
 
# because fuse doesn't work in docker.
 

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

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

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

	
 
add_custom_target(
 
    appimage
 

	
 
    DEPENDS ${LINUXDEPLOYQT_TOOL}
 

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

	
 
include(ExternalProject)
 

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

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

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

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

	
 
mark_as_advanced(LEDWIDGET_LIBRARY LEDWIDGET_INCLUDE_DIR)
 

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

	
 
import os, pty, time, struct, math
 

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

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

	
 
def float_test(port):
 
    """Put 32-bit float test data through pseudo terminal"""
 
    for i in range(0, 1000):
 
        f = float(i)/3.0
 
        data = struct.pack('f', f)
 
        os.write(port, data)
 
        import binascii
 
        time.sleep(0.1)
 

	
 
def float_sine(port):
 
    """Puts 2 channel sine and cosine wafeform through pseudo terminal
 
    continuously."""
 
    i = 0
 
    period = 200
 
    while True:
 
        sin = math.sin(2 * math.pi * i / period)
 
        # cos = math.sin(2 * math.pi * (i / period + 1 / 4))
 
        data = struct.pack('ff', sin, -sin)
 
        os.write(port, data)
 
        i = (i + 1) % period
 
        time.sleep(0.1)
 

	
 
def uint32_test(port, little):
 
    """Puts 32 bit unsigned integer data through pseudo terminal"""
 
    i = 0
 
    maxi = 200
 
    while True:
 
        data = struct.pack('>I', i)
 
        os.write(port, data)
 
        time.sleep(0.05)
 
        i = i+1 if i <= maxi else 0
 

	
 
def frame_test(port, fixed_size=False, hasChecksum=True):
 
    """Sends binary data in framed format."""
 
    SYNCWORD = [0xAA, 0xBB]
 
    NUMSAMPLES = 10
 
    SIZE = NUMSAMPLES * 4 # integer
 
    if fixed_size:
 
        HEADER = bytes(SYNCWORD)
 
    else:
 
        HEADER = bytes(SYNCWORD + [SIZE])
 
    i = 0
 
    checksum = 0
 
    bytesent = 0
 
    while True:
 
        if i > 100: i = 0
 
        if bytesent == 0: # beginning of a frame?
 
            os.write(port, HEADER)
 
        os.write(port, struct.pack('<I', i))
 
        bytesent += 4
 
        checksum += i
 
        i += 1
 
        if bytesent == SIZE: # end of a frame
 
            if hasChecksum:
 
                data = struct.pack('<I', checksum)[:1]
 
                os.write(port, data)
 
            checksum = 0
 
            bytesent = 0
 
        time.sleep(0.1)
 

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

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

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

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

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

	
 
QT       += core gui serialport
 

	
 
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
 

	
 
TARGET = serialplot
 
TEMPLATE = app
 

	
 
CONFIG += qwt
 

	
 
SOURCES += \
 
    src/main.cpp\
 
    src/mainwindow.cpp \
 
    src/portcontrol.cpp \
 
    src/plot.cpp \
 
    src/zoomer.cpp \
 
    src/hidabletabwidget.cpp \
 
    src/framebuffer.cpp \
 
    src/scalepicker.cpp \
 
    src/scalezoomer.cpp \
 
    src/portlist.cpp \
 
    src/snapshotview.cpp \
 
    src/snapshotmanager.cpp \
 
    src/snapshot.cpp \
 
    src/plotsnapshotoverlay.cpp \
 
    src/commandpanel.cpp \
 
    src/commandwidget.cpp \
 
    src/commandedit.cpp \
 
    src/dataformatpanel.cpp \
 
    src/tooltipfilter.cpp \
 
    src/sneakylineedit.cpp \
 
    src/channelmanager.cpp \
 
    src/framebufferseries.cpp \
 
    src/plotcontrolpanel.cpp \
 
    src/numberformatbox.cpp \
 
    src/endiannessbox.cpp \
 
    src/framedreadersettings.cpp \
 
    src/abstractreader.cpp \
 
    src/binarystreamreader.cpp \
 
    src/binarystreamreadersettings.cpp \
 
    src/asciireadersettings.cpp \
 
    src/asciireader.cpp \
 
    src/demoreader.cpp \
 
    src/framedreader.cpp \
 
    src/plotmanager.cpp \
 
    src/numberformat.cpp \
 
    src/recordpanel.cpp
 
    src/recordpanel.cpp \
 
    src/updatechecker.cpp \
 
    src/updatecheckdialog.cpp \
 
    src/demoreadersettings.cpp
 

	
 
HEADERS += \
 
    src/mainwindow.h \
 
    src/utils.h \
 
    src/portcontrol.h \
 
    src/floatswap.h \
 
    src/plot.h \
 
    src/zoomer.h \
 
    src/hidabletabwidget.h \
 
    src/framebuffer.h \
 
    src/scalepicker.h \
 
    src/scalezoomer.h \
 
    src/portlist.h \
 
    src/snapshotview.h \
 
    src/snapshotmanager.h \
 
    src/snapshot.h \
 
    src/plotsnapshotoverlay.h \
 
    src/commandpanel.h \
 
    src/commandwidget.h \
 
    src/commandedit.h \
 
    src/dataformatpanel.h \
 
    src/tooltipfilter.h \
 
    src/sneakylineedit.h \
 
    src/channelmanager.h \
 
    src/framebufferseries.h \
 
    src/plotcontrolpanel.h \
 
    src/numberformatbox.h \
 
    src/endiannessbox.h \
 
    src/framedreadersettings.h \
 
    src/abstractreader.h \
 
    src/binarystreamreader.h \
 
    src/binarystreamreadersettings.h \
 
    src/asciireadersettings.h \
 
    src/asciireader.h \
 
    src/demoreader.h \
 
    src/framedreader.h \
 
    src/plotmanager.h \
 
    src/setting_defines.h \
 
    src/numberformat.h \
 
    src/recordpanel.h
 
    src/recordpanel.h \
 
    src/updatechecker.h \
 
    src/updatecheckdialog.h \
 
    src/demoreadersettings.h
 

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

	
 
INCLUDEPATH += qmake/ src/
 

	
 
CONFIG += c++11
 

	
 
RESOURCES += misc/icons.qrc
 

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include "abstractreader.h"
 

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

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

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

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef ABSTRACTREADER_H
 
#define ABSTRACTREADER_H
 

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

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

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

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

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

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

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

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

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

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

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

	
 
protected:
 
    QIODevice* _device;
 

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

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

	
 
    unsigned sampleCount;
 
    unsigned samplesPerSecond;
 

	
 
    ChannelManager* _channelMan;
 
    DataRecorder* _recorder;
 
    QTimer spsTimer;
 

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

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QtDebug>
 

	
 
#include "asciireader.h"
 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	
 
        // commit data
 
        addData(channelSamples, _numOfChannels);
 

	
 
        delete[] channelSamples;
 
    }
 
    return samples;
 
}
 

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

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef ASCIIREADER_H
 
#define ASCIIREADER_H
 

	
 
#include <QSettings>
 
#include <QString>
 

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

	
 
class AsciiReader : public AbstractReader
 
{
 
    Q_OBJECT
 

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

	
 
public slots:
 
    void pause(bool);
 

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

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

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

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QRegularExpressionValidator>
 
#include <QRegularExpression>
 

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

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

	
 
#include <QtDebug>
 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	
 
    settings->endGroup();
 
}
 

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

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

	
 
    if (numOfChannelsSetting == "auto")
 
    {
 
        ui->spNumOfChannels->setValue(0);
 
    }
 
    else
 
    {
 
        bool ok;
 
        int nc = numOfChannelsSetting.toInt(&ok);
 
        if (ok)
 
        {
 
            ui->spNumOfChannels->setValue(nc);
 
        }
 
    }
 

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

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef ASCIIREADERSETTINGS_H
 
#define ASCIIREADERSETTINGS_H
 

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

	
 
namespace Ui {
 
class AsciiReaderSettings;
 
}
 

	
 
class AsciiReaderSettings : public QWidget
 
{
 
    Q_OBJECT
 

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

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

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

	
 
private:
 
    Ui::AsciiReaderSettings *ui;
 

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

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

	
 
  This file is part of serialplot.
 

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

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

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

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

	
 
#include "barchart.h"
 

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

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

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

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

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

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

	
 
    return symbol;
 
}
 

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

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

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

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

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef BARCHART_H
 
#define BARCHART_H
 

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

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

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

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

	
 
private:
 
    const Stream* _stream;
 

	
 
    QVector<double> chartData() const;
 
};
 

	
 

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

	
 
  This file is part of serialplot.
 

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

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

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

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

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

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

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

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

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

	
 

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef BARPLOT_H
 
#define BARPLOT_H
 

	
 
#include <qwt_plot.h>
 

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

	
 
class BarPlot : public QwtPlot
 
{
 
    Q_OBJECT
 

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

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

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

	
 
    QVector<double> chartData() const;
 

	
 
private slots:
 
    void update();
 
};
 

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include "barscaledraw.h"
 

	
 
#include <QtDebug>
 

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

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

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

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef BARSCALEDRAW_H
 
#define BARSCALEDRAW_H
 

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

	
 
#include "stream.h"
 

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

	
 
private:
 
    const Stream* _stream;
 
};
 

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QtEndian>
 
#include <QtDebug>
 

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

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

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

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

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

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

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

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

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

	
 
void BinaryStreamReader::onNumberFormatChanged(NumberFormat numberFormat)
 
{
 
    switch(numberFormat)
 
    {
 
        case NumberFormat_uint8:
 
            sampleSize = 1;
 
            readSample = &BinaryStreamReader::readSampleAs<quint8>;
 
            break;
 
        case NumberFormat_int8:
 
            sampleSize = 1;
 
            readSample = &BinaryStreamReader::readSampleAs<qint8>;
 
            break;
 
        case NumberFormat_uint16:
 
            sampleSize = 2;
 
            readSample = &BinaryStreamReader::readSampleAs<quint16>;
 
            break;
 
        case NumberFormat_int16:
 
            sampleSize = 2;
 
            readSample = &BinaryStreamReader::readSampleAs<qint16>;
 
            break;
 
        case NumberFormat_uint32:
 
            sampleSize = 4;
 
            readSample = &BinaryStreamReader::readSampleAs<quint32>;
 
            break;
 
        case NumberFormat_int32:
 
            sampleSize = 4;
 
            readSample = &BinaryStreamReader::readSampleAs<qint32>;
 
            break;
 
        case NumberFormat_float:
 
            sampleSize = 4;
 
            readSample = &BinaryStreamReader::readSampleAs<float>;
 
            break;
 
        case NumberFormat_INVALID:
 
            Q_ASSERT(1); // never
 
            break;
 
    }
 
}
 

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

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

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

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

	
 
    if (bytesAvailable < packageSize) return;
 

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

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

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

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

	
 
    addData(channelSamples, numOfPackagesToRead*_numOfChannels);
 

	
 
    delete[] channelSamples;
 
    feedOut(samples);
 
}
 

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

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

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

	
 
    return double(data);
 
}
 

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

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef BINARYSTREAMREADER_H
 
#define BINARYSTREAMREADER_H
 

	
 
#include <QSettings>
 

	
 
#include "abstractreader.h"
 
#include "binarystreamreadersettings.h"
 

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

	
 
public slots:
 
    void pause(bool);
 

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

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

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

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

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include "channelinfomodel.h"
 
#include "setting_defines.h"
 

	
 
#define NUMOF_COLORS  (32)
 

	
 
const QColor colors[NUMOF_COLORS] =
 
{
 
    QColor("#ff0056"),
 
    QColor("#7e2dd2"),
 
    QColor("#00ae7e"),
 
    QColor("#fe8900"),
 
    QColor("#ff937e"),
 
    QColor("#6a826c"),
 
    QColor("#ff029d"),
 
    QColor("#00b917"),
 
    QColor("#7a4782"),
 
    QColor("#85a900"),
 
    QColor("#a42400"),
 
    QColor("#683d3b"),
 
    QColor("#bdc6ff"),
 
    QColor("#263400"),
 
    QColor("#bdd393"),
 
    QColor("#d5ff00"),
 
    QColor("#9e008e"),
 
    QColor("#001544"),
 
    QColor("#c28c9f"),
 
    QColor("#ff74a3"),
 
    QColor("#01d0ff"),
 
    QColor("#004754"),
 
    QColor("#e56ffe"),
 
    QColor("#788231"),
 
    QColor("#0e4ca1"),
 
    QColor("#91d0cb"),
 
    QColor("#be9970"),
 
    QColor("#968ae8"),
 
    QColor("#bb8800"),
 
    QColor("#43002c"),
 
    QColor("#deff74"),
 
    QColor("#00ffc6")
 
};
 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	
 
int ChannelInfoModel::rowCount(const QModelIndex &parent) const
 
{
 
    return _numOfChannels;
 
}
 

	
 
int ChannelInfoModel::columnCount(const QModelIndex & parent) const
 
{
 
    return COLUMN_COUNT;
 
}
 

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

	
 
    return Qt::NoItemFlags;
 
}
 

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

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

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

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

	
 
    return QVariant();
 
}
 

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

	
 
    return QVariant();
 
}
 

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

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

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

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

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

	
 
    return r;
 
}
 

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

	
 
    bool isInserting = number > _numOfChannels;
 
    if (isInserting)
 
    {
 
        beginInsertRows(QModelIndex(), _numOfChannels, number-1);
 
    }
 
    else
 
    {
 
        beginRemoveRows(QModelIndex(), number, _numOfChannels-1);
 
    }
 

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

	
 
    // make sure newly available channels are visible, we don't
 
    // remember visibility option intentionally so that user doesn't
 
    // get confused
 
    if (number > _numOfChannels)
 
    {
 
        for (unsigned ci = _numOfChannels; ci < number; ci++)
 
        {
 
            infos[ci].visibility = true;
 
        }
 
    }
 

	
 
    _numOfChannels = number;
 
    updateGainOrOffsetEn();
 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	
 
    updateGainOrOffsetEn();
 

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef CHANNELINFOMODEL_H
 
#define CHANNELINFOMODEL_H
 

	
 
#include <QAbstractTableModel>
 
#include <QColor>
 
#include <QSettings>
 
#include <QStringList>
 

	
 
class ChannelInfoModel : public QAbstractTableModel
 
{
 
    Q_OBJECT
 

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

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

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

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

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

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

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

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

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

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

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

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

	
 
#endif // CHANNELINFOMODEL_H
src/channelmanager.cpp
Show inline comments
 
deleted file
src/channelmanager.h
Show inline comments
 
deleted file
src/commandedit.cpp
Show inline comments
 
/*
 
  Copyright © 2015 Hasan Yavuz Özderya
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QKeyEvent>
 

	
 
#include <QtDebug>
 

	
 
#include "commandedit.h"
 

	
 
class HexCommandValidator : public QRegExpValidator
 
{
 
public:
 
    explicit HexCommandValidator(QObject* parent = 0);
 
    QValidator::State validate(QString & input, int & pos) const;
 
};
 

	
 
HexCommandValidator::HexCommandValidator(QObject* parent) :
 
    QRegExpValidator(parent)
 
{
 
    QRegExp regExp("^(?:(?:[0-9A-F]{2}[ ])+(?:[0-9A-F]{2}))|(?:[0-9A-F]{2})$");
 
    setRegExp(regExp);
 
}
 

	
 
QValidator::State HexCommandValidator::validate(QString & input, int & pos) const
 
{
 
    input = input.toUpper();
 

	
 
    // don't let pos to be altered at this stage
 
    int orgPos = pos;
 
    auto r = QRegExpValidator::validate(input, pos);
 
    pos = orgPos;
 

	
 
    // try fixing up spaces
 
    if (r != QValidator::Acceptable)
 
    {
 
        input = input.replace(" ", "");
 
        input.replace(QRegExp("([0-9A-F]{2}(?!$))"), "\\1 ");
 
        if (pos == input.size()-1) pos = input.size();
 
        r = QRegExpValidator::validate(input, pos);
 
    }
 

	
 
    return r;
 
}
 

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

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

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

	
 
void CommandEdit::setMode(bool ascii)
 
{
 
    ascii_mode = ascii;
 
    if (ascii)
 
    {
 
        setValidator(asciiValidator);
 

	
 
        auto hexText = text().remove(" ");
 
        // try patching HEX string in case of missing nibble so that
 
        // input doesn't turn into gibberish
 
        if (hexText.size() % 2 == 1)
 
        {
 
            hexText.replace(hexText.size()-1, 1, "3F"); // 0x3F = '?'
 
            qWarning() << "Broken byte in hex command is replaced. Check your command!";
 
        }
 

	
 
        setText(escape(QByteArray::fromHex(hexText.toLatin1())));
 
    }
 
    else
 
    {
 
        setValidator(hexValidator);
 
        setText(unEscape(text()).toLatin1().toHex());
 
    }
 
}
 

	
 
void CommandEdit::keyPressEvent(QKeyEvent * event)
 
{
 
    if (ascii_mode)
 
    {
 
        QLineEdit::keyPressEvent(event);
 
        return;
 
    }
 

	
 
    if (event->key() == Qt::Key_Backspace && !hasSelectedText())
 
    {
 
        int cursor = cursorPosition();
 
        if (cursor != 0 && text()[cursor-1] == ' ')
 
        {
 
            setCursorPosition(cursor-1);
 
        }
 
    }
 

	
 
    QLineEdit::keyPressEvent(event);
 
}
 

	
 
QString CommandEdit::unEscapedText()
 
{
 
    return unEscape(text());
 
}
 

	
 
static QString unEscape(QString str)
 
{
 
    const QMap<QString, QString> replacements({
 
            {"\\\\", "\\"},
 
            {"\\n", "\n"},
 
            {"\\r", "\r"},
 
            {"\\t", "\t"}
 
        });
 

	
 
    QString result;
 

	
 
    int i = 0;
 
    while (i < str.size())
 
    {
 
        bool found = false;
 

	
 
        for (auto k : replacements.keys())
 
        {
 
            // has enough text left?
 
            if (str.size() - i < 1) continue;
 

	
 
            // try matching the key at current position
 
            if (k == str.midRef(i, k.size()))
 
            {
 
                // append replacement
 
                result += replacements[k];
 
                i += k.size();
 
                found = true;
 
                break; // skip other keys
 
            }
 
        }
 

	
 
        if (!found)
 
        {
 
            // append unmatched character
 
            result += str[i];
 
            i++;
 
        }
 
    }
 

	
 
    return result;
 
}
 

	
 
static QString escape(QString str)
 
{
 
    str.replace("\\", "\\\\");
 
    str.replace("\n", "\\n");
 
    str.replace("\r", "\\r");
 
    str.replace("\t", "\\t");
 
    return str;
 
}
src/commandwidget.cpp
Show inline comments
 
/*
 
  Copyright © 2016 Hasan Yavuz Özderya
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include "commandwidget.h"
 
#include "ui_commandwidget.h"
 

	
 
#include <QRegExp>
 
#include <QRegExpValidator>
 
#include <QtDebug>
 
#include <QIcon>
 

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

	
 
#ifdef Q_OS_WIN
 
    ui->pbDelete->setIcon(QIcon(":/icons/list-remove"));
 
#endif // Q_OS_WIN
 

	
 
    connect(ui->pbDelete, &QPushButton::clicked, this, &CommandWidget::onDeleteClicked);
 
    connect(ui->pbSend, &QPushButton::clicked, this, &CommandWidget::onSendClicked);
 
    connect(ui->pbASCII, &QPushButton::toggled, this, &CommandWidget::onASCIIToggled);
 
    connect(ui->leName, &QLineEdit::textChanged, [this](QString text)
 
            {
 
                this->_sendAction.setText(text);
 
            });
 
    connect(&_sendAction, &QAction::triggered, this, &CommandWidget::onSendClicked);
 
}
 

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

	
 
void CommandWidget::onDeleteClicked()
 
{
 
    this->deleteLater();
 
}
 

	
 
void CommandWidget::onSendClicked()
 
{
 
    auto command = ui->leCommand->text();
 

	
 
    if (command.isEmpty())
 
    {
 
        qWarning() << "Enter a command to send!";
 
        ui->leCommand->setFocus(Qt::OtherFocusReason);
 
        emit focusRequested();
 
        return;
 
    }
 

	
 
    if (isASCIIMode())
 
    {
 
        qDebug() << "Sending " << name() << ":" << command;
 
        emit sendCommand(ui->leCommand->unEscapedText().toLatin1());
 
    }
 
    else // hex mode
 
    {
 
        command = command.remove(' ');
 
        // check if nibbles are missing
 
        if (command.size() % 2 == 1)
 
        {
 
            qWarning() << "HEX command is missing a nibble at the end!";
 
            ui->leCommand->setFocus(Qt::OtherFocusReason);
 
            // highlight the byte that is missing a nibble (last byte obviously)
 
            int textSize = ui->leCommand->text().size();
 
            ui->leCommand->setSelection(textSize-1, textSize);
 
            return;
 
        }
 
        qDebug() << "Sending HEX:" << command;
 
        emit sendCommand(QByteArray::fromHex(command.toLatin1()));
 
    }
 
}
 

	
 
void CommandWidget::onASCIIToggled(bool checked)
 
{
 
    ui->leCommand->setMode(checked);
 
}
 

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

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

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

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

	
 
void CommandWidget::setFocusToEdit()
 
{
 
    ui->leCommand->setFocus(Qt::OtherFocusReason);
 
}
 

	
 
QAction* CommandWidget::sendAction()
 
{
 
    return &_sendAction;
 
}
 

	
 
QString CommandWidget::commandText()
 
{
 
    return ui->leCommand->text();
 
}
 

	
 
void CommandWidget::setCommandText(QString str)
 
{
 
    ui->leCommand->selectAll();
 
    ui->leCommand->insert(str);
 
}
src/dataformatpanel.cpp
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	
 
    currentReader = reader;
 
    emit sourceChanged(currentReader);
 
}
 

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

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

	
 
    settings->endGroup();
 

	
 
    // save reader settings
 
    bsReader.saveSettings(settings);
 
    asciiReader.saveSettings(settings);
 
    framedReader.saveSettings(settings);
 
}
 

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

	
 
    // load selected format
 
    QString format = settings->value(
 
        SG_DataFormat_Format, QString()).toString();
 

	
 
    if (format == "binary")
 
    {
 
        selectReader(&bsReader);
 
        ui->rbBinary->setChecked(true);
 
    }
 
    else if (format == "ascii")
 
    {
 
        selectReader(&asciiReader);
 
        ui->rbAscii->setChecked(true);
 
    }
 
    else if (format == "custom")
 
    {
 
        selectReader(&framedReader);
 
        ui->rbFramed->setChecked(true);
 
    } // else current selection stays
 

	
 
    settings->endGroup();
 

	
 
    // load reader settings
 
    bsReader.loadSettings(settings);
 
    asciiReader.loadSettings(settings);
 
    framedReader.loadSettings(settings);
 
}
src/dataformatpanel.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef DATAFORMATPANEL_H
 
#define DATAFORMATPANEL_H
 

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

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

	
 
namespace Ui {
 
class DataFormatPanel;
 
}
 

	
 
class DataFormatPanel : public QWidget
 
{
 
    Q_OBJECT
 

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

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

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

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

	
 
    /// Stops recording.
 
    void stopRecording();
 

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

	
 
private:
 
    Ui::DataFormatPanel *ui;
 

	
 
    QSerialPort* serialPort;
 
    ChannelManager* _channelMan;
 

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

	
 
    bool paused;
 

	
 
    bool demoEnabled;
 
    DemoReader demoReader;
 
    AbstractReader* readerBeforeDemo;
 

	
 
    bool isDemoEnabled() const;
 
};
 

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include "datarecorder.h"
 

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

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

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

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

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

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

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

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

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

	
 
    if (disableBuffering) fileStream.flush();
 
}
 

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

	
 
    file.close();
 
    lastNumChannels = 0;
 
}
 

	
 
const char* DataRecorder::le() const
 
{
 
    return windowsLE ? "\r\n" : "\n";
 
}
src/datarecorder.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef DATARECORDER_H
 
#define DATARECORDER_H
 

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

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

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

	
 
    /// Disables file buffering
 
    bool disableBuffering;
 

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

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

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

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

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

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

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

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <math.h>
 

	
 
#include "demoreader.h"
 

	
 
#ifndef M_PI
 
#define M_PI 3.14159265358979323846
 
#endif
 

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

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

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

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

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

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

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

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

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

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

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef DEMOREADER_H
 
#define DEMOREADER_H
 

	
 
#include <QTimer>
 

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

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

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

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

	
 
    unsigned numOfChannels();
 

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

	
 
public slots:
 
    void pause(bool);
 

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

	
 
private:
 
    bool paused;
 
    unsigned _numOfChannels;
 
    DemoReaderSettings _settingsWidget;
 

	
 
    unsigned _numChannels;
 
    QTimer timer;
 
    int count;
 

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

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

	
 
  This file is part of serialplot.
 

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

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

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

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

	
 
#include "utils.h"
 

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

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

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

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

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef DEMOREADERSETTINGS_H
 
#define DEMOREADERSETTINGS_H
 

	
 
#include <QWidget>
 

	
 
namespace Ui {
 
class DemoReaderSettings;
 
}
 

	
 
class DemoReaderSettings : public QWidget
 
{
 
    Q_OBJECT
 

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

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

	
 
private:
 
    Ui::DemoReaderSettings *ui;
 

	
 
signals:
 
    void numChannelsChanged(unsigned);
 
};
 

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

	
 
  This file is part of serialplot.
 

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

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

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

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

	
 
#ifndef FRAMEBUFFER_H
 
#define FRAMEBUFFER_H
 

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

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

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

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

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

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

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

	
 
  This file is part of serialplot.
 

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

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

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

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

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

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

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

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

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

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

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef FRAMEBUFFERSERIES_H
 
#define FRAMEBUFFERSERIES_H
 

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

	
 
#include "framebuffer.h"
 

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

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

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

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

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

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

	
 
  This file is part of serialplot.
 

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

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

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

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

	
 
#include "framedreader.h"
 

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

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

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

	
 
    connect(&_settingsWidget, &FramedReaderSettings::numOfChannelsChanged,
 
            this, &FramedReader::onNumOfChannelsChanged);
 

	
 
    connect(&_settingsWidget, &FramedReaderSettings::syncWordChanged,
 
            this, &FramedReader::onSyncWordChanged);
 

	
 
    connect(&_settingsWidget, &FramedReaderSettings::frameSizeChanged,
 
            this, &FramedReader::onFrameSizeChanged);
 

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

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

	
 
    // init reader state
 
    reset();
 
}
 

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

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

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

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

	
 
void FramedReader::onNumberFormatChanged(NumberFormat numberFormat)
 
{
 
    switch(numberFormat)
 
    {
 
        case NumberFormat_uint8:
 
            sampleSize = 1;
 
            readSample = &FramedReader::readSampleAs<quint8>;
 
            break;
 
        case NumberFormat_int8:
 
            sampleSize = 1;
 
            readSample = &FramedReader::readSampleAs<qint8>;
 
            break;
 
        case NumberFormat_uint16:
 
            sampleSize = 2;
 
            readSample = &FramedReader::readSampleAs<quint16>;
 
            break;
 
        case NumberFormat_int16:
 
            sampleSize = 2;
 
            readSample = &FramedReader::readSampleAs<qint16>;
 
            break;
 
        case NumberFormat_uint32:
 
            sampleSize = 4;
 
            readSample = &FramedReader::readSampleAs<quint32>;
 
            break;
 
        case NumberFormat_int32:
 
            sampleSize = 4;
 
            readSample = &FramedReader::readSampleAs<qint32>;
 
            break;
 
        case NumberFormat_float:
 
            sampleSize = 4;
 
            readSample = &FramedReader::readSampleAs<float>;
 
            break;
 
        case NumberFormat_INVALID:
 
            Q_ASSERT(1); // never
 
            break;
 
    }
 

	
 
    checkSettings();
 
    reset();
 
}
 

	
 
void FramedReader::checkSettings()
 
{
 
    // sync word is invalid (empty or missing a nibble at the end)
 
    if (!syncWord.size())
 
    {
 
        settingsInvalid |= SYNCWORD_INVALID;
 
    }
 
    else // sync word is valid
 
    {
 
        settingsInvalid &= ~SYNCWORD_INVALID;
 
    }
 

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

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

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

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

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

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

	
 
void FramedReader::onDataReady()
 
{
 
    if (settingsInvalid) return;
 

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

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

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

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

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

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

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

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

	
 
    delete[] channelSamples;
 
}
 

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

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

	
 
    if (checksumEnabled)
 
    {
 
        for (unsigned i = 0; i < sizeof(data); i++)
 
        {
 
            calcChecksum += ((unsigned char*) &data)[i];
 
        }
 
    }
 

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

	
 
    return double(data);
 
}
 

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

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef FRAMEDREADER_H
 
#define FRAMEDREADER_H
 

	
 
#include <QSettings>
 

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

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

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

	
 
public slots:
 
    void pause(bool);
 

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

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

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

	
 
    // read state related members
 
    unsigned sync_i; /// sync byte index to be read next
 
    bool gotSync;    /// indicates if sync word is captured
 
    bool gotSize;    /// indicates if size is captured, ignored if size byte is disabled (fixed size)
 
    unsigned calcChecksum;
 

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

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

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

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QtGlobal>
 

	
 
#include "indexbuffer.h"
 

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

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

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

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

	
 
    return i;
 
}
 

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef INDEXBUFFER_H
 
#define INDEXBUFFER_H
 

	
 
#include "framebuffer.h"
 

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

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

	
 
private:
 
    unsigned _size;
 
};
 

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QtGlobal>
 

	
 
#include "linindexbuffer.h"
 

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

	
 
    _size = n;
 
    setLimits(lim);
 
}
 

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

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

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

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

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef LININDEXBUFFER_H
 
#define LININDEXBUFFER_H
 

	
 
#include "framebuffer.h"
 

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

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

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

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

	
 
  This file is part of serialplot.
 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	
 
    setupAboutDialog();
 

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

	
 
    ui->menuView->addSeparator();
 

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

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

	
 
    // init UI signals
 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	
 
    plotControlPanel.setChannelInfoModel(channelMan.infoModel());
 

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

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

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

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

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

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

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

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

	
 
    // Important: This should be after newCommandAction is triggered
 
    // (above) we don't want user to be greeted with command panel on
 
    // the very first run.
 
    connect(commandPanel.newCommandAction(), &QAction::triggered, [this]()
 
            {
 
                this->ui->tabWidget->setCurrentWidget(&commandPanel);
 
                this->ui->tabWidget->showTabs();
 
            });
 
}
 

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

	
 
    delete plotMan;
 

	
 
    delete ui;
 
    ui = NULL; // we check if ui is deleted in messageHandler
 
}
 

	
 
void MainWindow::closeEvent(QCloseEvent * event)
 
{
 
    // save snapshots
 
    if (!snapshotMan.isAllSaved())
 
    {
 
        auto clickedButton = QMessageBox::warning(
 
            this, "Closing SerialPlot",
 
            "There are un-saved snapshots. If you close you will loose the data.",
 
            QMessageBox::Discard | QMessageBox::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;
 
    uiAboutDialog.setupUi(&aboutDialog);
 

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

	
 
    QString aboutText = uiAboutDialog.lbAbout->text();
 
    aboutText.replace("$VERSION_STRING$", VERSION_STRING);
 
    aboutText.replace("$VERSION_REVISION$", VERSION_REVISION);
 
    uiAboutDialog.lbAbout->setText(aboutText);
 
}
 

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

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

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

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

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

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

	
 
    plotMan->replot();
 
}
 

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

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

	
 
void MainWindow::enableDemo(bool enabled)
 
{
 
    if (enabled)
 
    {
 
        if (!serialPort.isOpen())
 
        {
 
            dataFormatPanel.enableDemo(true);
 
        }
 
        else
 
        {
 
            ui->actionDemoMode->setChecked(false);
 
        }
 
    }
 
    else
 
    {
 
        dataFormatPanel.enableDemo(false);
 
        ui->actionDemoMode->setChecked(false);
 
    }
 
}
 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	
 
void MainWindow::saveMWSettings(QSettings* settings)
 
{
 
    // save window geometry
 
    settings->beginGroup(SettingGroup_MainWindow);
 
    settings->setValue(SG_MainWindow_Size, size());
 
    settings->setValue(SG_MainWindow_Pos, pos());
 
    // save active panel
 
    settings->setValue(SG_MainWindow_ActivePanel,
 
                       panelSettingMap.value(ui->tabWidget->currentIndex()));
 
    // save panel minimization
 
    settings->setValue(SG_MainWindow_HidePanels,
 
                       ui->tabWidget->hideAction.isChecked());
 
    // save window maximized state
 
    settings->setValue(SG_MainWindow_Maximized,
 
                       bool(windowState() & Qt::WindowMaximized));
 
    // save toolbar/dockwidgets state
 
    settings->setValue(SG_MainWindow_State, saveState());
 
    settings->endGroup();
 
}
 

	
 
void MainWindow::loadMWSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_MainWindow);
 
    // load window geometry
 
    resize(settings->value(SG_MainWindow_Size, size()).toSize());
 
    move(settings->value(SG_MainWindow_Pos, pos()).toPoint());
 

	
 
    // set active panel
 
    QString tabSetting =
 
        settings->value(SG_MainWindow_ActivePanel, QString()).toString();
 
    ui->tabWidget->setCurrentIndex(
 
        panelSettingMap.key(tabSetting, ui->tabWidget->currentIndex()));
 

	
 
    // hide panels
 
    ui->tabWidget->hideAction.setChecked(
 
        settings->value(SG_MainWindow_HidePanels,
 
                        ui->tabWidget->hideAction.isChecked()).toBool());
 

	
 
    // maximize window
 
    if (settings->value(SG_MainWindow_Maximized).toBool())
 
    {
 
        showMaximized();
 
    }
 

	
 
    // load toolbar/dockwidgets state
 
    restoreState(settings->value(SG_MainWindow_State).toByteArray());
 
    settings->setValue(SG_MainWindow_State, saveState());
 

	
 
    settings->endGroup();
 
}
 

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

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

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

	
 
    if (!fileName.isNull()) // user canceled
 
    {
 
        QSettings settings(fileName, QSettings::IniFormat);
 
        loadAllSettings(&settings);
 
    }
 
}
src/mainwindow.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef MAINWINDOW_H
 
#define MAINWINDOW_H
 

	
 
#include <QMainWindow>
 
#include <QButtonGroup>
 
#include <QLabel>
 
#include <QString>
 
#include <QVector>
 
#include <QList>
 
#include <QSerialPort>
 
#include <QSignalMapper>
 
#include <QTimer>
 
#include <QColor>
 
#include <QtGlobal>
 
#include <QSettings>
 
#include <qwt_plot_curve.h>
 

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

	
 
namespace Ui {
 
class MainWindow;
 
}
 

	
 
class MainWindow : public QMainWindow
 
{
 
    Q_OBJECT
 

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

	
 
    PlotViewSettings viewSettings() const;
 

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

	
 
private:
 
    Ui::MainWindow *ui;
 

	
 
    QDialog aboutDialog;
 
    void setupAboutDialog();
 

	
 
    QSerialPort serialPort;
 
    PortControl portControl;
 

	
 
    unsigned int numOfSamples;
 

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

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

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

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

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

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

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

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

	
 
#endif // MAINWINDOW_H
src/mainwindow.ui
Show inline comments
 
<?xml version="1.0" encoding="UTF-8"?>
 
<ui version="4.0">
 
 <class>MainWindow</class>
 
 <widget class="QMainWindow" name="MainWindow">
 
  <property name="geometry">
 
   <rect>
 
    <x>0</x>
 
    <y>0</y>
 
    <width>653</width>
 
    <height>650</height>
 
   </rect>
 
  </property>
 
  <property name="windowTitle">
 
   <string>SerialPlot</string>
 
  </property>
 
  <widget class="QWidget" name="centralWidget">
 
   <layout class="QVBoxLayout" name="verticalLayout_2">
 
   <layout class="QVBoxLayout" name="verticalLayout">
 
    <item>
 
     <widget class="QWidget" name="plotArea" native="true">
 
     <widget class="QSplitter" name="splitter">
 
      <property name="sizePolicy">
 
       <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
 
       <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
 
        <horstretch>0</horstretch>
 
        <verstretch>0</verstretch>
 
       </sizepolicy>
 
      </property>
 
      <property name="orientation">
 
       <enum>Qt::Horizontal</enum>
 
      </property>
 
      <widget class="QWidget" name="plotArea" native="true">
 
       <property name="sizePolicy">
 
        <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
 
         <horstretch>0</horstretch>
 
         <verstretch>0</verstretch>
 
        </sizepolicy>
 
       </property>
 
      </widget>
 
     </widget>
 
    </item>
 
    <item>
 
     <widget class="HidableTabWidget" name="tabWidget">
 
      <property name="sizePolicy">
 
       <sizepolicy hsizetype="Expanding" vsizetype="Minimum">
 
        <horstretch>0</horstretch>
 
        <verstretch>0</verstretch>
 
       </sizepolicy>
 
      </property>
 
      <property name="minimumSize">
 
       <size>
 
        <width>0</width>
 
        <height>0</height>
 
       </size>
 
      </property>
 
      <property name="maximumSize">
 
       <size>
 
        <width>16777215</width>
 
        <height>16777215</height>
 
       </size>
 
      </property>
 
      <property name="currentIndex">
 
       <number>0</number>
 
      </property>
 
      <property name="movable">
 
       <bool>false</bool>
 
      </property>
 
      <widget class="QWidget" name="tabLog">
 
       <attribute name="title">
 
        <string>Log</string>
 
       </attribute>
 
       <attribute name="toolTip">
 
        <string>Error and Warning Messages</string>
 
       </attribute>
 
       <layout class="QHBoxLayout" name="horizontalLayout">
 
        <property name="leftMargin">
 
         <number>4</number>
 
        </property>
 
        <property name="topMargin">
 
         <number>4</number>
 
        </property>
 
        <property name="rightMargin">
 
         <number>4</number>
 
        </property>
 
        <property name="bottomMargin">
 
         <number>4</number>
 
        </property>
 
        <item>
 
         <widget class="QPlainTextEdit" name="ptLog">
 
          <property name="readOnly">
 
           <bool>true</bool>
 
          </property>
 
         </widget>
 
        </item>
 
       </layout>
 
      </widget>
 
     </widget>
 
    </item>
 
   </layout>
 
  </widget>
 
  <widget class="QMenuBar" name="menuBar">
 
   <property name="geometry">
 
    <rect>
 
     <x>0</x>
 
     <y>0</y>
 
     <width>653</width>
 
     <height>27</height>
 
     <height>24</height>
 
    </rect>
 
   </property>
 
   <widget class="QMenu" name="menuHelp">
 
    <property name="title">
 
     <string>&amp;Help</string>
 
    </property>
 
    <addaction name="actionDemoMode"/>
 
    <addaction name="actionReportBug"/>
 
    <addaction name="actionCheckUpdate"/>
 
    <addaction name="actionHelpAbout"/>
 
   </widget>
 
   <widget class="QMenu" name="menuFile">
 
    <property name="title">
 
     <string>&amp;File</string>
 
    </property>
 
    <addaction name="actionSaveSettings"/>
 
    <addaction name="actionLoadSettings"/>
 
    <addaction name="actionExportCsv"/>
 
    <addaction name="separator"/>
 
    <addaction name="actionQuit"/>
 
   </widget>
 
   <widget class="QMenu" name="menuView">
 
   <widget class="QMenu" name="menuSecondary">
 
    <property name="title">
 
     <string>&amp;View</string>
 
     <string>Secondary</string>
 
    </property>
 
    <addaction name="actionBarPlot"/>
 
    <addaction name="separator"/>
 
    <addaction name="actionHorizontal"/>
 
    <addaction name="actionVertical"/>
 
   </widget>
 
   <addaction name="menuFile"/>
 
   <addaction name="menuView"/>
 
   <addaction name="menuSecondary"/>
 
   <addaction name="menuHelp"/>
 
  </widget>
 
  <widget class="QToolBar" name="plotToolBar">
 
   <property name="windowTitle">
 
    <string>Plot Toolbar</string>
 
   </property>
 
   <attribute name="toolBarArea">
 
    <enum>TopToolBarArea</enum>
 
   </attribute>
 
   <attribute name="toolBarBreak">
 
    <bool>false</bool>
 
   </attribute>
 
   <addaction name="actionPause"/>
 
   <addaction name="actionClear"/>
 
  </widget>
 
  <widget class="QStatusBar" name="statusBar"/>
 
  <action name="actionPause">
 
   <property name="checkable">
 
    <bool>true</bool>
 
   </property>
 
   <property name="checked">
 
    <bool>false</bool>
 
   </property>
 
   <property name="icon">
 
    <iconset theme="player_pause"/>
 
    <iconset theme="player_pause">
 
     <normaloff>.</normaloff>.</iconset>
 
   </property>
 
   <property name="text">
 
    <string>Pause</string>
 
   </property>
 
   <property name="toolTip">
 
    <string>Pause Plotting</string>
 
   </property>
 
   <property name="shortcut">
 
    <string>P</string>
 
   </property>
 
  </action>
 
  <action name="actionClear">
 
   <property name="icon">
 
    <iconset theme="editclear"/>
 
    <iconset theme="editclear">
 
     <normaloff>.</normaloff>.</iconset>
 
   </property>
 
   <property name="text">
 
    <string>Clear</string>
 
   </property>
 
   <property name="shortcut">
 
    <string>Ctrl+K</string>
 
   </property>
 
  </action>
 
  <action name="actionHelpAbout">
 
   <property name="text">
 
    <string>&amp;About</string>
 
   </property>
 
  </action>
 
  <action name="actionDemoMode">
 
   <property name="checkable">
 
    <bool>true</bool>
 
   </property>
 
   <property name="text">
 
    <string>&amp;Demo Mode</string>
 
   </property>
 
   <property name="toolTip">
 
    <string>Toggle Demo Mode</string>
 
   </property>
 
  </action>
 
  <action name="actionExportCsv">
 
   <property name="text">
 
    <string>&amp;Export CSV</string>
 
   </property>
 
   <property name="toolTip">
 
    <string>Export plot data to CSV</string>
 
   </property>
 
  </action>
 
  <action name="actionQuit">
 
   <property name="text">
 
    <string>&amp;Quit</string>
 
   </property>
 
   <property name="shortcut">
 
    <string>Ctrl+Q</string>
 
   </property>
 
  </action>
 
  <action name="actionReportBug">
 
   <property name="text">
 
    <string>&amp;Report a Bug</string>
 
   </property>
 
   <property name="toolTip">
 
    <string>Report a Bug on SerialPlot Website</string>
 
   </property>
 
  </action>
 
  <action name="actionSaveSettings">
 
   <property name="text">
 
    <string>&amp;Save Settings</string>
 
   </property>
 
   <property name="toolTip">
 
    <string>Save Settings to a File</string>
 
   </property>
 
  </action>
 
  <action name="actionLoadSettings">
 
   <property name="text">
 
    <string>&amp;Load Settings</string>
 
   </property>
 
   <property name="toolTip">
 
    <string>Load Settings from a File</string>
 
   </property>
 
  </action>
 
  <action name="actionCheckUpdate">
 
   <property name="text">
 
    <string>&amp;Check Update</string>
 
   </property>
 
  </action>
 
  <action name="actionBarPlot">
 
   <property name="checkable">
 
    <bool>true</bool>
 
   </property>
 
   <property name="text">
 
    <string>Bar Plot</string>
 
   </property>
 
  </action>
 
  <action name="actionVertical">
 
   <property name="checkable">
 
    <bool>true</bool>
 
   </property>
 
   <property name="text">
 
    <string>Vertical</string>
 
   </property>
 
  </action>
 
  <action name="actionHorizontal">
 
   <property name="checkable">
 
    <bool>true</bool>
 
   </property>
 
   <property name="checked">
 
    <bool>true</bool>
 
   </property>
 
   <property name="text">
 
    <string>Horizontal</string>
 
   </property>
 
  </action>
 
 </widget>
 
 <layoutdefault spacing="6" margin="11"/>
 
 <customwidgets>
 
  <customwidget>
 
   <class>HidableTabWidget</class>
 
   <extends>QTabWidget</extends>
 
   <header>hidabletabwidget.h</header>
 
   <container>1</container>
 
  </customwidget>
 
 </customwidgets>
 
 <resources/>
 
 <connections/>
 
</ui>
src/plot.cpp
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QRectF>
 
#include <QKeySequence>
 
#include <QColor>
 
#include <qwt_symbol.h>
 
#include <qwt_plot_curve.h>
 
#include <math.h>
 
#include <algorithm>
 

	
 
#include "plot.h"
 
#include "utils.h"
 

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

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

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

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

	
 
    showGrid(false);
 
    darkBackground(false);
 

	
 
    snapshotOverlay = NULL;
 

	
 
    connect(&zoomer, &QwtPlotZoomer::zoomed,
 
            [this](const QRectF &rect)
 
            {
 
                onXScaleChanged();
 
            });
 

	
 
    connect(this, &QwtPlot::itemAttached,
 
            [this](QwtPlotItem *plotItem, bool on)
 
            {
 
                if (symbolSize) updateSymbols();
 
            });
 

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

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

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

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

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

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

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

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

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

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

	
 
    onXScaleChanged();
 
}
 

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

	
 
    zoomer.setZoomBase();
 

	
 
    replot();
 
}
 

	
 
void Plot::unzoomed()
 
{
 
    resetAxes();
 
    onXScaleChanged();
 
}
 

	
 
void Plot::showGrid(bool show)
 
{
 
    grid.enableX(show);
 
    grid.enableY(show);
 
    replot();
 
}
 

	
 
void Plot::showMinorGrid(bool show)
 
{
 
    grid.enableXMin(show);
 
    grid.enableYMin(show);
 
    replot();
 
}
 

	
 
void Plot::showLegend(bool show)
 
{
 
    legend.setVisible(show);
 
    replot();
 
}
 

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

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

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

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

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

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

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

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

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

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

	
 
    QColor color;
 
    if(light)
 
    {
 
        color = QColor(Qt::white);
 
    }
 
    else
 
    {
 
        color = QColor(Qt::black);
 
    }
 

	
 
    snapshotOverlay = new PlotSnapshotOverlay(this->canvas(), color);
 
    connect(snapshotOverlay, &PlotSnapshotOverlay::done,
 
            [this]()
 
            {
 
                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();
 
    auto fullScaleDist = zoomer.zoomBase().width();
 
    auto zoomRate = fullScaleDist / scaleDist;
 
    float samplesInView = numOfSamples / zoomRate;
 
    float plotWidthNumSamp = abs(numOfSamples * plotWidth / (_xMax - _xMin));
 
    float samplesInView = plotWidthNumSamp / zoomRate;
 
    int symDisPx = round(paintDist / samplesInView);
 

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

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

	
 
    if (curves.size() > 0)
 
    {
 
        for (int i = 0; i < curves.size(); i++)
 
        {
 
            QwtSymbol* symbol = NULL;
 
            QwtPlotCurve* curve = static_cast<QwtPlotCurve*>(curves[i]);
 
            if (symbolSize)
 
            {
 
                symbol = new QwtSymbol(QwtSymbol::Ellipse,
 
                                       canvasBackground(),
 
                                       curve->pen(),
 
                                       QSize(symbolSize, symbolSize));
 
            }
 
            curve->setSymbol(symbol);
 
        }
 
    }
 
}
 

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

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

	
 
void Plot::setPlotWidth(double width)
 
{
 
    plotWidth = width;
 
    zoomer.setHViewSize(width);
 
}
src/plot.h
Show inline comments
 
/*
 
  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 PLOT_H
 
#define PLOT_H
 

	
 
#include <QColor>
 
#include <QList>
 
#include <QAction>
 
#include <qwt_plot.h>
 
#include <qwt_plot_grid.h>
 
#include <qwt_plot_shapeitem.h>
 
#include <qwt_plot_legenditem.h>
 
#include <qwt_plot_textlabel.h>
 

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

	
 
class Plot : public QwtPlot
 
{
 
    Q_OBJECT
 

	
 
    friend class PlotManager;
 

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

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

	
 
    static QColor makeColor(unsigned int channelIndex);
 

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

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

	
 
    void setNumOfSamples(unsigned value);
 

	
 
    void setPlotWidth(double width);
 

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

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

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

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

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

	
 
  This file is part of serialplot.
 

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

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

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

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

	
 
#include <math.h>
 

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

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

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

	
 
Q_DECLARE_METATYPE(Range);
 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	
 
    QObject::connect(ui->cbRangePresets, SIGNAL(activated(int)),
 
                     this, SLOT(onRangeSelected()));
 

	
 
    // color selector starts disabled until a channel is selected
 
    ui->colorSelector->setColor(QColor(0,0,0,0));
 
    ui->colorSelector->setDisplayMode(color_widgets::ColorPreview::AllAlpha);
 
    ui->colorSelector->setDisabled(true);
 

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

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

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

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

	
 
void PlotControlPanel::onNumOfSamples(int value)
 
{
 
    if (warnNumOfSamples && value > NUMSAMPLES_CONFIRM_AT)
 
    {
 
        // ask confirmation
 
        if (!askNSConfirmation(value))
 
        {
 
            // revert to old value
 
            disconnect(ui->spNumOfSamples, SIGNAL(valueChanged(int)),
 
                       this, SLOT(onNumOfSamples(int)));
 

	
 
            ui->spNumOfSamples->setValue(_numOfSamples);
 

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

	
 
            return;
 
        }
 
    }
 

	
 
    _numOfSamples = value;
 
    emit numOfSamplesChanged(value);
 
}
 

	
 
bool PlotControlPanel::askNSConfirmation(int value)
 
{
 
    auto text = tr("Setting number of samples to a too big value "
 
                   "(>%1) can seriously impact the performance of "
 
                   "the application and cause freezes. Are you sure you "
 
                   "want to change the number of samples to %2?")
 
        .arg(QString::number(NUMSAMPLES_CONFIRM_AT), QString::number(value));
 

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

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

	
 
    mb.setCheckBox(cb);
 

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

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

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

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

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

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

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

	
 
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());
 
    }
 
    emit plotWidthChanged(plotWidth());
 
}
 

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

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

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

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

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

	
 
                if (current.isValid())
 
                {
 
                    ui->colorSelector->setEnabled(true);
 
                    auto model = ui->tvChannelInfo->model();
 
                    color = model->data(current, Qt::ForegroundRole).value<QColor>();
 
                }
 
                else
 
                {
 
                    ui->colorSelector->setDisabled(true);
 
                }
 

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

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

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

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

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

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

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

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

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

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

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef PLOTCONTROLPANEL_H
 
#define PLOTCONTROLPANEL_H
 

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

	
 
#include "channelinfomodel.h"
 

	
 
namespace Ui {
 
class PlotControlPanel;
 
}
 

	
 
class PlotControlPanel : public QWidget
 
{
 
    Q_OBJECT
 

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

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

	
 
    void setChannelInfoModel(ChannelInfoModel* model);
 

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

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

	
 
private:
 
    Ui::PlotControlPanel *ui;
 

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

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

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

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

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

	
 
  This file is part of serialplot.
 

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

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

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

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

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

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

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

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

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

	
 
}
 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	
 
    replot();
 
}
 

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

	
 
    for (int ci = start; ci <= end; ci++)
 
    {
 
        QString name = topLeft.sibling(ci, ChannelInfoModel::COLUMN_NAME).data(Qt::EditRole).toString();
 
        QColor color = topLeft.sibling(ci, ChannelInfoModel::COLUMN_NAME).data(Qt::ForegroundRole).value<QColor>();
 
        bool visible = topLeft.sibling(ci, ChannelInfoModel::COLUMN_VISIBILITY).data(Qt::CheckStateRole).toBool();
 

	
 
        curves[ci]->setTitle(name);
 
        curves[ci]->setPen(color);
 
        curves[ci]->setVisible(visible);
 
        curves[ci]->setItemAttribute(QwtPlotItem::Legend, visible);
 

	
 
        // replot only updated widgets
 
        if (isMulti)
 
        {
 
            plotWidgets[ci]->updateSymbols(); // required for color change
 
            plotWidgets[ci]->updateLegend(curves[ci]);
 
            plotWidgets[ci]->setVisible(visible);
 
            if (visible)
 
            {
 
                plotWidgets[ci]->replot();
 
            }
 
        }
 
    }
 

	
 
    checkNoVisChannels();
 

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

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

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

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

	
 
    isMulti = enabled;
 

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

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

	
 
    // setup new layout
 
    setupLayout(isMulti);
 

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

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

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

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

	
 
    if (multiPlot)
 
    {
 
        // setup a scroll area
 
        scrollArea = new QScrollArea();
 
        auto scrolledPlotArea = new QWidget(scrollArea);
 
        scrollArea->setWidget(scrolledPlotArea);
 
        scrollArea->setWidgetResizable(true);
 

	
 
        _plotArea->setLayout(new QVBoxLayout());
 
        _plotArea->layout()->addWidget(scrollArea);
 
        _plotArea->layout()->setContentsMargins(0,0,0,0);
 

	
 
        layout = new QVBoxLayout(scrolledPlotArea);
 
    }
 
    else
 
    {
 
        // delete scrollArea left from multi layout
 
        if (scrollArea != NULL)
 
        {
 
            delete scrollArea;
 
            scrollArea = NULL;
 
        }
 

	
 
        layout = new QVBoxLayout(_plotArea);
 
    }
 

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

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

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

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

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

	
 
    return plot;
 
}
 

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

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

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

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

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

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

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

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

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

	
 
    plotWidget(index)->replot();
 
}
 

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

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

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

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

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

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

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

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

	
 
void PlotManager::darkBackground(bool enabled)
 
{
 
    for (auto plot : plotWidgets)
 
    {
 
        plot->darkBackground(enabled);
 
    }
 
}
 

	
 
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->setYAxis(autoScaled, yAxisMin, yAxisMax);
 
    }
 
}
 

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

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

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

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

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

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

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

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

	
 
    settings->endGroup();
 
}
 

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

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

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef PLOTMANAGER_H
 
#define PLOTMANAGER_H
 

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

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

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

	
 
class PlotManager : public QObject
 
{
 
    Q_OBJECT
 

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

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

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

	
 
    // menu actions
 
    QAction showGridAction;
 
    QAction showMinorGridAction;
 
    QAction unzoomAction;
 
    QAction darkBackgroundAction;
 
    QAction showLegendAction;
 
    QAction showMultiAction;
 
    QAction setSymbolsAction;
 
    QMenu setSymbolsMenu;
 
    QAction* setSymbolsAutoAct;
 
    QAction* setSymbolsShowAct;
 
    QAction* setSymbolsHideAct;
 

	
 
    /// Common constructor
 
    void construct(QWidget* plotArea, PlotMenu* menu);
 
    /// Setups the layout for multi or single plot
 
    void setupLayout(bool multiPlot);
 
    /// Inserts a new plot widget to the current layout.
 
    Plot* addPlotWidget();
 
    /// Returns the plot widget that given curve is attached to
 
    Plot* plotWidget(unsigned curveIndex);
 
    /// Common part of overloaded `addCurve` functions
 
    void _addCurve(QwtPlotCurve* curve);
 
    void setSymbols(Plot::ShowSymbols shown);
 
    /// Check and make sure "no visible channels" text is shown
 
    void checkNoVisChannels();
 

	
 
private slots:
 
    void showGrid(bool show = true);
 
    void showMinorGrid(bool show = true);
 
    void showLegend(bool show = true);
 
    void unzoom();
 
    void darkBackground(bool enabled = true);
 
    void setSymbols(Plot::ShowSymbols shown);
 

	
 
    void onNumChannelsChanged(unsigned value);
 
    void onChannelInfoChanged(const QModelIndex & topLeft,
 
                              const QModelIndex & bottomRight,
 
                              const QVector<int> & roles = QVector<int> ());
 
};
 

	
 
#endif // PLOTMANAGER_H
src/plotmenu.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include "plotmenu.h"
 
#include "setting_defines.h"
 
#include "utils.h"
 

	
 
PlotMenu::PlotMenu(QWidget* parent) :
 
    QMenu(tr("&View"), parent),
 
    showGridAction("&Grid", this),
 
    showMinorGridAction("&Minor Grid", this),
 
    unzoomAction("&Unzoom", this),
 
    darkBackgroundAction("&Dark Background", this),
 
    showLegendAction("&Legend", this),
 
    showMultiAction("Multi &Plot", this),
 
    setSymbolsAction("&Symbols", this),
 
    setSymbolsAutoAct("Show When &Zoomed", this),
 
    setSymbolsShowAct("Always &Show", this),
 
    setSymbolsHideAct("Always &Hide", this)
 
{
 
    showGridAction.setToolTip("Show Grid");
 
    showMinorGridAction.setToolTip("Show Minor Grid");
 
    unzoomAction.setToolTip("Unzoom the Plot");
 
    darkBackgroundAction.setToolTip("Enable Dark Plot Background");
 
    showLegendAction.setToolTip("Display the Legend on Plot");
 
    showMultiAction.setToolTip("Display All Channels Separately");
 
    setSymbolsAction.setToolTip("Show/Hide symbols");
 

	
 
    showGridAction.setShortcut(QKeySequence("G"));
 
    showMinorGridAction.setShortcut(QKeySequence("M"));
 

	
 
    showGridAction.setCheckable(true);
 
    showMinorGridAction.setCheckable(true);
 
    darkBackgroundAction.setCheckable(true);
 
    showLegendAction.setCheckable(true);
 
    showMultiAction.setCheckable(true);
 

	
 
    showGridAction.setChecked(false);
 
    showMinorGridAction.setChecked(false);
 
    darkBackgroundAction.setChecked(false);
 
    showLegendAction.setChecked(true);
 
    showMultiAction.setChecked(false);
 

	
 
    // minor grid is only enabled when _major_ grid is enabled
 
    showMinorGridAction.setEnabled(false);
 
    connect(&showGridAction, SELECT<bool>::OVERLOAD_OF(&QAction::triggered),
 
            &showMinorGridAction, &QAction::setEnabled);
 

	
 
    // setup set symbols menu
 
    setSymbolsMenu.addAction(&setSymbolsAutoAct);
 
    setSymbolsAutoAct.setCheckable(true);
 
    setSymbolsAutoAct.setChecked(true);
 
    connect(&setSymbolsAutoAct, SELECT<bool>::OVERLOAD_OF(&QAction::triggered),
 
            [this](bool checked)
 
            {
 
                if (checked) emit symbolShowChanged(Plot::ShowSymbolsAuto);
 
            });
 

	
 
    setSymbolsMenu.addAction(&setSymbolsShowAct);
 
    setSymbolsShowAct.setCheckable(true);
 
    connect(&setSymbolsShowAct, SELECT<bool>::OVERLOAD_OF(&QAction::triggered),
 
            [this](bool checked)
 
            {
 
                if (checked) symbolShowChanged(Plot::ShowSymbolsShow);
 
            });
 

	
 
    setSymbolsMenu.addAction(&setSymbolsHideAct);
 
    setSymbolsHideAct.setCheckable(true);
 
    connect(&setSymbolsHideAct, SELECT<bool>::OVERLOAD_OF(&QAction::triggered),
 
            [this](bool checked)
 
            {
 
                if (checked) symbolShowChanged(Plot::ShowSymbolsHide);
 
            });
 

	
 
    // add symbol actions to same group so that they appear as radio buttons
 
    auto group = new QActionGroup(this);
 
    group->addAction(&setSymbolsAutoAct);
 
    group->addAction(&setSymbolsShowAct);
 
    group->addAction(&setSymbolsHideAct);
 

	
 
    setSymbolsAction.setMenu(&setSymbolsMenu);
 

	
 
    // add all actions to create this menu
 
    addAction(&showGridAction);
 
    addAction(&showMinorGridAction);
 
    addAction(&unzoomAction);
 
    addAction(&darkBackgroundAction);
 
    addAction(&showLegendAction);
 
    addAction(&showMultiAction);
 
    addAction(&setSymbolsAction);
 
}
 

	
 
PlotMenu::PlotMenu(PlotViewSettings s, QWidget* parent) :
 
    PlotMenu(parent)
 
{
 
    showGridAction.setChecked(s.showGrid);
 
    showMinorGridAction.setChecked(s.showMinorGrid);
 
    darkBackgroundAction.setChecked(s.darkBackground);
 
    showLegendAction.setChecked(s.showLegend);
 
    showMultiAction.setChecked(s.showMulti);
 
    switch (s.showSymbols)
 
    {
 
        case Plot::ShowSymbolsAuto:
 
             setSymbolsAutoAct.setChecked(true);
 
            break;
 
        case Plot::ShowSymbolsShow:
 
            setSymbolsShowAct.setChecked(true);
 
            break;
 
        case Plot::ShowSymbolsHide:
 
            setSymbolsHideAct.setChecked(true);
 
            break;
 
    }
 
}
 

	
 
PlotViewSettings PlotMenu::viewSettings() const
 
{
 
    return PlotViewSettings(
 
        {
 
            showGridAction.isChecked(),
 
            showMinorGridAction.isChecked(),
 
            darkBackgroundAction.isChecked(),
 
            showLegendAction.isChecked(),
 
            showMultiAction.isChecked(),
 
            showSymbols()
 
        });
 
}
 

	
 
Plot::ShowSymbols PlotMenu::showSymbols() const
 
{
 
    if (setSymbolsAutoAct.isChecked())
 
    {
 
        return Plot::ShowSymbolsAuto;
 
    }
 
    else if (setSymbolsShowAct.isChecked())
 
    {
 
        return Plot::ShowSymbolsShow;
 
    }
 
    else // setSymbolsHideAct.isChecked()
 
    {
 
        return Plot::ShowSymbolsHide;
 
    }
 
}
 

	
 
void PlotMenu::saveSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_Plot);
 
    settings->setValue(SG_Plot_DarkBackground, darkBackgroundAction.isChecked());
 
    settings->setValue(SG_Plot_Grid, showGridAction.isChecked());
 
    settings->setValue(SG_Plot_MinorGrid, showMinorGridAction.isChecked());
 
    settings->setValue(SG_Plot_Legend, showLegendAction.isChecked());
 
    settings->setValue(SG_Plot_MultiPlot, showMultiAction.isChecked());
 

	
 
    QString showSymbolsStr;
 
    if (showSymbols() == Plot::ShowSymbolsAuto)
 
    {
 
        showSymbolsStr = "auto";
 
    }
 
    else if (showSymbols() == Plot::ShowSymbolsShow)
 
    {
 
        showSymbolsStr = "show";
 
    }
 
    else
 
    {
 
        showSymbolsStr = "hide";
 
    }
 
    settings->setValue(SG_Plot_Symbols, showSymbolsStr);
 

	
 
    settings->endGroup();
 
}
 

	
 
void PlotMenu::loadSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_Plot);
 
    darkBackgroundAction.setChecked(
 
        settings->value(SG_Plot_DarkBackground, darkBackgroundAction.isChecked()).toBool());
 
    showGridAction.setChecked(
 
        settings->value(SG_Plot_Grid, showGridAction.isChecked()).toBool());
 
    showMinorGridAction.setChecked(
 
        settings->value(SG_Plot_MinorGrid, showMinorGridAction.isChecked()).toBool());
 
    showMinorGridAction.setEnabled(showGridAction.isChecked());
 
    showLegendAction.setChecked(
 
        settings->value(SG_Plot_Legend, showLegendAction.isChecked()).toBool());
 
    showMultiAction.setChecked(
 
        settings->value(SG_Plot_MultiPlot, showMultiAction.isChecked()).toBool());
 

	
 
    QString showSymbolsStr = settings->value(SG_Plot_Symbols, QString()).toString();
 
    if (showSymbolsStr == "auto")
 
    {
 
        // setSymbols(Plot::ShowSymbolsAuto);
 
        setSymbolsAutoAct.setChecked(true);
 
    }
 
    else if (showSymbolsStr == "show")
 
    {
 
        // setSymbols(Plot::ShowSymbolsShow);
 
        setSymbolsShowAct.setChecked(true);
 
    }
 
    else if (showSymbolsStr == "hide")
 
    {
 
        // setSymbols(Plot::ShowSymbolsHide);
 
        setSymbolsHideAct.setChecked(true);
 
    }
 
    else
 
    {
 
        qCritical() << "Invalid symbol setting:" << showSymbolsStr;
 
    }
 

	
 
    settings->endGroup();
 
}
src/plotmenu.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef PLOTMENU_H
 
#define PLOTMENU_H
 

	
 
#include <QMenu>
 
#include <QAction>
 
#include <QSettings>
 

	
 
#include "plot.h"
 

	
 
/// Used to quickly transfer view options between different menus
 
struct PlotViewSettings
 
{
 
    bool showGrid;
 
    bool showMinorGrid;
 
    bool darkBackground;
 
    bool showLegend;
 
    bool showMulti;
 
    Plot::ShowSymbols showSymbols;
 
};
 

	
 
class PlotMenu : public QMenu
 
{
 
    Q_OBJECT
 

	
 
public:
 
    PlotMenu(QWidget* parent = 0);
 
    PlotMenu(PlotViewSettings s, QWidget* parent = 0);
 

	
 
    QAction showGridAction;
 
    QAction showMinorGridAction;
 
    QAction unzoomAction;
 
    QAction darkBackgroundAction;
 
    QAction showLegendAction;
 
    QAction showMultiAction;
 
    QAction setSymbolsAction;
 
    QMenu setSymbolsMenu;
 
    QAction setSymbolsAutoAct;
 
    QAction setSymbolsShowAct;
 
    QAction setSymbolsHideAct;
 

	
 
    /// Returns a bundle of current view settings (menu selections)
 
    PlotViewSettings viewSettings() const;
 
    /// Selected "show symbol" option
 
    Plot::ShowSymbols showSymbols() const;
 
    /// Stores plot settings into a `QSettings`.
 
    void saveSettings(QSettings* settings);
 
    /// Loads plot settings from a `QSettings`.
 
    void loadSettings(QSettings* settings);
 

	
 
signals:
 
    void symbolShowChanged(Plot::ShowSymbols shown);
 
};
 

	
 
#endif // PLOTMENU_H
src/portcontrol.cpp
Show inline comments
 
/*
 
  Copyright © 2016 Hasan Yavuz Özderya
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include "portcontrol.h"
 
#include "ui_portcontrol.h"
 

	
 
#include <QSerialPortInfo>
 
#include <QKeySequence>
 
#include <QLabel>
 
#include <QMap>
 
#include <QtDebug>
 

	
 
#include "setting_defines.h"
 
#include "utils.h"
 

	
 
#define TBPORTLIST_MINWIDTH (200)
 

	
 
// setting mappings
 
const QMap<QSerialPort::Parity, QString> paritySettingMap({
 
        {QSerialPort::NoParity, "none"},
 
        {QSerialPort::OddParity, "odd"},
 
        {QSerialPort::EvenParity, "even"},
 
    });
 

	
 
PortControl::PortControl(QSerialPort* port, QWidget* parent) :
 
    QWidget(parent),
 
    ui(new Ui::PortControl),
 
    portToolBar("Port Toolbar"),
 
    openAction("Open", this),
 
    loadPortListAction("↺", this)
 
{
 
    ui->setupUi(this);
 

	
 
    serialPort = port;
 
    connect(serialPort, SIGNAL(error(QSerialPort::SerialPortError)),
 
            this, SLOT(onPortError(QSerialPort::SerialPortError)));
 

	
 
    // setup actions
 
    openAction.setCheckable(true);
 
    openAction.setShortcut(QKeySequence("F12"));
 
    openAction.setToolTip("Open Port");
 
    QObject::connect(&openAction, &QAction::triggered,
 
                     this, &PortControl::openActionTriggered);
 

	
 
    loadPortListAction.setToolTip("Reload port list");
 
    QObject::connect(&loadPortListAction, &QAction::triggered,
 
                     [this](bool checked){loadPortList();});
 

	
 
    // setup toolbar
 
    portToolBar.addWidget(&tbPortList);
 
    portToolBar.addAction(&loadPortListAction);
 
    portToolBar.addAction(&openAction);
 

	
 
    // setup port selection widgets
 
    tbPortList.setMinimumWidth(TBPORTLIST_MINWIDTH);
 
    tbPortList.setModel(&portList);
 
    ui->cbPortList->setModel(&portList);
 
    QObject::connect(ui->cbPortList,
 
                     SELECT<int>::OVERLOAD_OF(&QComboBox::activated),
 
                     this, &PortControl::onCbPortListActivated);
 
    QObject::connect(&tbPortList,
 
                     SELECT<int>::OVERLOAD_OF(&QComboBox::activated),
 
                     this, &PortControl::onTbPortListActivated);
 
    QObject::connect(ui->cbPortList,
 
                     SELECT<const QString&>::OVERLOAD_OF(&QComboBox::activated),
 
                     this, &PortControl::selectPort);
 
    QObject::connect(&tbPortList,
 
                     SELECT<const QString&>::OVERLOAD_OF(&QComboBox::activated),
 
                     this, &PortControl::selectPort);
 

	
 
    // setup buttons
 
    ui->pbOpenPort->setDefaultAction(&openAction);
 
    ui->pbReloadPorts->setDefaultAction(&loadPortListAction);
 

	
 
    // setup baud rate selection widget
 
    QObject::connect(ui->cbBaudRate,
 
                     SELECT<const QString&>::OVERLOAD_OF(&QComboBox::activated),
 
                     this, &PortControl::selectBaudRate);
 

	
 
    // setup parity selection buttons
 
    parityButtons.addButton(ui->rbNoParity, (int) QSerialPort::NoParity);
 
    parityButtons.addButton(ui->rbEvenParity, (int) QSerialPort::EvenParity);
 
    parityButtons.addButton(ui->rbOddParity, (int) QSerialPort::OddParity);
 

	
 
    QObject::connect(&parityButtons,
 
                     SELECT<int>::OVERLOAD_OF(&QButtonGroup::buttonClicked),
 
                     this, &PortControl::selectParity);
 

	
 
    // setup data bits selection buttons
 
    dataBitsButtons.addButton(ui->rb8Bits, (int) QSerialPort::Data8);
 
    dataBitsButtons.addButton(ui->rb7Bits, (int) QSerialPort::Data7);
 
    dataBitsButtons.addButton(ui->rb6Bits, (int) QSerialPort::Data6);
 
    dataBitsButtons.addButton(ui->rb5Bits, (int) QSerialPort::Data5);
 

	
 
    QObject::connect(&dataBitsButtons,
 
                     SELECT<int>::OVERLOAD_OF(&QButtonGroup::buttonClicked),
 
                     this, &PortControl::selectDataBits);
 

	
 
    // setup stop bits selection buttons
 
    stopBitsButtons.addButton(ui->rb1StopBit, (int) QSerialPort::OneStop);
 
    stopBitsButtons.addButton(ui->rb2StopBit, (int) QSerialPort::TwoStop);
 

	
 
    QObject::connect(&stopBitsButtons,
 
                     SELECT<int>::OVERLOAD_OF(&QButtonGroup::buttonClicked),
 
                     this, &PortControl::selectStopBits);
 

	
 
    // setup flow control selection buttons
 
    flowControlButtons.addButton(ui->rbNoFlowControl,
 
                                 (int) QSerialPort::NoFlowControl);
 
    flowControlButtons.addButton(ui->rbHardwareControl,
 
                                 (int) QSerialPort::HardwareControl);
 
    flowControlButtons.addButton(ui->rbSoftwareControl,
 
                                 (int) QSerialPort::SoftwareControl);
 

	
 
    QObject::connect(&flowControlButtons,
 
                     SELECT<int>::OVERLOAD_OF(&QButtonGroup::buttonClicked),
 
                     this, &PortControl::selectFlowControl);
 

	
 
    // initialize signal leds
 
    ui->ledDTR->setOn(true);
 
    ui->ledRTS->setOn(true);
 

	
 
    // connect output signals
 
    connect(ui->pbDTR, &QPushButton::clicked, [this]()
 
            {
 
                // toggle DTR
 
                ui->ledDTR->toggle();
 
                if (serialPort->isOpen())
 
                {
 
                    serialPort->setDataTerminalReady(ui->ledDTR->isOn());
 
                }
 
            });
 

	
 
    connect(ui->pbRTS, &QPushButton::clicked, [this]()
 
            {
 
                // toggle RTS
 
                ui->ledRTS->toggle();
 
                if (serialPort->isOpen())
 
                {
 
                    serialPort->setRequestToSend(ui->ledRTS->isOn());
 
                }
 
            });
 

	
 
    // setup pin update leds
 
    ui->ledDCD->setColor(Qt::yellow);
 
    ui->ledDSR->setColor(Qt::yellow);
 
    ui->ledRI->setColor(Qt::yellow);
 
    ui->ledCTS->setColor(Qt::yellow);
 

	
 
    pinUpdateTimer.setInterval(1000); // ms
 
    connect(&pinUpdateTimer, &QTimer::timeout, this, &PortControl::updatePinLeds);
 

	
 
    loadPortList();
 
    loadBaudRateList();
 
    ui->cbBaudRate->setCurrentIndex(ui->cbBaudRate->findText("9600"));
 
}
 

	
 
PortControl::~PortControl()
 
{
 
    delete ui;
 
}
 

	
 
void PortControl::loadPortList()
 
{
 
    QString currentSelection = ui->cbPortList->currentData(PortNameRole).toString();
 
    portList.loadPortList();
 
    int index = portList.indexOf(currentSelection);
 
    if (index >= 0)
 
    {
 
        ui->cbPortList->setCurrentIndex(index);
 
        tbPortList.setCurrentIndex(index);
 
    }
 
}
 

	
 
void PortControl::loadBaudRateList()
 
{
 
    ui->cbBaudRate->clear();
 

	
 
    for (auto baudRate : QSerialPortInfo::standardBaudRates())
 
    {
 
        ui->cbBaudRate->addItem(QString::number(baudRate));
 
    }
 
}
 

	
 
void PortControl::selectBaudRate(QString baudRate)
 
{
 
    if (serialPort->isOpen())
 
    {
 
        if (!serialPort->setBaudRate(baudRate.toInt()))
 
        {
 
            qCritical() << "Can't set baud rate!";
 
        }
 
    }
 
}
 

	
 
void PortControl::selectParity(int parity)
 
{
 
    if (serialPort->isOpen())
 
    {
 
        if(!serialPort->setParity((QSerialPort::Parity) parity))
 
        {
 
            qCritical() << "Can't set parity option!";
 
        }
 
    }
 
}
 

	
 
void PortControl::selectDataBits(int dataBits)
 
{
 
    if (serialPort->isOpen())
 
    {
 
        if(!serialPort->setDataBits((QSerialPort::DataBits) dataBits))
 
        {
 
            qCritical() << "Can't set numer of data bits!";
 
        }
 
    }
 
}
 

	
 
void PortControl::selectStopBits(int stopBits)
 
{
 
    if (serialPort->isOpen())
 
    {
 
        if(!serialPort->setStopBits((QSerialPort::StopBits) stopBits))
 
        {
 
            qCritical() << "Can't set number of stop bits!";
 
        }
 
    }
 
}
 

	
 
void PortControl::selectFlowControl(int flowControl)
 
{
 
    if (serialPort->isOpen())
 
    {
 
        if(!serialPort->setFlowControl((QSerialPort::FlowControl) flowControl))
 
        {
 
            qCritical() << "Can't set flow control option!";
 
        }
 
    }
 
}
 

	
 
void PortControl::togglePort()
 
{
 
    if (serialPort->isOpen())
 
    {
 
        pinUpdateTimer.stop();
 
        serialPort->close();
 
        qDebug() << "Closed port:" << serialPort->portName();
 
        emit portToggled(false);
 
    }
 
    else
 
    {
 
        // we get the port name from the edit text, which may not be
 
        // in the portList if user hasn't pressed Enter
 
        // Also note that, portText may not be the `portName`
 
        QString portText = ui->cbPortList->currentText();
 
        QString portName;
 
        int portIndex = portList.indexOf(portText);
 
        if (portIndex < 0) // not in list, add to model and update the selections
 
        {
 
            portList.appendRow(new PortListItem(portText));
 
            ui->cbPortList->setCurrentIndex(portList.rowCount()-1);
 
            tbPortList.setCurrentIndex(portList.rowCount()-1);
 
            portName = portText;
 
        }
 
        else
 
        {
 
            // get the port name from the data field
 
            portName = static_cast<PortListItem*>(portList.item(portIndex))->portName();
 
        }
 

	
 
        serialPort->setPortName(ui->cbPortList->currentData(PortNameRole).toString());
 

	
 
        // open port
 
        if (serialPort->open(QIODevice::ReadWrite))
 
        {
 
            // set port settings
 
            selectBaudRate(ui->cbBaudRate->currentText());
 
            selectParity((QSerialPort::Parity) parityButtons.checkedId());
 
            selectDataBits((QSerialPort::DataBits) dataBitsButtons.checkedId());
 
            selectStopBits((QSerialPort::StopBits) stopBitsButtons.checkedId());
 
            selectFlowControl((QSerialPort::FlowControl) flowControlButtons.checkedId());
 

	
 
            // set output signals
 
            serialPort->setDataTerminalReady(ui->ledDTR->isOn());
 
            serialPort->setRequestToSend(ui->ledRTS->isOn());
 

	
 
            // update pin signals
 
            updatePinLeds();
 
            pinUpdateTimer.start();
 

	
 
            qDebug() << "Opened port:" << serialPort->portName();
 
            emit portToggled(true);
 
        }
 
    }
 
    openAction.setChecked(serialPort->isOpen());
 
}
 

	
 
void PortControl::selectPort(QString portName)
 
{
 
    // portName may be coming from combobox
 
    portName = portName.split(" ")[0];
 
    // has selection actually changed
 
    if (portName != serialPort->portName())
 
    {
 
        // if another port is already open, close it by toggling
 
        if (serialPort->isOpen())
 
        {
 
            togglePort();
 

	
 
            // open new selection by toggling
 
            togglePort();
 
        }
 
    }
 
}
 

	
 
QString PortControl::selectedPortName()
 
{
 
    QString portText = ui->cbPortList->currentText();
 
    int portIndex = portList.indexOf(portText);
 
    if (portIndex < 0) // not in the list yet
 
    {
 
        // return the displayed name as port name
 
        return portText;
 
    }
 
    else
 
    {
 
        // get the port name from the 'port list'
 
        return static_cast<PortListItem*>(portList.item(portIndex))->portName();
 
    }
 
}
 

	
 
QToolBar* PortControl::toolBar()
 
{
 
    return &portToolBar;
 
}
 

	
 
void PortControl::openActionTriggered(bool checked)
 
{
 
    togglePort();
 
}
 

	
 
void PortControl::onCbPortListActivated(int index)
 
{
 
    tbPortList.setCurrentIndex(index);
 
}
 

	
 
void PortControl::onTbPortListActivated(int index)
 
{
 
    ui->cbPortList->setCurrentIndex(index);
 
}
 

	
 
void PortControl::onPortError(QSerialPort::SerialPortError error)
 
{
 
    switch(error)
 
    {
 
        case QSerialPort::NoError :
 
            break;
 
        case QSerialPort::ResourceError :
 
            qWarning() << "Port error: resource unavaliable; most likely device removed.";
 
            if (serialPort->isOpen())
 
            {
 
                qWarning() << "Closing port on resource error: " << serialPort->portName();
 
                togglePort();
 
            }
 
            loadPortList();
 
            break;
 
        case QSerialPort::DeviceNotFoundError:
 
            qCritical() << "Device doesn't exists: " << serialPort->portName();
 
            break;
 
        case QSerialPort::PermissionError:
 
            qCritical() << "Permission denied. Either you don't have \
 
required privileges or device is already opened by another process.";
 
            break;
 
        case QSerialPort::OpenError:
 
            qWarning() << "Device is already opened!";
 
            break;
 
        case QSerialPort::NotOpenError:
 
            qCritical() << "Device is not open!";
 
            break;
 
        case QSerialPort::ParityError:
 
            qCritical() << "Parity error detected.";
 
            break;
 
        case QSerialPort::FramingError:
 
            qCritical() << "Framing error detected.";
 
            break;
 
        case QSerialPort::BreakConditionError:
 
            qCritical() << "Break condition is detected.";
 
            break;
 
        case QSerialPort::WriteError:
 
            qCritical() << "An error occurred while writing data.";
 
            break;
 
        case QSerialPort::ReadError:
 
            qCritical() << "An error occurred while reading data.";
 
            break;
 
        case QSerialPort::UnsupportedOperationError:
 
            qCritical() << "Operation is not supported.";
 
            break;
 
        case QSerialPort::TimeoutError:
 
            qCritical() << "A timeout error occurred.";
 
            break;
 
        case QSerialPort::UnknownError:
 
            qCritical() << "Unknown error! Error: " << serialPort->errorString();
 
            break;
 
        default:
 
            qCritical() << "Unhandled port error: " << error;
 
            break;
 
    }
 
}
 

	
 
void PortControl::updatePinLeds(void)
 
{
 
    auto pins = serialPort->pinoutSignals();
 
    ui->ledDCD->setOn(pins & QSerialPort::DataCarrierDetectSignal);
 
    ui->ledDSR->setOn(pins & QSerialPort::DataSetReadySignal);
 
    ui->ledRI->setOn(pins & QSerialPort::RingIndicatorSignal);
 
    ui->ledCTS->setOn(pins & QSerialPort::ClearToSendSignal);
 
}
 

	
 
QString PortControl::currentParityText()
 
{
 
    return paritySettingMap.value(
 
        (QSerialPort::Parity) parityButtons.checkedId());
 
}
 

	
 
QString PortControl::currentFlowControlText()
 
{
 
    if (flowControlButtons.checkedId() == QSerialPort::HardwareControl)
 
    {
 
        return "hardware";
 
    }
 
    else if (flowControlButtons.checkedId() == QSerialPort::SoftwareControl)
 
    {
 
        return "software";
 
    }
 
    else // no parity
 
    {
 
        return "none";
 
    }
 
}
 

	
 
void PortControl::saveSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_Port);
 
    settings->setValue(SG_Port_SelectedPort, selectedPortName());
 
    settings->setValue(SG_Port_BaudRate, ui->cbBaudRate->currentText());
 
    settings->setValue(SG_Port_Parity, currentParityText());
 
    settings->setValue(SG_Port_DataBits, dataBitsButtons.checkedId());
 
    settings->setValue(SG_Port_StopBits, stopBitsButtons.checkedId());
 
    settings->setValue(SG_Port_FlowControl, currentFlowControlText());
 
    settings->endGroup();
 
}
 

	
 
void PortControl::loadSettings(QSettings* settings)
 
{
 
    // make sure the port is closed
 
    if (serialPort->isOpen()) togglePort();
 

	
 
    settings->beginGroup(SettingGroup_Port);
 

	
 
    // set port name if it exists in the current list otherwise ignore
 
    QString portName = settings->value(SG_Port_SelectedPort, QString()).toString();
 
    if (!portName.isEmpty())
 
    {
 
        int index = portList.indexOfName(portName);
 
        if (index > -1) ui->cbPortList->setCurrentIndex(index);
 
    }
 

	
 
    // load baud rate setting if it exists in baud rate list
 
    QString baudSetting = settings->value(
 
        SG_Port_BaudRate, ui->cbBaudRate->currentText()).toString();
 
    int baudIndex = ui->cbBaudRate->findText(baudSetting);
 
    if (baudIndex > -1) ui->cbBaudRate->setCurrentIndex(baudIndex);
 

	
 
    // load parity setting
 
    QString parityText =
 
        settings->value(SG_Port_Parity, currentParityText()).toString();
 
    QSerialPort::Parity paritySetting = paritySettingMap.key(
 
        parityText, (QSerialPort::Parity) parityButtons.checkedId());
 
    parityButtons.button(paritySetting)->setChecked(true);
 

	
 
    // load number of bits
 
    int dataBits = settings->value(SG_Port_Parity, dataBitsButtons.checkedId()).toInt();
 
    if (dataBits >=5 && dataBits <= 8)
 
    {
 
        dataBitsButtons.button((QSerialPort::DataBits) dataBits)->setChecked(true);
 
    }
 

	
 
    // load stop bits
 
    int stopBits = settings->value(SG_Port_StopBits, stopBitsButtons.checkedId()).toInt();
 
    if (stopBits == QSerialPort::OneStop)
 
    {
 
        ui->rb1StopBit->setChecked(true);
 
    }
 
    else if (stopBits == QSerialPort::TwoStop)
 
    {
 
        ui->rb2StopBit->setChecked(true);
 
    }
 

	
 
    // load flow control
 
    QString flowControlSetting =
 
        settings->value(SG_Port_FlowControl, currentFlowControlText()).toString();
 
    if (flowControlSetting == "hardware")
 
    {
 
        ui->rbHardwareControl->setChecked(true);
 
    }
 
    else if (flowControlSetting == "software")
 
    {
 
        ui->rbSoftwareControl->setChecked(true);
 
    }
 
    else
 
    {
 
        ui->rbNoFlowControl->setChecked(true);
 
    }
 

	
 
    settings->endGroup();
 
}
src/portcontrol.h
Show inline comments
 
/*
 
  Copyright © 2016 Hasan Yavuz Özderya
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef PORTCONTROL_H
 
#define PORTCONTROL_H
 

	
 
#include <QWidget>
 
#include <QButtonGroup>
 
#include <QSerialPort>
 
#include <QStringList>
 
#include <QToolBar>
 
#include <QAction>
 
#include <QComboBox>
 
#include <QSettings>
 
#include <QTimer>
 

	
 
#include "portlist.h"
 

	
 
namespace Ui {
 
class PortControl;
 
}
 

	
 
class PortControl : public QWidget
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit PortControl(QSerialPort* port, QWidget* parent = 0);
 
    ~PortControl();
 

	
 
    QSerialPort* serialPort;
 
    QToolBar* toolBar();
 

	
 
    /// Stores port settings into a `QSettings`
 
    void saveSettings(QSettings* settings);
 
    /// Loads port settings from a `QSettings`. If open serial port is closed.
 
    void loadSettings(QSettings* settings);
 

	
 
private:
 
    Ui::PortControl *ui;
 

	
 
    QButtonGroup parityButtons;
 
    QButtonGroup dataBitsButtons;
 
    QButtonGroup stopBitsButtons;
 
    QButtonGroup flowControlButtons;
 

	
 
    QToolBar portToolBar;
 
    QAction openAction;
 
    QAction loadPortListAction;
 
    QComboBox tbPortList;
 
    PortList portList;
 

	
 
    /// Used to refresh pinout signal leds periodically
 
    QTimer pinUpdateTimer;
 

	
 
    /// Returns the currently selected (entered) "portName" in the UI
 
    QString selectedPortName();
 
    /// Returns currently selected parity as text to be saved in settings
 
    QString currentParityText();
 
    /// Returns currently selected flow control as text to be saved in settings
 
    QString currentFlowControlText();
 

	
 
public slots:
 
    void loadPortList();
 
    void loadBaudRateList();
 
    void togglePort();
 
    void selectPort(QString portName);
 

	
 
    void selectBaudRate(QString baudRate);
 
    void selectParity(int parity); // parity must be one of QSerialPort::Parity
 
    void selectDataBits(int dataBits); // bits must be one of QSerialPort::DataBits
 
    void selectStopBits(int stopBits); // stopBits must be one of QSerialPort::StopBits
 
    void selectFlowControl(int flowControl); // flowControl must be one of QSerialPort::FlowControl
 

	
 
private slots:
 
    void openActionTriggered(bool checked);
 

	
 
    void onCbPortListActivated(int index);
 
    void onTbPortListActivated(int index);
 
    void onPortError(QSerialPort::SerialPortError error);
 
    void updatePinLeds(void);
 

	
 
signals:
 
    void portToggled(bool open);
 
};
 

	
 
#endif // PORTCONTROL_H
src/portcontrol.ui
Show inline comments
 
<?xml version="1.0" encoding="UTF-8"?>
 
<ui version="4.0">
 
 <class>PortControl</class>
 
 <widget class="QWidget" name="PortControl">
 
  <property name="geometry">
 
   <rect>
 
    <x>0</x>
 
    <y>0</y>
 
    <width>631</width>
 
    <height>232</height>
 
   </rect>
 
  </property>
 
  <property name="windowTitle">
 
   <string>Form</string>
 
  </property>
 
  <layout class="QHBoxLayout" name="horizontalLayout">
 
   <item>
 
    <layout class="QVBoxLayout" name="verticalLayout_3">
 
     <item>
 
      <layout class="QGridLayout" name="gridLayout">
 
       <item row="0" column="0">
 
        <widget class="QLabel" name="label">
 
         <property name="text">
 
          <string>Port:</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="0" column="1">
 
        <widget class="QComboBox" name="cbPortList">
 
         <property name="sizePolicy">
 
          <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
 
           <horstretch>0</horstretch>
 
           <verstretch>0</verstretch>
 
          </sizepolicy>
 
         </property>
 
         <property name="toolTip">
 
          <string>You can enter a port name even if it's not listed, such as pseudo terminals.</string>
 
         </property>
 
         <property name="editable">
 
          <bool>true</bool>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="1" column="1">
 
        <widget class="QComboBox" name="cbBaudRate">
 
         <property name="toolTip">
 
          <string>You can enter a custom baud rate if it's supported by your OS/adapter.</string>
 
         </property>
 
         <property name="inputMethodHints">
 
          <set>Qt::ImhPreferNumbers</set>
 
         </property>
 
         <property name="editable">
 
          <bool>true</bool>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="1" column="0">
 
        <widget class="QLabel" name="label_2">
 
         <property name="text">
 
          <string>Baud Rate:</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="0" column="2">
 
        <widget class="QToolButton" name="pbReloadPorts">
 
         <property name="sizePolicy">
 
          <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
 
           <horstretch>0</horstretch>
 
           <verstretch>0</verstretch>
 
          </sizepolicy>
 
         </property>
 
         <property name="maximumSize">
 
          <size>
 
           <width>30</width>
 
           <height>16777215</height>
 
          </size>
 
         </property>
 
         <property name="toolTip">
 
          <string/>
 
          <string>Reload the list of ports</string>
 
         </property>
 
         <property name="text">
 
          <string>↺</string>
 
         </property>
 
        </widget>
 
       </item>
 
      </layout>
 
     </item>
 
     <item>
 
      <layout class="QHBoxLayout" name="horizontalLayout_2">
 
       <item>
 
        <widget class="QFrame" name="frame">
 
         <property name="frameShape">
 
          <enum>QFrame::NoFrame</enum>
 
         </property>
 
         <layout class="QVBoxLayout" name="verticalLayout_5">
 
          <item>
 
           <widget class="QRadioButton" name="rbNoParity">
 
            <property name="text">
 
             <string>No Parity</string>
 
            </property>
 
            <property name="checked">
 
             <bool>true</bool>
 
            </property>
 
           </widget>
 
          </item>
 
          <item>
 
           <widget class="QRadioButton" name="rbOddParity">
 
            <property name="text">
 
             <string>Odd Parity</string>
 
            </property>
 
           </widget>
 
          </item>
 
          <item>
 
           <widget class="QRadioButton" name="rbEvenParity">
 
            <property name="text">
 
             <string>Even Parity</string>
 
            </property>
 
           </widget>
 
          </item>
 
          <item>
 
           <spacer name="verticalSpacer_2">
 
            <property name="orientation">
 
             <enum>Qt::Vertical</enum>
 
            </property>
 
            <property name="sizeHint" stdset="0">
 
             <size>
 
              <width>20</width>
 
              <height>2</height>
 
             </size>
 
            </property>
 
           </spacer>
 
          </item>
 
         </layout>
 
        </widget>
 
       </item>
 
       <item>
 
        <widget class="QFrame" name="frame_3">
 
         <layout class="QVBoxLayout" name="verticalLayout_7">
 
          <item>
 
           <widget class="QRadioButton" name="rb8Bits">
 
            <property name="text">
 
             <string>8 bits</string>
 
            </property>
 
            <property name="checked">
 
             <bool>true</bool>
 
            </property>
 
           </widget>
 
          </item>
 
          <item>
 
           <widget class="QRadioButton" name="rb7Bits">
 
            <property name="text">
 
             <string>7 bits</string>
 
            </property>
 
           </widget>
 
          </item>
 
          <item>
 
           <widget class="QRadioButton" name="rb6Bits">
 
            <property name="text">
 
             <string>6 bits</string>
 
            </property>
 
           </widget>
 
          </item>
 
          <item>
 
           <widget class="QRadioButton" name="rb5Bits">
 
            <property name="text">
 
             <string>5 bits</string>
 
            </property>
 
           </widget>
 
          </item>
 
          <item>
 
           <spacer name="verticalSpacer_3">
 
            <property name="orientation">
 
             <enum>Qt::Vertical</enum>
 
            </property>
 
            <property name="sizeHint" stdset="0">
 
             <size>
 
              <width>20</width>
 
              <height>2</height>
 
             </size>
 
            </property>
 
           </spacer>
 
          </item>
 
         </layout>
 
        </widget>
 
       </item>
 
       <item>
 
        <widget class="QFrame" name="frame_4">
 
         <layout class="QVBoxLayout" name="verticalLayout">
 
          <item>
 
           <widget class="QRadioButton" name="rb1StopBit">
 
            <property name="text">
 
             <string>1 Stop Bit</string>
 
            </property>
 
            <property name="checked">
 
             <bool>true</bool>
 
            </property>
 
           </widget>
 
          </item>
 
          <item>
 
           <widget class="QRadioButton" name="rb2StopBit">
 
            <property name="text">
 
             <string>2 Stop Bit</string>
 
            </property>
 
           </widget>
 
          </item>
 
          <item>
 
           <spacer name="verticalSpacer_4">
 
            <property name="orientation">
 
             <enum>Qt::Vertical</enum>
 
            </property>
 
            <property name="sizeHint" stdset="0">
 
             <size>
 
              <width>20</width>
 
              <height>2</height>
 
             </size>
 
            </property>
 
           </spacer>
 
          </item>
 
         </layout>
 
        </widget>
 
       </item>
 
       <item>
 
        <widget class="QFrame" name="frame_2">
 
         <layout class="QVBoxLayout" name="verticalLayout_6">
 
          <item>
 
           <widget class="QRadioButton" name="rbNoFlowControl">
 
            <property name="text">
 
             <string>No Flow Control</string>
 
            </property>
 
            <property name="checked">
 
             <bool>true</bool>
 
            </property>
 
           </widget>
 
          </item>
 
          <item>
 
           <widget class="QRadioButton" name="rbHardwareControl">
 
            <property name="text">
 
             <string>Hardware Control</string>
 
            </property>
 
           </widget>
 
          </item>
 
          <item>
 
           <widget class="QRadioButton" name="rbSoftwareControl">
 
            <property name="text">
 
             <string>Software Control</string>
 
            </property>
 
           </widget>
 
          </item>
 
          <item>
 
           <spacer name="verticalSpacer_5">
 
            <property name="orientation">
 
             <enum>Qt::Vertical</enum>
 
            </property>
 
            <property name="sizeHint" stdset="0">
 
             <size>
 
              <width>20</width>
 
              <height>2</height>
 
             </size>
 
            </property>
 
           </spacer>
 
          </item>
 
         </layout>
 
        </widget>
 
       </item>
 
      </layout>
 
     </item>
 
    </layout>
 
   </item>
 
   <item>
 
    <spacer name="horizontalSpacer">
 
     <property name="orientation">
 
      <enum>Qt::Horizontal</enum>
 
     </property>
 
     <property name="sizeHint" stdset="0">
 
      <size>
 
       <width>10000</width>
 
       <height>20</height>
 
      </size>
 
     </property>
 
    </spacer>
 
   </item>
 
   <item>
 
    <layout class="QVBoxLayout" name="verticalLayout_4">
 
     <item>
 
      <widget class="QToolButton" name="pbOpenPort">
 
       <property name="sizePolicy">
 
        <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
 
         <horstretch>0</horstretch>
 
         <verstretch>0</verstretch>
 
        </sizepolicy>
 
       </property>
 
       <property name="minimumSize">
 
        <size>
 
         <width>85</width>
 
         <height>50</height>
 
        </size>
 
       </property>
 
       <property name="toolTip">
 
        <string>Toggle port status</string>
 
       </property>
 
       <property name="text">
 
        <string>Open</string>
 
       </property>
 
       <property name="checkable">
 
        <bool>true</bool>
 
       </property>
 
      </widget>
 
     </item>
 
     <item>
 
      <layout class="QGridLayout" name="gridLayout_2">
 
       <property name="spacing">
 
        <number>2</number>
 
       </property>
 
       <item row="1" column="2">
 
        <widget class="LedWidget" name="ledRTS" native="true">
 
         <property name="minimumSize">
 
          <size>
 
           <width>15</width>
 
           <height>15</height>
 
          </size>
 
         </property>
 
         <property name="maximumSize">
 
          <size>
 
           <width>15</width>
 
           <height>15</height>
 
          </size>
 
         </property>
 
         <property name="toolTip">
 
          <string>Request To Send</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="0" column="0">
 
        <spacer name="horizontalSpacer_2">
 
         <property name="orientation">
 
          <enum>Qt::Horizontal</enum>
 
         </property>
 
         <property name="sizeHint" stdset="0">
 
          <size>
 
           <width>40</width>
 
           <height>20</height>
 
          </size>
 
         </property>
 
        </spacer>
 
       </item>
 
       <item row="0" column="1">
 
        <widget class="QPushButton" name="pbDTR">
 
         <property name="maximumSize">
 
          <size>
 
           <width>40</width>
 
           <height>20</height>
 
          </size>
 
         </property>
 
         <property name="toolTip">
 
          <string>Data Terminal Ready</string>
 
         </property>
 
         <property name="text">
 
          <string>DTR</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="0" column="2">
 
        <widget class="LedWidget" name="ledDTR" native="true">
 
         <property name="minimumSize">
 
          <size>
 
           <width>15</width>
 
           <height>15</height>
 
          </size>
 
         </property>
 
         <property name="maximumSize">
 
          <size>
 
           <width>15</width>
 
           <height>15</height>
 
          </size>
 
         </property>
 
         <property name="toolTip">
 
          <string>Data Terminal Ready</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="3" column="2">
 
        <widget class="LedWidget" name="ledDSR" native="true">
 
         <property name="minimumSize">
 
          <size>
 
           <width>15</width>
 
           <height>15</height>
 
          </size>
 
         </property>
 
         <property name="maximumSize">
 
          <size>
 
           <width>15</width>
 
           <height>15</height>
 
          </size>
 
         </property>
 
         <property name="toolTip">
 
          <string>Data Set Ready</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="3" column="1">
 
        <widget class="QLabel" name="labDSR">
 
         <property name="toolTip">
 
          <string>Data Set Ready</string>
 
         </property>
 
         <property name="text">
 
          <string>DSR</string>
 
         </property>
 
         <property name="alignment">
 
          <set>Qt::AlignCenter</set>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="1" column="1">
 
        <widget class="QPushButton" name="pbRTS">
 
         <property name="maximumSize">
 
          <size>
 
           <width>40</width>
 
           <height>20</height>
 
          </size>
 
         </property>
 
         <property name="toolTip">
 
          <string>Request To Send</string>
 
         </property>
 
         <property name="text">
 
          <string>RTS</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="2" column="1">
 
        <widget class="QLabel" name="labDCD">
 
         <property name="toolTip">
 
          <string>Data Carrier Detect</string>
 
         </property>
 
         <property name="text">
 
          <string>DCD</string>
 
         </property>
 
         <property name="alignment">
 
          <set>Qt::AlignCenter</set>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="2" column="2">
 
        <widget class="LedWidget" name="ledDCD" native="true">
 
         <property name="minimumSize">
 
          <size>
 
           <width>15</width>
 
           <height>15</height>
 
          </size>
 
         </property>
 
         <property name="maximumSize">
 
          <size>
 
           <width>15</width>
 
           <height>15</height>
 
          </size>
 
         </property>
 
         <property name="toolTip">
 
          <string>Data Carrier Detect</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="4" column="1">
 
        <widget class="QLabel" name="labRI">
 
         <property name="toolTip">
 
          <string>Ring Indicator</string>
 
         </property>
 
         <property name="text">
 
          <string>RI</string>
 
         </property>
 
         <property name="alignment">
 
          <set>Qt::AlignCenter</set>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="4" column="2">
 
        <widget class="LedWidget" name="ledRI" native="true">
 
         <property name="minimumSize">
 
          <size>
 
           <width>15</width>
 
           <height>15</height>
 
          </size>
 
         </property>
 
         <property name="maximumSize">
 
          <size>
 
           <width>15</width>
 
           <height>15</height>
 
          </size>
 
         </property>
 
         <property name="toolTip">
 
          <string>Ring Indicator</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="5" column="2">
 
        <widget class="LedWidget" name="ledCTS" native="true">
 
         <property name="minimumSize">
 
          <size>
 
           <width>15</width>
 
           <height>15</height>
 
          </size>
 
         </property>
 
         <property name="maximumSize">
 
          <size>
 
           <width>15</width>
 
           <height>15</height>
 
          </size>
 
         </property>
 
         <property name="toolTip">
 
          <string>Clear To Send</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="5" column="1">
 
        <widget class="QLabel" name="labCTS">
 
         <property name="toolTip">
 
          <string>Clear To Send</string>
 
         </property>
 
         <property name="text">
 
          <string>CTS</string>
 
         </property>
 
         <property name="alignment">
 
          <set>Qt::AlignCenter</set>
 
         </property>
 
        </widget>
 
       </item>
 
      </layout>
 
     </item>
 
     <item>
 
      <spacer name="verticalSpacer">
 
       <property name="orientation">
 
        <enum>Qt::Vertical</enum>
 
       </property>
 
       <property name="sizeHint" stdset="0">
 
        <size>
 
         <width>20</width>
 
         <height>40</height>
 
         <height>1</height>
 
        </size>
 
       </property>
 
      </spacer>
 
     </item>
 
    </layout>
 
   </item>
 
  </layout>
 
 </widget>
 
 <customwidgets>
 
  <customwidget>
 
   <class>LedWidget</class>
 
   <extends>QWidget</extends>
 
   <header>ledwidget.h</header>
 
   <container>1</container>
 
  </customwidget>
 
 </customwidgets>
 
 <tabstops>
 
  <tabstop>cbPortList</tabstop>
 
  <tabstop>pbReloadPorts</tabstop>
 
  <tabstop>pbOpenPort</tabstop>
 
  <tabstop>cbBaudRate</tabstop>
 
  <tabstop>rbNoParity</tabstop>
 
  <tabstop>rbOddParity</tabstop>
 
  <tabstop>rbEvenParity</tabstop>
 
  <tabstop>rb8Bits</tabstop>
 
  <tabstop>rb7Bits</tabstop>
 
  <tabstop>rb6Bits</tabstop>
 
  <tabstop>rb5Bits</tabstop>
 
  <tabstop>rb1StopBit</tabstop>
 
  <tabstop>rb2StopBit</tabstop>
 
  <tabstop>rbNoFlowControl</tabstop>
 
  <tabstop>rbHardwareControl</tabstop>
 
  <tabstop>rbSoftwareControl</tabstop>
 
 </tabstops>
 
 <resources/>
 
 <connections/>
 
</ui>
src/portlist.cpp
Show inline comments
 
/*
 
  Copyright © 2016 Hasan Yavuz Özderya
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <QVariant>
 
#include <QIcon>
 
#include <QtDebug>
 

	
 
#include "portlist.h"
 

	
 
PortListItem::PortListItem(QString name, QString description, quint16 vid, quint16 pid)
 
{
 
    construct(name, description, vid, pid);
 
}
 

	
 
PortListItem::PortListItem(QSerialPortInfo* portInfo)
 
{
 
    if (portInfo->hasProductIdentifier())
 
    {
 
        construct(portInfo->portName(),
 
                  portInfo->description(),
 
                  portInfo->vendorIdentifier(),
 
                  portInfo->productIdentifier());
 
    }
 
    else
 
    {
 
        construct(portInfo->portName());
 
    }
 
}
 

	
 
void PortListItem::construct(QString name, QString description, quint16 vid, quint16 pid)
 
{
 
    QString text = name;
 
    if (!description.isEmpty())
 
    {
 
        text += QString(" ") + description;
 
    }
 
    if (vid && pid)
 
    // Note: in some cases internal ports or RS232 ports may have VID&PID
 
    if (vid && pid && !name.contains("tty"))
 
    {
 
        text += QString("[%1:").arg(vid, 4, 16, QChar('0'));
 
        text += QString("%1]").arg(pid, 4, 16, QChar('0'));
 
        setIcon(QIcon(":/usb_icon.png"));
 
    }
 
    else if (name.contains("rfcomm"))
 
    {
 
        setIcon(QIcon(":/bluetooth_icon.png"));
 
    }
 
    else
 
    {
 
        setIcon(QIcon(":/rs232_icon.png"));
 
    }
 
    setText(text);
 
    setData(name, PortNameRole);
 
}
 

	
 
QString PortListItem::portName()
 
{
 
    return data(PortNameRole).toString();
 
}
 

	
 
PortList::PortList(QObject* parent) :
 
    QStandardItemModel(parent)
 
{
 
    loadPortList();
 

	
 
    // we have to use macro based notation to be able to disconnect
 
    QObject::connect(this, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
 
                     this, SLOT(onRowsInserted(QModelIndex, int, int)));
 
}
 

	
 
void PortList::loadPortList()
 
{
 
    clear();
 

	
 
    disconnect(this, SLOT(onRowsInserted(QModelIndex,int,int)));
 
    for (auto portInfo : QSerialPortInfo::availablePorts())
 
    {
 
        appendRow(new PortListItem(&portInfo));
 
    }
 
    for (auto portName : userEnteredPorts)
 
    {
 
        appendRow(new PortListItem(portName));
 
    }
 
    QObject::connect(this, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
 
                     this, SLOT(onRowsInserted(QModelIndex, int, int)));
 
}
 

	
 
int PortList::indexOf(QString portText)
 
{
 
    for (int i = 0; i < rowCount(); i++)
 
    {
 
        if (item(i)->text() == portText)
 
        {
 
            return i;
 
        }
 
    }
 
    return -1; // not found
 
}
 

	
 
int PortList::indexOfName(QString portName)
 
{
 
    for (int i = 0; i < rowCount(); i++)
 
    {
 
        if (item(i)->data(PortNameRole) == portName)
 
        {
 
            return i;
 
        }
 
    }
 
    return -1; // not found
 
}
 

	
 
void PortList::onRowsInserted(QModelIndex parent, int start, int end)
 
{
 
    PortListItem* newItem = static_cast<PortListItem*>(item(start));
 
    QString portName = newItem->text();
 
    newItem->setData(portName, PortNameRole);
 
    userEnteredPorts << portName;
 
}
src/readonlybuffer.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <QtGlobal>
 
#include <string.h>
 

	
 
#include "readonlybuffer.h"
 

	
 
ReadOnlyBuffer::ReadOnlyBuffer(const FrameBuffer* source) :
 
    ReadOnlyBuffer(source, 0, source->size())
 
{
 
    // intentionally empty, see ↑
 
}
 

	
 
ReadOnlyBuffer::ReadOnlyBuffer(const FrameBuffer* source, unsigned start, unsigned n)
 
{
 
    Q_ASSERT(source->size() > 0);
 
    Q_ASSERT(start + n <= source->size());
 

	
 
    _size = n;
 
    data = new double[_size];
 

	
 
    for (unsigned i = 0; i < n; i++)
 
    {
 
        data[i] = source->sample(start + i);
 
    }
 

	
 
    /// if not exact copy of source re-calculate limits
 
    if (start == 0 && n == source->size())
 
    {
 
        _limits = source->limits();
 
    }
 
    else
 
    {
 
        updateLimits();
 
    }
 
}
 

	
 
ReadOnlyBuffer::ReadOnlyBuffer(const double* source, unsigned ssize)
 
{
 
    Q_ASSERT(source != nullptr && ssize);
 

	
 
    _size = ssize;
 
    data = new double[_size];
 
    memcpy(data, source, sizeof(double) * ssize);
 
    updateLimits();
 
}
 

	
 
ReadOnlyBuffer::~ReadOnlyBuffer()
 
{
 
    delete[] data;
 
}
 

	
 
unsigned ReadOnlyBuffer::size() const
 
{
 
    return _size;
 
}
 

	
 
double ReadOnlyBuffer::sample(unsigned i) const
 
{
 
    return data[i];
 
}
 

	
 
Range ReadOnlyBuffer::limits() const
 
{
 
    return _limits;
 
}
 

	
 
void ReadOnlyBuffer::updateLimits()
 
{
 
    Q_ASSERT(_size);
 

	
 
    _limits.start = data[0];
 
    _limits.end = data[0];
 

	
 
    for (unsigned i = 0; i < _size; i++)
 
    {
 
        if (data[i] > _limits.end)
 
        {
 
            _limits.end = data[i];
 
        }
 
        else if (data[i] < _limits.start)
 
        {
 
            _limits.start = data[i];
 
        }
 
    }
 
}
src/readonlybuffer.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef READONLYBUFFER_H
 
#define READONLYBUFFER_H
 

	
 
#include "framebuffer.h"
 

	
 
/// A read only frame buffer used for storing snapshot data. Main advantage of
 
/// this compared to `RingBuffer` is that reading data should be somewhat
 
/// faster.
 
class ReadOnlyBuffer : public FrameBuffer
 
{
 
public:
 
    /// Creates a buffer with data copied from `source`. Source buffer cannot be
 
    /// empty.
 
    ReadOnlyBuffer(const FrameBuffer* source);
 

	
 
    /// Creates a buffer from a slice of the `source`.
 
    ///
 
    /// @param start start of the slice
 
    /// @param n number of samples
 
    ///
 
    /// @important (start + n) should be smaller or equal than `source->size()`,
 
    /// otherwise it's an error.
 
    ReadOnlyBuffer(const FrameBuffer* source, unsigned start, unsigned n);
 

	
 
    /// Creates a buffer with data copied from an array
 
    ReadOnlyBuffer(const double* source, unsigned ssize);
 

	
 
    ~ReadOnlyBuffer();
 

	
 
    virtual unsigned size() const;
 
    virtual double sample(unsigned i) const;
 
    virtual Range limits() const;
 

	
 
private:
 
    double* data;    ///< data storage
 
    unsigned _size;  ///< data size
 
    Range _limits;   ///< limits cache
 

	
 
    // TODO: duplicate with `RingBuffer`
 
    void updateLimits(); ///< Updates limits cache
 
};
 

	
 
#endif // READONLYBUFFER_H
src/recordpanel.cpp
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <QIcon>
 
#include <QFile>
 
#include <QFileInfo>
 
#include <QMessageBox>
 
#include <QFileDialog>
 
#include <QRegularExpression>
 
#include <QCompleter>
 
#include <QFileSystemModel>
 
#include <QDirModel>
 
#include <QtDebug>
 
#include <ctime>
 

	
 
#include "recordpanel.h"
 
#include "ui_recordpanel.h"
 
#include "setting_defines.h"
 

	
 
RecordPanel::RecordPanel(DataRecorder* recorder, ChannelManager* channelMan, QWidget *parent) :
 
RecordPanel::RecordPanel(Stream* stream, QWidget *parent) :
 
    QWidget(parent),
 
    ui(new Ui::RecordPanel),
 
    recordToolBar(tr("Record Toolbar")),
 
    recordAction(QIcon::fromTheme("media-record"), tr("Record"), this)
 
    recordAction(QIcon::fromTheme("media-record"), tr("Record"), this),
 
    recorder(this)
 
{
 
    overwriteSelected = false;
 
    _recorder = recorder;
 
    _channelMan = channelMan;
 
    _stream = stream;
 

	
 
    ui->setupUi(this);
 

	
 
    recordToolBar.setObjectName("tbRecord");
 

	
 
    recordAction.setCheckable(true);
 
    recordToolBar.addAction(&recordAction);
 
    ui->pbRecord->setDefaultAction(&recordAction);
 

	
 
    connect(ui->pbBrowse, &QPushButton::clicked,
 
            this, &RecordPanel::selectFile);
 
    connect(&recordAction, &QAction::triggered,
 
            this, &RecordPanel::onRecord);
 

	
 
    connect(ui->cbRecordPaused, SIGNAL(toggled(bool)),
 
            this, SIGNAL(recordPausedChanged(bool)));
 

	
 
    connect(ui->cbDisableBuffering, &QCheckBox::toggled,
 
            [this](bool enabled)
 
            {
 
                _recorder->disableBuffering = enabled;
 
                recorder.disableBuffering = enabled;
 
            });
 

	
 
    connect(ui->cbWindowsLE, &QCheckBox::toggled,
 
            [this](bool enabled)
 
            {
 
                _recorder->windowsLE = enabled;
 
                recorder.windowsLE = enabled;
 
            });
 

	
 
    connect(&recordAction, &QAction::toggled, ui->cbWindowsLE, &QWidget::setDisabled);
 
    connect(&recordAction, &QAction::toggled, ui->cbTimestamp, &QWidget::setDisabled);
 
    connect(&recordAction, &QAction::toggled, ui->leSeparator, &QWidget::setDisabled);
 
    connect(&recordAction, &QAction::toggled, ui->pbBrowse, &QWidget::setDisabled);
 

	
 
    QCompleter *completer = new QCompleter(this);
 
    // TODO: QDirModel is deprecated, use QFileSystemModel (but it doesn't work)
 
    completer->setModel(new QDirModel(completer));
 
    completer->setCaseSensitivity(Qt::CaseInsensitive);
 
    ui->leFileName->setCompleter(completer);
 
}
 

	
 
RecordPanel::~RecordPanel()
 
{
 
    delete ui;
 
}
 

	
 
QToolBar* RecordPanel::toolbar()
 
{
 
    return &recordToolBar;
 
}
 

	
 
bool RecordPanel::isRecording()
 
{
 
    return recordAction.isChecked();
 
}
 

	
 
bool RecordPanel::recordPaused()
 
{
 
    return ui->cbRecordPaused->isChecked();
 
}
 

	
 
bool RecordPanel::selectFile()
 
{
 
    QString fileName = QFileDialog::getSaveFileName(
 
        parentWidget(), tr("Select recording file"));
 

	
 
    if (fileName.isEmpty())
 
    {
 
        return false;
 
    }
 
    else
 
    {
 
        selectedFile = fileName;
 
        ui->lbFileName->setText(selectedFile);
 
        setSelectedFile(fileName);
 
        overwriteSelected = QFile::exists(fileName);
 
        return true;
 
    }
 
}
 

	
 
QString RecordPanel::selectedFile() const
 
{
 
    return ui->leFileName->text();
 
}
 

	
 
void RecordPanel::setSelectedFile(QString f)
 
{
 
    ui->leFileName->setText(f);
 
}
 

	
 
QString RecordPanel::getSelectedFile()
 
{
 
    if (selectedFile().isEmpty())
 
    {
 
        if (!selectFile()) return QString();
 
    }
 

	
 
    // assume that file name contains a time format specifier
 
    if (selectedFile().contains("%"))
 
    {
 
        auto ts = formatTimeStamp(selectedFile());
 
        if (!QFile::exists(ts) || // file doesn't exists
 
            confirmOverwrite(ts)) // exists but user accepted overwrite
 
        {
 
            return ts;
 
        }
 
        return QString();
 
    }
 

	
 
    // if no timestamp and file exists try autoincrement option
 
    if (!overwriteSelected && QFile::exists(selectedFile()))
 
    {
 
        if (ui->cbAutoIncrement->isChecked())
 
        {
 
            if (!incrementFileName()) return QString();
 
        }
 
        else
 
        {
 
            if (!confirmOverwrite(selectedFile()))
 
                return QString();
 
        }
 
    }
 

	
 
    return selectedFile();
 
}
 

	
 
QString RecordPanel::formatTimeStamp(QString t) const
 
{
 
    auto maxSize = t.size() + 1024;
 
    auto r = new char[maxSize];
 

	
 
    time_t rawtime;
 
    struct tm * timeinfo;
 

	
 
    time(&rawtime);
 
    timeinfo = localtime (&rawtime);
 
    strftime(r, maxSize, t.toLatin1().data(), timeinfo);
 

	
 
    auto rs = QString(r);
 
    delete r;
 
    return rs;
 
}
 

	
 
void RecordPanel::onRecord(bool start)
 
{
 
    if (!start)
 
    {
 
        stopRecording();
 
        return;
 
    }
 

	
 
    bool canceled = false;
 
    if (ui->leSeparator->text().isEmpty())
 
    {
 
        QMessageBox::critical(this, "Error",
 
                              "Column separator cannot be empty! Please select a separator.");
 
        ui->leSeparator->setFocus(Qt::OtherFocusReason);
 
        canceled = true;
 
    }
 

	
 
    // check file name
 
    if (!canceled && selectedFile.isEmpty() && !selectFile())
 
    {
 
        canceled = true;
 
    }
 

	
 
    if (!canceled && !overwriteSelected && QFile::exists(selectedFile))
 
    QString fn;
 
    if (!canceled)
 
    {
 
        if (ui->cbAutoIncrement->isChecked())
 
        {
 
            // TODO: should we increment even if user selected to replace?
 
            canceled = !incrementFileName();
 
        }
 
        else
 
        {
 
            canceled = !confirmOverwrite(selectedFile);
 
        }
 
        fn = getSelectedFile();
 
        canceled = fn.isEmpty();
 
    }
 

	
 
    if (canceled)
 
    {
 
        recordAction.setChecked(false);
 
    }
 
    else
 
    {
 
        overwriteSelected = false;
 
        startRecording();
 
        // TODO: show more visible error message when recording fails
 
        if (!startRecording(fn))
 
            recordAction.setChecked(false);
 
    }
 
}
 

	
 
bool RecordPanel::incrementFileName(void)
 
{
 
    QFileInfo fileInfo(selectedFile);
 
    QFileInfo fileInfo(selectedFile());
 

	
 
    QString base = fileInfo.completeBaseName();
 
    QRegularExpression regex("(.*?)(\\d+)(?!.*\\d)(.*)");
 
    auto match = regex.match(base);
 

	
 
    if (match.hasMatch())
 
    {
 
        bool ok;
 
        int fileNum = match.captured(2).toInt(&ok);
 
        base = match.captured(1) + QString::number(fileNum + 1) + match.captured(3);
 
    }
 
    else
 
    {
 
        base += "_1";
 
    }
 

	
 
    QString suffix = fileInfo.suffix();;
 
    if (!suffix.isEmpty())
 
    {
 
        suffix = "." + suffix;
 
    }
 

	
 
    QString autoFileName = fileInfo.path() + "/" + base + suffix;
 

	
 
    // check if auto generated file name exists, ask user another name
 
    if (QFile::exists(autoFileName))
 
    {
 
        if (!confirmOverwrite(autoFileName))
 
        {
 
            return false;
 
        }
 
    }
 
    else
 
    {
 
        selectedFile = autoFileName;
 
        setSelectedFile(autoFileName);
 
    }
 

	
 
    ui->lbFileName->setText(selectedFile);
 
    return true;
 
}
 

	
 
bool RecordPanel::confirmOverwrite(QString fileName)
 
{
 
    // prepare message box
 
    QMessageBox mb(parentWidget());
 
    mb.setWindowTitle(tr("File Already Exists"));
 
    mb.setIcon(QMessageBox::Warning);
 
    mb.setText(tr("File (%1) already exists. How to continue?").arg(fileName));
 

	
 
    auto bCancel    = mb.addButton(QMessageBox::Cancel);
 
    auto bOverwrite = mb.addButton(tr("Overwrite"), QMessageBox::DestructiveRole);
 
    mb.addButton(tr("Select Another File"), QMessageBox::YesRole);
 

	
 
    mb.setEscapeButton(bCancel);
 

	
 
    // show message box
 
    mb.exec();
 

	
 
    if (mb.clickedButton() == bCancel)
 
    {
 
        return false;
 
    }
 
    else if (mb.clickedButton() == bOverwrite)
 
    {
 
        selectedFile = fileName;
 
        setSelectedFile(fileName);
 
        return true;
 
    }
 
    else                    // select button
 
    {
 
        return selectFile();
 
    }
 
}
 

	
 
void RecordPanel::startRecording(void)
 
bool RecordPanel::startRecording(QString fileName)
 
{
 
    QStringList channelNames;
 
    if (ui->cbHeader->isChecked())
 
    {
 
        channelNames = _channelMan->infoModel()->channelNames();
 
        channelNames = _stream->infoModel()->channelNames();
 
    }
 
    _recorder->startRecording(selectedFile, getSeparator(), channelNames);
 
    emit recordStarted();
 
    if (recorder.startRecording(fileName, getSeparator(),
 
                                channelNames, ui->cbTimestamp->isChecked()))
 
    {
 
        _stream->connectFollower(&recorder);
 
        return true;
 
    }
 
    else
 
    {
 
        return false;
 
    }
 
}
 

	
 
void RecordPanel::stopRecording(void)
 
{
 
    emit recordStopped();
 
    _recorder->stopRecording();
 
    recorder.stopRecording();
 
    _stream->disconnectFollower(&recorder);
 
}
 

	
 
void RecordPanel::onPortClose()
 
{
 
    if (recordAction.isChecked() && ui->cbStopOnClose->isChecked())
 
    {
 
        stopRecording();
 
        recordAction.setChecked(false);
 
    }
 
}
 

	
 
QString RecordPanel::getSeparator() const
 
{
 
    QString sep = ui->leSeparator->text();
 
    sep.replace("\\t", "\t");
 
    return sep;
 
}
 

	
 
void RecordPanel::saveSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_Record);
 
    settings->setValue(SG_Record_AutoIncrement, ui->cbAutoIncrement->isChecked());
 
    settings->setValue(SG_Record_RecordPaused, ui->cbRecordPaused->isChecked());
 
    settings->setValue(SG_Record_StopOnClose, ui->cbStopOnClose->isChecked());
 
    settings->setValue(SG_Record_Header, ui->cbHeader->isChecked());
 
    settings->setValue(SG_Record_DisableBuffering, ui->cbDisableBuffering->isChecked());
 
    settings->setValue(SG_Record_Timestamp, ui->cbTimestamp->isChecked());
 
    settings->setValue(SG_Record_Separator, ui->leSeparator->text());
 
    settings->endGroup();
 
}
 

	
 
void RecordPanel::loadSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_Record);
 
    ui->cbAutoIncrement->setChecked(
 
        settings->value(SG_Record_AutoIncrement, ui->cbAutoIncrement->isChecked()).toBool());
 
    ui->cbRecordPaused->setChecked(
 
        settings->value(SG_Record_RecordPaused, ui->cbRecordPaused->isChecked()).toBool());
 
    ui->cbStopOnClose->setChecked(
 
        settings->value(SG_Record_StopOnClose, ui->cbStopOnClose->isChecked()).toBool());
 
    ui->cbHeader->setChecked(
 
        settings->value(SG_Record_Header, ui->cbHeader->isChecked()).toBool());
 
    ui->cbDisableBuffering->setChecked(
 
        settings->value(SG_Record_DisableBuffering, ui->cbDisableBuffering->isChecked()).toBool());
 
    ui->cbTimestamp->setChecked(
 
        settings->value(SG_Record_Timestamp, ui->cbTimestamp->isChecked()).toBool());
 
    ui->leSeparator->setText(settings->value(SG_Record_Separator, ui->leSeparator->text()).toString());
 
    settings->endGroup();
 
}
src/recordpanel.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef RECORDPANEL_H
 
#define RECORDPANEL_H
 

	
 
#include <QWidget>
 
#include <QString>
 
#include <QToolBar>
 
#include <QAction>
 

	
 
#include "datarecorder.h"
 
#include "channelmanager.h"
 
#include "stream.h"
 

	
 
namespace Ui {
 
class RecordPanel;
 
}
 

	
 
class RecordPanel : public QWidget
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit RecordPanel(DataRecorder* recorder, ChannelManager* channelMan,
 
                         QWidget* parent = 0);
 
    explicit RecordPanel(Stream* stream, QWidget* parent = 0);
 
    ~RecordPanel();
 

	
 
    QToolBar* toolbar();
 

	
 
    bool isRecording();
 
    bool recordPaused();
 

	
 
    /// Stores settings into a `QSettings`
 
    void saveSettings(QSettings* settings);
 
    /// Loads settings from a `QSettings`.
 
    void loadSettings(QSettings* settings);
 

	
 
signals:
 
    void recordStarted();
 
    void recordStopped();
 
    void recordPausedChanged(bool enabled);
 

	
 
public slots:
 
    /// Must be called when port is closed
 
    void onPortClose();
 

	
 
private:
 
    Ui::RecordPanel *ui;
 
    QToolBar recordToolBar;
 
    QAction recordAction;
 
    QString selectedFile;
 
    bool overwriteSelected;
 
    DataRecorder* _recorder;
 
    ChannelManager* _channelMan;
 
    DataRecorder recorder;
 
    Stream* _stream;
 

	
 
    /**
 
     * @brief Increments the file name.
 
     *
 
     * If file name doesn't have a number at the end of it, a number is appended
 
     * with underscore starting from 1.
 
     *
 
     * @return false if user cancels
 
     */
 
    bool incrementFileName(void);
 

	
 
    /**
 
     * @brief Used to ask user confirmation if auto generated file
 
     * name exists.
 
     *
 
     * If user confirms overwrite, `selectedFile` is set to
 
     * `fileName`. User is also given option to select file and is
 
     * shown a file select dialog in this case.
 
     *
 
     * @param fileName auto generated file name.
 
     * @return false if user cancels
 
     */
 
    bool confirmOverwrite(QString fileName);
 

	
 
    void startRecording(void);
 
    /// Returns filename in edit box. May be invalid!
 
    QString selectedFile() const;
 
    /// Sets the filename in edit box.
 
    void setSelectedFile(QString f);
 

	
 
    /**
 
     * Tries to get a valid file name by handling user interactions and
 
     * automatic naming (increment, timestamp etc).
 
     *
 
     * Returned file name can be used immediately. File name box should also be
 
     * set to selected file name.
 
     *
 
     * @return empty if failure otherwise valid filename
 
     */
 
    QString getSelectedFile();
 

	
 
    /// Formats timestamp in given text
 
    QString formatTimeStamp(QString t) const;
 

	
 
    bool startRecording(QString fileName);
 
    void stopRecording(void);
 

	
 
    /// Returns separator text from ui. "\t" is converted to TAB
 
    /// character.
 
    QString getSeparator() const;
 

	
 
private slots:
 
    /**
 
     * @brief Opens up the file select dialog
 
     *
 
     * If you cancel the selection operation, currently selected file is not
 
     * changed.
 
     *
 
     * @return true if file selected, false if user cancels
 
     */
 
    bool selectFile();
 

	
 
    void onRecord(bool start);
 

	
 
};
 

	
 
#endif // RECORDPANEL_H
src/recordpanel.ui
Show inline comments
 
<?xml version="1.0" encoding="UTF-8"?>
 
<ui version="4.0">
 
 <class>RecordPanel</class>
 
 <widget class="QWidget" name="RecordPanel">
 
  <property name="geometry">
 
   <rect>
 
    <x>0</x>
 
    <y>0</y>
 
    <width>627</width>
 
    <height>261</height>
 
    <height>207</height>
 
   </rect>
 
  </property>
 
  <property name="windowTitle">
 
   <string>Form</string>
 
  </property>
 
  <layout class="QHBoxLayout" name="horizontalLayout_2">
 
   <item>
 
    <layout class="QVBoxLayout" name="verticalLayout">
 
     <item>
 
      <layout class="QHBoxLayout" name="horizontalLayout">
 
       <item>
 
        <widget class="QLineEdit" name="leFileName">
 
         <property name="sizePolicy">
 
          <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
 
           <horstretch>0</horstretch>
 
           <verstretch>0</verstretch>
 
          </sizepolicy>
 
         </property>
 
         <property name="toolTip">
 
          <string>You can use C `strftime` function format specifiers for timestamps in your file name.</string>
 
         </property>
 
         <property name="placeholderText">
 
          <string>Enter file name or browse</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item>
 
        <widget class="QPushButton" name="pbBrowse">
 
         <property name="toolTip">
 
          <string>Select record file</string>
 
         </property>
 
         <property name="text">
 
          <string>Browse</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item>
 
        <widget class="QLabel" name="lbFileName">
 
         <property name="sizePolicy">
 
          <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
 
           <horstretch>0</horstretch>
 
           <verstretch>0</verstretch>
 
          </sizepolicy>
 
         </property>
 
         <property name="text">
 
          <string>Select file...</string>
 
         </property>
 
        </widget>
 
       </item>
 
      </layout>
 
     </item>
 
     <item>
 
      <layout class="QGridLayout" name="gridLayout">
 
       <item row="2" column="1">
 
        <widget class="QCheckBox" name="cbStopOnClose">
 
         <property name="text">
 
          <string>Stop recording when port closed</string>
 
         </property>
 
         <property name="checked">
 
          <bool>true</bool>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="1" column="0">
 
        <widget class="QCheckBox" name="cbAutoIncrement">
 
         <property name="toolTip">
 
          <string>Increments file name automatically everytime a new recording starts</string>
 
         </property>
 
         <property name="text">
 
          <string>Auto increment file name</string>
 
         </property>
 
         <property name="checked">
 
          <bool>true</bool>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="3" column="0">
 
        <widget class="QCheckBox" name="cbDisableBuffering">
 
         <property name="toolTip">
 
          <string>Do not buffer when writing to file. Check this if you are using other software to open the file during recording.</string>
 
         </property>
 
         <property name="text">
 
          <string>Disable buffering</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="3" column="1">
 
        <widget class="QCheckBox" name="cbWindowsLE">
 
         <property name="toolTip">
 
          <string>Use CR+LF as line endings. Some windows software may not show lines correctly otherwise. Can't be changed during recording.</string>
 
         </property>
 
         <property name="text">
 
          <string>Windows Style Line Endings</string>
 
         </property>
 
         <property name="checked">
 
          <bool>false</bool>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="1" column="1">
 
        <widget class="QCheckBox" name="cbHeader">
 
         <property name="toolTip">
 
          <string>Channel names are written to the first line of record file</string>
 
         </property>
 
         <property name="text">
 
          <string>Write header line</string>
 
         </property>
 
         <property name="checked">
 
          <bool>true</bool>
 
         </property>
 
        </widget>
 
       </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>
 
       <item row="4" column="0">
 
        <widget class="QCheckBox" name="cbTimestamp">
 
         <property name="toolTip">
 
          <string>Insert timestamp (milliseconds from epoch) as first column</string>
 
         </property>
 
         <property name="text">
 
          <string>Insert timestamp</string>
 
         </property>
 
        </widget>
 
       </item>
 
      </layout>
 
     </item>
 
     <item>
 
      <layout class="QHBoxLayout" name="horizontalLayout_3">
 
       <item>
 
        <widget class="QLabel" name="label">
 
         <property name="text">
 
          <string>Column Separator:</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item>
 
        <widget class="QLineEdit" name="leSeparator">
 
         <property name="maximumSize">
 
          <size>
 
           <width>30</width>
 
           <height>16777215</height>
 
          </size>
 
         </property>
 
         <property name="toolTip">
 
          <string>For TAB character enter \t</string>
 
         </property>
 
         <property name="text">
 
          <string>,</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item>
 
        <spacer name="horizontalSpacer">
 
         <property name="orientation">
 
          <enum>Qt::Horizontal</enum>
 
         </property>
 
         <property name="sizeHint" stdset="0">
 
          <size>
 
           <width>40</width>
 
           <height>20</height>
 
          </size>
 
         </property>
 
        </spacer>
 
       </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>
 
   </item>
 
   <item>
 
    <layout class="QVBoxLayout" name="verticalLayout_2">
 
     <item>
 
      <widget class="QToolButton" name="pbRecord">
 
       <property name="minimumSize">
 
        <size>
 
         <width>85</width>
 
         <height>50</height>
 
        </size>
 
       </property>
 
       <property name="toolTip">
 
        <string>Start/Stop Recording</string>
 
       </property>
 
       <property name="text">
 
        <string>Record</string>
 
       </property>
 
      </widget>
 
     </item>
 
     <item>
 
      <spacer name="verticalSpacer_2">
 
       <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>
 
   </item>
 
  </layout>
 
 </widget>
 
 <resources/>
 
 <connections/>
 
</ui>
src/ringbuffer.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <QtGlobal>
 

	
 
#include "ringbuffer.h"
 

	
 
RingBuffer::RingBuffer(unsigned n)
 
{
 
    _size = n;
 
    data = new double[_size]();
 
    headIndex = 0;
 

	
 
    limInvalid = false;
 
    limCache = {0, 0};
 
}
 

	
 
RingBuffer::~RingBuffer()
 
{
 
    delete[] data;
 
}
 

	
 
unsigned RingBuffer::size() const
 
{
 
    return _size;
 
}
 

	
 
double RingBuffer::sample(unsigned i) const
 
{
 
    unsigned index = headIndex + i;
 
    if (index >= _size) index -= _size;
 
    return data[index];
 
}
 

	
 
Range RingBuffer::limits() const
 
{
 
    if (limInvalid) updateLimits();
 
    return limCache;
 
}
 

	
 
void RingBuffer::resize(unsigned n)
 
{
 
    Q_ASSERT(n != _size);
 

	
 
    int offset = (int) n - (int) _size;
 
    if (offset == 0) return;
 

	
 
    double* newData = new double[n];
 

	
 
    // move data to new array
 
    int fill_start = offset > 0 ? offset : 0;
 

	
 
    for (int i = fill_start; i < int(n); i++)
 
    {
 
        newData[i] = sample(i - offset);
 
    }
 

	
 
    // fill the beginning of the new data
 
    if (fill_start > 0)
 
    {
 
        for (int i = 0; i < fill_start; i++)
 
        {
 
            newData[i] = 0;
 
        }
 
    }
 

	
 
    // data is ready, clean up and re-point
 
    delete data;
 
    data = newData;
 
    headIndex = 0;
 
    _size = n;
 

	
 
    // invalidate bounding rectangle
 
    limInvalid = true;
 
}
 

	
 
void RingBuffer::addSamples(double* samples, unsigned n)
 
{
 
    unsigned shift = n;
 
    if (shift < _size)
 
    {
 
        unsigned x = _size - headIndex; // distance of `head` to end
 

	
 
        if (shift <= x) // there is enough room at the end of array
 
        {
 
            for (unsigned i = 0; i < shift; i++)
 
            {
 
                data[i+headIndex] = samples[i];
 
            }
 

	
 
            if (shift == x) // we used all the room at the end
 
            {
 
                headIndex = 0;
 
            }
 
            else
 
            {
 
                headIndex += shift;
 
            }
 
        }
 
        else // there isn't enough room
 
        {
 
            for (unsigned i = 0; i < x; i++) // fill the end part
 
            {
 
                data[i+headIndex] = samples[i];
 
            }
 
            for (unsigned i = 0; i < (shift-x); i++) // continue from the beginning
 
            {
 
                data[i] = samples[i+x];
 
            }
 
            headIndex = shift-x;
 
        }
 
    }
 
    else // number of new samples equal or bigger than current size (doesn't fit)
 
    {
 
        int x = shift - _size;
 
        for (unsigned i = 0; i < _size; i++)
 
        {
 
            data[i] = samples[i+x];
 
        }
 
        headIndex = 0;
 
    }
 

	
 
    // invalidate cache
 
    limInvalid = true;
 
}
 

	
 
void RingBuffer::clear()
 
{
 
    for (unsigned i=0; i < _size; i++)
 
    {
 
        data[i] = 0.;
 
    }
 

	
 
    limCache = {0, 0};
 
    limInvalid = false;
 
}
 

	
 
void RingBuffer::updateLimits() const
 
{
 
    limCache.start = data[0];
 
    limCache.end = data[0];
 

	
 
    for (unsigned i = 0; i < _size; i++)
 
    {
 
        if (data[i] > limCache.end)
 
        {
 
            limCache.end = data[i];
 
        }
 
        else if (data[i] < limCache.start)
 
        {
 
            limCache.start = data[i];
 
        }
 
    }
 

	
 
    limInvalid = false;
 
}
src/ringbuffer.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef RINGBUFFER_H
 
#define RINGBUFFER_H
 

	
 
#include "framebuffer.h"
 

	
 
/// A fast buffer implementation for storing data.
 
class RingBuffer : public WFrameBuffer
 
{
 
public:
 
    RingBuffer(unsigned n);
 
    ~RingBuffer();
 

	
 
    virtual unsigned size() const;
 
    virtual double sample(unsigned i) const;
 
    virtual Range limits() const;
 
    virtual void resize(unsigned n);
 
    virtual void addSamples(double* samples, unsigned n);
 
    virtual void clear();
 

	
 
private:
 
    unsigned _size;            ///< size of `data`
 
    double* data;              ///< storage
 
    unsigned headIndex;        ///< indicates the actual `0` index of the ring buffer
 

	
 
    mutable bool limInvalid;   ///< Indicates that limits needs to be re-calculated
 
    mutable Range limCache;    ///< Cache for limits()
 
    void updateLimits() const; ///< Updates limits cache
 
};
 

	
 
#endif
src/samplecounter.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <QDateTime>
 
#include "samplecounter.h"
 

	
 
SampleCounter::SampleCounter()
 
{
 
    prevTimeMs = QDateTime::currentMSecsSinceEpoch();
 
    count = 0;
 
}
 

	
 
#include <QtDebug>
 

	
 
void SampleCounter::feedIn(const SamplePack& data)
 
{
 
    count += data.numSamples();
 

	
 
    qint64 current = QDateTime::currentMSecsSinceEpoch();
 
    auto diff = current - prevTimeMs;
 
    if (diff > 1000) // 1sec
 
    {
 
        emit spsChanged(1000 * float(count) / diff);
 

	
 
        prevTimeMs = current;
 
        count = 0;
 
    }
 
}
src/samplecounter.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef SAMPLECOUNTER_H
 
#define SAMPLECOUNTER_H
 

	
 
#include <QObject>
 
#include "sink.h"
 

	
 
/// A `Sink` class for counting and reporting number of samples per second.
 
class SampleCounter : public QObject, public Sink
 
{
 
    Q_OBJECT
 

	
 
public:
 
    SampleCounter();
 

	
 
protected:
 
    // implementations for `Sink`
 
    virtual void feedIn(const SamplePack& data);
 

	
 
signals:
 
    /// Emitted per second if SPS value has changed.
 
    void spsChanged(float value);
 

	
 
private:
 
    qint64 prevTimeMs;
 
    unsigned count;
 
};
 

	
 
#endif // SAMPLECOUNTER_H
src/samplepack.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <cstring>
 
#include <QtGlobal>
 

	
 
#include "samplepack.h"
 

	
 
SamplePack::SamplePack(unsigned ns, unsigned nc, bool x)
 
{
 
    Q_ASSERT(ns > 0 && nc > 0);
 

	
 
    _numSamples = ns;
 
    _numChannels = nc;
 

	
 
    _yData = new double[_numSamples * _numChannels]();
 
    if (x)
 
    {
 
        _xData = new double[_numSamples]();
 
    }
 
    else
 
    {
 
        _xData = nullptr;
 
    }
 
}
 

	
 
SamplePack::SamplePack(const SamplePack& other) :
 
    SamplePack(other.numSamples(), other.numChannels(), other.hasX())
 
{
 
    size_t dataSize = sizeof(double) * numSamples();
 
    if (hasX())
 
        memcpy(xData(), other.xData(), dataSize);
 
    memcpy(_yData, other._yData, dataSize * numChannels());
 
}
 

	
 
SamplePack::~SamplePack()
 
{
 
    delete[] _yData;
 
    if (_xData != nullptr)
 
    {
 
        delete[] _xData;
 
    }
 
}
 

	
 
bool SamplePack::hasX() const
 
{
 
    return _xData != nullptr;
 
}
 

	
 
unsigned SamplePack::numChannels() const
 
{
 
    return _numChannels;
 
}
 

	
 
unsigned SamplePack::numSamples() const
 
{
 
    return _numSamples;
 
}
 

	
 
double* SamplePack::xData() const
 
{
 
    Q_ASSERT(_xData != nullptr);
 

	
 
    return _xData;
 
}
 

	
 
double* SamplePack::data(unsigned channel) const
 
{
 
    Q_ASSERT(channel < _numChannels);
 

	
 
    return &_yData[channel * _numSamples];
 
}
 

	
 
double* SamplePack::xData()
 
{
 
    return const_cast<double*>(static_cast<const SamplePack&>(*this).xData());
 
}
 

	
 
double* SamplePack::data(unsigned channel)
 
{
 
    return const_cast<double*>(static_cast<const SamplePack&>(*this).data(channel));
 
}
src/samplepack.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef SAMPLEPACK_H
 
#define SAMPLEPACK_H
 

	
 
class SamplePack
 
{
 
public:
 
    /**
 
     * @param ns number of samples
 
     * @param nc number of channels
 
     * @param x has X channel
 
     */
 
    SamplePack(unsigned ns, unsigned nc, bool x = false);
 
    SamplePack(const SamplePack& other);
 
    ~SamplePack();
 

	
 
    bool hasX() const;
 
    unsigned numChannels() const;
 
    unsigned numSamples() const;
 
    double* xData() const;
 
    double* data(unsigned channel) const;
 

	
 
    double* xData();
 
    double* data(unsigned channel);
 

	
 
private:
 
    unsigned _numSamples, _numChannels;
 
    double* _xData;
 
    double* _yData;
 
};
 

	
 
#endif // SAMPLEPACK_H
src/scrollzoomer.cpp
Show inline comments
 
/*
 
  Copyright © 2014 Uwe Rathmann
 

	
 
  This file is copied from Qwt project; you can redistribute it and/or modify it
 
  under the terms of the Qwt License, Version 1.0. You can obtain the original
 
  source code and the details of the Qwt License from the Qwt website:
 
  http://qwt.sourceforge.net/
 
*/
 

	
 
#include <qevent.h>
 
#include <qwt_plot_canvas.h>
 
#include <qwt_plot_layout.h>
 
#include <qwt_scale_engine.h>
 
#include <qwt_scale_widget.h>
 
#include "scrollbar.h"
 
#include "scrollzoomer.h"
 

	
 
class ScrollData
 
{
 
public:
 
    ScrollData():
 
        scrollBar( NULL ),
 
        position( ScrollZoomer::OppositeToScale ),
 
        mode( Qt::ScrollBarAsNeeded )
 
    {
 
    }
 

	
 
    ~ScrollData()
 
    {
 
        delete scrollBar;
 
    }
 

	
 
    ScrollBar *scrollBar;
 
    ScrollZoomer::ScrollBarPosition position;
 
    Qt::ScrollBarPolicy mode;
 
};
 

	
 
ScrollZoomer::ScrollZoomer( QWidget *canvas ):
 
    QwtPlotZoomer( canvas ),
 
    d_cornerWidget( NULL ),
 
    d_hScrollData( NULL ),
 
    d_vScrollData( NULL ),
 
    d_inZoom( false )
 
{
 
    xMin = 0.;
 
    xMax = 10000.;
 
    hViewSize = 10000;
 

	
 
    for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
 
        d_alignCanvasToScales[ axis ] = false;
 

	
 
    if ( !canvas )
 
        return;
 

	
 
    d_hScrollData = new ScrollData;
 
    d_vScrollData = new ScrollData;
 
    hscrollmove = false;
 
    vscrollmove = false;
 
}
 

	
 
ScrollZoomer::~ScrollZoomer()
 
{
 
    delete d_cornerWidget;
 
    delete d_vScrollData;
 
    delete d_hScrollData;
 
}
 

	
 
void ScrollZoomer::setXLimits(double min, double max)
 
{
 
    xMin = min;
 
    xMax = max;
 
    setZoomBase();
 
}
 

	
 
void ScrollZoomer::setHViewSize(double size)
 
{
 
    hscrollmove = true;
 
    hViewSize = size;
 
    setZoomBase();
 
    hscrollmove = false;
 
}
 

	
 
void ScrollZoomer::setZoomBase(bool doReplot)
 
{
 
    QwtPlotZoomer::setZoomBase(doReplot);
 
    auto zb = zoomBase();
 
    auto zs = zoomStack();
 
    zb.setRight(xMax);
 
    if ((xMax - xMin) < hViewSize)
 
    {
 
        zb.setLeft(xMin);
 
    }
 
    else
 
    {
 
        zb.setLeft(xMax-hViewSize);
 
    }
 
    zs[0] = zb;
 
    setZoomStack(zs);
 
}
 

	
 
void ScrollZoomer::rescale()
 
{
 
    QwtScaleWidget *xScale = plot()->axisWidget( xAxis() );
 
    QwtScaleWidget *yScale = plot()->axisWidget( yAxis() );
 

	
 
    if ( zoomRectIndex() <= 0 )
 
    {
 
        if ( d_inZoom )
 
        {
 
            xScale->setMinBorderDist( 0, 0 );
 
            yScale->setMinBorderDist( 0, 0 );
 

	
 
            QwtPlotLayout *layout = plot()->plotLayout();
 

	
 
            for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
 
                layout->setAlignCanvasToScale( axis, d_alignCanvasToScales[ axis ] );
 

	
 
            d_inZoom = false;
 
        }
 
    }
 
    else
 
    {
 
        if ( !d_inZoom )
 
        {
 
            /*
 
             We set a minimum border distance.
 
             Otherwise the canvas size changes when scrolling,
 
             between situations where the major ticks are at
 
             the canvas borders (requiring extra space for the label)
 
             and situations where all labels can be painted below/top
 
             or left/right of the canvas.
 
             */
 
            int start, end;
 

	
 
            xScale->getBorderDistHint( start, end );
 
            xScale->setMinBorderDist( start, end );
 

	
 
            yScale->getBorderDistHint( start, end );
 
            yScale->setMinBorderDist( start, end );
 

	
 
            QwtPlotLayout *layout = plot()->plotLayout();
 
            for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
 
            {
 
                d_alignCanvasToScales[axis] = 
 
                    layout->alignCanvasToScale( axis );
 
            }
 

	
 
            layout->setAlignCanvasToScales( false );
 

	
 
            d_inZoom = true;
 
        }
 
    }
 

	
 
    QwtPlotZoomer::rescale();
 
    // NOTE: Below snippet is copied from QwtPlotZoomer::rescale() just so that
 
    // we can refrain from updating y axis when moving horizontal scrollbar, so
 
    // that auto-scale isn't disrupted. Also we don't want to jump around in
 
    // x-axis when moving vertical scroll.
 
    {
 
        QwtPlot *plt = plot();
 
        if ( !plt )
 
            return;
 

	
 
        const QRectF &rect = zoomStack()[zoomRectIndex()];
 
        if ( rect != scaleRect() )
 
        {
 
            const bool doReplot = plt->autoReplot();
 
            plt->setAutoReplot( false );
 

	
 
            if (!vscrollmove)
 
            {
 
                double x1 = rect.left();
 
                double x2 = rect.right();
 
                if ( !plt->axisScaleDiv( xAxis() ).isIncreasing() )
 
                    qSwap( x1, x2 );
 

	
 
                plt->setAxisScale( xAxis(), x1, x2 );
 
            }
 

	
 
            if (!hscrollmove)
 
            {
 
                double y1 = rect.top();
 
                double y2 = rect.bottom();
 
                if ( !plt->axisScaleDiv( yAxis() ).isIncreasing() )
 
                    qSwap( y1, y2 );
 

	
 
                plt->setAxisScale( yAxis(), y1, y2 );
 

	
 
                plt->setAutoReplot( doReplot );
 
            }
 
            plt->replot();
 
        }
 
    }
 
    updateScrollBars();
 
}
 

	
 
ScrollBar *ScrollZoomer::scrollBar( Qt::Orientation orientation )
 
{
 
    ScrollBar *&sb = ( orientation == Qt::Vertical )
 
        ? d_vScrollData->scrollBar : d_hScrollData->scrollBar;
 

	
 
    if ( sb == NULL )
 
    {
 
        sb = new ScrollBar( orientation, canvas() );
 
        sb->hide();
 
        connect( sb,
 
            SIGNAL( valueChanged( Qt::Orientation, double, double ) ),
 
            SLOT( scrollBarMoved( Qt::Orientation, double, double ) ) );
 
    }
 
    return sb;
 
}
 

	
 
ScrollBar *ScrollZoomer::horizontalScrollBar() const
 
{
 
    return d_hScrollData->scrollBar;
 
}
 

	
 
ScrollBar *ScrollZoomer::verticalScrollBar() const
 
{
 
    return d_vScrollData->scrollBar;
 
}
 

	
 
void ScrollZoomer::setHScrollBarMode( Qt::ScrollBarPolicy mode )
 
{
 
    if ( hScrollBarMode() != mode )
 
    {
 
        d_hScrollData->mode = mode;
 
        updateScrollBars();
 
    }
 
}
 

	
 
void ScrollZoomer::setVScrollBarMode( Qt::ScrollBarPolicy mode )
 
{
 
    if ( vScrollBarMode() != mode )
 
    {
 
        d_vScrollData->mode = mode;
 
        updateScrollBars();
 
    }
 
}
 

	
 
Qt::ScrollBarPolicy ScrollZoomer::hScrollBarMode() const
 
{
 
    return d_hScrollData->mode;
 
}
 

	
 
Qt::ScrollBarPolicy ScrollZoomer::vScrollBarMode() const
 
{
 
    return d_vScrollData->mode;
 
}
 

	
 
void ScrollZoomer::setHScrollBarPosition( ScrollBarPosition pos )
 
{
 
    if ( d_hScrollData->position != pos )
 
    {
 
        d_hScrollData->position = pos;
 
        updateScrollBars();
 
    }
 
}
 

	
 
void ScrollZoomer::setVScrollBarPosition( ScrollBarPosition pos )
 
{
 
    if ( d_vScrollData->position != pos )
 
    {
 
        d_vScrollData->position = pos;
 
        updateScrollBars();
 
    }
 
}
 

	
 
ScrollZoomer::ScrollBarPosition ScrollZoomer::hScrollBarPosition() const
 
{
 
    return d_hScrollData->position;
 
}
 

	
 
ScrollZoomer::ScrollBarPosition ScrollZoomer::vScrollBarPosition() const
 
{
 
    return d_vScrollData->position;
 
}
 

	
 
void ScrollZoomer::setCornerWidget( QWidget *w )
 
{
 
    if ( w != d_cornerWidget )
 
    {
 
        if ( canvas() )
 
        {
 
            delete d_cornerWidget;
 
            d_cornerWidget = w;
 
            if ( d_cornerWidget->parent() != canvas() )
 
                d_cornerWidget->setParent( canvas() );
 

	
 
            updateScrollBars();
 
        }
 
    }
 
}
 

	
 
QWidget *ScrollZoomer::cornerWidget() const
 
{
 
    return d_cornerWidget;
 
}
 

	
 
bool ScrollZoomer::eventFilter( QObject *object, QEvent *event )
 
{
 
    if ( object == canvas() )
 
    {
 
        switch( event->type() )
 
        {
 
            case QEvent::Resize:
 
            {
 
                int left, top, right, bottom;
 
                canvas()->getContentsMargins( &left, &top, &right, &bottom );
 

	
 
                QRect rect;
 
                rect.setSize( static_cast<QResizeEvent *>( event )->size() );
 
                rect.adjust( left, top, -right, -bottom );
 

	
 
                layoutScrollBars( rect );
 
                break;
 
            }
 
            case QEvent::Show:
 
            {
 
                layoutScrollBars( canvas()->contentsRect() );
 
                break;
 
            }
 
            case QEvent::ChildRemoved:
 
            {
 
                const QObject *child =
 
                    static_cast<QChildEvent *>( event )->child();
 

	
 
                if ( child == d_cornerWidget )
 
                    d_cornerWidget = NULL;
 
                else if ( child == d_hScrollData->scrollBar )
 
                    d_hScrollData->scrollBar = NULL;
 
                else if ( child == d_vScrollData->scrollBar )
 
                    d_vScrollData->scrollBar = NULL;
 
                break;
 
            }
 
            default:
 
                break;
 
        }
 
    }
 
    return QwtPlotZoomer::eventFilter( object, event );
 
}
 

	
 
bool ScrollZoomer::needScrollBar( Qt::Orientation orientation ) const
 
{
 
    Qt::ScrollBarPolicy mode;
 
    double zoomMin, zoomMax, baseMin, baseMax;
 

	
 
    if ( orientation == Qt::Horizontal )
 
    {
 
        mode = d_hScrollData->mode;
 
        baseMin = zoomBase().left();
 
        baseMax = zoomBase().right();
 
        baseMin = xMin;
 
        baseMax = xMax;
 
        zoomMin = zoomRect().left();
 
        zoomMax = zoomRect().right();
 
    }
 
    else
 
    {
 
        mode = d_vScrollData->mode;
 
        baseMin = zoomBase().top();
 
        baseMax = zoomBase().bottom();
 
        zoomMin = zoomRect().top();
 
        zoomMax = zoomRect().bottom();
 
    }
 

	
 
    bool needed = false;
 
    switch( mode )
 
    {
 
        case Qt::ScrollBarAlwaysOn:
 
            needed = true;
 
            break;
 
        case Qt::ScrollBarAlwaysOff:
 
            needed = false;
 
            break;
 
        default:
 
        {
 
            if ( baseMin < zoomMin || baseMax > zoomMax )
 
                needed = true;
 
            break;
 
        }
 
    }
 
    return needed;
 
}
 

	
 
void ScrollZoomer::updateScrollBars()
 
{
 
    if ( !canvas() )
 
        return;
 

	
 
    const int xAxis = QwtPlotZoomer::xAxis();
 
    const int yAxis = QwtPlotZoomer::yAxis();
 

	
 
    int xScrollBarAxis = xAxis;
 
    if ( hScrollBarPosition() == OppositeToScale )
 
        xScrollBarAxis = oppositeAxis( xScrollBarAxis );
 

	
 
    int yScrollBarAxis = yAxis;
 
    if ( vScrollBarPosition() == OppositeToScale )
 
        yScrollBarAxis = oppositeAxis( yScrollBarAxis );
 

	
 

	
 
    QwtPlotLayout *layout = plot()->plotLayout();
 

	
 
    bool showHScrollBar = needScrollBar( Qt::Horizontal );
 
    if ( showHScrollBar )
 
    {
 
        ScrollBar *sb = scrollBar( Qt::Horizontal );
 
        sb->setPalette( plot()->palette() );
 
        sb->setInverted( !plot()->axisScaleDiv( xAxis ).isIncreasing() );
 
        sb->setBase( zoomBase().left(), zoomBase().right() );
 
        sb->setBase( xMin, xMax );
 
        sb->moveSlider( zoomRect().left(), zoomRect().right() );
 

	
 
        if ( !sb->isVisibleTo( canvas() ) )
 
        {
 
            sb->show();
 
            layout->setCanvasMargin( layout->canvasMargin( xScrollBarAxis )
 
                + sb->extent(), xScrollBarAxis );
 
        }
 
    }
 
    else
 
    {
 
        if ( horizontalScrollBar() )
 
        {
 
            horizontalScrollBar()->hide();
 
            layout->setCanvasMargin( layout->canvasMargin( xScrollBarAxis )
 
                - horizontalScrollBar()->extent(), xScrollBarAxis );
 
        }
 
    }
 

	
 
    bool showVScrollBar = needScrollBar( Qt::Vertical );
 
    if ( showVScrollBar )
 
    {
 
        ScrollBar *sb = scrollBar( Qt::Vertical );
 
        sb->setPalette( plot()->palette() );
 
        sb->setInverted( plot()->axisScaleDiv( yAxis ).isIncreasing() );
 
        sb->setBase( zoomBase().top(), zoomBase().bottom() );
 
        sb->moveSlider( zoomRect().top(), zoomRect().bottom() );
 

	
 
        if ( !sb->isVisibleTo( canvas() ) )
 
        {
 
            sb->show();
 
            layout->setCanvasMargin( layout->canvasMargin( yScrollBarAxis )
 
                + sb->extent(), yScrollBarAxis );
 
        }
 
    }
 
    else
 
    {
 
        if ( verticalScrollBar() )
 
        {
 
            verticalScrollBar()->hide();
 
            layout->setCanvasMargin( layout->canvasMargin( yScrollBarAxis )
 
                - verticalScrollBar()->extent(), yScrollBarAxis );
 
        }
 
    }
 

	
 
    if ( showHScrollBar && showVScrollBar )
 
    {
 
        if ( d_cornerWidget == NULL )
 
        {
 
            d_cornerWidget = new QWidget( canvas() );
 
            d_cornerWidget->setAutoFillBackground( true );
 
            d_cornerWidget->setPalette( plot()->palette() );
 
        }
 
        d_cornerWidget->show();
 
    }
 
    else
 
    {
 
        if ( d_cornerWidget )
 
            d_cornerWidget->hide();
 
    }
 

	
 
    layoutScrollBars( canvas()->contentsRect() );
 
    plot()->updateLayout();
 
}
 

	
 
void ScrollZoomer::layoutScrollBars( const QRect &rect )
 
{
 
    int hPos = xAxis();
 
    if ( hScrollBarPosition() == OppositeToScale )
 
        hPos = oppositeAxis( hPos );
 

	
 
    int vPos = yAxis();
 
    if ( vScrollBarPosition() == OppositeToScale )
 
        vPos = oppositeAxis( vPos );
 

	
 
    ScrollBar *hScrollBar = horizontalScrollBar();
 
    ScrollBar *vScrollBar = verticalScrollBar();
 

	
 
    const int hdim = hScrollBar ? hScrollBar->extent() : 0;
 
    const int vdim = vScrollBar ? vScrollBar->extent() : 0;
 

	
 
    if ( hScrollBar && hScrollBar->isVisible() )
 
    {
 
        int x = rect.x();
 
        int y = ( hPos == QwtPlot::xTop )
 
            ? rect.top() : rect.bottom() - hdim + 1;
 
        int w = rect.width();
 

	
 
        if ( vScrollBar && vScrollBar->isVisible() )
 
        {
 
            if ( vPos == QwtPlot::yLeft )
 
                x += vdim;
 
            w -= vdim;
 
        }
 

	
 
        hScrollBar->setGeometry( x, y, w, hdim );
 
    }
 
    if ( vScrollBar && vScrollBar->isVisible() )
 
    {
 
        int pos = yAxis();
 
        if ( vScrollBarPosition() == OppositeToScale )
 
            pos = oppositeAxis( pos );
 

	
 
        int x = ( vPos == QwtPlot::yLeft )
 
            ? rect.left() : rect.right() - vdim + 1;
 
        int y = rect.y();
 

	
 
        int h = rect.height();
 

	
 
        if ( hScrollBar && hScrollBar->isVisible() )
 
        {
 
            if ( hPos == QwtPlot::xTop )
 
                y += hdim;
 

	
 
            h -= hdim;
 
        }
 

	
 
        vScrollBar->setGeometry( x, y, vdim, h );
 
    }
 
    if ( hScrollBar && hScrollBar->isVisible() &&
 
        vScrollBar && vScrollBar->isVisible() )
 
    {
 
        if ( d_cornerWidget )
 
        {
 
            QRect cornerRect(
 
                vScrollBar->pos().x(), hScrollBar->pos().y(),
 
                vdim, hdim );
 
            d_cornerWidget->setGeometry( cornerRect );
 
        }
 
    }
 
}
 

	
 
void ScrollZoomer::scrollBarMoved(
 
    Qt::Orientation o, double min, double max )
 
{
 
    Q_UNUSED( max );
 

	
 
    if ( o == Qt::Horizontal )
 
    {
 
        hscrollmove = true;
 
        moveTo( QPointF( min, zoomRect().top() ) );
 
        hscrollmove = false;
 
    }
 
    else
 
    {
 
        vscrollmove = true;
 
        moveTo( QPointF( zoomRect().left(), min ) );
 
        vscrollmove = false;
 
    }
 

	
 
    Q_EMIT zoomed( zoomRect() );
 
}
 

	
 
int ScrollZoomer::oppositeAxis( int axis ) const
 
{
 
    switch( axis )
 
    {
 
        case QwtPlot::xBottom:
 
            return QwtPlot::xTop;
 
        case QwtPlot::xTop:
 
            return QwtPlot::xBottom;
 
        case QwtPlot::yLeft:
 
            return QwtPlot::yRight;
 
        case QwtPlot::yRight:
 
            return QwtPlot::yLeft;
 
        default:
 
            break;
 
    }
 

	
 
    return axis;
 
}
 

	
 
void ScrollZoomer::moveTo( const QPointF &pos )
 
{
 
    // QwtPlotZoomer::moveTo(pos);
 
    // return;
 

	
 
    double x = pos.x();
 
    double y = pos.y();
 

	
 
    if ( x < xMin )
 
        x = xMin;
 
    if ( x > xMax - zoomRect().width() )
 
        x = xMax - zoomRect().width();
 

	
 
    if ( y < zoomBase().top() )
 
        y = zoomBase().top();
 
    if ( y > zoomBase().bottom() - zoomRect().height() )
 
        y = zoomBase().bottom() - zoomRect().height();
 

	
 
    if ( x != zoomRect().left() || y != zoomRect().top() )
 
    {
 
        auto zs = zoomStack();
 
        zs[zoomRectIndex()].moveTo( x, y );
 
        setZoomStack(zs, zoomRectIndex());
 
        rescale();
 
    }
 
}
src/scrollzoomer.h
Show inline comments
 
/*
 
  Copyright © 2014 Uwe Rathmann
 

	
 
  This file is copied from Qwt project; you can redistribute it and/or modify it
 
  under the terms of the Qwt License, Version 1.0. You can obtain the original
 
  source code and the details of the Qwt License from the Qwt website:
 
  http://qwt.sourceforge.net/
 
*/
 

	
 
#ifndef _SCROLLZOOMER_H
 
#define _SCROLLZOOMER_H
 

	
 
#include <qglobal.h>
 
#include <qwt_plot_zoomer.h>
 
#include <qwt_plot.h>
 

	
 
class ScrollData;
 
class ScrollBar;
 

	
 
class ScrollZoomer: public QwtPlotZoomer
 
{
 
    Q_OBJECT
 
public:
 
    enum ScrollBarPosition
 
    {
 
        AttachedToScale,
 
        OppositeToScale
 
    };
 

	
 
    ScrollZoomer( QWidget * );
 
    virtual ~ScrollZoomer();
 

	
 
    ScrollBar *horizontalScrollBar() const;
 
    ScrollBar *verticalScrollBar() const;
 

	
 
    void setHScrollBarMode( Qt::ScrollBarPolicy );
 
    void setVScrollBarMode( Qt::ScrollBarPolicy );
 

	
 
    Qt::ScrollBarPolicy vScrollBarMode () const;
 
    Qt::ScrollBarPolicy hScrollBarMode () const;
 

	
 
    void setHScrollBarPosition( ScrollBarPosition );
 
    void setVScrollBarPosition( ScrollBarPosition );
 

	
 
    ScrollBarPosition hScrollBarPosition() const;
 
    ScrollBarPosition vScrollBarPosition() const;
 

	
 
    QWidget* cornerWidget() const;
 
    virtual void setCornerWidget( QWidget * );
 

	
 
    virtual bool eventFilter( QObject *, QEvent * );
 

	
 
    void setXLimits(double min, double max);
 
    void setHViewSize(double size);
 
    virtual void setZoomBase(bool doReplot = true);
 
    virtual void rescale();
 

	
 
public Q_SLOTS:
 
    virtual void moveTo( const QPointF & );
 

	
 
protected:
 
    virtual ScrollBar *scrollBar( Qt::Orientation );
 
    virtual void updateScrollBars();
 
    virtual void layoutScrollBars( const QRect & );
 

	
 
private Q_SLOTS:
 
    void scrollBarMoved( Qt::Orientation o, double min, double max );
 

	
 
private:
 
    QRectF d_limits;
 
    double xMin, xMax;
 
    double hViewSize;
 

	
 
    bool needScrollBar( Qt::Orientation ) const;
 
    int oppositeAxis( int ) const;
 

	
 
    QWidget *d_cornerWidget;
 

	
 
    ScrollData *d_hScrollData;
 
    ScrollData *d_vScrollData;
 

	
 
    bool d_inZoom;
 
    bool d_alignCanvasToScales[ QwtPlot::axisCnt ];
 
    bool hscrollmove;
 
    bool vscrollmove;
 
};
 

	
 
#endif
src/setting_defines.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef SETTING_DEFINES_H
 
#define SETTING_DEFINES_H
 

	
 
const char SettingGroup_MainWindow[] = "MainWindow";
 
const char SettingGroup_Port[] = "Port";
 
const char SettingGroup_DataFormat[] = "DataFormat";
 
const char SettingGroup_Binary[] = "DataFormat_Binary";
 
const char SettingGroup_ASCII[] = "DataFormat_ASCII";
 
const char SettingGroup_CustomFrame[] = "DataFormat_CustomFrame";
 
const char SettingGroup_Channels[] = "Channels";
 
const char SettingGroup_Plot[] = "Plot";
 
const char SettingGroup_Commands[] = "Commands";
 
const char SettingGroup_Record[] = "Record";
 
const char SettingGroup_UpdateCheck[] = "UpdateCheck";
 

	
 
// mainwindow setting keys
 
const char SG_MainWindow_Size[] = "size";
 
const char SG_MainWindow_Pos[] = "pos";
 
const char SG_MainWindow_ActivePanel[] = "activePanel";
 
const char SG_MainWindow_HidePanels[] = "hidePanels";
 
const char SG_MainWindow_Maximized[] = "maximized";
 
const char SG_MainWindow_State[] = "state";
 

	
 
// port setting keys
 
const char SG_Port_SelectedPort[] = "selectedPort";
 
const char SG_Port_BaudRate[] = "baudRate";
 
const char SG_Port_Parity[] = "parity";
 
const char SG_Port_DataBits[] = "dataBits";
 
const char SG_Port_StopBits[] = "stopBits";
 
const char SG_Port_FlowControl[] = "flowControl";
 

	
 
// data format panel keys
 
const char SG_DataFormat_Format[] = "format";
 

	
 
// binary stream reader keys
 
const char SG_Binary_NumOfChannels[] = "numOfChannels";
 
const char SG_Binary_NumberFormat[] = "numberFormat";
 
const char SG_Binary_Endianness[] = "endianness";
 

	
 
// ascii reader keys
 
const char SG_ASCII_NumOfChannels[] = "numOfChannels";
 
const char SG_ASCII_Delimiter[] = "delimiter";
 
const char SG_ASCII_CustomDelimiter[] = "customDelimiter";
 

	
 
// framed reader keys
 
const char SG_CustomFrame_NumOfChannels[] = "numOfChannels";
 
const char SG_CustomFrame_FrameStart[] = "frameStart";
 
const char SG_CustomFrame_FixedSize[] = "fixedSize";
 
const char SG_CustomFrame_FrameSize[] = "frameSize";
 
const char SG_CustomFrame_NumberFormat[] = "numberFormat";
 
const char SG_CustomFrame_Endianness[] = "endianness";
 
const char SG_CustomFrame_Checksum[] = "checksum";
 
const char SG_CustomFrame_DebugMode[] = "debugMode";
 

	
 
// channel manager keys
 
// channel info keys
 
const char SG_Channels_Channel[] = "channel";
 
const char SG_Channels_Name[] = "name";
 
const char SG_Channels_Color[] = "color";
 
const char SG_Channels_Visible[] = "visible";
 
const char SG_Channels_Gain[] = "gain";
 
const char SG_Channels_GainEn[] = "gainEnabled";
 
const char SG_Channels_Offset[] = "offset";
 
const char SG_Channels_OffsetEn[] = "offsetEnabled";
 

	
 
// plot settings keys
 
const char SG_Plot_NumOfSamples[] = "numOfSamples";
 
const char SG_Plot_PlotWidth[] = "plotWidth";
 
const char SG_Plot_IndexAsX[] = "indexAsX";
 
const char SG_Plot_XMax[] = "xMax";
 
const char SG_Plot_XMin[] = "xMin";
 
const char SG_Plot_AutoScale[] = "autoScale";
 
const char SG_Plot_YMax[] = "yMax";
 
const char SG_Plot_YMin[] = "yMin";
 
const char SG_Plot_DarkBackground[] = "darkBackground";
 
const char SG_Plot_Grid[] = "grid";
 
const char SG_Plot_MinorGrid[] = "minorGrid";
 
const char SG_Plot_Legend[] = "legend";
 
const char SG_Plot_MultiPlot[] = "multiPlot";
 
const char SG_Plot_Symbols[] = "symbols";
 

	
 
// command setting keys
 
const char SG_Commands_Command[] = "command";
 
const char SG_Commands_Name[] = "name";
 
const char SG_Commands_Type[] = "type";
 
const char SG_Commands_Data[] = "data";
 

	
 
// record panel settings keys
 
const char SG_Record_AutoIncrement[]    = "autoIncrement";
 
const char SG_Record_RecordPaused[]     = "recordPaused";
 
const char SG_Record_StopOnClose[]      = "stopOnClose";
 
const char SG_Record_Header[]           = "header";
 
const char SG_Record_Separator[]        = "separator";
 
const char SG_Record_DisableBuffering[] = "disableBuffering";
 
const char SG_Record_Timestamp[]        = "timestamp";
 

	
 
// update check settings keys
 
const char SG_UpdateCheck_Periodic[]  = "periodicCheck";
 
const char SG_UpdateCheck_LastCheck[] = "lastCheck";
 

	
 
#endif // SETTING_DEFINES_H
src/sink.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <QtGlobal>
 
#include "sink.h"
 

	
 
void Sink::connectFollower(Sink* sink)
 
{
 
    Q_ASSERT(!followers.contains(sink));
 

	
 
    followers.append(sink);
 
    sink->setNumChannels(_numChannels, _hasX);
 
}
 

	
 
void Sink::disconnectFollower(Sink* sink)
 
{
 
    Q_ASSERT(followers.contains(sink));
 

	
 
    followers.removeOne(sink);
 
}
 

	
 
void Sink::feedIn(const SamplePack& data)
 
{
 
    for (auto sink : followers)
 
    {
 
        sink->feedIn(data);
 
    }
 
}
 

	
 
void Sink::setNumChannels(unsigned nc, bool x)
 
{
 
    _numChannels = nc;
 
    _hasX = x;
 
    for (auto sink : followers)
 
    {
 
        sink->setNumChannels(nc, x);
 
    }
 
}
 

	
 
void Sink::setSource(Source* s)
 
{
 
    Q_ASSERT((source == nullptr) != (s == nullptr));
 
    source = s;
 
}
 

	
 
const Source* Sink::connectedSource() const
 
{
 
    return source;
 
}
 

	
 
Source* Sink::connectedSource()
 
{
 
    return const_cast<Source*>(static_cast<const Sink&>(*this).connectedSource());
 
}
src/sink.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef SINK_H
 
#define SINK_H
 

	
 
#include <QList>
 
#include "samplepack.h"
 

	
 
class Source;
 

	
 
class Sink
 
{
 
public:
 
    /// Placeholder virtual destructor
 
    virtual ~Sink() {};
 

	
 
    /// Connects a sink to get any data that this sink
 
    /// gets. Connecting an already connected sink is an error.
 
    void connectFollower(Sink* sink);
 

	
 
    /// Disconnects a follower. Disconnecting an unconnected sink is
 
    /// an error.
 
    void disconnectFollower(Sink* sink);
 

	
 
    /// Returns the connected source. `nullptr` if it's not connected.
 
    const Source* connectedSource() const;
 
    Source* connectedSource();
 

	
 
protected:
 
    /// Entry point for incoming data. Re-implementations should
 
    /// call this function to feed followers.
 
    virtual void feedIn(const SamplePack& data);
 

	
 
    /// Is set by connected source. Re-implementations should call
 
    /// this function to update followers.
 
    virtual void setNumChannels(unsigned nc, bool x);
 

	
 
    /// Set by the connected source when its connected. When
 
    /// disconnecting it's set to `nullptr`.
 
    ///
 
    /// @note Previous source is disconnected.
 
    ///
 
    /// @important Trying to connect a source while its already
 
    /// connected is an error.
 
    void setSource(Source* s);
 

	
 
    friend Source;
 

	
 
private:
 
    QList<Sink*> followers;
 
    Source* source = nullptr;   ///< source that this sink is connected to
 
    bool _hasX;
 
    unsigned _numChannels;
 
};
 

	
 
#endif // SINK_H
src/snapshot.cpp
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <stddef.h>
 
#include <QSaveFile>
 
#include <QTextStream>
 

	
 
#include "mainwindow.h"
 
#include "snapshot.h"
 
#include "snapshotview.h"
 

	
 
Snapshot::Snapshot(MainWindow* parent, QString name, ChannelInfoModel infoModel) :
 
Snapshot::Snapshot(MainWindow* parent, QString name, ChannelInfoModel infoModel, bool saved) :
 
    QObject(parent),
 
    cInfoModel(infoModel),
 
    _showAction(this),
 
    _deleteAction("&Delete", this)
 
{
 
    _name = name;
 
    _saved = false;
 
    _saved = saved;
 

	
 
    view = NULL;
 
    mainWindow = parent;
 
    _showAction.setText(displayName());
 
    connect(&_showAction, &QAction::triggered, this, &Snapshot::show);
 

	
 
    _deleteAction.setToolTip(QString("Delete ") + _name);
 
    connect(&_deleteAction, &QAction::triggered, this, &Snapshot::onDeleteTriggered);
 
}
 

	
 
Snapshot::~Snapshot()
 
{
 
    if (view != NULL)
 
    {
 
        delete view;
 
    }
 
}
 

	
 
QAction* Snapshot::showAction()
 
{
 
    return &_showAction;
 
}
 

	
 
QAction* Snapshot::deleteAction()
 
{
 
    return &_deleteAction;
 
}
 

	
 
void Snapshot::show()
 
{
 
    if (view == NULL)
 
    {
 
        view = new SnapshotView(mainWindow, this);
 
        connect(view, &SnapshotView::closed, this, &Snapshot::viewClosed);
 
    }
 
    view->show();
 
    view->activateWindow();
 
    view->raise();
 
}
 

	
 
void Snapshot::viewClosed()
 
{
 
    view->deleteLater();
 
    view = NULL;
 
}
 

	
 
void Snapshot::onDeleteTriggered()
 
{
 
    emit deleteRequested(this);
 
}
 

	
 
QString Snapshot::name()
 
{
 
    return _name;
 
}
 

	
 
QString Snapshot::displayName()
 
{
 
    if (_saved)
 
    {
 
        return name();
 
    }
 
    else
 
    {
 
        return name() + "*";
 
    }
 
}
 

	
 
void Snapshot::setName(QString name)
 
{
 
    _name = name;
 
    _showAction.setText(_name);
 
    emit nameChanged(this);
 
}
 

	
 
unsigned Snapshot::numChannels() const
 
{
 
    return yData.size();
 
}
 

	
 
unsigned Snapshot::numSamples() const
 
{
 
    return yData[0]->size();
 
}
 

	
 
const ChannelInfoModel* Snapshot::infoModel() const
 
{
 
    return &cInfoModel;
 
}
 

	
 
ChannelInfoModel* Snapshot::infoModel()
 
{
 
    return &cInfoModel;
 
    return const_cast<ChannelInfoModel*>(static_cast<const Snapshot&>(*this).infoModel());
 
}
 

	
 
QString Snapshot::channelName(unsigned channel)
 
{
 
    return cInfoModel.name(channel);
 
}
 

	
 
void Snapshot::save(QString fileName)
 
{
 
    // TODO: remove code duplication (MainWindow::onExportCsv)
 
    QSaveFile file(fileName);
 

	
 
    if (file.open(QIODevice::WriteOnly | QIODevice::Text))
 
    {
 
        QTextStream fileStream(&file);
 

	
 
        unsigned numOfChannels = data.size();
 
        unsigned numOfSamples = data[0].size();
 

	
 
        // print header
 
        for (unsigned int ci = 0; ci < numOfChannels; ci++)
 
        for (unsigned int ci = 0; ci < numChannels(); ci++)
 
        {
 
            fileStream << channelName(ci);
 
            if (ci != numOfChannels-1) fileStream << ",";
 
            if (ci != numChannels()-1) fileStream << ",";
 
        }
 
        fileStream << '\n';
 

	
 
        // print rows
 
        for (unsigned int i = 0; i < numOfSamples; i++)
 
        for (unsigned int i = 0; i < numSamples(); i++)
 
        {
 
            for (unsigned int ci = 0; ci < numOfChannels; ci++)
 
            for (unsigned int ci = 0; ci < numChannels(); ci++)
 
            {
 
                fileStream << data[ci][i].y();
 
                if (ci != numOfChannels-1) fileStream << ",";
 
                fileStream << yData[ci]->sample(i);
 
                if (ci != numChannels()-1) fileStream << ",";
 
            }
 
            fileStream << '\n';
 
        }
 

	
 
        if (!file.commit())
 
        {
 
            qCritical() << "File save error during snapshot save: " << file.error();
 
        }
 
        else
 
        {
 
            _saved = true;
 
            _showAction.setText(displayName());
 
        }
 
    }
 
    else
 
    {
 
        qCritical() << "File open error during snapshot save: " << file.error();
 
    }
 
}
 

	
 
bool Snapshot::isSaved()
 
{
 
    return _saved;
 
}
src/snapshot.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef SNAPSHOT_H
 
#define SNAPSHOT_H
 

	
 
#include <QObject>
 
#include <QAction>
 
#include <QVector>
 
#include <QString>
 
#include <QStringList>
 

	
 
#include "channelinfomodel.h"
 
#include "readonlybuffer.h"
 

	
 
class SnapshotView;
 
class MainWindow;
 

	
 
class Snapshot : public QObject
 
{
 
    Q_OBJECT
 

	
 
public:
 
    Snapshot(MainWindow* parent, QString name, ChannelInfoModel infoModel);
 
    Snapshot(MainWindow* parent, QString name, ChannelInfoModel infoModel, bool saved = false);
 
    ~Snapshot();
 

	
 
    QVector<QVector<QPointF>> data;
 
    // TODO: yData of snapshot shouldn't be public, preferable should be handled in constructor
 
    QVector<ReadOnlyBuffer*> yData;
 
    QAction* showAction();
 
    QAction* deleteAction();
 

	
 
    QString name();
 
    QString displayName(); ///< `name()` plus '*' if snapshot is not saved
 
    unsigned numChannels() const; ///< number of channels in this snapshot
 
    unsigned numSamples() const;  ///< number of samples in every channel
 
    const ChannelInfoModel* infoModel() const;
 
    ChannelInfoModel* infoModel();
 
    void setName(QString name);
 
    QString channelName(unsigned channel);
 

	
 
    void save(QString fileName); ///< save snapshot data as CSV
 
    bool isSaved(); ///< snapshot has been saved at least once
 

	
 
signals:
 
    void deleteRequested(Snapshot*);
 
    void nameChanged(Snapshot*);
 

	
 
private:
 
    QString _name;
 
    ChannelInfoModel cInfoModel;
 
    QAction _showAction;
 
    QAction _deleteAction;
 
    MainWindow* mainWindow;
 
    SnapshotView* view;
 
    bool _saved;
 

	
 
private slots:
 
    void show();
 
    void viewClosed();
 

	
 
    void onDeleteTriggered();
 
};
 

	
 
#endif /* SNAPSHOT_H */
src/snapshotmanager.cpp
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <QTime>
 
#include <QMenuBar>
 
#include <QKeySequence>
 
#include <QFileDialog>
 
#include <QFile>
 
#include <QTextStream>
 
#include <QVector>
 
#include <QPointF>
 
#include <QIcon>
 
#include <QtDebug>
 

	
 
#include "mainwindow.h"
 
#include "snapshotmanager.h"
 

	
 
SnapshotManager::SnapshotManager(MainWindow* mainWindow,
 
                                 ChannelManager* channelMan) :
 
                                 Stream* stream) :
 
    _menu("&Snapshots"),
 
    _takeSnapshotAction("&Take Snapshot", this),
 
    loadSnapshotAction("&Load Snapshots", this),
 
    clearAction("&Clear Snapshots", this)
 
{
 
    _mainWindow = mainWindow;
 
    _channelMan = channelMan;
 
    _stream = stream;
 

	
 
    _takeSnapshotAction.setToolTip("Take a snapshot of current plot");
 
    _takeSnapshotAction.setShortcut(QKeySequence("F5"));
 
    _takeSnapshotAction.setIcon(QIcon::fromTheme("camera"));
 
    loadSnapshotAction.setToolTip("Load snapshots from CSV files");
 
    clearAction.setToolTip("Delete all snapshots");
 
    connect(&_takeSnapshotAction, SIGNAL(triggered(bool)),
 
            this, SLOT(takeSnapshot()));
 
    connect(&clearAction, SIGNAL(triggered(bool)),
 
            this, SLOT(clearSnapshots()));
 
    connect(&loadSnapshotAction, SIGNAL(triggered(bool)),
 
            this, SLOT(loadSnapshots()));
 

	
 
    updateMenu();
 
}
 

	
 
SnapshotManager::~SnapshotManager()
 
{
 
    for (auto snapshot : snapshots)
 
    {
 
        delete snapshot;
 
    }
 
}
 

	
 
Snapshot* SnapshotManager::makeSnapshot()
 
Snapshot* SnapshotManager::makeSnapshot() const
 
{
 
    QString name = QTime::currentTime().toString("'Snapshot ['HH:mm:ss']'");
 
    auto snapshot = new Snapshot(_mainWindow, name, *(_channelMan->infoModel()));
 

	
 
    unsigned numOfChannels = _channelMan->numOfChannels();
 
    unsigned numOfSamples = _channelMan->numOfSamples();
 
    auto snapshot = new Snapshot(_mainWindow, name, *(_stream->infoModel()));
 

	
 
    for (unsigned ci = 0; ci < numOfChannels; ci++)
 
    for (unsigned ci = 0; ci < _stream->numChannels(); ci++)
 
    {
 
        snapshot->data.append(QVector<QPointF>(numOfSamples));
 
        for (unsigned i = 0; i < numOfSamples; i++)
 
        {
 
            snapshot->data[ci][i] = QPointF(i, _channelMan->channelBuffer(ci)->sample(i));
 
        }
 
        snapshot->yData.append(new ReadOnlyBuffer(_stream->channel(ci)->yData()));
 
    }
 

	
 
    return snapshot;
 
}
 

	
 
void SnapshotManager::takeSnapshot()
 
{
 
    addSnapshot(makeSnapshot());
 
}
 

	
 
void SnapshotManager::addSnapshot(Snapshot* snapshot, bool update_menu)
 
{
 
    snapshots.append(snapshot);
 
    QObject::connect(snapshot, &Snapshot::deleteRequested,
 
                     this, &SnapshotManager::deleteSnapshot);
 
    if (update_menu) updateMenu();
 
}
 

	
 
void SnapshotManager::updateMenu()
 
{
 
    _menu.clear();
 
    _menu.addAction(&_takeSnapshotAction);
 
    _menu.addAction(&loadSnapshotAction);
 
    if (snapshots.size())
 
    {
 
        _menu.addSeparator();
 
        for (auto ss : snapshots)
 
        {
 
            _menu.addAction(ss->showAction());
 
        }
 
        _menu.addSeparator();
 
        _menu.addAction(&clearAction);
 
    }
 
}
 

	
 
void SnapshotManager::clearSnapshots()
 
{
 
    for (auto snapshot : snapshots)
 
    {
 
        delete snapshot;
 
    }
 
    snapshots.clear();
 
    updateMenu();
 
}
 

	
 
void SnapshotManager::deleteSnapshot(Snapshot* snapshot)
 
{
 
    snapshots.removeOne(snapshot);
 
    snapshot->deleteLater(); // regular delete causes a crash when triggered from menu
 
    updateMenu();
 
}
 

	
 
void SnapshotManager::loadSnapshots()
 
{
 
    auto files = QFileDialog::getOpenFileNames(_mainWindow, tr("Load CSV File"));
 

	
 
    for (auto f : files)
 
    {
 
        if (!f.isNull()) loadSnapshotFromFile(f);
 
    }
 

	
 
    updateMenu();
 
}
 

	
 
void SnapshotManager::loadSnapshotFromFile(QString fileName)
 
{
 
    QFile file(fileName);
 
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
 
    {
 
        qCritical() << "Couldn't open file: " << fileName;
 
        qCritical() << file.errorString();
 
        return;
 
    }
 

	
 
    // read first row as headlines and determine number of channels
 
    auto headLine = QString(file.readLine());
 
    QStringList channelNames = headLine.split(',');
 
    unsigned numOfChannels = channelNames.size();
 

	
 
    // read data
 
    QVector<QVector<QPointF>> data(numOfChannels);
 
    QVector<QVector<double>> data(numOfChannels);
 
    QTextStream ts(&file);
 
    QString line;
 
    unsigned lineNum = 1;
 
    while (file.canReadLine())
 
    while (ts.readLineInto(&line))
 
    {
 
        // parse line
 
        auto line = QString(file.readLine());
 
        auto split = line.split(',');
 

	
 
        if (split.size() != (int) numOfChannels)
 
        {
 
            qCritical() << "Parsing error at line " << lineNum
 
                        << ": number of columns is not consistent.";
 
            qCritical() << "Line " << lineNum << ": " << line;
 
            return;
 
        }
 

	
 
        for (unsigned ci = 0; ci < numOfChannels; ci++)
 
        {
 
            // parse column
 
            bool ok;
 
            double y = split[ci].toDouble(&ok);
 
            if (!ok)
 
            {
 
                qCritical() << "Parsing error at line " << lineNum
 
                            << ", column " << ci
 
                            << ": can't convert \"" << split[ci]
 
                            << "\" to double.";
 
                return;
 
            }
 
            data[ci].append(QPointF(lineNum-1, y));
 
            data[ci].append(y);
 
        }
 
        lineNum++;
 
    }
 

	
 
    ChannelInfoModel channelInfo(channelNames);
 

	
 
    // create snapshot
 
    auto snapshot = new Snapshot(
 
        _mainWindow, QFileInfo(fileName).baseName(), ChannelInfoModel(channelNames));
 
    snapshot->data = data;
 
        _mainWindow, QFileInfo(fileName).baseName(),
 
        ChannelInfoModel(channelNames), true);
 

	
 
    for (unsigned ci = 0; ci < numOfChannels; ci++)
 
    {
 
        snapshot->yData.append(new ReadOnlyBuffer(data[ci].data(), data[ci].size()));
 
    }
 

	
 
    addSnapshot(snapshot, false);
 
}
 

	
 
QMenu* SnapshotManager::menu()
 
{
 
    return &_menu;
 
}
 

	
 
QAction* SnapshotManager::takeSnapshotAction()
 
{
 
    return &_takeSnapshotAction;
 
}
 

	
 
bool SnapshotManager::isAllSaved()
 
{
 
    for (auto snapshot : snapshots)
 
    {
 
        if (!snapshot->isSaved()) return false;
 
    }
 
    return true;
 
}
src/snapshotmanager.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef SNAPSHOTMANAGER_H
 
#define SNAPSHOTMANAGER_H
 

	
 
#include <QObject>
 
#include <QAction>
 
#include <QMenu>
 

	
 
#include "framebuffer.h"
 
#include "channelmanager.h"
 
#include "stream.h"
 
#include "snapshot.h"
 

	
 
class MainWindow;
 

	
 
class SnapshotManager : public QObject
 
{
 
    Q_OBJECT
 

	
 
public:
 
    SnapshotManager(MainWindow* mainWindow, ChannelManager* channelMan);
 
    SnapshotManager(MainWindow* mainWindow, Stream* stream);
 
    ~SnapshotManager();
 

	
 
    QMenu* menu();
 
    QAction* takeSnapshotAction();
 

	
 
    /// Creates a dynamically allocated snapshot object but doesn't record it in snapshots list.
 
    /// @note Caller is responsible for deletion of the returned `Snapshot` object.
 
    Snapshot* makeSnapshot();
 
    Snapshot* makeSnapshot() const;
 

	
 
    bool isAllSaved(); ///< returns `true` if all snapshots are saved to a file
 

	
 
private:
 
    MainWindow* _mainWindow;
 
    ChannelManager* _channelMan;
 
    Stream* _stream;
 

	
 
    QList<Snapshot*> snapshots;
 

	
 
    QMenu _menu;
 
    QAction _takeSnapshotAction;
 
    QAction loadSnapshotAction;
 
    QAction clearAction;
 

	
 
    void addSnapshot(Snapshot* snapshot, bool update_menu=true);
 
    void updateMenu();
 

	
 
private slots:
 
    void takeSnapshot();
 
    void clearSnapshots();
 
    void deleteSnapshot(Snapshot* snapshot);
 
    void loadSnapshots();
 
    void loadSnapshotFromFile(QString fileName);
 
};
 

	
 
#endif /* SNAPSHOTMANAGER_H */
src/snapshotview.cpp
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include "snapshotview.h"
 
#include "ui_snapshotview.h"
 

	
 
SnapshotView::SnapshotView(MainWindow* parent, Snapshot* snapshot) :
 
    QMainWindow(parent),
 
    ui(new Ui::SnapshotView),
 
    renameDialog(this)
 
    renameDialog(this),
 
    plotMenu(parent->viewSettings())
 
{
 
    _snapshot = snapshot;
 

	
 
    ui->setupUi(this);
 

	
 
    plotMan = new PlotManager(ui->plotArea, snapshot->infoModel(), this);
 
    plotMan->setViewSettings(parent->viewSettings());
 
    plotMan = new PlotManager(ui->plotArea, &plotMenu, snapshot, this);
 

	
 
    ui->menuSnapshot->insertAction(ui->actionClose, snapshot->deleteAction());
 
    this->setWindowTitle(snapshot->displayName());
 

	
 
    // initialize curves
 
    unsigned numOfChannels = snapshot->data.size();
 
    unsigned numOfSamples = snapshot->data[0].size();
 
    for (unsigned ci = 0; ci < numOfChannels; ci++)
 
    {
 
        plotMan->addCurve(snapshot->channelName(ci), snapshot->data[ci]);
 
    }
 
    plotMan->setNumOfSamples(numOfSamples);
 
    // unsigned numOfChannels = snapshot->data.size();
 
    // unsigned numOfSamples = snapshot->data[0].size();
 
    // for (unsigned ci = 0; ci < numOfChannels; ci++)
 
    // {
 
    //     plotMan->addCurve(snapshot->channelName(ci), snapshot->data[ci]);
 
    // }
 
    // plotMan->setNumOfSamples(numOfSamples);
 
    // plotMan->setPlotWidth(numOfSamples);
 

	
 
    renameDialog.setWindowTitle("Rename Snapshot");
 
    renameDialog.setLabelText("Enter new name:");
 
    connect(ui->actionRename, &QAction::triggered,
 
            this, &SnapshotView::showRenameDialog);
 

	
 
    connect(ui->actionSave, &QAction::triggered,
 
            this, &SnapshotView::save);
 

	
 
    // add 'View' menu items
 
    for (auto a : plotMan->menuActions())
 
    {
 
        ui->menuView->addAction(a);
 
    }
 
    // add "View" menu
 
    menuBar()->insertMenu(NULL, &plotMenu);
 
}
 

	
 
SnapshotView::~SnapshotView()
 
{
 
    for (auto curve : curves)
 
    {
 
        delete curve;
 
    }
 
    delete plotMan;
 
    delete ui;
 
}
 

	
 
void SnapshotView::closeEvent(QCloseEvent *event)
 
{
 
    QMainWindow::closeEvent(event);
 
    emit closed();
 
}
 

	
 
void SnapshotView::showRenameDialog()
 
{
 
    renameDialog.setTextValue(_snapshot->name());
 
    renameDialog.open(this, SLOT(renameSnapshot(QString)));
 
}
 

	
 
void SnapshotView::renameSnapshot(QString name)
 
{
 
    _snapshot->setName(name);
 
    setWindowTitle(_snapshot->displayName());
 
}
 

	
 
void SnapshotView::save()
 
{
 
    QString fileName = QFileDialog::getSaveFileName(this, tr("Export CSV File"));
 
    if (fileName.isNull()) return; // user canceled
 

	
 
    _snapshot->save(fileName);
 

	
 
    setWindowTitle(_snapshot->displayName());
 
}
src/snapshotview.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef SNAPSHOTVIEW_H
 
#define SNAPSHOTVIEW_H
 

	
 
#include <QMainWindow>
 
#include <QInputDialog>
 
#include <QFileDialog>
 
#include <QVector>
 
#include <QPointF>
 
#include <QPen>
 
#include <QCloseEvent>
 
#include <qwt_plot_curve.h>
 

	
 
#include "mainwindow.h"
 
#include "plotmanager.h"
 
#include "plotmenu.h"
 
#include "snapshot.h"
 

	
 
namespace Ui {
 
class SnapshotView;
 
}
 

	
 
class SnapshotView : public QMainWindow
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit SnapshotView(MainWindow* parent, Snapshot* snapshot);
 
    ~SnapshotView();
 

	
 
signals:
 
    void closed();
 

	
 
private:
 
    Ui::SnapshotView *ui;
 
    QList<QwtPlotCurve*> curves;
 
    Snapshot* _snapshot;
 
    QInputDialog renameDialog;
 
    PlotManager* plotMan;
 
    PlotMenu plotMenu;
 

	
 
    void closeEvent(QCloseEvent *event);
 

	
 
private slots:
 
    void showRenameDialog();
 
    void renameSnapshot(QString name);
 
    void save();
 
};
 

	
 
#endif // SNAPSHOTVIEW_H
src/snapshotview.ui
Show inline comments
 
<?xml version="1.0" encoding="UTF-8"?>
 
<ui version="4.0">
 
 <class>SnapshotView</class>
 
 <widget class="QMainWindow" name="SnapshotView">
 
  <property name="geometry">
 
   <rect>
 
    <x>0</x>
 
    <y>0</y>
 
    <width>544</width>
 
    <height>449</height>
 
   </rect>
 
  </property>
 
  <property name="windowTitle">
 
   <string>MainWindow</string>
 
  </property>
 
  <widget class="QWidget" name="centralwidget">
 
   <layout class="QVBoxLayout" name="verticalLayout">
 
    <item>
 
     <widget class="QWidget" name="plotArea" native="true"/>
 
    </item>
 
   </layout>
 
  </widget>
 
  <widget class="QMenuBar" name="menubar">
 
   <property name="geometry">
 
    <rect>
 
     <x>0</x>
 
     <y>0</y>
 
     <width>544</width>
 
     <height>25</height>
 
     <height>24</height>
 
    </rect>
 
   </property>
 
   <widget class="QMenu" name="menuSnapshot">
 
    <property name="title">
 
     <string>&amp;Snapshot</string>
 
    </property>
 
    <addaction name="actionRename"/>
 
    <addaction name="actionSave"/>
 
    <addaction name="actionClose"/>
 
   </widget>
 
   <widget class="QMenu" name="menuView">
 
    <property name="title">
 
     <string>&amp;View</string>
 
    </property>
 
   </widget>
 
   <addaction name="menuSnapshot"/>
 
   <addaction name="menuView"/>
 
  </widget>
 
  <action name="actionSave">
 
   <property name="text">
 
    <string>&amp;Save as CSV</string>
 
   </property>
 
   <property name="toolTip">
 
    <string>Save snapshot as CSV file</string>
 
   </property>
 
  </action>
 
  <action name="actionRename">
 
   <property name="text">
 
    <string>&amp;Rename</string>
 
   </property>
 
   <property name="toolTip">
 
    <string>Rename this snapshot</string>
 
   </property>
 
  </action>
 
  <action name="actionClose">
 
   <property name="text">
 
    <string>&amp;Close</string>
 
   </property>
 
   <property name="toolTip">
 
    <string>Close Window</string>
 
   </property>
 
   <property name="shortcut">
 
    <string>Ctrl+W</string>
 
   </property>
 
  </action>
 
 </widget>
 
 <resources/>
 
 <connections>
 
  <connection>
 
   <sender>actionClose</sender>
 
   <signal>triggered()</signal>
 
   <receiver>SnapshotView</receiver>
 
   <slot>close()</slot>
 
   <hints>
 
    <hint type="sourcelabel">
 
     <x>-1</x>
 
     <y>-1</y>
 
    </hint>
 
    <hint type="destinationlabel">
 
     <x>271</x>
 
     <y>224</y>
 
    </hint>
 
   </hints>
 
  </connection>
 
 </connections>
 
</ui>
src/source.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <QtGlobal>
 

	
 
#include "source.h"
 

	
 
Source::~Source()
 
{
 
    for (auto sink : sinks)
 
    {
 
        sink->setSource(nullptr);
 
    }
 
}
 

	
 
void Source::connectSink(Sink* sink)
 
{
 
    Q_ASSERT(!sinks.contains(sink));
 

	
 
    auto prevSource = sink->connectedSource();
 
    if (prevSource != nullptr)
 
    {
 
        prevSource->disconnect(sink);
 
    }
 

	
 
    sinks.append(sink);
 
    sink->setSource(this);
 
    sink->setNumChannels(numChannels(), hasX());
 
}
 

	
 
void Source::disconnect(Sink* sink)
 
{
 
    Q_ASSERT(sinks.contains(sink));
 
    Q_ASSERT(sink->connectedSource() == this);
 

	
 
    sink->setSource(nullptr);
 
    sinks.removeOne(sink);
 
}
 

	
 
void Source::disconnectSinks()
 
{
 
    while (!sinks.isEmpty())
 
    {
 
        auto sink = sinks.takeFirst();
 
        sink->setSource(nullptr);
 
    }
 
}
 

	
 
void Source::feedOut(const SamplePack& data) const
 
{
 
    for (auto sink : sinks)
 
    {
 
        sink->feedIn(data);
 
    }
 
}
 

	
 
void Source::updateNumChannels() const
 
{
 
    for (auto sink : sinks)
 
    {
 
        sink->setNumChannels(numChannels(), hasX());
 
    }
 
}
src/source.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef SOURCE_H
 
#define SOURCE_H
 

	
 
#include <QList>
 

	
 
#include "sink.h"
 
#include "samplepack.h"
 

	
 
class Source
 
{
 
public:
 
    /// Virtual destructor. Must be called by implementors to notify sinks.
 
    virtual ~Source();
 

	
 
    /// Returns true if source has X data
 
    virtual bool hasX() const = 0;
 

	
 
    /// Returns number of channels
 
    virtual unsigned numChannels() const = 0;
 

	
 
    /// Connects a sink to this source.
 
    ///
 
    /// If `Sink` is already connected to a source, it's disconnected first.
 
    void connectSink(Sink* sink);
 

	
 
    /// Disconnects an already connected sink. Trying to disconnect an
 
    /// unconnected sink is an error.
 
    void disconnect(Sink* sink);
 

	
 
    /// Disconnects all connected sinks.
 
    void disconnectSinks();
 

	
 
protected:
 
    /// Feeds "in" given data to connected sinks
 
    virtual void feedOut(const SamplePack& data) const;
 

	
 
    /// Updates "number of channels" of connected sinks. Must be
 
    /// called when num. channels or hasX changes.
 
    void updateNumChannels() const;
 

	
 
private:
 
    QList<Sink*> sinks;
 
};
 

	
 
#endif // SOURCE_H
src/stream.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include "stream.h"
 
#include "ringbuffer.h"
 
#include "indexbuffer.h"
 

	
 
Stream::Stream(unsigned nc, bool x, unsigned ns) :
 
    _infoModel(nc)
 
{
 
    _numSamples = ns;
 
    _paused = false;
 

	
 
    // create xdata buffer
 
    _hasx = x;
 
    if (x)
 
    {
 
        xData = new RingBuffer(ns);
 
    }
 
    else
 
    {
 
        xData = new IndexBuffer(ns);
 
    }
 

	
 
    // create channels
 
    for (unsigned i = 0; i < nc; i++)
 
    {
 
        auto c = new StreamChannel(i, xData, new RingBuffer(ns), &_infoModel);
 
        channels.append(c);
 
    }
 
}
 

	
 
Stream::~Stream()
 
{
 
    for (auto ch : channels)
 
    {
 
        delete ch;
 
    }
 
    delete xData;
 
}
 

	
 
bool Stream::hasX() const
 
{
 
    return _hasx;
 
}
 

	
 
unsigned Stream::numChannels() const
 
{
 
    return channels.length();
 
}
 

	
 
unsigned Stream::numSamples() const
 
{
 
    return _numSamples;
 
}
 

	
 
const StreamChannel* Stream::channel(unsigned index) const
 
{
 
    Q_ASSERT(index < numChannels());
 
    return channels[index];
 
}
 

	
 
StreamChannel* Stream::channel(unsigned index)
 
{
 
    return const_cast<StreamChannel*>(static_cast<const Stream&>(*this).channel(index));
 
}
 

	
 
const ChannelInfoModel* Stream::infoModel() const
 
{
 
    return &_infoModel;
 
}
 

	
 
ChannelInfoModel* Stream::infoModel()
 
{
 
    return const_cast<ChannelInfoModel*>(static_cast<const Stream&>(*this).infoModel());
 
}
 

	
 
void Stream::setNumChannels(unsigned nc, bool x)
 
{
 
    unsigned oldNum = numChannels();
 
    if (oldNum == nc && x == _hasx) return;
 

	
 
    // adjust the number of channels
 
    if (nc > oldNum)
 
    {
 
        for (unsigned i = oldNum; i < nc; i++)
 
        {
 
            auto c = new StreamChannel(i, xData, new RingBuffer(_numSamples), &_infoModel);
 
            channels.append(c);
 
        }
 
    }
 
    else if (nc < oldNum)
 
    {
 
        for (unsigned i = oldNum-1; i > nc-1; i--)
 
        {
 
            delete channels.takeLast();
 
        }
 
    }
 

	
 
    // change the xdata
 
    if (x != _hasx)
 
    {
 
        if (x)
 
        {
 
            xData = new RingBuffer(_numSamples);
 
        }
 
        else
 
        {
 
            xData = new IndexBuffer(_numSamples);
 
        }
 

	
 
        for (auto c : channels)
 
        {
 
            c->setX(xData);
 
        }
 
        _hasx = x;
 
    }
 

	
 
    if (nc != oldNum)
 
    {
 
        _infoModel.setNumOfChannels(nc);
 
        // TODO: how about X change?
 
        emit numChannelsChanged(nc);
 
    }
 

	
 
    Sink::setNumChannels(nc, x);
 
}
 

	
 
const SamplePack* Stream::applyGainOffset(const SamplePack& pack) const
 
{
 
    Q_ASSERT(infoModel()->gainOrOffsetEn());
 

	
 
    SamplePack* mPack = new SamplePack(pack);
 
    unsigned ns = pack.numSamples();
 

	
 
    for (unsigned ci = 0; ci < numChannels(); ci++)
 
    {
 
        // TODO: we could use some kind of map (int32, int64 would suffice) to speed things up
 
        bool gainEn = infoModel()->gainEn(ci);
 
        bool offsetEn = infoModel()->offsetEn(ci);
 
        if (gainEn || offsetEn)
 
        {
 
            double* mdata = mPack->data(ci);
 

	
 
            double gain = infoModel()->gain(ci);
 
            double offset = infoModel()->offset(ci);
 

	
 
            if (gainEn)
 
            {
 
                for (unsigned i = 0; i < ns; i++)
 
                {
 
                    mdata[i] *= gain;
 
                }
 
            }
 
            if (offsetEn)
 
            {
 
                for (unsigned i = 0; i < ns; i++)
 
                {
 
                    mdata[i] += offset;
 
                }
 
            }
 
        }
 
    }
 

	
 
    return mPack;
 
}
 

	
 
void Stream::feedIn(const SamplePack& pack)
 
{
 
    Q_ASSERT(pack.numChannels() == numChannels() &&
 
             pack.hasX() == hasX());
 

	
 
    if (_paused) return;
 

	
 
    unsigned ns = pack.numSamples();
 
    if (_hasx)
 
    {
 
        static_cast<RingBuffer*>(xData)->addSamples(pack.xData(), ns);
 
    }
 

	
 
    // modified pack that gain and offset is applied to
 
    const SamplePack* mPack = nullptr;
 
    if (infoModel()->gainOrOffsetEn())
 
        mPack = applyGainOffset(pack);
 

	
 
    for (unsigned ci = 0; ci < numChannels(); ci++)
 
    {
 
        auto buf = static_cast<RingBuffer*>(channels[ci]->yData());
 
        double* data = (mPack == nullptr) ? pack.data(ci) : mPack->data(ci);
 
        buf->addSamples(data, ns);
 
    }
 

	
 
    Sink::feedIn((mPack == nullptr) ? pack : *mPack);
 

	
 
    if (mPack != nullptr) delete mPack;
 
    emit dataAdded();
 
}
 

	
 
void Stream::pause(bool paused)
 
{
 
    _paused = paused;
 
}
 

	
 
void Stream::clear()
 
{
 
    for (auto c : channels)
 
    {
 
        static_cast<RingBuffer*>(c->yData())->clear();
 
    }
 
}
 

	
 
void Stream::setNumSamples(unsigned value)
 
{
 
    if (value == _numSamples) return;
 
    _numSamples = value;
 

	
 
    xData->resize(value);
 
    for (auto c : channels)
 
    {
 
        static_cast<RingBuffer*>(c->yData())->resize(value);
 
    }
 
}
 

	
 
void Stream::saveSettings(QSettings* settings) const
 
{
 
    _infoModel.saveSettings(settings);
 
}
 

	
 
void Stream::loadSettings(QSettings* settings)
 
{
 
    _infoModel.loadSettings(settings);
 
}
src/stream.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef STREAM_H
 
#define STREAM_H
 

	
 
#include <QObject>
 
#include <QModelIndex>
 
#include <QVector>
 
#include <QSettings>
 

	
 
#include "sink.h"
 
#include "source.h"
 
#include "channelinfomodel.h"
 
#include "streamchannel.h"
 
#include "framebuffer.h"
 

	
 
/**
 
 * Main waveform storage class. It consists of channels. Channels are
 
 * synchronized with each other.
 
 *
 
 * Implements `Sink` class for data entry. It's expected to be
 
 * connected to a `Device` source.
 
 */
 
class Stream : public QObject, public Sink
 
{
 
    Q_OBJECT
 

	
 
public:
 
    /**
 
     * @param nc number of channels
 
     * @param x has X data input
 
     * @param ns number of samples
 
     */
 
    Stream(unsigned nc = 0, bool x = false, unsigned ns = 0);
 
    ~Stream();
 

	
 
    // implementations for `Source`
 
    virtual bool hasX() const;
 
    virtual unsigned numChannels() const;
 

	
 
    unsigned numSamples() const;
 
    const StreamChannel* channel(unsigned index) const;
 
    StreamChannel* channel(unsigned index);
 
    const ChannelInfoModel* infoModel() const;
 
    ChannelInfoModel* infoModel();
 

	
 
    /// Saves channel information
 
    void saveSettings(QSettings* settings) const;
 
    /// Load channel information
 
    void loadSettings(QSettings* settings);
 

	
 
protected:
 
    // implementations for `Sink`
 
    virtual void setNumChannels(unsigned nc, bool x);
 
    virtual void feedIn(const SamplePack& pack);
 

	
 
signals:
 
    void numChannelsChanged(unsigned value);
 
    void numSamplesChanged(unsigned value);
 
    void channelAdded(const StreamChannel* chan);
 
    void channelNameChanged(unsigned channel, QString name); // TODO: does it stay?
 
    void dataAdded(); ///< emitted when data added to channel man.
 

	
 
public slots:
 
    // TODO: these won't be public
 
    // void setNumChannels(unsigned number);
 
    void setNumSamples(unsigned value);
 

	
 
    /// When paused data feed is ignored
 
    void pause(bool paused);
 

	
 
    /// Clears buffer data (fills with 0)
 
    void clear();
 

	
 
private:
 
    unsigned _numSamples;
 
    bool _paused;
 

	
 
    bool _hasx;
 
    ResizableBuffer* xData;
 
    QList<StreamChannel*> channels;
 

	
 
    ChannelInfoModel _infoModel;
 

	
 
    /**
 
     * Applies gain and offset to given pack.
 
     *
 
     * Caller is responsible for deleting returned `SamplePack`.
 
     *
 
     * @note Should be called only when gain or offset is enabled. Guard with
 
     * `ChannelInfoModel::gainOrOffsetEn()`.
 
     *
 
     * @param pack input data
 
     * @return modified data
 
     */
 
    const SamplePack* applyGainOffset(const SamplePack& pack) const;
 
};
 

	
 

	
 
#endif // STREAM_H
src/streamchannel.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include "streamchannel.h"
 

	
 
StreamChannel::StreamChannel(unsigned i, const FrameBuffer* x,
 
              FrameBuffer* y, ChannelInfoModel* info)
 
{
 
    _index = i;
 
    _x = x;
 
    _y = y;
 
    _info = info;
 
}
 

	
 
StreamChannel::~StreamChannel()
 
{
 
    delete _y;
 
}
 

	
 
unsigned StreamChannel::index() const {return _index;}
 
QString StreamChannel::name() const {return _info->name(_index);};
 
QColor StreamChannel::color() const {return _info->color(_index);};
 
bool StreamChannel::visible() const {return _info->isVisible(_index);};
 
const FrameBuffer* StreamChannel::xData() const {return _x;}
 
const FrameBuffer* StreamChannel::yData() const {return _y;}
 
FrameBuffer* StreamChannel::yData() {return _y;}
 
const ChannelInfoModel* StreamChannel::info() const {return _info;}
 
void StreamChannel::setX(const FrameBuffer* x) {_x = x;};
src/streamchannel.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef STREAMCHANNEL_H
 
#define STREAMCHANNEL_H
 

	
 
#include "framebuffer.h"
 
#include "channelinfomodel.h"
 

	
 
class StreamChannel
 
{
 
public:
 
    /**
 
     * Creates a stream channel.
 
     *
 
     * @param i index of the channel
 
     * @param x x axis buffer
 
     * @param y data buffer of this channel, takes ownership
 
     * @param info channel info model
 
     */
 
    StreamChannel(unsigned i,
 
                  const FrameBuffer* x,
 
                  FrameBuffer* y,
 
                  ChannelInfoModel* info);
 
    ~StreamChannel();
 

	
 
    unsigned index() const;
 
    QString name() const;
 
    QColor color() const;
 
    bool visible() const;
 
    const FrameBuffer* xData() const;
 
    FrameBuffer* yData();
 
    const FrameBuffer* yData() const;
 
    const ChannelInfoModel* info() const;
 
    void setX(const FrameBuffer* x);
 

	
 
private:
 
    unsigned _index;
 
    const FrameBuffer* _x;
 
    FrameBuffer* _y;
 
    ChannelInfoModel* _info;
 
};
 

	
 
#endif // STREAMCHANNEL_H
src/updatecheckdialog.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include "setting_defines.h"
 
#include "updatecheckdialog.h"
 
#include "ui_updatecheckdialog.h"
 

	
 
UpdateCheckDialog::UpdateCheckDialog(QWidget *parent) :
 
    QDialog(parent),
 
    ui(new Ui::UpdateCheckDialog)
 
{
 
    ui->setupUi(this);
 

	
 
    // by default start from yesterday, so that we check at first run
 
    lastCheck = QDate::currentDate().addDays(-1);
 

	
 
    connect(&updateChecker, &UpdateChecker::checkFailed,
 
            [this](QString errorMessage)
 
            {
 
                lastCheck = QDate::currentDate();
 
                ui->label->setText(QString("Update check failed.\n") + errorMessage);
 
            });
 

	
 
    connect(&updateChecker, &UpdateChecker::checkFinished,
 
            [this](bool found, QString newVersion, QString downloadUrl)
 
            {
 
                QString text;
 
                if (!found)
 
                {
 
                    text = "There is no update yet.";
 
                }
 
                else
 
                {
 
                    show();
 
#ifdef UPDATE_TYPE_PKGMAN
 
                    text = QString("There is a new version: %1. "
 
                                   "Use your package manager to update"
 
                                   " or click to <a href=\"%2\">download</a>.")\
 
                        .arg(newVersion).arg(downloadUrl);
 
#else
 
                    text = QString("Found update to version %1. Click to <a href=\"%2\">download</a>.")\
 
                        .arg(newVersion).arg(downloadUrl);
 
#endif
 
                }
 

	
 
                lastCheck = QDate::currentDate();
 
                ui->label->setText(text);
 
            });
 
}
 

	
 
UpdateCheckDialog::~UpdateCheckDialog()
 
{
 
    delete ui;
 
}
 

	
 
void UpdateCheckDialog::showEvent(QShowEvent *event)
 
{
 
    updateChecker.checkUpdate();
 
    ui->label->setText("Checking update...");
 
}
 

	
 
void UpdateCheckDialog::closeEvent(QShowEvent *event)
 
{
 
    if (updateChecker.isChecking()) updateChecker.cancelCheck();
 
}
 

	
 
void UpdateCheckDialog::saveSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_UpdateCheck);
 
    settings->setValue(SG_UpdateCheck_Periodic, ui->cbPeriodic->isChecked());
 
    settings->setValue(SG_UpdateCheck_LastCheck, lastCheck.toString(Qt::ISODate));
 
    settings->endGroup();
 
}
 

	
 
void UpdateCheckDialog::loadSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_UpdateCheck);
 
    ui->cbPeriodic->setChecked(settings->value(SG_UpdateCheck_Periodic,
 
                                               ui->cbPeriodic->isChecked()).toBool());
 
    auto lastCheckS = settings->value(SG_UpdateCheck_LastCheck, lastCheck.toString(Qt::ISODate)).toString();
 
    lastCheck = QDate::fromString(lastCheckS, Qt::ISODate);
 
    settings->endGroup();
 

	
 
    // start the periodic update if required
 
    if (ui->cbPeriodic->isChecked() && lastCheck < QDate::currentDate())
 
    {
 
        updateChecker.checkUpdate();
 
    }
 
}
src/updatecheckdialog.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef UPDATECHECKDIALOG_H
 
#define UPDATECHECKDIALOG_H
 

	
 
#include <QDialog>
 
#include <QDate>
 
#include <QSettings>
 
#include "updatechecker.h"
 

	
 
namespace Ui {
 
class UpdateCheckDialog;
 
}
 

	
 
class UpdateCheckDialog : public QDialog
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit UpdateCheckDialog(QWidget *parent = 0);
 
    ~UpdateCheckDialog();
 

	
 
    /// Stores update settings into a `QSettings`.
 
    void saveSettings(QSettings* settings);
 
    /// Loads update settings from a `QSettings`.
 
    void loadSettings(QSettings* settings);
 

	
 
private:
 
    Ui::UpdateCheckDialog *ui;
 
    UpdateChecker updateChecker;
 
    QDate lastCheck;
 

	
 
    void showEvent(QShowEvent *event);
 
    void closeEvent(QShowEvent *event);
 
};
 

	
 
#endif // UPDATECHECKDIALOG_H
src/updatecheckdialog.ui
Show inline comments
 
new file 100644
 
<?xml version="1.0" encoding="UTF-8"?>
 
<ui version="4.0">
 
 <class>UpdateCheckDialog</class>
 
 <widget class="QDialog" name="UpdateCheckDialog">
 
  <property name="geometry">
 
   <rect>
 
    <x>0</x>
 
    <y>0</y>
 
    <width>400</width>
 
    <height>148</height>
 
   </rect>
 
  </property>
 
  <property name="windowTitle">
 
   <string>Check Update</string>
 
  </property>
 
  <layout class="QVBoxLayout" name="verticalLayout">
 
   <item>
 
    <widget class="QLabel" name="label">
 
     <property name="text">
 
      <string>Checking update...</string>
 
     </property>
 
     <property name="openExternalLinks">
 
      <bool>true</bool>
 
     </property>
 
    </widget>
 
   </item>
 
   <item>
 
    <widget class="QCheckBox" name="cbPeriodic">
 
     <property name="toolTip">
 
      <string>Updates will be checked only once a day at first start of the application</string>
 
     </property>
 
     <property name="text">
 
      <string>Check updates periodically</string>
 
     </property>
 
     <property name="checked">
 
      <bool>true</bool>
 
     </property>
 
    </widget>
 
   </item>
 
   <item>
 
    <widget class="QDialogButtonBox" name="buttonBox">
 
     <property name="orientation">
 
      <enum>Qt::Horizontal</enum>
 
     </property>
 
     <property name="standardButtons">
 
      <set>QDialogButtonBox::Close</set>
 
     </property>
 
    </widget>
 
   </item>
 
  </layout>
 
 </widget>
 
 <resources/>
 
 <connections>
 
  <connection>
 
   <sender>buttonBox</sender>
 
   <signal>clicked(QAbstractButton*)</signal>
 
   <receiver>UpdateCheckDialog</receiver>
 
   <slot>close()</slot>
 
   <hints>
 
    <hint type="sourcelabel">
 
     <x>199</x>
 
     <y>125</y>
 
    </hint>
 
    <hint type="destinationlabel">
 
     <x>199</x>
 
     <y>73</y>
 
    </hint>
 
   </hints>
 
  </connection>
 
 </connections>
 
</ui>
src/updatechecker.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <QJsonDocument>
 
#include <QJsonObject>
 
#include <QJsonArray>
 
#include <QJsonValue>
 
#include <QRegularExpression>
 
#include <algorithm>
 
#include <functional>
 

	
 
#include "updatechecker.h"
 

	
 
// This link returns the list of downloads in JSON format. Note that we only use
 
// the first page because results are sorted new to old.
 
const char BB_DOWNLOADS_URL[] = "https://api.bitbucket.org/2.0/repositories/hyozd/serialplot/downloads?fields=values.name,values.links.self.href";
 

	
 
UpdateChecker::UpdateChecker(QObject *parent) :
 
    QObject(parent), nam(this)
 
{
 
    activeReply = NULL;
 

	
 
    connect(&nam, &QNetworkAccessManager::finished,
 
            this, &UpdateChecker::onReqFinished);
 
}
 

	
 
bool UpdateChecker::isChecking() const
 
{
 
    return activeReply != NULL && !activeReply->isFinished();
 
}
 

	
 
void UpdateChecker::checkUpdate()
 
{
 
    if (isChecking()) return;
 

	
 
    auto req = QNetworkRequest(QUrl(BB_DOWNLOADS_URL));
 
    activeReply = nam.get(req);
 
}
 

	
 
void UpdateChecker::cancelCheck()
 
{
 
    if (activeReply != NULL) activeReply->abort();
 
}
 

	
 
void UpdateChecker::onReqFinished(QNetworkReply* reply)
 
{
 
    if (reply->error() != QNetworkReply::NoError)
 
    {
 
        emit checkFailed(QString("Network error: ") + reply->errorString());
 
    }
 
    else
 
    {
 
        QJsonParseError error;
 
        auto data = QJsonDocument::fromJson(reply->readAll(), &error);
 
        if (error.error != QJsonParseError::NoError)
 
        {
 
            emit checkFailed(QString("JSon parsing error: ") + error.errorString());
 
        }
 
        else
 
        {
 
            QList<FileInfo> files;
 
            if (!parseData(data, files))
 
            {
 
                // TODO: emit detailed data contents for logging
 
                emit checkFailed("Data parsing error.");
 
            }
 
            else
 
            {
 
                FileInfo updateFile;
 
                if (findUpdate(files, updateFile))
 
                {
 
                    emit checkFinished(
 
                        true, updateFile.version.toString(), updateFile.link);
 
                }
 
                else
 
                {
 
                    emit checkFinished(false, "", "");
 
                }
 
            }
 
        }
 
    }
 
    reply->deleteLater();
 
    activeReply = NULL;
 
}
 

	
 
bool UpdateChecker::parseData(const QJsonDocument& data, QList<FileInfo>& files) const
 
{
 
    /* Data is expected to be in this form:
 

	
 
    {
 
       "values": [
 
       {
 
         "name": "serialplot-0.9.1-x86_64.AppImage",
 
         "links": {
 
           "self": {
 
             "href": "https://api.bitbucket.org/2.0/repositories/hyOzd/serialplot/downloads/serialplot-0.9.1-x86_64.AppImage"
 
            }
 
          }
 
       }, ... ]
 
    }
 
    */
 

	
 
    if (!data.isObject()) return false;
 

	
 
    auto values = data.object()["values"];
 
    if (values == QJsonValue::Undefined || !values.isArray()) return false;
 

	
 
    for (auto value : values.toArray())
 
    {
 
        if (!value.isObject()) return false;
 

	
 
        auto name = value.toObject().value("name");
 
        if (name.isUndefined() || !name.isString())
 
             return false;
 

	
 
        auto links = value.toObject().value("links");
 
        if (links.isUndefined() || !links.isObject())
 
            return false;
 

	
 
        auto self = links.toObject().value("self");
 
        if (self.isUndefined() || !self.isObject())
 
            return false;
 

	
 
        auto href = self.toObject().value("href");
 
        if (href.isUndefined() || !href.isString())
 
            return false;
 

	
 
        FileInfo finfo;
 
        finfo.name = name.toString();
 
        finfo.link = href.toString();
 
        finfo.hasVersion = VersionNumber::extract(name.toString(), finfo.version);
 

	
 
        if (finfo.name.contains("amd64") ||
 
            finfo.name.contains("x86_64") ||
 
            finfo.name.contains("win64"))
 
        {
 
            finfo.arch = FileArch::amd64;
 
        }
 
        else if (finfo.name.contains("win32") ||
 
                 finfo.name.contains("i386"))
 
        {
 
            finfo.arch = FileArch::_i386;
 
        }
 
        else
 
        {
 
            finfo.arch = FileArch::unknown;
 
        }
 

	
 
        files += finfo;
 
    }
 

	
 
    return true;
 
}
 

	
 
bool UpdateChecker::findUpdate(const QList<FileInfo>& files, FileInfo& foundFile) const
 
{
 
    QList<FileInfo> fflist;
 

	
 
    // filter the file list according to extension and version number
 
    for (int i = 0; i < files.length(); i++)
 
    {
 
        // file type to look
 
#if defined(Q_OS_WIN)
 
        const char ext[] = ".exe";
 
#else  // of course linux
 
        const char ext[] = ".appimage";
 
#endif
 

	
 
        // file architecture to look
 
#if defined(Q_PROCESSOR_X86_64)
 
        const FileArch arch = FileArch::amd64;
 
#elif defined(Q_PROCESSOR_X86_32)
 
        const FileArch arch = FileArch::_i386;
 
#elif defined(Q_PROCESSOR_ARM)
 
        const FileArch arch = FileArch::arm;
 
#else
 
        #error Unknown architecture for update file detection.
 
#endif
 

	
 
        // filter the file list
 
        auto file = files[i];
 
        if (file.name.contains(ext, Qt::CaseInsensitive) &&
 
            file.arch == arch &&
 
            file.hasVersion && file.version > CurrentVersion)
 
        {
 
            fflist += file;
 
        }
 
    }
 

	
 
    // sort and find most up to date file
 
    if (!fflist.empty())
 
    {
 
        std::sort(fflist.begin(), fflist.end(),
 
                  [](const FileInfo& a, const FileInfo& b)
 
                  {
 
                      return a.version > b.version;
 
                  });
 

	
 
        foundFile = fflist[0];
 
        return true;
 
    }
 
    else
 
    {
 
        return false;
 
    }
 
}
src/updatechecker.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef UPDATECHECKER_H
 
#define UPDATECHECKER_H
 

	
 
#include <QObject>
 
#include <QNetworkAccessManager>
 
#include <QNetworkReply>
 
#include <QList>
 

	
 
#include "versionnumber.h"
 

	
 
class UpdateChecker : public QObject
 
{
 
    Q_OBJECT
 
public:
 
    explicit UpdateChecker(QObject *parent = 0);
 

	
 
    bool isChecking() const;
 

	
 
signals:
 
    void checkFinished(bool found, QString newVersion, QString downloadUrl);
 
    void checkFailed(QString errorMessage);
 

	
 
public slots:
 
    void checkUpdate();
 
    void cancelCheck();
 

	
 
private:
 
    enum class FileArch
 
    {
 
        unknown,
 
        _i386,
 
        amd64,
 
        arm
 
    };
 

	
 
    struct FileInfo
 
    {
 
        QString name;
 
        QString link;
 
        bool hasVersion;
 
        VersionNumber version;
 
        FileArch arch;
 
    };
 

	
 
    QNetworkAccessManager nam;
 
    QNetworkReply* activeReply;
 

	
 
    /// Parses json and creates a list of files
 
    bool parseData(const QJsonDocument& data, QList<FileInfo>& files) const;
 
    /// Finds the update file in the file list. Returns `-1` if no new version
 
    /// is found.
 
    bool findUpdate(const QList<FileInfo>& files, FileInfo& foundFile) const;
 

	
 
private slots:
 
    void onReqFinished(QNetworkReply* reply);
 
};
 

	
 
#endif // UPDATECHECKER_H
src/versionnumber.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <QRegularExpression>
 

	
 
#include "versionnumber.h"
 

	
 
VersionNumber::VersionNumber(unsigned mj, unsigned mn, unsigned pt)
 
{
 
    major = mj;
 
    minor = mn;
 
    patch = pt;
 
}
 

	
 
QString VersionNumber::toString() const
 
{
 
    return QString("%1.%2.%3").arg(major).arg(minor).arg(patch);
 
}
 

	
 
bool VersionNumber::extract(const QString& str, VersionNumber& number)
 
{
 
    QRegularExpression regexp("(?:[-_vV \\t]|^)(?<major>\\d+)"
 
                              "(?:\\.(?<minor>\\d+))(?:\\.(?<patch>\\d+))?[-_ \\t]?");
 
    auto match = regexp.match(str, 0, QRegularExpression::PartialPreferCompleteMatch);
 

	
 
    if (!(match.hasMatch() || match.hasPartialMatch())) return false;
 

	
 
    number.major = match.captured("major").toUInt();
 

	
 
    auto zeroIfNull = [](QString str) -> unsigned
 
        {
 
            if (str.isNull()) return 0;
 
            return str.toUInt();
 
        };
 

	
 
    number.minor = zeroIfNull(match.captured("minor"));
 
    number.patch = zeroIfNull(match.captured("patch"));
 

	
 
    return true;
 
}
 

	
 
bool operator==(const VersionNumber& lhs, const VersionNumber& rhs)
 
{
 
    return lhs.major == rhs.major &&
 
           lhs.minor == rhs.minor &&
 
           lhs.patch == rhs.patch;
 
}
 

	
 
bool operator<(const VersionNumber& lhs, const VersionNumber& rhs)
 
{
 
    if (lhs.major < rhs.major)
 
    {
 
        return true;
 
    }
 
    else if (lhs.major == rhs.major)
 
    {
 
        if (lhs.minor < rhs.minor)
 
        {
 
             return true;
 
        }
 
        else if (lhs.minor == rhs.minor)
 
        {
 
            if (lhs.patch < rhs.patch) return true;
 
        }
 
    }
 
    return false;
 
}
 

	
 
bool operator>(const VersionNumber& lhs, const VersionNumber& rhs)
 
{
 
    if (lhs.major > rhs.major)
 
    {
 
        return true;
 
    }
 
    else if (lhs.major == rhs.major)
 
    {
 
        if (lhs.minor > rhs.minor)
 
        {
 
             return true;
 
        }
 
        else if (lhs.minor == rhs.minor)
 
        {
 
            if (lhs.patch > rhs.patch) return true;
 
        }
 
    }
 
    return false;
 
}
src/versionnumber.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef VERSIONNUMBER_H
 
#define VERSIONNUMBER_H
 

	
 
#include <QString>
 

	
 
struct VersionNumber
 
{
 
    unsigned major = 0;
 
    unsigned minor = 0;
 
    unsigned patch = 0;
 

	
 
    VersionNumber(unsigned mj=0, unsigned mn=0, unsigned pt=0);
 

	
 
    /// Convert version number to string.
 
    QString toString() const;
 

	
 
    /// Extracts the version number from given string.
 
    static bool extract(const QString& str, VersionNumber& number);
 
};
 

	
 
bool operator==(const VersionNumber& lhs, const VersionNumber& rhs);
 
bool operator<(const VersionNumber& lhs, const VersionNumber& rhs);
 
bool operator>(const VersionNumber& lhs, const VersionNumber& rhs);
 

	
 
const VersionNumber CurrentVersion(VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
 

	
 
#endif // VERSIONNUMBER_H
src/zoomer.cpp
Show inline comments
 
/*
 
  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 "zoomer.h"
 
#include <qwt_plot.h>
 
#include <QtDebug>
 

	
 
#include <QMouseEvent>
 

	
 
Zoomer::Zoomer(QWidget* widget, bool doReplot) :
 
    ScrollZoomer(widget)
 
{
 
    is_panning = false;
 

	
 
    setTrackerMode(AlwaysOn);
 

	
 
    // set corner widget between the scrollbars with default background color
 
    auto cornerWidget = new QWidget();
 
    auto bgColor = cornerWidget->palette().color(QPalette::Window).name();
 
    auto styleSheet = QString("background-color:%1;").arg(bgColor);
 
    cornerWidget->setStyleSheet(styleSheet);
 
    ScrollZoomer::setCornerWidget(cornerWidget);
 
}
 

	
 
void Zoomer::zoom(int up)
 
{
 
    ScrollZoomer::zoom(up);
 

	
 
    if(zoomRectIndex() == 0)
 
    {
 
        emit unzoomed();
 
    }
 
}
 

	
 
void Zoomer::zoom( const QRectF & rect)
 
{
 
    // set the zoom base when user zooms in to first level
 
    if (zoomRectIndex() == 0)
 
    {
 
        this->setZoomBase(false);
 
    }
 

	
 
    ScrollZoomer::zoom(rect);
 
}
 

	
 
QwtText Zoomer::trackerTextF(const QPointF& pos) const
 
{
 
    QwtText b = ScrollZoomer::trackerTextF(pos);
 

	
 
    const QPolygon pa = selection();
 
    if (pa.count() < 2)
 
    if (!isActive() || pa.count() < 2)
 
    {
 
        return b;
 
    }
 

	
 
    const QRectF rect = invTransform(QRect(pa.first(), pa.last()).normalized());
 

	
 
    QString sizeText = QString(" [%1, %2]").\
 
        arg(rect.width(), 0, 'g', 4).\
 
        arg(rect.height(), 0, 'g', 4);
 

	
 
    b.setText(b.text() + sizeText);
 

	
 
    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);
 
    }
 
}
tests/CMakeLists.txt
Show inline comments
 
#
 
# Copyright © 2017 Hasan Yavuz Özderya
 
# Copyright © 2018 Hasan Yavuz Özderya
 
#
 
# This file is part of serialplot.
 
#
 
# serialplot is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# serialplot is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
#
 

	
 
# Find the QtWidgets library
 
find_package(Qt5Widgets)
 
find_package(Qt5Test)
 

	
 
include_directories("../src")
 

	
 
add_executable(Test EXCLUDE_FROM_ALL
 
  test.cpp
 
  test_stream.cpp
 
  ../src/samplepack.cpp
 
  ../src/sink.cpp
 
  ../src/source.cpp
 
  ../src/indexbuffer.cpp
 
  ../src/linindexbuffer.cpp
 
  ../src/ringbuffer.cpp
 
  ../src/readonlybuffer.cpp
 
  ../src/stream.cpp
 
  ../src/streamchannel.cpp
 
  ../src/channelinfomodel.cpp  
 
  ../src/datachunk.cpp
 
  ../src/chunkedbuffer.cpp)
 
  ../src/chunkedbuffer.cpp
 
  )
 
add_test(NAME test1 COMMAND Test)
 
qt5_use_modules(Test Widgets)
 

	
 
qt5_wrap_ui(UI_FILES_T
 
  ../src/binarystreamreadersettings.ui
 
  ../src/asciireadersettings.ui
 
  ../src/framedreadersettings.ui
 
  ../src/demoreadersettings.ui
 
  ../src/numberformatbox.ui
 
  ../src/endiannessbox.ui
 
  )
 

	
 
# test for readers
 
add_executable(TestReaders EXCLUDE_FROM_ALL
 
  test_readers.cpp
 
  ../src/samplepack.cpp
 
  ../src/sink.cpp
 
  ../src/source.cpp
 
  ../src/abstractreader.cpp
 
  ../src/binarystreamreader.cpp
 
  ../src/binarystreamreadersettings.cpp
 
  ../src/asciireader.cpp
 
  ../src/asciireadersettings.cpp
 
  ../src/framedreader.cpp
 
  ../src/framedreadersettings.cpp
 
  ../src/demoreader.cpp
 
  ../src/demoreadersettings.cpp
 
  ../src/commandedit.cpp
 
  ../src/endiannessbox.cpp
 
  ../src/numberformatbox.cpp
 
  ../src/numberformat.cpp
 
  ${UI_FILES_T}
 
  )
 
qt5_use_modules(TestReaders Widgets Test)
 
add_test(NAME test_readers COMMAND TestReaders)
 

	
 
# test for recroder
 
add_executable(TestRecorder EXCLUDE_FROM_ALL
 
  test_recorder.cpp
 
  ../src/samplepack.cpp
 
  ../src/sink.cpp
 
  ../src/source.cpp
 
  ../src/datarecorder.cpp
 
)
 
qt5_use_modules(TestRecorder Widgets Test)
 
add_test(NAME test_recorder COMMAND TestRecorder)
 

	
 
set(CMAKE_CTEST_COMMAND ctest -V)
 
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND})
 
add_dependencies(check Test)
 
add_dependencies(check
 
  Test
 
  TestReaders
 
  TestRecorder
 
  )
tests/catch.hpp
Show inline comments
 
/*
 
 *  Catch v1.9.6
 
 *  Generated: 2017-06-27 12:19:54.557875
 
 *  ----------------------------------------------------------
 
 *  This file has been merged from multiple headers. Please don't edit it directly
 
 *  Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved.
 
 *
 
 *  Distributed under the Boost Software License, Version 1.0. (See accompanying
 
 *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
 
 */
 
#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
 
#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
 

	
 
#define TWOBLUECUBES_CATCH_HPP_INCLUDED
 

	
 
#ifdef __clang__
 
#    pragma clang system_header
 
#elif defined __GNUC__
 
#    pragma GCC system_header
 
#endif
 

	
 
// #included from: internal/catch_suppress_warnings.h
 

	
 
#ifdef __clang__
 
#   ifdef __ICC // icpc defines the __clang__ macro
 
#       pragma warning(push)
 
#       pragma warning(disable: 161 1682)
 
#   else // __ICC
 
#       pragma clang diagnostic ignored "-Wglobal-constructors"
 
#       pragma clang diagnostic ignored "-Wvariadic-macros"
 
#       pragma clang diagnostic ignored "-Wc99-extensions"
 
#       pragma clang diagnostic ignored "-Wunused-variable"
 
#       pragma clang diagnostic push
 
#       pragma clang diagnostic ignored "-Wpadded"
 
#       pragma clang diagnostic ignored "-Wc++98-compat"
 
#       pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
 
#       pragma clang diagnostic ignored "-Wswitch-enum"
 
#       pragma clang diagnostic ignored "-Wcovered-switch-default"
 
#    endif
 
#elif defined __GNUC__
 
#    pragma GCC diagnostic ignored "-Wvariadic-macros"
 
#    pragma GCC diagnostic ignored "-Wunused-variable"
 
#    pragma GCC diagnostic ignored "-Wparentheses"
 

	
 
#    pragma GCC diagnostic push
 
#    pragma GCC diagnostic ignored "-Wpadded"
 
#endif
 
#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER)
 
#  define CATCH_IMPL
 
#endif
 

	
 
#ifdef CATCH_IMPL
 
#  ifndef CLARA_CONFIG_MAIN
 
#    define CLARA_CONFIG_MAIN_NOT_DEFINED
 
#    define CLARA_CONFIG_MAIN
 
#  endif
 
#endif
 

	
 
// #included from: internal/catch_notimplemented_exception.h
 
#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_H_INCLUDED
 

	
 
// #included from: catch_common.h
 
#define TWOBLUECUBES_CATCH_COMMON_H_INCLUDED
 

	
 
// #included from: catch_compiler_capabilities.h
 
#define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED
 

	
 
// Detect a number of compiler features - mostly C++11/14 conformance - by compiler
 
// The following features are defined:
 
//
 
// CATCH_CONFIG_CPP11_NULLPTR : is nullptr supported?
 
// CATCH_CONFIG_CPP11_NOEXCEPT : is noexcept supported?
 
// CATCH_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods
 
// CATCH_CONFIG_CPP11_IS_ENUM : std::is_enum is supported?
 
// CATCH_CONFIG_CPP11_TUPLE : std::tuple is supported
 
// CATCH_CONFIG_CPP11_LONG_LONG : is long long supported?
 
// CATCH_CONFIG_CPP11_OVERRIDE : is override supported?
 
// CATCH_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr)
 
// CATCH_CONFIG_CPP11_SHUFFLE : is std::shuffle supported?
 
// CATCH_CONFIG_CPP11_TYPE_TRAITS : are type_traits and enable_if supported?
 

	
 
// CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported?
 

	
 
// CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported?
 
// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported?
 
// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported?
 
// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported?
 
// ****************
 
// Note to maintainers: if new toggles are added please document them
 
// in configuration.md, too
 
// ****************
 

	
 
// In general each macro has a _NO_<feature name> form
 
// (e.g. CATCH_CONFIG_CPP11_NO_NULLPTR) which disables the feature.
 
// Many features, at point of detection, define an _INTERNAL_ macro, so they
 
// can be combined, en-mass, with the _NO_ forms later.
 

	
 
// All the C++11 features can be disabled with CATCH_CONFIG_NO_CPP11
 

	
 
#ifdef __cplusplus
 

	
 
#  if __cplusplus >= 201103L
 
#    define CATCH_CPP11_OR_GREATER
 
#  endif
 

	
 
#  if __cplusplus >= 201402L
 
#    define CATCH_CPP14_OR_GREATER
 
#  endif
 

	
 
#endif
 

	
 
#ifdef __clang__
 

	
 
#  if __has_feature(cxx_nullptr)
 
#    define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR
 
#  endif
 

	
 
#  if __has_feature(cxx_noexcept)
 
#    define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT
 
#  endif
 

	
 
#   if defined(CATCH_CPP11_OR_GREATER)
 
#       define CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
 
            _Pragma( "clang diagnostic push" ) \
 
            _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" )
 
#       define CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \
 
            _Pragma( "clang diagnostic pop" )
 

	
 
#       define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \
 
            _Pragma( "clang diagnostic push" ) \
 
            _Pragma( "clang diagnostic ignored \"-Wparentheses\"" )
 
#       define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \
 
            _Pragma( "clang diagnostic pop" )
 
#   endif
 

	
 
#endif // __clang__
 

	
 
////////////////////////////////////////////////////////////////////////////////
 
// We know some environments not to support full POSIX signals
 
#if defined(__CYGWIN__) || defined(__QNX__)
 

	
 
#   if !defined(CATCH_CONFIG_POSIX_SIGNALS)
 
#       define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS
 
#   endif
 

	
 
#endif
 

	
 
////////////////////////////////////////////////////////////////////////////////
 
// Cygwin
 
#ifdef __CYGWIN__
 

	
 
// Required for some versions of Cygwin to declare gettimeofday
 
// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin
 
#   define _BSD_SOURCE
 

	
 
#endif // __CYGWIN__
 

	
 
////////////////////////////////////////////////////////////////////////////////
 
// Borland
 
#ifdef __BORLANDC__
 

	
 
#endif // __BORLANDC__
 

	
 
////////////////////////////////////////////////////////////////////////////////
 
// EDG
 
#ifdef __EDG_VERSION__
 

	
 
#endif // __EDG_VERSION__
 

	
 
////////////////////////////////////////////////////////////////////////////////
 
// Digital Mars
 
#ifdef __DMC__
 

	
 
#endif // __DMC__
 

	
 
////////////////////////////////////////////////////////////////////////////////
 
// GCC
 
#ifdef __GNUC__
 

	
 
#   if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__)
 
#       define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR
 
#   endif
 

	
 
// - otherwise more recent versions define __cplusplus >= 201103L
 
// and will get picked up below
 

	
 
#endif // __GNUC__
 

	
 
////////////////////////////////////////////////////////////////////////////////
 
// Visual C++
 
#ifdef _MSC_VER
 

	
 
#define CATCH_INTERNAL_CONFIG_WINDOWS_SEH
 

	
 
#if (_MSC_VER >= 1600)
 
#   define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR
 
#   define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR
 
#endif
 

	
 
#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015))
 
#define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT
 
#define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS
 
#define CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE
 
#define CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS
 
#endif
 

	
 
#endif // _MSC_VER
 

	
 
////////////////////////////////////////////////////////////////////////////////
 

	
 
// Use variadic macros if the compiler supports them
 
#if ( defined _MSC_VER && _MSC_VER > 1400 && !defined __EDGE__) || \
 
    ( defined __WAVE__ && __WAVE_HAS_VARIADICS ) || \
 
    ( defined __GNUC__ && __GNUC__ >= 3 ) || \
 
    ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L )
 

	
 
#define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS
 

	
 
#endif
 

	
 
// Use __COUNTER__ if the compiler supports it
 
#if ( defined _MSC_VER && _MSC_VER >= 1300 ) || \
 
    ( defined __GNUC__  && ( __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3 )) ) || \
 
    ( defined __clang__ && __clang_major__ >= 3 )
 

	
 
#define CATCH_INTERNAL_CONFIG_COUNTER
 

	
 
#endif
 

	
 
////////////////////////////////////////////////////////////////////////////////
 
// C++ language feature support
 

	
 
// catch all support for C++11
 
#if defined(CATCH_CPP11_OR_GREATER)
 

	
 
#  if !defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR)
 
#    define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR
 
#  endif
 

	
 
#  ifndef CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT
 
#    define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT
 
#  endif
 

	
 
#  ifndef CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS
 
#    define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS
 
#  endif
 

	
 
#  ifndef CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM
 
#    define CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM
 
#  endif
 

	
 
#  ifndef CATCH_INTERNAL_CONFIG_CPP11_TUPLE
 
#    define CATCH_INTERNAL_CONFIG_CPP11_TUPLE
 
#  endif
 

	
 
#  ifndef CATCH_INTERNAL_CONFIG_VARIADIC_MACROS
 
#    define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS
 
#  endif
 

	
 
#  if !defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG)
 
#    define CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG
 
#  endif
 

	
 
#  if !defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE)
 
#    define CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE
 
#  endif
 
#  if !defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR)
 
#    define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR
 
#  endif
 
# if !defined(CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE)
 
#   define CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE
 
#  endif
 
# if !defined(CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS)
 
#  define CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS
 
# endif
 

	
 
#endif // __cplusplus >= 201103L
 

	
 
// Now set the actual defines based on the above + anything the user has configured
 
#if defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NO_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_NO_CPP11)
 
#   define CATCH_CONFIG_CPP11_NULLPTR
 
#endif
 
#if defined(CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_NO_CPP11)
 
#   define CATCH_CONFIG_CPP11_NOEXCEPT
 
#endif
 
#if defined(CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_NO_CPP11)
 
#   define CATCH_CONFIG_CPP11_GENERATED_METHODS
 
#endif
 
#if defined(CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_NO_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_NO_CPP11)
 
#   define CATCH_CONFIG_CPP11_IS_ENUM
 
#endif
 
#if defined(CATCH_INTERNAL_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_CPP11_NO_TUPLE) && !defined(CATCH_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_NO_CPP11)
 
#   define CATCH_CONFIG_CPP11_TUPLE
 
#endif
 
#if defined(CATCH_INTERNAL_CONFIG_VARIADIC_MACROS) && !defined(CATCH_CONFIG_NO_VARIADIC_MACROS) && !defined(CATCH_CONFIG_VARIADIC_MACROS)
 
#   define CATCH_CONFIG_VARIADIC_MACROS
 
#endif
 
#if defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_NO_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_NO_CPP11)
 
#   define CATCH_CONFIG_CPP11_LONG_LONG
 
#endif
 
#if defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_NO_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_NO_CPP11)
 
#   define CATCH_CONFIG_CPP11_OVERRIDE
 
#endif
 
#if defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_NO_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_CPP11)
 
#   define CATCH_CONFIG_CPP11_UNIQUE_PTR
 
#endif
 
// Use of __COUNTER__ is suppressed if __JETBRAINS_IDE__ is #defined (meaning we're being parsed by a JetBrains IDE for
 
// analytics) because, at time of writing, __COUNTER__ is not properly handled by it.
 
// This does not affect compilation
 
#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) && !defined(__JETBRAINS_IDE__)
 
#   define CATCH_CONFIG_COUNTER
 
#endif
 
#if defined(CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE) && !defined(CATCH_CONFIG_CPP11_NO_SHUFFLE) && !defined(CATCH_CONFIG_CPP11_SHUFFLE) && !defined(CATCH_CONFIG_NO_CPP11)
 
#   define CATCH_CONFIG_CPP11_SHUFFLE
 
#endif
 
# if defined(CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS) && !defined(CATCH_CONFIG_CPP11_NO_TYPE_TRAITS) && !defined(CATCH_CONFIG_CPP11_TYPE_TRAITS) && !defined(CATCH_CONFIG_NO_CPP11)
 
#  define CATCH_CONFIG_CPP11_TYPE_TRAITS
 
# endif
 
#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH)
 
#   define CATCH_CONFIG_WINDOWS_SEH
 
#endif
 
// This is set by default, because we assume that unix compilers are posix-signal-compatible by default.
 
#if !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS)
 
#   define CATCH_CONFIG_POSIX_SIGNALS
 
#endif
 

	
 
#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS)
 
#   define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS
 
#   define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS
 
#endif
 
#if !defined(CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS)
 
#   define CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS
 
#   define CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS
 
#endif
 

	
 
// noexcept support:
 
#if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT)
 
#  define CATCH_NOEXCEPT noexcept
 
#  define CATCH_NOEXCEPT_IS(x) noexcept(x)
 
#else
 
#  define CATCH_NOEXCEPT throw()
 
#  define CATCH_NOEXCEPT_IS(x)
 
#endif
 

	
 
// nullptr support
 
#ifdef CATCH_CONFIG_CPP11_NULLPTR
 
#   define CATCH_NULL nullptr
 
#else
 
#   define CATCH_NULL NULL
 
#endif
 

	
 
// override support
 
#ifdef CATCH_CONFIG_CPP11_OVERRIDE
 
#   define CATCH_OVERRIDE override
 
#else
 
#   define CATCH_OVERRIDE
 
#endif
 

	
 
// unique_ptr support
 
#ifdef CATCH_CONFIG_CPP11_UNIQUE_PTR
 
#   define CATCH_AUTO_PTR( T ) std::unique_ptr<T>
 
#else
 
#   define CATCH_AUTO_PTR( T ) std::auto_ptr<T>
 
#endif
 

	
 
#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line
 
#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line )
 
#ifdef CATCH_CONFIG_COUNTER
 
#  define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ )
 
#else
 
#  define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ )
 
#endif
 

	
 
#define INTERNAL_CATCH_STRINGIFY2( expr ) #expr
 
#define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr )
 

	
 
#include <sstream>
 
#include <algorithm>
 

	
 
namespace Catch {
 

	
 
    struct IConfig;
 

	
 
    struct CaseSensitive { enum Choice {
 
        Yes,
 
        No
 
    }; };
 

	
 
    class NonCopyable {
 
#ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
 
        NonCopyable( NonCopyable const& )              = delete;
 
        NonCopyable( NonCopyable && )                  = delete;
 
        NonCopyable& operator = ( NonCopyable const& ) = delete;
 
        NonCopyable& operator = ( NonCopyable && )     = delete;
 
#else
 
        NonCopyable( NonCopyable const& info );
 
        NonCopyable& operator = ( NonCopyable const& );
 
#endif
 

	
 
    protected:
 
        NonCopyable() {}
 
        virtual ~NonCopyable();
 
    };
 

	
 
    class SafeBool {
 
    public:
 
        typedef void (SafeBool::*type)() const;
 

	
 
        static type makeSafe( bool value ) {
 
            return value ? &SafeBool::trueValue : 0;
 
        }
 
    private:
 
        void trueValue() const {}
 
    };
 

	
 
    template<typename ContainerT>
 
    inline void deleteAll( ContainerT& container ) {
 
        typename ContainerT::const_iterator it = container.begin();
 
        typename ContainerT::const_iterator itEnd = container.end();
 
        for(; it != itEnd; ++it )
 
            delete *it;
 
    }
 
    template<typename AssociativeContainerT>
 
    inline void deleteAllValues( AssociativeContainerT& container ) {
 
        typename AssociativeContainerT::const_iterator it = container.begin();
 
        typename AssociativeContainerT::const_iterator itEnd = container.end();
 
        for(; it != itEnd; ++it )
 
            delete it->second;
 
    }
 

	
 
    bool startsWith( std::string const& s, std::string const& prefix );
 
    bool startsWith( std::string const& s, char prefix );
 
    bool endsWith( std::string const& s, std::string const& suffix );
 
    bool endsWith( std::string const& s, char suffix );
 
    bool contains( std::string const& s, std::string const& infix );
 
    void toLowerInPlace( std::string& s );
 
    std::string toLower( std::string const& s );
 
    std::string trim( std::string const& str );
 
    bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis );
 

	
 
    struct pluralise {
 
        pluralise( std::size_t count, std::string const& label );
 

	
 
        friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser );
 

	
 
        std::size_t m_count;
 
        std::string m_label;
 
    };
 

	
 
    struct SourceLineInfo {
 

	
 
        SourceLineInfo();
 
        SourceLineInfo( char const* _file, std::size_t _line );
 
#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
 
        SourceLineInfo(SourceLineInfo const& other)          = default;
 
        SourceLineInfo( SourceLineInfo && )                  = default;
 
        SourceLineInfo& operator = ( SourceLineInfo const& ) = default;
 
        SourceLineInfo& operator = ( SourceLineInfo && )     = default;
 
#  endif
 
        bool empty() const;
 
        bool operator == ( SourceLineInfo const& other ) const;
 
        bool operator < ( SourceLineInfo const& other ) const;
 

	
 
        char const* file;
 
        std::size_t line;
 
    };
 

	
 
    std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info );
 

	
 
    // This is just here to avoid compiler warnings with macro constants and boolean literals
 
    inline bool isTrue( bool value ){ return value; }
 
    inline bool alwaysTrue() { return true; }
 
    inline bool alwaysFalse() { return false; }
 

	
 
    void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo );
 

	
 
    void seedRng( IConfig const& config );
 
    unsigned int rngSeed();
 

	
 
    // Use this in variadic streaming macros to allow
 
    //    >> +StreamEndStop
 
    // as well as
 
    //    >> stuff +StreamEndStop
 
    struct StreamEndStop {
 
        std::string operator+() {
 
            return std::string();
 
        }
 
    };
 
    template<typename T>
 
    T const& operator + ( T const& value, StreamEndStop ) {
 
        return value;
 
    }
 
}
 

	
 
#define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo( __FILE__, static_cast<std::size_t>( __LINE__ ) )
 
#define CATCH_INTERNAL_ERROR( msg ) ::Catch::throwLogicError( msg, CATCH_INTERNAL_LINEINFO );
 

	
 
namespace Catch {
 

	
 
    class NotImplementedException : public std::exception
 
    {
 
    public:
 
        NotImplementedException( SourceLineInfo const& lineInfo );
 
        NotImplementedException( NotImplementedException const& ) {}
 

	
 
        virtual ~NotImplementedException() CATCH_NOEXCEPT {}
 

	
 
        virtual const char* what() const CATCH_NOEXCEPT;
 

	
 
    private:
 
        std::string m_what;
 
        SourceLineInfo m_lineInfo;
 
    };
 

	
 
} // end namespace Catch
 

	
 
///////////////////////////////////////////////////////////////////////////////
 
#define CATCH_NOT_IMPLEMENTED throw Catch::NotImplementedException( CATCH_INTERNAL_LINEINFO )
 

	
 
// #included from: internal/catch_context.h
 
#define TWOBLUECUBES_CATCH_CONTEXT_H_INCLUDED
 

	
 
// #included from: catch_interfaces_generators.h
 
#define TWOBLUECUBES_CATCH_INTERFACES_GENERATORS_H_INCLUDED
 

	
 
#include <string>
 

	
 
namespace Catch {
 

	
 
    struct IGeneratorInfo {
 
        virtual ~IGeneratorInfo();
 
        virtual bool moveNext() = 0;
 
        virtual std::size_t getCurrentIndex() const = 0;
 
    };
 

	
 
    struct IGeneratorsForTest {
 
        virtual ~IGeneratorsForTest();
 

	
 
        virtual IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) = 0;
 
        virtual bool moveNext() = 0;
 
    };
 

	
 
    IGeneratorsForTest* createGeneratorsForTest();
 

	
 
} // end namespace Catch
 

	
 
// #included from: catch_ptr.hpp
 
#define TWOBLUECUBES_CATCH_PTR_HPP_INCLUDED
 

	
 
#ifdef __clang__
 
#pragma clang diagnostic push
 
#pragma clang diagnostic ignored "-Wpadded"
 
#endif
 

	
 
namespace Catch {
 

	
 
    // An intrusive reference counting smart pointer.
 
    // T must implement addRef() and release() methods
 
    // typically implementing the IShared interface
 
    template<typename T>
 
    class Ptr {
 
    public:
 
        Ptr() : m_p( CATCH_NULL ){}
 
        Ptr( T* p ) : m_p( p ){
 
            if( m_p )
 
                m_p->addRef();
 
        }
 
        Ptr( Ptr const& other ) : m_p( other.m_p ){
 
            if( m_p )
 
                m_p->addRef();
 
        }
 
        ~Ptr(){
 
            if( m_p )
 
                m_p->release();
 
        }
 
        void reset() {
 
            if( m_p )
 
                m_p->release();
 
            m_p = CATCH_NULL;
 
        }
 
        Ptr& operator = ( T* p ){
 
            Ptr temp( p );
 
            swap( temp );
 
            return *this;
 
        }
 
        Ptr& operator = ( Ptr const& other ){
 
            Ptr temp( other );
 
            swap( temp );
 
            return *this;
 
        }
 
        void swap( Ptr& other ) { std::swap( m_p, other.m_p ); }
 
        T* get() const{ return m_p; }
 
        T& operator*() const { return *m_p; }
 
        T* operator->() const { return m_p; }
 
        bool operator !() const { return m_p == CATCH_NULL; }
 
        operator SafeBool::type() const { return SafeBool::makeSafe( m_p != CATCH_NULL ); }
 

	
 
    private:
 
        T* m_p;
 
    };
 

	
 
    struct IShared : NonCopyable {
 
        virtual ~IShared();
 
        virtual void addRef() const = 0;
 
        virtual void release() const = 0;
 
    };
 

	
 
    template<typename T = IShared>
 
    struct SharedImpl : T {
 

	
 
        SharedImpl() : m_rc( 0 ){}
 

	
 
        virtual void addRef() const {
 
            ++m_rc;
 
        }
 
        virtual void release() const {
 
            if( --m_rc == 0 )
 
                delete this;
 
        }
 

	
 
        mutable unsigned int m_rc;
 
    };
 

	
 
} // end namespace Catch
 

	
 
#ifdef __clang__
 
#pragma clang diagnostic pop
 
#endif
 

	
 
namespace Catch {
 

	
 
    class TestCase;
 
    class Stream;
 
    struct IResultCapture;
 
    struct IRunner;
 
    struct IGeneratorsForTest;
 
    struct IConfig;
 

	
 
    struct IContext
 
    {
 
        virtual ~IContext();
 

	
 
        virtual IResultCapture* getResultCapture() = 0;
 
        virtual IRunner* getRunner() = 0;
 
        virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) = 0;
 
        virtual bool advanceGeneratorsForCurrentTest() = 0;
 
        virtual Ptr<IConfig const> getConfig() const = 0;
 
    };
 

	
 
    struct IMutableContext : IContext
 
    {
 
        virtual ~IMutableContext();
 
        virtual void setResultCapture( IResultCapture* resultCapture ) = 0;
 
        virtual void setRunner( IRunner* runner ) = 0;
 
        virtual void setConfig( Ptr<IConfig const> const& config ) = 0;
 
    };
 

	
 
    IContext& getCurrentContext();
 
    IMutableContext& getCurrentMutableContext();
 
    void cleanUpContext();
 
    Stream createStream( std::string const& streamName );
 

	
 
}
 

	
 
// #included from: internal/catch_test_registry.hpp
 
#define TWOBLUECUBES_CATCH_TEST_REGISTRY_HPP_INCLUDED
 

	
 
// #included from: catch_interfaces_testcase.h
 
#define TWOBLUECUBES_CATCH_INTERFACES_TESTCASE_H_INCLUDED
 

	
 
#include <vector>
 

	
 
namespace Catch {
 

	
 
    class TestSpec;
 

	
 
    struct ITestCase : IShared {
 
        virtual void invoke () const = 0;
 
    protected:
 
        virtual ~ITestCase();
 
    };
 

	
 
    class TestCase;
 
    struct IConfig;
 

	
 
    struct ITestCaseRegistry {
 
        virtual ~ITestCaseRegistry();
 
        virtual std::vector<TestCase> const& getAllTests() const = 0;
 
        virtual std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const = 0;
 
    };
 

	
 
    bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config );
 
    std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config );
 
    std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config );
 

	
 
}
 

	
 
namespace Catch {
 

	
 
template<typename C>
 
class MethodTestCase : public SharedImpl<ITestCase> {
 

	
 
public:
 
    MethodTestCase( void (C::*method)() ) : m_method( method ) {}
 

	
 
    virtual void invoke() const {
 
        C obj;
 
        (obj.*m_method)();
 
    }
 

	
 
private:
 
    virtual ~MethodTestCase() {}
 

	
 
    void (C::*m_method)();
 
};
 

	
 
typedef void(*TestFunction)();
 

	
 
struct NameAndDesc {
 
    NameAndDesc( const char* _name = "", const char* _description= "" )
 
    : name( _name ), description( _description )
 
    {}
 

	
 
    const char* name;
 
    const char* description;
 
};
 

	
 
void registerTestCase
 
    (   ITestCase* testCase,
 
        char const* className,
 
        NameAndDesc const& nameAndDesc,
 
        SourceLineInfo const& lineInfo );
 

	
 
struct AutoReg {
 

	
 
    AutoReg
 
        (   TestFunction function,
 
            SourceLineInfo const& lineInfo,
 
            NameAndDesc const& nameAndDesc );
 

	
 
    template<typename C>
 
    AutoReg
 
        (   void (C::*method)(),
 
            char const* className,
 
            NameAndDesc const& nameAndDesc,
 
            SourceLineInfo const& lineInfo ) {
 

	
 
        registerTestCase
 
            (   new MethodTestCase<C>( method ),
 
                className,
 
                nameAndDesc,
 
                lineInfo );
 
    }
 

	
 
    ~AutoReg();
 

	
 
private:
 
    AutoReg( AutoReg const& );
 
    void operator= ( AutoReg const& );
 
};
 

	
 
void registerTestCaseFunction
 
    (   TestFunction function,
 
        SourceLineInfo const& lineInfo,
 
        NameAndDesc const& nameAndDesc );
 

	
 
} // end namespace Catch
 

	
 
#ifdef CATCH_CONFIG_VARIADIC_MACROS
 
    ///////////////////////////////////////////////////////////////////////////////
 
    #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \
 
        static void TestName(); \
 
        CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
 
        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); } \
 
        CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \
 
        static void TestName()
 
    #define INTERNAL_CATCH_TESTCASE( ... ) \
 
        INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ )
 

	
 
    ///////////////////////////////////////////////////////////////////////////////
 
    #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \
 
        CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
 
        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); } \
 
        CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS
 

	
 
    ///////////////////////////////////////////////////////////////////////////////
 
    #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\
 
        CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
 
        namespace{ \
 
            struct TestName : ClassName{ \
 
                void test(); \
 
            }; \
 
            Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestName::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \
 
        } \
 
        CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \
 
        void TestName::test()
 
    #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \
 
        INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ )
 

	
 
    ///////////////////////////////////////////////////////////////////////////////
 
    #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \
 
        CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
 
        Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); \
 
        CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS
 

	
 
#else
 
    ///////////////////////////////////////////////////////////////////////////////
 
    #define INTERNAL_CATCH_TESTCASE2( TestName, Name, Desc ) \
 
        static void TestName(); \
 
        CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
 
        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\
 
        CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \
 
        static void TestName()
 
    #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \
 
        INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), Name, Desc )
 

	
 
    ///////////////////////////////////////////////////////////////////////////////
 
    #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \
 
        CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
 
        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); } \
 
        CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS
 

	
 
    ///////////////////////////////////////////////////////////////////////////////
 
    #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestCaseName, ClassName, TestName, Desc )\
 
        CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
 
        namespace{ \
 
            struct TestCaseName : ClassName{ \
 
                void test(); \
 
            }; \
 
            Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestCaseName::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \
 
        } \
 
        CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \
 
        void TestCaseName::test()
 
    #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\
 
        INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, TestName, Desc )
 

	
 
    ///////////////////////////////////////////////////////////////////////////////
 
    #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, Name, Desc ) \
 
        CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
 
        Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); \
 
        CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS
 

	
 
#endif
 

	
 
// #included from: internal/catch_capture.hpp
 
#define TWOBLUECUBES_CATCH_CAPTURE_HPP_INCLUDED
 

	
 
// #included from: catch_result_builder.h
 
#define TWOBLUECUBES_CATCH_RESULT_BUILDER_H_INCLUDED
 

	
 
// #included from: catch_result_type.h
 
#define TWOBLUECUBES_CATCH_RESULT_TYPE_H_INCLUDED
 

	
 
namespace Catch {
 

	
 
    // ResultWas::OfType enum
 
    struct ResultWas { enum OfType {
 
        Unknown = -1,
 
        Ok = 0,
 
        Info = 1,
 
        Warning = 2,
 

	
 
        FailureBit = 0x10,
 

	
 
        ExpressionFailed = FailureBit | 1,
 
        ExplicitFailure = FailureBit | 2,
 

	
 
        Exception = 0x100 | FailureBit,
 

	
 
        ThrewException = Exception | 1,
 
        DidntThrowException = Exception | 2,
 

	
 
        FatalErrorCondition = 0x200 | FailureBit
 

	
 
    }; };
 

	
 
    inline bool isOk( ResultWas::OfType resultType ) {
 
        return ( resultType & ResultWas::FailureBit ) == 0;
 
    }
 
    inline bool isJustInfo( int flags ) {
 
        return flags == ResultWas::Info;
 
    }
 

	
 
    // ResultDisposition::Flags enum
 
    struct ResultDisposition { enum Flags {
 
        Normal = 0x01,
 

	
 
        ContinueOnFailure = 0x02,   // Failures fail test, but execution continues
 
        FalseTest = 0x04,           // Prefix expression with !
 
        SuppressFail = 0x08         // Failures are reported but do not fail the test
 
    }; };
 

	
 
    inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) {
 
        return static_cast<ResultDisposition::Flags>( static_cast<int>( lhs ) | static_cast<int>( rhs ) );
 
    }
 

	
 
    inline bool shouldContinueOnFailure( int flags )    { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; }
 
    inline bool isFalseTest( int flags )                { return ( flags & ResultDisposition::FalseTest ) != 0; }
 
    inline bool shouldSuppressFailure( int flags )      { return ( flags & ResultDisposition::SuppressFail ) != 0; }
 

	
 
} // end namespace Catch
 

	
 
// #included from: catch_assertionresult.h
 
#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_H_INCLUDED
 

	
 
#include <string>
 

	
 
namespace Catch {
 

	
 
    struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison;
 

	
 
    struct DecomposedExpression
 
    {
 
        virtual ~DecomposedExpression() {}
 
        virtual bool isBinaryExpression() const {
 
            return false;
 
        }
 
        virtual void reconstructExpression( std::string& dest ) const = 0;
 

	
 
        // Only simple binary comparisons can be decomposed.
 
        // If more complex check is required then wrap sub-expressions in parentheses.
 
        template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( T const& );
 
        template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( T const& );
 
        template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( T const& );
 
        template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( T const& );
 
        template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator % ( T const& );
 
        template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( T const& );
 
        template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( T const& );
 

	
 
    private:
 
        DecomposedExpression& operator = (DecomposedExpression const&);
 
    };
 

	
 
    struct AssertionInfo
 
    {
 
        AssertionInfo() {}
 
        AssertionInfo(  char const * _macroName,
 
                        SourceLineInfo const& _lineInfo,
 
                        char const * _capturedExpression,
 
                        ResultDisposition::Flags _resultDisposition,
 
                        char const * _secondArg = "");
 

	
 
        char const * macroName;
 
        SourceLineInfo lineInfo;
 
        char const * capturedExpression;
 
        ResultDisposition::Flags resultDisposition;
 
        char const * secondArg;
 
    };
 

	
 
    struct AssertionResultData
 
    {
 
        AssertionResultData() : decomposedExpression( CATCH_NULL )
 
                              , resultType( ResultWas::Unknown )
 
                              , negated( false )
 
                              , parenthesized( false ) {}
 

	
 
        void negate( bool parenthesize ) {
 
            negated = !negated;
 
            parenthesized = parenthesize;
 
            if( resultType == ResultWas::Ok )
 
                resultType = ResultWas::ExpressionFailed;
 
            else if( resultType == ResultWas::ExpressionFailed )
 
                resultType = ResultWas::Ok;
 
        }
 

	
 
        std::string const& reconstructExpression() const {
 
            if( decomposedExpression != CATCH_NULL ) {
 
                decomposedExpression->reconstructExpression( reconstructedExpression );
 
                if( parenthesized ) {
 
                    reconstructedExpression.insert( 0, 1, '(' );
 
                    reconstructedExpression.append( 1, ')' );
 
                }
 
                if( negated ) {
 
                    reconstructedExpression.insert( 0, 1, '!' );
 
                }
 
                decomposedExpression = CATCH_NULL;
 
            }
 
            return reconstructedExpression;
 
        }
 

	
 
        mutable DecomposedExpression const* decomposedExpression;
 
        mutable std::string reconstructedExpression;
 
        std::string message;
 
        ResultWas::OfType resultType;
 
        bool negated;
 
        bool parenthesized;
 
    };
 

	
 
    class AssertionResult {
 
    public:
 
        AssertionResult();
 
        AssertionResult( AssertionInfo const& info, AssertionResultData const& data );
 
        ~AssertionResult();
 
#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
 
         AssertionResult( AssertionResult const& )              = default;
 
         AssertionResult( AssertionResult && )                  = default;
 
         AssertionResult& operator = ( AssertionResult const& ) = default;
 
         AssertionResult& operator = ( AssertionResult && )     = default;
 
#  endif
 

	
 
        bool isOk() const;
 
        bool succeeded() const;
 
        ResultWas::OfType getResultType() const;
 
        bool hasExpression() const;
 
        bool hasMessage() const;
 
        std::string getExpression() const;
 
        std::string getExpressionInMacro() const;
 
        bool hasExpandedExpression() const;
 
        std::string getExpandedExpression() const;
 
        std::string getMessage() const;
 
        SourceLineInfo getSourceInfo() const;
 
        std::string getTestMacroName() const;
 
        void discardDecomposedExpression() const;
 
        void expandDecomposedExpression() const;
 

	
 
    protected:
 
        AssertionInfo m_info;
 
        AssertionResultData m_resultData;
 
    };
 

	
 
} // end namespace Catch
 

	
 
// #included from: catch_matchers.hpp
 
#define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED
 

	
 
namespace Catch {
 
namespace Matchers {
 
    namespace Impl {
 

	
 
        template<typename ArgT> struct MatchAllOf;
 
        template<typename ArgT> struct MatchAnyOf;
 
        template<typename ArgT> struct MatchNotOf;
 

	
 
        class MatcherUntypedBase {
 
        public:
 
            std::string toString() const {
 
                if( m_cachedToString.empty() )
 
                    m_cachedToString = describe();
 
                return m_cachedToString;
 
            }
 

	
 
        protected:
 
            virtual ~MatcherUntypedBase();
 
            virtual std::string describe() const = 0;
 
            mutable std::string m_cachedToString;
 
        private:
 
            MatcherUntypedBase& operator = ( MatcherUntypedBase const& );
 
        };
 

	
 
        template<typename ObjectT>
 
        struct MatcherMethod {
 
            virtual bool match( ObjectT const& arg ) const = 0;
 
        };
 
        template<typename PtrT>
 
        struct MatcherMethod<PtrT*> {
 
            virtual bool match( PtrT* arg ) const = 0;
 
        };
 

	
 
        template<typename ObjectT, typename ComparatorT = ObjectT>
 
        struct MatcherBase : MatcherUntypedBase, MatcherMethod<ObjectT> {
 

	
 
            MatchAllOf<ComparatorT> operator && ( MatcherBase const& other ) const;
 
            MatchAnyOf<ComparatorT> operator || ( MatcherBase const& other ) const;
 
            MatchNotOf<ComparatorT> operator ! () const;
 
        };
 

	
 
        template<typename ArgT>
 
        struct MatchAllOf : MatcherBase<ArgT> {
 
            virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE {
 
                for( std::size_t i = 0; i < m_matchers.size(); ++i ) {
 
                    if (!m_matchers[i]->match(arg))
 
                        return false;
 
                }
 
                return true;
 
            }
 
            virtual std::string describe() const CATCH_OVERRIDE {
 
                std::string description;
 
                description.reserve( 4 + m_matchers.size()*32 );
 
                description += "( ";
 
                for( std::size_t i = 0; i < m_matchers.size(); ++i ) {
 
                    if( i != 0 )
 
                        description += " and ";
 
                    description += m_matchers[i]->toString();
 
                }
 
                description += " )";
 
                return description;
 
            }
 

	
 
            MatchAllOf<ArgT>& operator && ( MatcherBase<ArgT> const& other ) {
 
                m_matchers.push_back( &other );
 
                return *this;
 
            }
 

	
 
            std::vector<MatcherBase<ArgT> const*> m_matchers;
 
        };
 
        template<typename ArgT>
 
        struct MatchAnyOf : MatcherBase<ArgT> {
 

	
 
            virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE {
 
                for( std::size_t i = 0; i < m_matchers.size(); ++i ) {
 
                    if (m_matchers[i]->match(arg))
 
                        return true;
 
                }
 
                return false;
 
            }
 
            virtual std::string describe() const CATCH_OVERRIDE {
 
                std::string description;
 
                description.reserve( 4 + m_matchers.size()*32 );
 
                description += "( ";
 
                for( std::size_t i = 0; i < m_matchers.size(); ++i ) {
 
                    if( i != 0 )
 
                        description += " or ";
 
                    description += m_matchers[i]->toString();
 
                }
 
                description += " )";
 
                return description;
 
            }
 

	
 
            MatchAnyOf<ArgT>& operator || ( MatcherBase<ArgT> const& other ) {
 
                m_matchers.push_back( &other );
 
                return *this;
 
            }
 

	
 
            std::vector<MatcherBase<ArgT> const*> m_matchers;
 
        };
 

	
 
        template<typename ArgT>
 
        struct MatchNotOf : MatcherBase<ArgT> {
 

	
 
            MatchNotOf( MatcherBase<ArgT> const& underlyingMatcher ) : m_underlyingMatcher( underlyingMatcher ) {}
 

	
 
            virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE {
 
                return !m_underlyingMatcher.match( arg );
 
            }
 

	
 
            virtual std::string describe() const CATCH_OVERRIDE {
 
                return "not " + m_underlyingMatcher.toString();
 
            }
 
            MatcherBase<ArgT> const& m_underlyingMatcher;
 
        };
 

	
 
        template<typename ObjectT, typename ComparatorT>
 
        MatchAllOf<ComparatorT> MatcherBase<ObjectT, ComparatorT>::operator && ( MatcherBase const& other ) const {
 
            return MatchAllOf<ComparatorT>() && *this && other;
 
        }
 
        template<typename ObjectT, typename ComparatorT>
 
        MatchAnyOf<ComparatorT> MatcherBase<ObjectT, ComparatorT>::operator || ( MatcherBase const& other ) const {
 
            return MatchAnyOf<ComparatorT>() || *this || other;
 
        }
 
        template<typename ObjectT, typename ComparatorT>
 
        MatchNotOf<ComparatorT> MatcherBase<ObjectT, ComparatorT>::operator ! () const {
 
            return MatchNotOf<ComparatorT>( *this );
 
        }
 

	
 
    } // namespace Impl
 

	
 
    // The following functions create the actual matcher objects.
 
    // This allows the types to be inferred
 
    // - deprecated: prefer ||, && and !
 
    template<typename T>
 
    inline Impl::MatchNotOf<T> Not( Impl::MatcherBase<T> const& underlyingMatcher ) {
 
        return Impl::MatchNotOf<T>( underlyingMatcher );
 
    }
 
    template<typename T>
 
    inline Impl::MatchAllOf<T> AllOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2 ) {
 
        return Impl::MatchAllOf<T>() && m1 && m2;
 
    }
 
    template<typename T>
 
    inline Impl::MatchAllOf<T> AllOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2, Impl::MatcherBase<T> const& m3 ) {
 
        return Impl::MatchAllOf<T>() && m1 && m2 && m3;
 
    }
 
    template<typename T>
 
    inline Impl::MatchAnyOf<T> AnyOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2 ) {
 
        return Impl::MatchAnyOf<T>() || m1 || m2;
 
    }
 
    template<typename T>
 
    inline Impl::MatchAnyOf<T> AnyOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2, Impl::MatcherBase<T> const& m3 ) {
 
        return Impl::MatchAnyOf<T>() || m1 || m2 || m3;
 
    }
 

	
 
} // namespace Matchers
 

	
 
using namespace Matchers;
 
using Matchers::Impl::MatcherBase;
 

	
 
} // namespace Catch
 

	
 
namespace Catch {
 

	
 
    struct TestFailureException{};
 

	
 
    template<typename T> class ExpressionLhs;
 

	
 
    struct CopyableStream {
 
        CopyableStream() {}
 
        CopyableStream( CopyableStream const& other ) {
 
            oss << other.oss.str();
 
        }
 
        CopyableStream& operator=( CopyableStream const& other ) {
 
            oss.str(std::string());
 
            oss << other.oss.str();
 
            return *this;
 
        }
 
        std::ostringstream oss;
 
    };
 

	
 
    class ResultBuilder : public DecomposedExpression {
 
    public:
 
        ResultBuilder(  char const* macroName,
 
                        SourceLineInfo const& lineInfo,
 
                        char const* capturedExpression,
 
                        ResultDisposition::Flags resultDisposition,
 
                        char const* secondArg = "" );
 
        ~ResultBuilder();
 

	
 
        template<typename T>
 
        ExpressionLhs<T const&> operator <= ( T const& operand );
 
        ExpressionLhs<bool> operator <= ( bool value );
 

	
 
        template<typename T>
 
        ResultBuilder& operator << ( T const& value ) {
 
            m_stream().oss << value;
 
            return *this;
 
        }
 

	
 
        ResultBuilder& setResultType( ResultWas::OfType result );
 
        ResultBuilder& setResultType( bool result );
 

	
 
        void endExpression( DecomposedExpression const& expr );
 

	
 
        virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE;
 

	
 
        AssertionResult build() const;
 
        AssertionResult build( DecomposedExpression const& expr ) const;
 

	
 
        void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal );
 
        void captureResult( ResultWas::OfType resultType );
 
        void captureExpression();
 
        void captureExpectedException( std::string const& expectedMessage );
 
        void captureExpectedException( Matchers::Impl::MatcherBase<std::string> const& matcher );
 
        void handleResult( AssertionResult const& result );
 
        void react();
 
        bool shouldDebugBreak() const;
 
        bool allowThrows() const;
 

	
 
        template<typename ArgT, typename MatcherT>
 
        void captureMatch( ArgT const& arg, MatcherT const& matcher, char const* matcherString );
 

	
 
        void setExceptionGuard();
 
        void unsetExceptionGuard();
 

	
 
    private:
 
        AssertionInfo m_assertionInfo;
 
        AssertionResultData m_data;
 

	
 
        static CopyableStream &m_stream()
 
        {
 
            static CopyableStream s;
 
            return s;
 
        }
 

	
 
        bool m_shouldDebugBreak;
 
        bool m_shouldThrow;
 
        bool m_guardException;
 
    };
 

	
 
} // namespace Catch
 

	
 
// Include after due to circular dependency:
 
// #included from: catch_expression_lhs.hpp
 
#define TWOBLUECUBES_CATCH_EXPRESSION_LHS_HPP_INCLUDED
 

	
 
// #included from: catch_evaluate.hpp
 
#define TWOBLUECUBES_CATCH_EVALUATE_HPP_INCLUDED
 

	
 
#ifdef _MSC_VER
 
#pragma warning(push)
 
#pragma warning(disable:4389) // '==' : signed/unsigned mismatch
 
#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform)
 
#endif
 

	
 
#include <cstddef>
 

	
 
namespace Catch {
 
namespace Internal {
 

	
 
    enum Operator {
 
        IsEqualTo,
 
        IsNotEqualTo,
 
        IsLessThan,
 
        IsGreaterThan,
 
        IsLessThanOrEqualTo,
 
        IsGreaterThanOrEqualTo
 
    };
 

	
 
    template<Operator Op> struct OperatorTraits             { static const char* getName(){ return "*error*"; } };
 
    template<> struct OperatorTraits<IsEqualTo>             { static const char* getName(){ return "=="; } };
 
    template<> struct OperatorTraits<IsNotEqualTo>          { static const char* getName(){ return "!="; } };
 
    template<> struct OperatorTraits<IsLessThan>            { static const char* getName(){ return "<"; } };
 
    template<> struct OperatorTraits<IsGreaterThan>         { static const char* getName(){ return ">"; } };
 
    template<> struct OperatorTraits<IsLessThanOrEqualTo>   { static const char* getName(){ return "<="; } };
 
    template<> struct OperatorTraits<IsGreaterThanOrEqualTo>{ static const char* getName(){ return ">="; } };
 

	
 
    template<typename T>
 
    inline T& opCast(T const& t) { return const_cast<T&>(t); }
 

	
 
// nullptr_t support based on pull request #154 from Konstantin Baumann
 
#ifdef CATCH_CONFIG_CPP11_NULLPTR
 
    inline std::nullptr_t opCast(std::nullptr_t) { return nullptr; }
 
#endif // CATCH_CONFIG_CPP11_NULLPTR
 

	
 
    // So the compare overloads can be operator agnostic we convey the operator as a template
 
    // enum, which is used to specialise an Evaluator for doing the comparison.
 
    template<typename T1, typename T2, Operator Op>
 
    class Evaluator{};
 

	
 
    template<typename T1, typename T2>
 
    struct Evaluator<T1, T2, IsEqualTo> {
 
        static bool evaluate( T1 const& lhs, T2 const& rhs) {
 
            return bool( opCast( lhs ) ==  opCast( rhs ) );
 
        }
 
    };
 
    template<typename T1, typename T2>
 
    struct Evaluator<T1, T2, IsNotEqualTo> {
 
        static bool evaluate( T1 const& lhs, T2 const& rhs ) {
 
            return bool( opCast( lhs ) != opCast( rhs ) );
 
        }
 
    };
 
    template<typename T1, typename T2>
 
    struct Evaluator<T1, T2, IsLessThan> {
 
        static bool evaluate( T1 const& lhs, T2 const& rhs ) {
 
            return bool( opCast( lhs ) < opCast( rhs ) );
 
        }
 
    };
 
    template<typename T1, typename T2>
 
    struct Evaluator<T1, T2, IsGreaterThan> {
 
        static bool evaluate( T1 const& lhs, T2 const& rhs ) {
 
            return bool( opCast( lhs ) > opCast( rhs ) );
 
        }
 
    };
 
    template<typename T1, typename T2>
 
    struct Evaluator<T1, T2, IsGreaterThanOrEqualTo> {
 
        static bool evaluate( T1 const& lhs, T2 const& rhs ) {
 
            return bool( opCast( lhs ) >= opCast( rhs ) );
 
        }
 
    };
 
    template<typename T1, typename T2>
 
    struct Evaluator<T1, T2, IsLessThanOrEqualTo> {
 
        static bool evaluate( T1 const& lhs, T2 const& rhs ) {
 
            return bool( opCast( lhs ) <= opCast( rhs ) );
 
        }
 
    };
 

	
 
    template<Operator Op, typename T1, typename T2>
 
    bool applyEvaluator( T1 const& lhs, T2 const& rhs ) {
 
        return Evaluator<T1, T2, Op>::evaluate( lhs, rhs );
 
    }
 

	
 
    // This level of indirection allows us to specialise for integer types
 
    // to avoid signed/ unsigned warnings
 

	
 
    // "base" overload
 
    template<Operator Op, typename T1, typename T2>
 
    bool compare( T1 const& lhs, T2 const& rhs ) {
 
        return Evaluator<T1, T2, Op>::evaluate( lhs, rhs );
 
    }
 

	
 
    // unsigned X to int
 
    template<Operator Op> bool compare( unsigned int lhs, int rhs ) {
 
        return applyEvaluator<Op>( lhs, static_cast<unsigned int>( rhs ) );
 
    }
 
    template<Operator Op> bool compare( unsigned long lhs, int rhs ) {
 
        return applyEvaluator<Op>( lhs, static_cast<unsigned int>( rhs ) );
 
    }
 
    template<Operator Op> bool compare( unsigned char lhs, int rhs ) {
 
        return applyEvaluator<Op>( lhs, static_cast<unsigned int>( rhs ) );
 
    }
 

	
 
    // unsigned X to long
 
    template<Operator Op> bool compare( unsigned int lhs, long rhs ) {
 
        return applyEvaluator<Op>( lhs, static_cast<unsigned long>( rhs ) );
 
    }
 
    template<Operator Op> bool compare( unsigned long lhs, long rhs ) {
 
        return applyEvaluator<Op>( lhs, static_cast<unsigned long>( rhs ) );
 
    }
 
    template<Operator Op> bool compare( unsigned char lhs, long rhs ) {
 
        return applyEvaluator<Op>( lhs, static_cast<unsigned long>( rhs ) );
 
    }
 

	
 
    // int to unsigned X
 
    template<Operator Op> bool compare( int lhs, unsigned int rhs ) {
 
        return applyEvaluator<Op>( static_cast<unsigned int>( lhs ), rhs );
 
    }
 
    template<Operator Op> bool compare( int lhs, unsigned long rhs ) {
 
        return applyEvaluator<Op>( static_cast<unsigned int>( lhs ), rhs );
 
    }
 
    template<Operator Op> bool compare( int lhs, unsigned char rhs ) {
 
        return applyEvaluator<Op>( static_cast<unsigned int>( lhs ), rhs );
 
    }
 

	
 
    // long to unsigned X
 
    template<Operator Op> bool compare( long lhs, unsigned int rhs ) {
 
        return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs );
 
    }
 
    template<Operator Op> bool compare( long lhs, unsigned long rhs ) {
 
        return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs );
 
    }
 
    template<Operator Op> bool compare( long lhs, unsigned char rhs ) {
 
        return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs );
 
    }
 

	
 
    // pointer to long (when comparing against NULL)
 
    template<Operator Op, typename T> bool compare( long lhs, T* rhs ) {
 
        return Evaluator<T*, T*, Op>::evaluate( reinterpret_cast<T*>( lhs ), rhs );
 
    }
 
    template<Operator Op, typename T> bool compare( T* lhs, long rhs ) {
 
        return Evaluator<T*, T*, Op>::evaluate( lhs, reinterpret_cast<T*>( rhs ) );
 
    }
 

	
 
    // pointer to int (when comparing against NULL)
 
    template<Operator Op, typename T> bool compare( int lhs, T* rhs ) {
 
        return Evaluator<T*, T*, Op>::evaluate( reinterpret_cast<T*>( lhs ), rhs );
 
    }
 
    template<Operator Op, typename T> bool compare( T* lhs, int rhs ) {
 
        return Evaluator<T*, T*, Op>::evaluate( lhs, reinterpret_cast<T*>( rhs ) );
 
    }
 

	
 
#ifdef CATCH_CONFIG_CPP11_LONG_LONG
 
    // long long to unsigned X
 
    template<Operator Op> bool compare( long long lhs, unsigned int rhs ) {
 
        return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs );
 
    }
 
    template<Operator Op> bool compare( long long lhs, unsigned long rhs ) {
 
        return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs );
 
    }
 
    template<Operator Op> bool compare( long long lhs, unsigned long long rhs ) {
 
        return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs );
 
    }
 
    template<Operator Op> bool compare( long long lhs, unsigned char rhs ) {
 
        return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs );
 
    }
 

	
 
    // unsigned long long to X
 
    template<Operator Op> bool compare( unsigned long long lhs, int rhs ) {
 
        return applyEvaluator<Op>( static_cast<long>( lhs ), rhs );
 
    }
 
    template<Operator Op> bool compare( unsigned long long lhs, long rhs ) {
 
        return applyEvaluator<Op>( static_cast<long>( lhs ), rhs );
 
    }
 
    template<Operator Op> bool compare( unsigned long long lhs, long long rhs ) {
 
        return applyEvaluator<Op>( static_cast<long>( lhs ), rhs );
 
    }
 
    template<Operator Op> bool compare( unsigned long long lhs, char rhs ) {
 
        return applyEvaluator<Op>( static_cast<long>( lhs ), rhs );
 
    }
 

	
 
    // pointer to long long (when comparing against NULL)
 
    template<Operator Op, typename T> bool compare( long long lhs, T* rhs ) {
 
        return Evaluator<T*, T*, Op>::evaluate( reinterpret_cast<T*>( lhs ), rhs );
 
    }
 
    template<Operator Op, typename T> bool compare( T* lhs, long long rhs ) {
 
        return Evaluator<T*, T*, Op>::evaluate( lhs, reinterpret_cast<T*>( rhs ) );
 
    }
 
#endif // CATCH_CONFIG_CPP11_LONG_LONG
 

	
 
#ifdef CATCH_CONFIG_CPP11_NULLPTR
 
    // pointer to nullptr_t (when comparing against nullptr)
 
    template<Operator Op, typename T> bool compare( std::nullptr_t, T* rhs ) {
 
        return Evaluator<T*, T*, Op>::evaluate( nullptr, rhs );
 
    }
 
    template<Operator Op, typename T> bool compare( T* lhs, std::nullptr_t ) {
 
        return Evaluator<T*, T*, Op>::evaluate( lhs, nullptr );
 
    }
 
#endif // CATCH_CONFIG_CPP11_NULLPTR
 

	
 
} // end of namespace Internal
 
} // end of namespace Catch
 

	
 
#ifdef _MSC_VER
 
#pragma warning(pop)
 
#endif
 

	
 
// #included from: catch_tostring.h
 
#define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED
 

	
 
#include <sstream>
 
#include <iomanip>
 
#include <limits>
 
#include <vector>
 
#include <cstddef>
 

	
 
#ifdef __OBJC__
 
// #included from: catch_objc_arc.hpp
 
#define TWOBLUECUBES_CATCH_OBJC_ARC_HPP_INCLUDED
 

	
 
#import <Foundation/Foundation.h>
 

	
 
#ifdef __has_feature
 
#define CATCH_ARC_ENABLED __has_feature(objc_arc)
 
#else
 
#define CATCH_ARC_ENABLED 0
 
#endif
 

	
 
void arcSafeRelease( NSObject* obj );
 
id performOptionalSelector( id obj, SEL sel );
 

	
 
#if !CATCH_ARC_ENABLED
 
inline void arcSafeRelease( NSObject* obj ) {
 
    [obj release];
 
}
 
inline id performOptionalSelector( id obj, SEL sel ) {
 
    if( [obj respondsToSelector: sel] )
 
        return [obj performSelector: sel];
 
    return nil;
 
}
 
#define CATCH_UNSAFE_UNRETAINED
 
#define CATCH_ARC_STRONG
 
#else
 
inline void arcSafeRelease( NSObject* ){}
 
inline id performOptionalSelector( id obj, SEL sel ) {
 
#ifdef __clang__
 
#pragma clang diagnostic push
 
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
 
#endif
 
    if( [obj respondsToSelector: sel] )
 
        return [obj performSelector: sel];
 
#ifdef __clang__
 
#pragma clang diagnostic pop
 
#endif
 
    return nil;
 
}
 
#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained
 
#define CATCH_ARC_STRONG __strong
 
#endif
 

	
 
#endif
 

	
 
#ifdef CATCH_CONFIG_CPP11_TUPLE
 
#include <tuple>
 
#endif
 

	
 
#ifdef CATCH_CONFIG_CPP11_IS_ENUM
 
#include <type_traits>
 
#endif
 

	
 
namespace Catch {
 

	
 
// Why we're here.
 
template<typename T>
 
std::string toString( T const& value );
 

	
 
// Built in overloads
 

	
 
std::string toString( std::string const& value );
 
std::string toString( std::wstring const& value );
 
std::string toString( const char* const value );
 
std::string toString( char* const value );
 
std::string toString( const wchar_t* const value );
 
std::string toString( wchar_t* const value );
 
std::string toString( int value );
 
std::string toString( unsigned long value );
 
std::string toString( unsigned int value );
 
std::string toString( const double value );
 
std::string toString( const float value );
 
std::string toString( bool value );
 
std::string toString( char value );
 
std::string toString( signed char value );
 
std::string toString( unsigned char value );
 

	
 
#ifdef CATCH_CONFIG_CPP11_LONG_LONG
 
std::string toString( long long value );
 
std::string toString( unsigned long long value );
 
#endif
 

	
 
#ifdef CATCH_CONFIG_CPP11_NULLPTR
 
std::string toString( std::nullptr_t );
 
#endif
 

	
 
#ifdef __OBJC__
 
    std::string toString( NSString const * const& nsstring );
 
    std::string toString( NSString * CATCH_ARC_STRONG & nsstring );
 
    std::string toString( NSObject* const& nsObject );
 
#endif
 

	
 
namespace Detail {
 

	
 
    extern const std::string unprintableString;
 

	
 
 #if !defined(CATCH_CONFIG_CPP11_STREAM_INSERTABLE_CHECK)
 
    struct BorgType {
 
        template<typename T> BorgType( T const& );
 
    };
 

	
 
    struct TrueType { char sizer[1]; };
 
    struct FalseType { char sizer[2]; };
 

	
 
    TrueType& testStreamable( std::ostream& );
 
    FalseType testStreamable( FalseType );
 

	
 
    FalseType operator<<( std::ostream const&, BorgType const& );
 

	
 
    template<typename T>
 
    struct IsStreamInsertable {
 
        static std::ostream &s;
 
        static T  const&t;
 
        enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) };
 
    };
 
#else
 
    template<typename T>
 
    class IsStreamInsertable {
 
        template<typename SS, typename TT>
 
        static auto test(int)
 
        -> decltype( std::declval<SS&>() << std::declval<TT>(), std::true_type() );
 

	
 
        template<typename, typename>
 
        static auto test(...) -> std::false_type;
 

	
 
    public:
 
        static const bool value = decltype(test<std::ostream,const T&>(0))::value;
 
    };
 
#endif
 

	
 
#if defined(CATCH_CONFIG_CPP11_IS_ENUM)
 
    template<typename T,
 
             bool IsEnum = std::is_enum<T>::value
 
             >
 
    struct EnumStringMaker
 
    {
 
        static std::string convert( T const& ) { return unprintableString; }
 
    };
 

	
 
    template<typename T>
 
    struct EnumStringMaker<T,true>
 
    {
 
        static std::string convert( T const& v )
 
        {
 
            return ::Catch::toString(
 
                static_cast<typename std::underlying_type<T>::type>(v)
 
                );
 
        }
 
    };
 
#endif
 
    template<bool C>
 
    struct StringMakerBase {
 
#if defined(CATCH_CONFIG_CPP11_IS_ENUM)
 
        template<typename T>
 
        static std::string convert( T const& v )
 
        {
 
            return EnumStringMaker<T>::convert( v );
 
        }
 
#else
 
        template<typename T>
 
        static std::string convert( T const& ) { return unprintableString; }
 
#endif
 
    };
 

	
 
    template<>
 
    struct StringMakerBase<true> {
 
        template<typename T>
 
        static std::string convert( T const& _value ) {
 
            std::ostringstream oss;
 
            oss << _value;
 
            return oss.str();
 
        }
 
    };
 

	
 
    std::string rawMemoryToString( const void *object, std::size_t size );
 

	
 
    template<typename T>
 
    inline std::string rawMemoryToString( const T& object ) {
 
      return rawMemoryToString( &object, sizeof(object) );
 
    }
 

	
 
} // end namespace Detail
 

	
 
template<typename T>
 
struct StringMaker :
 
    Detail::StringMakerBase<Detail::IsStreamInsertable<T>::value> {};
 

	
 
template<typename T>
 
struct StringMaker<T*> {
 
    template<typename U>
 
    static std::string convert( U* p ) {
 
        if( !p )
 
            return "NULL";
 
        else
 
            return Detail::rawMemoryToString( p );
 
    }
 
};
 

	
 
template<typename R, typename C>
 
struct StringMaker<R C::*> {
 
    static std::string convert( R C::* p ) {
 
        if( !p )
 
            return "NULL";
 
        else
 
            return Detail::rawMemoryToString( p );
 
    }
 
};
 

	
 
namespace Detail {
 
    template<typename InputIterator>
 
    std::string rangeToString( InputIterator first, InputIterator last );
 
}
 

	
 
//template<typename T, typename Allocator>
 
//struct StringMaker<std::vector<T, Allocator> > {
 
//    static std::string convert( std::vector<T,Allocator> const& v ) {
 
//        return Detail::rangeToString( v.begin(), v.end() );
 
//    }
 
//};
 

	
 
template<typename T, typename Allocator>
 
std::string toString( std::vector<T,Allocator> const& v ) {
 
    return Detail::rangeToString( v.begin(), v.end() );
 
}
 

	
 
#ifdef CATCH_CONFIG_CPP11_TUPLE
 

	
 
// toString for tuples
 
namespace TupleDetail {
 
  template<
 
      typename Tuple,
 
      std::size_t N = 0,
 
      bool = (N < std::tuple_size<Tuple>::value)
 
      >
 
  struct ElementPrinter {
 
      static void print( const Tuple& tuple, std::ostream& os )
 
      {
 
          os << ( N ? ", " : " " )
 
             << Catch::toString(std::get<N>(tuple));
 
          ElementPrinter<Tuple,N+1>::print(tuple,os);
 
      }
 
  };
 

	
 
  template<
 
      typename Tuple,
 
      std::size_t N
 
      >
 
  struct ElementPrinter<Tuple,N,false> {
 
      static void print( const Tuple&, std::ostream& ) {}
 
  };
 

	
 
}
 

	
 
template<typename ...Types>
 
struct StringMaker<std::tuple<Types...>> {
 

	
 
    static std::string convert( const std::tuple<Types...>& tuple )
 
    {
 
        std::ostringstream os;
 
        os << '{';
 
        TupleDetail::ElementPrinter<std::tuple<Types...>>::print( tuple, os );
 
        os << " }";
 
        return os.str();
 
    }
 
};
 
#endif // CATCH_CONFIG_CPP11_TUPLE
 

	
 
namespace Detail {
 
    template<typename T>
 
    std::string makeString( T const& value ) {
 
        return StringMaker<T>::convert( value );
 
    }
 
} // end namespace Detail
 

	
 
/// \brief converts any type to a string
 
///
 
/// The default template forwards on to ostringstream - except when an
 
/// ostringstream overload does not exist - in which case it attempts to detect
 
/// that and writes {?}.
 
/// Overload (not specialise) this template for custom typs that you don't want
 
/// to provide an ostream overload for.
 
template<typename T>
 
std::string toString( T const& value ) {
 
    return StringMaker<T>::convert( value );
 
}
 

	
 
    namespace Detail {
 
    template<typename InputIterator>
 
    std::string rangeToString( InputIterator first, InputIterator last ) {
 
        std::ostringstream oss;
 
        oss << "{ ";
 
        if( first != last ) {
 
            oss << Catch::toString( *first );
 
            for( ++first ; first != last ; ++first )
 
                oss << ", " << Catch::toString( *first );
 
        }
 
        oss << " }";
 
        return oss.str();
 
    }
 
}
 

	
 
} // end namespace Catch
 

	
 
namespace Catch {
 

	
 
template<typename LhsT, Internal::Operator Op, typename RhsT>
 
class BinaryExpression;
 

	
 
template<typename ArgT, typename MatcherT>
 
class MatchExpression;
 

	
 
// Wraps the LHS of an expression and overloads comparison operators
 
// for also capturing those and RHS (if any)
 
template<typename T>
 
class ExpressionLhs : public DecomposedExpression {
 
public:
 
    ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ), m_truthy(false) {}
 

	
 
    ExpressionLhs& operator = ( const ExpressionLhs& );
 

	
 
    template<typename RhsT>
 
    BinaryExpression<T, Internal::IsEqualTo, RhsT const&>
 
    operator == ( RhsT const& rhs ) {
 
        return captureExpression<Internal::IsEqualTo>( rhs );
 
    }
 

	
 
    template<typename RhsT>
 
    BinaryExpression<T, Internal::IsNotEqualTo, RhsT const&>
 
    operator != ( RhsT const& rhs ) {
 
        return captureExpression<Internal::IsNotEqualTo>( rhs );
 
    }
 

	
 
    template<typename RhsT>
 
    BinaryExpression<T, Internal::IsLessThan, RhsT const&>
 
    operator < ( RhsT const& rhs ) {
 
        return captureExpression<Internal::IsLessThan>( rhs );
 
    }
 

	
 
    template<typename RhsT>
 
    BinaryExpression<T, Internal::IsGreaterThan, RhsT const&>
 
    operator > ( RhsT const& rhs ) {
 
        return captureExpression<Internal::IsGreaterThan>( rhs );
 
    }
 

	
 
    template<typename RhsT>
 
    BinaryExpression<T, Internal::IsLessThanOrEqualTo, RhsT const&>
 
    operator <= ( RhsT const& rhs ) {
 
        return captureExpression<Internal::IsLessThanOrEqualTo>( rhs );
 
    }
 

	
 
    template<typename RhsT>
 
    BinaryExpression<T, Internal::IsGreaterThanOrEqualTo, RhsT const&>
 
    operator >= ( RhsT const& rhs ) {
 
        return captureExpression<Internal::IsGreaterThanOrEqualTo>( rhs );
 
    }
 

	
 
    BinaryExpression<T, Internal::IsEqualTo, bool> operator == ( bool rhs ) {
 
        return captureExpression<Internal::IsEqualTo>( rhs );
 
    }
 

	
 
    BinaryExpression<T, Internal::IsNotEqualTo, bool> operator != ( bool rhs ) {
 
        return captureExpression<Internal::IsNotEqualTo>( rhs );
 
    }
 

	
 
    void endExpression() {
 
        m_truthy = m_lhs ? true : false;
 
        m_rb
 
            .setResultType( m_truthy )
 
            .endExpression( *this );
 
    }
 

	
 
    virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE {
 
        dest = Catch::toString( m_lhs );
 
    }
 

	
 
private:
 
    template<Internal::Operator Op, typename RhsT>
 
    BinaryExpression<T, Op, RhsT&> captureExpression( RhsT& rhs ) const {
 
        return BinaryExpression<T, Op, RhsT&>( m_rb, m_lhs, rhs );
 
    }
 

	
 
    template<Internal::Operator Op>
 
    BinaryExpression<T, Op, bool> captureExpression( bool rhs ) const {
 
        return BinaryExpression<T, Op, bool>( m_rb, m_lhs, rhs );
 
    }
 

	
 
private:
 
    ResultBuilder& m_rb;
 
    T m_lhs;
 
    bool m_truthy;
 
};
 

	
 
template<typename LhsT, Internal::Operator Op, typename RhsT>
 
class BinaryExpression : public DecomposedExpression {
 
public:
 
    BinaryExpression( ResultBuilder& rb, LhsT lhs, RhsT rhs )
 
        : m_rb( rb ), m_lhs( lhs ), m_rhs( rhs ) {}
 

	
 
    BinaryExpression& operator = ( BinaryExpression& );
 

	
 
    void endExpression() const {
 
        m_rb
 
            .setResultType( Internal::compare<Op>( m_lhs, m_rhs ) )
 
            .endExpression( *this );
 
    }
 

	
 
    virtual bool isBinaryExpression() const CATCH_OVERRIDE {
 
        return true;
 
    }
 

	
 
    virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE {
 
        std::string lhs = Catch::toString( m_lhs );
 
        std::string rhs = Catch::toString( m_rhs );
 
        char delim = lhs.size() + rhs.size() < 40 &&
 
                     lhs.find('\n') == std::string::npos &&
 
                     rhs.find('\n') == std::string::npos ? ' ' : '\n';
 
        dest.reserve( 7 + lhs.size() + rhs.size() );
 
                   // 2 for spaces around operator
 
                   // 2 for operator
 
                   // 2 for parentheses (conditionally added later)
 
                   // 1 for negation (conditionally added later)
 
        dest = lhs;
 
        dest += delim;
 
        dest += Internal::OperatorTraits<Op>::getName();
 
        dest += delim;
 
        dest += rhs;
 
    }
 

	
 
private:
 
    ResultBuilder& m_rb;
 
    LhsT m_lhs;
 
    RhsT m_rhs;
 
};
 

	
 
template<typename ArgT, typename MatcherT>
 
class MatchExpression : public DecomposedExpression {
 
public:
 
    MatchExpression( ArgT arg, MatcherT matcher, char const* matcherString )
 
        : m_arg( arg ), m_matcher( matcher ), m_matcherString( matcherString ) {}
 

	
 
    virtual bool isBinaryExpression() const CATCH_OVERRIDE {
 
        return true;
 
    }
 

	
 
    virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE {
 
        std::string matcherAsString = m_matcher.toString();
 
        dest = Catch::toString( m_arg );
 
        dest += ' ';
 
        if( matcherAsString == Detail::unprintableString )
 
            dest += m_matcherString;
 
        else
 
            dest += matcherAsString;
 
    }
 

	
 
private:
 
    ArgT m_arg;
 
    MatcherT m_matcher;
 
    char const* m_matcherString;
 
};
 

	
 
} // end namespace Catch
 

	
 

	
 
namespace Catch {
 

	
 
    template<typename T>
 
    inline ExpressionLhs<T const&> ResultBuilder::operator <= ( T const& operand ) {
 
        return ExpressionLhs<T const&>( *this, operand );
 
    }
 

	
 
    inline ExpressionLhs<bool> ResultBuilder::operator <= ( bool value ) {
 
        return ExpressionLhs<bool>( *this, value );
 
    }
 

	
 
    template<typename ArgT, typename MatcherT>
 
    inline void ResultBuilder::captureMatch( ArgT const& arg, MatcherT const& matcher,
 
                                             char const* matcherString ) {
 
        MatchExpression<ArgT const&, MatcherT const&> expr( arg, matcher, matcherString );
 
        setResultType( matcher.match( arg ) );
 
        endExpression( expr );
 
    }
 

	
 
} // namespace Catch
 

	
 
// #included from: catch_message.h
 
#define TWOBLUECUBES_CATCH_MESSAGE_H_INCLUDED
 

	
 
#include <string>
 

	
 
namespace Catch {
 

	
 
    struct MessageInfo {
 
        MessageInfo(    std::string const& _macroName,
 
                        SourceLineInfo const& _lineInfo,
 
                        ResultWas::OfType _type );
 

	
 
        std::string macroName;
 
        SourceLineInfo lineInfo;
 
        ResultWas::OfType type;
 
        std::string message;
 
        unsigned int sequence;
 

	
 
        bool operator == ( MessageInfo const& other ) const {
 
            return sequence == other.sequence;
 
        }
 
        bool operator < ( MessageInfo const& other ) const {
 
            return sequence < other.sequence;
 
        }
 
    private:
 
        static unsigned int globalCount;
 
    };
 

	
 
    struct MessageBuilder {
 
        MessageBuilder( std::string const& macroName,
 
                        SourceLineInfo const& lineInfo,
 
                        ResultWas::OfType type )
 
        : m_info( macroName, lineInfo, type )
 
        {}
 

	
 
        template<typename T>
 
        MessageBuilder& operator << ( T const& value ) {
 
            m_stream << value;
 
            return *this;
 
        }
 

	
 
        MessageInfo m_info;
 
        std::ostringstream m_stream;
 
    };
 

	
 
    class ScopedMessage {
 
    public:
 
        ScopedMessage( MessageBuilder const& builder );
 
        ScopedMessage( ScopedMessage const& other );
 
        ~ScopedMessage();
 

	
 
        MessageInfo m_info;
 
    };
 

	
 
} // end namespace Catch
 

	
 
// #included from: catch_interfaces_capture.h
 
#define TWOBLUECUBES_CATCH_INTERFACES_CAPTURE_H_INCLUDED
 

	
 
#include <string>
 

	
 
namespace Catch {
 

	
 
    class TestCase;
 
    class AssertionResult;
 
    struct AssertionInfo;
 
    struct SectionInfo;
 
    struct SectionEndInfo;
 
    struct MessageInfo;
 
    class ScopedMessageBuilder;
 
    struct Counts;
 

	
 
    struct IResultCapture {
 

	
 
        virtual ~IResultCapture();
 

	
 
        virtual void assertionEnded( AssertionResult const& result ) = 0;
 
        virtual bool sectionStarted(    SectionInfo const& sectionInfo,
 
                                        Counts& assertions ) = 0;
 
        virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0;
 
        virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0;
 
        virtual void pushScopedMessage( MessageInfo const& message ) = 0;
 
        virtual void popScopedMessage( MessageInfo const& message ) = 0;
 

	
 
        virtual std::string getCurrentTestName() const = 0;
 
        virtual const AssertionResult* getLastResult() const = 0;
 

	
 
        virtual void exceptionEarlyReported() = 0;
 

	
 
        virtual void handleFatalErrorCondition( std::string const& message ) = 0;
 
    };
 

	
 
    IResultCapture& getResultCapture();
 
}
 

	
 
// #included from: catch_debugger.h
 
#define TWOBLUECUBES_CATCH_DEBUGGER_H_INCLUDED
 

	
 
// #included from: catch_platform.h
 
#define TWOBLUECUBES_CATCH_PLATFORM_H_INCLUDED
 

	
 
#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
 
#  define CATCH_PLATFORM_MAC
 
#elif  defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
 
#  define CATCH_PLATFORM_IPHONE
 
#elif defined(linux) || defined(__linux) || defined(__linux__)
 
#  define CATCH_PLATFORM_LINUX
 
#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER)
 
#  define CATCH_PLATFORM_WINDOWS
 
#  if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX)
 
#    define CATCH_DEFINES_NOMINMAX
 
#  endif
 
#  if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN)
 
#    define CATCH_DEFINES_WIN32_LEAN_AND_MEAN
 
#  endif
 
#endif
 

	
 
#include <string>
 

	
 
namespace Catch{
 

	
 
    bool isDebuggerActive();
 
    void writeToDebugConsole( std::string const& text );
 
}
 

	
 
#ifdef CATCH_PLATFORM_MAC
 

	
 
    // The following code snippet based on:
 
    // http://cocoawithlove.com/2008/03/break-into-debugger.html
 
    #if defined(__ppc64__) || defined(__ppc__)
 
        #define CATCH_TRAP() \
 
                __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \
 
                : : : "memory","r0","r3","r4" )
 
    #else
 
        #define CATCH_TRAP() __asm__("int $3\n" : : )
 
    #endif
 

	
 
#elif defined(CATCH_PLATFORM_LINUX)
 
    // If we can use inline assembler, do it because this allows us to break
 
    // directly at the location of the failing check instead of breaking inside
 
    // raise() called from it, i.e. one stack frame below.
 
    #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64))
 
        #define CATCH_TRAP() asm volatile ("int $3")
 
    #else // Fall back to the generic way.
 
        #include <signal.h>
 

	
 
        #define CATCH_TRAP() raise(SIGTRAP)
 
    #endif
 
#elif defined(_MSC_VER)
 
    #define CATCH_TRAP() __debugbreak()
 
#elif defined(__MINGW32__)
 
    extern "C" __declspec(dllimport) void __stdcall DebugBreak();
 
    #define CATCH_TRAP() DebugBreak()
 
#endif
 

	
 
#ifdef CATCH_TRAP
 
    #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { CATCH_TRAP(); }
 
#else
 
    #define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue();
 
#endif
 

	
 
// #included from: catch_interfaces_runner.h
 
#define TWOBLUECUBES_CATCH_INTERFACES_RUNNER_H_INCLUDED
 

	
 
namespace Catch {
 
    class TestCase;
 

	
 
    struct IRunner {
 
        virtual ~IRunner();
 
        virtual bool aborting() const = 0;
 
    };
 
}
 

	
 
#if defined(CATCH_CONFIG_FAST_COMPILE)
 
///////////////////////////////////////////////////////////////////////////////
 
// We can speedup compilation significantly by breaking into debugger lower in
 
// the callstack, because then we don't have to expand CATCH_BREAK_INTO_DEBUGGER
 
// macro in each assertion
 
#define INTERNAL_CATCH_REACT( resultBuilder ) \
 
    resultBuilder.react();
 

	
 
///////////////////////////////////////////////////////////////////////////////
 
// Another way to speed-up compilation is to omit local try-catch for REQUIRE*
 
// macros.
 
// This can potentially cause false negative, if the test code catches
 
// the exception before it propagates back up to the runner.
 
#define INTERNAL_CATCH_TEST_NO_TRY( macroName, resultDisposition, expr ) \
 
    do { \
 
        Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \
 
        __catchResult.setExceptionGuard(); \
 
        CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \
 
        ( __catchResult <= expr ).endExpression(); \
 
        CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \
 
        __catchResult.unsetExceptionGuard(); \
 
        INTERNAL_CATCH_REACT( __catchResult ) \
 
    } while( Catch::isTrue( false && static_cast<bool>( !!(expr) ) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look
 
// The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&.
 

	
 
#define INTERNAL_CHECK_THAT_NO_TRY( macroName, matcher, resultDisposition, arg ) \
 
    do { \
 
        Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg ", " #matcher, resultDisposition ); \
 
        __catchResult.setExceptionGuard(); \
 
        __catchResult.captureMatch( arg, matcher, #matcher ); \
 
        __catchResult.unsetExceptionGuard(); \
 
        INTERNAL_CATCH_REACT( __catchResult ) \
 
    } while( Catch::alwaysFalse() )
 

	
 
#else
 
///////////////////////////////////////////////////////////////////////////////
 
// In the event of a failure works out if the debugger needs to be invoked
 
// and/or an exception thrown and takes appropriate action.
 
// This needs to be done as a macro so the debugger will stop in the user
 
// source code rather than in Catch library code
 
#define INTERNAL_CATCH_REACT( resultBuilder ) \
 
    if( resultBuilder.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \
 
    resultBuilder.react();
 
#endif
 

	
 
///////////////////////////////////////////////////////////////////////////////
 
#define INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ) \
 
    do { \
 
        Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \
 
        try { \
 
            CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \
 
            ( __catchResult <= expr ).endExpression(); \
 
            CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \
 
        } \
 
        catch( ... ) { \
 
            __catchResult.useActiveException( resultDisposition ); \
 
        } \
 
        INTERNAL_CATCH_REACT( __catchResult ) \
 
    } while( Catch::isTrue( false && static_cast<bool>( !!(expr) ) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look
 
    // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&.
 

	
 
///////////////////////////////////////////////////////////////////////////////
 
#define INTERNAL_CATCH_IF( macroName, resultDisposition, expr ) \
 
    INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ); \
 
    if( Catch::getResultCapture().getLastResult()->succeeded() )
 

	
 
///////////////////////////////////////////////////////////////////////////////
 
#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, expr ) \
 
    INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ); \
 
    if( !Catch::getResultCapture().getLastResult()->succeeded() )
 

	
 
///////////////////////////////////////////////////////////////////////////////
 
#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, expr ) \
 
    do { \
 
        Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \
 
        try { \
 
            static_cast<void>(expr); \
 
            __catchResult.captureResult( Catch::ResultWas::Ok ); \
 
        } \
 
        catch( ... ) { \
 
            __catchResult.useActiveException( resultDisposition ); \
 
        } \
 
        INTERNAL_CATCH_REACT( __catchResult ) \
 
    } while( Catch::alwaysFalse() )
 

	
 
///////////////////////////////////////////////////////////////////////////////
 
#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, matcher, expr ) \
 
    do { \
 
        Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition, #matcher ); \
 
        if( __catchResult.allowThrows() ) \
 
            try { \
 
                static_cast<void>(expr); \
 
                __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \
 
            } \
 
            catch( ... ) { \
 
                __catchResult.captureExpectedException( matcher ); \
 
            } \
 
        else \
 
            __catchResult.captureResult( Catch::ResultWas::Ok ); \
 
        INTERNAL_CATCH_REACT( __catchResult ) \
 
    } while( Catch::alwaysFalse() )
 

	
 
///////////////////////////////////////////////////////////////////////////////
 
#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \
 
    do { \
 
        Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr ", " #exceptionType, resultDisposition ); \
 
        if( __catchResult.allowThrows() ) \
 
            try { \
 
                static_cast<void>(expr); \
 
                __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \
 
            } \
 
            catch( exceptionType ) { \
 
                __catchResult.captureResult( Catch::ResultWas::Ok ); \
 
            } \
 
            catch( ... ) { \
 
                __catchResult.useActiveException( resultDisposition ); \
 
            } \
 
        else \
 
            __catchResult.captureResult( Catch::ResultWas::Ok ); \
 
        INTERNAL_CATCH_REACT( __catchResult ) \
 
    } while( Catch::alwaysFalse() )
 

	
 
///////////////////////////////////////////////////////////////////////////////
 
#ifdef CATCH_CONFIG_VARIADIC_MACROS
 
    #define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \
 
        do { \
 
            Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \
 
            __catchResult << __VA_ARGS__ + ::Catch::StreamEndStop(); \
 
            __catchResult.captureResult( messageType ); \
 
            INTERNAL_CATCH_REACT( __catchResult ) \
 
        } while( Catch::alwaysFalse() )
 
#else
 
    #define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, log ) \
 
        do { \
 
            Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \
 
            __catchResult << log + ::Catch::StreamEndStop(); \
 
            __catchResult.captureResult( messageType ); \
 
            INTERNAL_CATCH_REACT( __catchResult ) \
 
        } while( Catch::alwaysFalse() )
 
#endif
 

	
 
///////////////////////////////////////////////////////////////////////////////
 
#define INTERNAL_CATCH_INFO( macroName, log ) \
 
    Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log;
 

	
 
///////////////////////////////////////////////////////////////////////////////
 
#define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \
 
    do { \
 
        Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg ", " #matcher, resultDisposition ); \
 
        try { \
 
            __catchResult.captureMatch( arg, matcher, #matcher ); \
 
        } catch( ... ) { \
 
            __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \
 
        } \
 
        INTERNAL_CATCH_REACT( __catchResult ) \
 
    } while( Catch::alwaysFalse() )
 

	
 
// #included from: internal/catch_section.h
 
#define TWOBLUECUBES_CATCH_SECTION_H_INCLUDED
 

	
 
// #included from: catch_section_info.h
 
#define TWOBLUECUBES_CATCH_SECTION_INFO_H_INCLUDED
 

	
 
// #included from: catch_totals.hpp
 
#define TWOBLUECUBES_CATCH_TOTALS_HPP_INCLUDED
 

	
 
#include <cstddef>
 

	
 
namespace Catch {
 

	
 
    struct Counts {
 
        Counts() : passed( 0 ), failed( 0 ), failedButOk( 0 ) {}
 

	
 
        Counts operator - ( Counts const& other ) const {
 
            Counts diff;
 
            diff.passed = passed - other.passed;
 
            diff.failed = failed - other.failed;
 
            diff.failedButOk = failedButOk - other.failedButOk;
 
            return diff;
 
        }
 
        Counts& operator += ( Counts const& other ) {
 
            passed += other.passed;
 
            failed += other.failed;
 
            failedButOk += other.failedButOk;
 
            return *this;
 
        }
 

	
 
        std::size_t total() const {
 
            return passed + failed + failedButOk;
 
        }
 
        bool allPassed() const {
 
            return failed == 0 && failedButOk == 0;
 
        }
 
        bool allOk() const {
 
            return failed == 0;
 
        }
 

	
 
        std::size_t passed;
 
        std::size_t failed;
 
        std::size_t failedButOk;
 
    };
 

	
 
    struct Totals {
 

	
 
        Totals operator - ( Totals const& other ) const {
 
            Totals diff;
 
            diff.assertions = assertions - other.assertions;
 
            diff.testCases = testCases - other.testCases;
 
            return diff;
 
        }
 

	
 
        Totals delta( Totals const& prevTotals ) const {
 
            Totals diff = *this - prevTotals;
 
            if( diff.assertions.failed > 0 )
 
                ++diff.testCases.failed;
 
            else if( diff.assertions.failedButOk > 0 )
 
                ++diff.testCases.failedButOk;
 
            else
 
                ++diff.testCases.passed;
 
            return diff;
 
        }
 

	
 
        Totals& operator += ( Totals const& other ) {
 
            assertions += other.assertions;
 
            testCases += other.testCases;
 
            return *this;
 
        }
 

	
 
        Counts assertions;
 
        Counts testCases;
 
    };
 
}
 

	
 
#include <string>
 

	
 
namespace Catch {
 

	
 
    struct SectionInfo {
 
        SectionInfo
 
            (   SourceLineInfo const& _lineInfo,
 
                std::string const& _name,
 
                std::string const& _description = std::string() );
 

	
 
        std::string name;
 
        std::string description;
 
        SourceLineInfo lineInfo;
 
    };
 

	
 
    struct SectionEndInfo {
 
        SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds )
 
        : sectionInfo( _sectionInfo ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds )
 
        {}
 

	
 
        SectionInfo sectionInfo;
 
        Counts prevAssertions;
 
        double durationInSeconds;
 
    };
 

	
 
} // end namespace Catch
 

	
 
// #included from: catch_timer.h
 
#define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED
 

	
 
#ifdef _MSC_VER
 

	
 
namespace Catch {
 
    typedef unsigned long long UInt64;
 
}
 
#else
 
#include <stdint.h>
 
namespace Catch {
 
    typedef uint64_t UInt64;
 
}
 
#endif
 

	
 
namespace Catch {
 
    class Timer {
 
    public:
 
        Timer() : m_ticks( 0 ) {}
 
        void start();
 
        unsigned int getElapsedMicroseconds() const;
 
        unsigned int getElapsedMilliseconds() const;
 
        double getElapsedSeconds() const;
 

	
 
    private:
 
        UInt64 m_ticks;
 
    };
 

	
 
} // namespace Catch
 

	
 
#include <string>
 

	
 
namespace Catch {
 

	
 
    class Section : NonCopyable {
 
    public:
 
        Section( SectionInfo const& info );
 
        ~Section();
 

	
 
        // This indicates whether the section should be executed or not
 
        operator bool() const;
 

	
 
    private:
 
        SectionInfo m_info;
 

	
 
        std::string m_name;
 
        Counts m_assertions;
 
        bool m_sectionIncluded;
 
        Timer m_timer;
 
    };
 

	
 
} // end namespace Catch
 

	
 
#ifdef CATCH_CONFIG_VARIADIC_MACROS
 
    #define INTERNAL_CATCH_SECTION( ... ) \
 
        if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) )
 
#else
 
    #define INTERNAL_CATCH_SECTION( name, desc ) \
 
        if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, name, desc ) )
 
#endif
 

	
 
// #included from: internal/catch_generators.hpp
 
#define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED
 

	
 
#include <vector>
 
#include <string>
 
#include <stdlib.h>
 

	
 
namespace Catch {
 

	
 
template<typename T>
 
struct IGenerator {
 
    virtual ~IGenerator() {}
 
    virtual T getValue( std::size_t index ) const = 0;
 
    virtual std::size_t size () const = 0;
 
};
 

	
 
template<typename T>
 
class BetweenGenerator : public IGenerator<T> {
 
public:
 
    BetweenGenerator( T from, T to ) : m_from( from ), m_to( to ){}
 

	
 
    virtual T getValue( std::size_t index ) const {
 
        return m_from+static_cast<int>( index );
 
    }
 

	
 
    virtual std::size_t size() const {
 
        return static_cast<std::size_t>( 1+m_to-m_from );
 
    }
 

	
 
private:
 

	
 
    T m_from;
 
    T m_to;
 
};
 

	
 
template<typename T>
 
class ValuesGenerator : public IGenerator<T> {
 
public:
 
    ValuesGenerator(){}
 

	
 
    void add( T value ) {
 
        m_values.push_back( value );
 
    }
 

	
 
    virtual T getValue( std::size_t index ) const {
 
        return m_values[index];
 
    }
 

	
 
    virtual std::size_t size() const {
 
        return m_values.size();
 
    }
 

	
 
private:
 
    std::vector<T> m_values;
 
};
 

	
 
template<typename T>
 
class CompositeGenerator {
 
public:
 
    CompositeGenerator() : m_totalSize( 0 ) {}
 

	
 
    // *** Move semantics, similar to auto_ptr ***
 
    CompositeGenerator( CompositeGenerator& other )
 
    :   m_fileInfo( other.m_fileInfo ),
 
        m_totalSize( 0 )
 
    {
 
        move( other );
 
    }
 

	
 
    CompositeGenerator& setFileInfo( const char* fileInfo ) {
 
        m_fileInfo = fileInfo;
 
        return *this;
 
    }
 

	
 
    ~CompositeGenerator() {
 
        deleteAll( m_composed );
 
    }
 

	
 
    operator T () const {
 
        size_t overallIndex = getCurrentContext().getGeneratorIndex( m_fileInfo, m_totalSize );
 

	
 
        typename std::vector<const IGenerator<T>*>::const_iterator it = m_composed.begin();
 
        typename std::vector<const IGenerator<T>*>::const_iterator itEnd = m_composed.end();
 
        for( size_t index = 0; it != itEnd; ++it )
 
        {
 
            const IGenerator<T>* generator = *it;
 
            if( overallIndex >= index && overallIndex < index + generator->size() )
 
            {
 
                return generator->getValue( overallIndex-index );
 
            }
 
            index += generator->size();
 
        }
 
        CATCH_INTERNAL_ERROR( "Indexed past end of generated range" );
 
        return T(); // Suppress spurious "not all control paths return a value" warning in Visual Studio - if you know how to fix this please do so
 
    }
 

	
 
    void add( const IGenerator<T>* generator ) {
 
        m_totalSize += generator->size();
 
        m_composed.push_back( generator );
 
    }
 

	
 
    CompositeGenerator& then( CompositeGenerator& other ) {
 
        move( other );
 
        return *this;
 
    }
 

	
 
    CompositeGenerator& then( T value ) {
 
        ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>();
 
        valuesGen->add( value );
 
        add( valuesGen );
 
        return *this;
 
    }
 

	
 
private:
 

	
 
    void move( CompositeGenerator& other ) {
 
        m_composed.insert( m_composed.end(), other.m_composed.begin(), other.m_composed.end() );
 
        m_totalSize += other.m_totalSize;
 
        other.m_composed.clear();
 
    }
 

	
 
    std::vector<const IGenerator<T>*> m_composed;
 
    std::string m_fileInfo;
 
    size_t m_totalSize;
 
};
 

	
 
namespace Generators
 
{
 
    template<typename T>
 
    CompositeGenerator<T> between( T from, T to ) {
 
        CompositeGenerator<T> generators;
 
        generators.add( new BetweenGenerator<T>( from, to ) );
 
        return generators;
 
    }
 

	
 
    template<typename T>
 
    CompositeGenerator<T> values( T val1, T val2 ) {
 
        CompositeGenerator<T> generators;
 
        ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>();
 
        valuesGen->add( val1 );
 
        valuesGen->add( val2 );
 
        generators.add( valuesGen );
 
        return generators;
 
    }
 

	
 
    template<typename T>
 
    CompositeGenerator<T> values( T val1, T val2, T val3 ){
 
        CompositeGenerator<T> generators;
 
        ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>();
 
        valuesGen->add( val1 );
 
        valuesGen->add( val2 );
 
        valuesGen->add( val3 );
 
        generators.add( valuesGen );
 
        return generators;
 
    }
 

	
 
    template<typename T>
 
    CompositeGenerator<T> values( T val1, T val2, T val3, T val4 ) {
 
        CompositeGenerator<T> generators;
 
        ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>();
 
        valuesGen->add( val1 );
 
        valuesGen->add( val2 );
 
        valuesGen->add( val3 );
 
        valuesGen->add( val4 );
 
        generators.add( valuesGen );
 
        return generators;
 
    }
 

	
 
} // end namespace Generators
 

	
 
using namespace Generators;
 

	
 
} // end namespace Catch
 

	
 
#define INTERNAL_CATCH_LINESTR2( line ) #line
 
#define INTERNAL_CATCH_LINESTR( line ) INTERNAL_CATCH_LINESTR2( line )
 

	
 
#define INTERNAL_CATCH_GENERATE( expr ) expr.setFileInfo( __FILE__ "(" INTERNAL_CATCH_LINESTR( __LINE__ ) ")" )
 

	
 
// #included from: internal/catch_interfaces_exception.h
 
#define TWOBLUECUBES_CATCH_INTERFACES_EXCEPTION_H_INCLUDED
 

	
 
#include <string>
 
#include <vector>
 

	
 
// #included from: catch_interfaces_registry_hub.h
 
#define TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED
 

	
 
#include <string>
 

	
 
namespace Catch {
 

	
 
    class TestCase;
 
    struct ITestCaseRegistry;
 
    struct IExceptionTranslatorRegistry;
 
    struct IExceptionTranslator;
 
    struct IReporterRegistry;
 
    struct IReporterFactory;
 
    struct ITagAliasRegistry;
 

	
 
    struct IRegistryHub {
 
        virtual ~IRegistryHub();
 

	
 
        virtual IReporterRegistry const& getReporterRegistry() const = 0;
 
        virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0;
 
        virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0;
 

	
 
        virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0;
 
    };
 

	
 
    struct IMutableRegistryHub {
 
        virtual ~IMutableRegistryHub();
 
        virtual void registerReporter( std::string const& name, Ptr<IReporterFactory> const& factory ) = 0;
 
        virtual void registerListener( Ptr<IReporterFactory> const& factory ) = 0;
 
        virtual void registerTest( TestCase const& testInfo ) = 0;
 
        virtual void registerTranslator( const IExceptionTranslator* translator ) = 0;
 
        virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0;
 
    };
 

	
 
    IRegistryHub& getRegistryHub();
 
    IMutableRegistryHub& getMutableRegistryHub();
 
    void cleanUp();
 
    std::string translateActiveException();
 

	
 
}
 

	
 
namespace Catch {
 

	
 
    typedef std::string(*exceptionTranslateFunction)();
 

	
 
    struct IExceptionTranslator;
 
    typedef std::vector<const IExceptionTranslator*> ExceptionTranslators;
 

	
 
    struct IExceptionTranslator {
 
        virtual ~IExceptionTranslator();
 
        virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0;
 
    };
 

	
 
    struct IExceptionTranslatorRegistry {
 
        virtual ~IExceptionTranslatorRegistry();
 

	
 
        virtual std::string translateActiveException() const = 0;
 
    };
 

	
 
    class ExceptionTranslatorRegistrar {
 
        template<typename T>
 
        class ExceptionTranslator : public IExceptionTranslator {
 
        public:
 

	
 
            ExceptionTranslator( std::string(*translateFunction)( T& ) )
 
            : m_translateFunction( translateFunction )
 
            {}
 

	
 
            virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const CATCH_OVERRIDE {
 
                try {
 
                    if( it == itEnd )
 
                        throw;
 
                    else
 
                        return (*it)->translate( it+1, itEnd );
 
                }
 
                catch( T& ex ) {
 
                    return m_translateFunction( ex );
 
                }
 
            }
 

	
 
        protected:
 
            std::string(*m_translateFunction)( T& );
 
        };
 

	
 
    public:
 
        template<typename T>
 
        ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) {
 
            getMutableRegistryHub().registerTranslator
 
                ( new ExceptionTranslator<T>( translateFunction ) );
 
        }
 
    };
 
}
 

	
 
///////////////////////////////////////////////////////////////////////////////
 
#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \
 
    static std::string translatorName( signature ); \
 
    namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); }\
 
    static std::string translatorName( signature )
 

	
 
#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature )
 

	
 
// #included from: internal/catch_approx.hpp
 
#define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED
 

	
 
#include <cmath>
 
#include <limits>
 

	
 
#if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS)
 
#include <type_traits>
 
#endif
 

	
 
namespace Catch {
 
namespace Detail {
 

	
 
    class Approx {
 
    public:
 
        explicit Approx ( double value )
 
        :   m_epsilon( std::numeric_limits<float>::epsilon()*100 ),
 
            m_margin( 0.0 ),
 
            m_scale( 1.0 ),
 
            m_value( value )
 
        {}
 

	
 
        Approx( Approx const& other )
 
        :   m_epsilon( other.m_epsilon ),
 
            m_margin( other.m_margin ),
 
            m_scale( other.m_scale ),
 
            m_value( other.m_value )
 
        {}
 

	
 
        static Approx custom() {
 
            return Approx( 0 );
 
        }
 

	
 
#if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS)
 

	
 
        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
 
        Approx operator()( T value ) {
 
            Approx approx( static_cast<double>(value) );
 
            approx.epsilon( m_epsilon );
 
            approx.margin( m_margin );
 
            approx.scale( m_scale );
 
            return approx;
 
        }
 

	
 
        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
 
        explicit Approx( T value ): Approx(static_cast<double>(value))
 
        {}
 

	
 
        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
 
        friend bool operator == ( const T& lhs, Approx const& rhs ) {
 
            // Thanks to Richard Harris for his help refining this formula
 
            auto lhs_v = double(lhs);
 
            bool relativeOK = std::fabs(lhs_v - rhs.m_value) < rhs.m_epsilon * (rhs.m_scale + (std::max)(std::fabs(lhs_v), std::fabs(rhs.m_value)));
 
            if (relativeOK) {
 
                return true;
 
            }
 
            return std::fabs(lhs_v - rhs.m_value) < rhs.m_margin;
 
        }
 

	
 
        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
 
        friend bool operator == ( Approx const& lhs, const T& rhs ) {
 
            return operator==( rhs, lhs );
 
        }
 

	
 
        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
 
        friend bool operator != ( T lhs, Approx const& rhs ) {
 
            return !operator==( lhs, rhs );
 
        }
 

	
 
        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
 
        friend bool operator != ( Approx const& lhs, T rhs ) {
 
            return !operator==( rhs, lhs );
 
        }
 

	
 
        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
 
        friend bool operator <= ( T lhs, Approx const& rhs ) {
 
            return double(lhs) < rhs.m_value || lhs == rhs;
 
        }
 

	
 
        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
 
        friend bool operator <= ( Approx const& lhs, T rhs ) {
 
            return lhs.m_value < double(rhs) || lhs == rhs;
 
        }
 

	
 
        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
 
        friend bool operator >= ( T lhs, Approx const& rhs ) {
 
            return double(lhs) > rhs.m_value || lhs == rhs;
 
        }
 

	
 
        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
 
        friend bool operator >= ( Approx const& lhs, T rhs ) {
 
            return lhs.m_value > double(rhs) || lhs == rhs;
 
        }
 

	
 
        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
 
        Approx& epsilon( T newEpsilon ) {
 
            m_epsilon = double(newEpsilon);
 
            return *this;
 
        }
 

	
 
        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
 
        Approx& margin( T newMargin ) {
 
            m_margin = double(newMargin);
 
            return *this;
 
        }
 

	
 
        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
 
        Approx& scale( T newScale ) {
 
            m_scale = double(newScale);
 
            return *this;
 
        }
 

	
 
#else
 

	
 
        Approx operator()( double value ) {
 
            Approx approx( value );
 
            approx.epsilon( m_epsilon );
 
            approx.margin( m_margin );
 
            approx.scale( m_scale );
 
            return approx;
 
        }
 

	
 
        friend bool operator == ( double lhs, Approx const& rhs ) {
 
            // Thanks to Richard Harris for his help refining this formula
 
            bool relativeOK = std::fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( std::fabs(lhs), std::fabs(rhs.m_value) ) );
 
            if (relativeOK) {
 
                return true;
 
            }
 
            return std::fabs(lhs - rhs.m_value) < rhs.m_margin;
 
        }
 

	
 
        friend bool operator == ( Approx const& lhs, double rhs ) {
 
            return operator==( rhs, lhs );
 
        }
 

	
 
        friend bool operator != ( double lhs, Approx const& rhs ) {
 
            return !operator==( lhs, rhs );
 
        }
 

	
 
        friend bool operator != ( Approx const& lhs, double rhs ) {
 
            return !operator==( rhs, lhs );
 
        }
 

	
 
        friend bool operator <= ( double lhs, Approx const& rhs ) {
 
            return lhs < rhs.m_value || lhs == rhs;
 
        }
 

	
 
        friend bool operator <= ( Approx const& lhs, double rhs ) {
 
            return lhs.m_value < rhs || lhs == rhs;
 
        }
 

	
 
        friend bool operator >= ( double lhs, Approx const& rhs ) {
 
            return lhs > rhs.m_value || lhs == rhs;
 
        }
 

	
 
        friend bool operator >= ( Approx const& lhs, double rhs ) {
 
            return lhs.m_value > rhs || lhs == rhs;
 
        }
 

	
 
        Approx& epsilon( double newEpsilon ) {
 
            m_epsilon = newEpsilon;
 
            return *this;
 
        }
 

	
 
        Approx& margin( double newMargin ) {
 
            m_margin = newMargin;
 
            return *this;
 
        }
 

	
 
        Approx& scale( double newScale ) {
 
            m_scale = newScale;
 
            return *this;
 
        }
 
#endif
 

	
 
        std::string toString() const {
 
            std::ostringstream oss;
 
            oss << "Approx( " << Catch::toString( m_value ) << " )";
 
            return oss.str();
 
        }
 

	
 
    private:
 
        double m_epsilon;
 
        double m_margin;
 
        double m_scale;
 
        double m_value;
 
    };
 
}
 

	
 
template<>
 
inline std::string toString<Detail::Approx>( Detail::Approx const& value ) {
 
    return value.toString();
 
}
 

	
 
} // end namespace Catch
 

	
 
// #included from: internal/catch_matchers_string.h
 
#define TWOBLUECUBES_CATCH_MATCHERS_STRING_H_INCLUDED
 

	
 
namespace Catch {
 
namespace Matchers {
 

	
 
    namespace StdString {
 

	
 
        struct CasedString
 
        {
 
            CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity );
 
            std::string adjustString( std::string const& str ) const;
 
            std::string caseSensitivitySuffix() const;
 

	
 
            CaseSensitive::Choice m_caseSensitivity;
 
            std::string m_str;
 
        };
 

	
 
        struct StringMatcherBase : MatcherBase<std::string> {
 
            StringMatcherBase( std::string const& operation, CasedString const& comparator );
 
            virtual std::string describe() const CATCH_OVERRIDE;
 

	
 
            CasedString m_comparator;
 
            std::string m_operation;
 
        };
 

	
 
        struct EqualsMatcher : StringMatcherBase {
 
            EqualsMatcher( CasedString const& comparator );
 
            virtual bool match( std::string const& source ) const CATCH_OVERRIDE;
 
        };
 
        struct ContainsMatcher : StringMatcherBase {
 
            ContainsMatcher( CasedString const& comparator );
 
            virtual bool match( std::string const& source ) const CATCH_OVERRIDE;
 
        };
 
        struct StartsWithMatcher : StringMatcherBase {
 
            StartsWithMatcher( CasedString const& comparator );
 
            virtual bool match( std::string const& source ) const CATCH_OVERRIDE;
 
        };
 
        struct EndsWithMatcher : StringMatcherBase {
 
            EndsWithMatcher( CasedString const& comparator );
 
            virtual bool match( std::string const& source ) const CATCH_OVERRIDE;
 
        };
 

	
 
    } // namespace StdString
 

	
 
    // The following functions create the actual matcher objects.
 
    // This allows the types to be inferred
 

	
 
    StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );
 
    StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );
 
    StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );
 
    StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );
 

	
 
} // namespace Matchers
 
} // namespace Catch
 

	
 
// #included from: internal/catch_matchers_vector.h
 
#define TWOBLUECUBES_CATCH_MATCHERS_VECTOR_H_INCLUDED
 

	
 
namespace Catch {
 
namespace Matchers {
 

	
 
    namespace Vector {
 

	
 
        template<typename T>
 
        struct ContainsElementMatcher : MatcherBase<std::vector<T>, T> {
 

	
 
            ContainsElementMatcher(T const &comparator) : m_comparator( comparator) {}
 

	
 
            bool match(std::vector<T> const &v) const CATCH_OVERRIDE {
 
                return std::find(v.begin(), v.end(), m_comparator) != v.end();
 
            }
 

	
 
            virtual std::string describe() const CATCH_OVERRIDE {
 
                return "Contains: " + Catch::toString( m_comparator );
 
            }
 

	
 
            T const& m_comparator;
 
        };
 

	
 
        template<typename T>
 
        struct ContainsMatcher : MatcherBase<std::vector<T>, std::vector<T> > {
 

	
 
            ContainsMatcher(std::vector<T> const &comparator) : m_comparator( comparator ) {}
 

	
 
            bool match(std::vector<T> const &v) const CATCH_OVERRIDE {
 
                // !TBD: see note in EqualsMatcher
 
                if (m_comparator.size() > v.size())
 
                    return false;
 
                for (size_t i = 0; i < m_comparator.size(); ++i)
 
                    if (std::find(v.begin(), v.end(), m_comparator[i]) == v.end())
 
                        return false;
 
                return true;
 
            }
 
            virtual std::string describe() const CATCH_OVERRIDE {
 
                return "Contains: " + Catch::toString( m_comparator );
 
            }
 

	
 
            std::vector<T> const& m_comparator;
 
        };
 

	
 
        template<typename T>
 
        struct EqualsMatcher : MatcherBase<std::vector<T>, std::vector<T> > {
 

	
 
            EqualsMatcher(std::vector<T> const &comparator) : m_comparator( comparator ) {}
 

	
 
            bool match(std::vector<T> const &v) const CATCH_OVERRIDE {
 
                // !TBD: This currently works if all elements can be compared using !=
 
                // - a more general approach would be via a compare template that defaults
 
                // to using !=. but could be specialised for, e.g. std::vector<T> etc
 
                // - then just call that directly
 
                if (m_comparator.size() != v.size())
 
                    return false;
 
                for (size_t i = 0; i < v.size(); ++i)
 
                    if (m_comparator[i] != v[i])
 
                        return false;
 
                return true;
 
            }
 
            virtual std::string describe() const CATCH_OVERRIDE {
 
                return "Equals: " + Catch::toString( m_comparator );
 
            }
 
            std::vector<T> const& m_comparator;
 
        };
 

	
 
    } // namespace Vector
 

	
 
    // The following functions create the actual matcher objects.
 
    // This allows the types to be inferred
 

	
 
    template<typename T>
 
    Vector::ContainsMatcher<T> Contains( std::vector<T> const& comparator ) {
 
        return Vector::ContainsMatcher<T>( comparator );
 
    }
 

	
 
    template<typename T>
 
    Vector::ContainsElementMatcher<T> VectorContains( T const& comparator ) {
 
        return Vector::ContainsElementMatcher<T>( comparator );
 
    }
 

	
 
    template<typename T>
 
    Vector::EqualsMatcher<T> Equals( std::vector<T> const& comparator ) {
 
        return Vector::EqualsMatcher<T>( comparator );
 
    }
 

	
 
} // namespace Matchers
 
} // namespace Catch
 

	
 
// #included from: internal/catch_interfaces_tag_alias_registry.h
 
#define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED
 

	
 
// #included from: catch_tag_alias.h
 
#define TWOBLUECUBES_CATCH_TAG_ALIAS_H_INCLUDED
 

	
 
#include <string>
 

	
 
namespace Catch {
 

	
 
    struct TagAlias {
 
        TagAlias( std::string const& _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {}
 

	
 
        std::string tag;
 
        SourceLineInfo lineInfo;
 
    };
 

	
 
    struct RegistrarForTagAliases {
 
        RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo );
 
    };
 

	
 
} // end namespace Catch
 

	
 
#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); }
 
// #included from: catch_option.hpp
 
#define TWOBLUECUBES_CATCH_OPTION_HPP_INCLUDED
 

	
 
namespace Catch {
 

	
 
    // An optional type
 
    template<typename T>
 
    class Option {
 
    public:
 
        Option() : nullableValue( CATCH_NULL ) {}
 
        Option( T const& _value )
 
        : nullableValue( new( storage ) T( _value ) )
 
        {}
 
        Option( Option const& _other )
 
        : nullableValue( _other ? new( storage ) T( *_other ) : CATCH_NULL )
 
        {}
 

	
 
        ~Option() {
 
            reset();
 
        }
 

	
 
        Option& operator= ( Option const& _other ) {
 
            if( &_other != this ) {
 
                reset();
 
                if( _other )
 
                    nullableValue = new( storage ) T( *_other );
 
            }
 
            return *this;
 
        }
 
        Option& operator = ( T const& _value ) {
 
            reset();
 
            nullableValue = new( storage ) T( _value );
 
            return *this;
 
        }
 

	
 
        void reset() {
 
            if( nullableValue )
 
                nullableValue->~T();
 
            nullableValue = CATCH_NULL;
 
        }
 

	
 
        T& operator*() { return *nullableValue; }
 
        T const& operator*() const { return *nullableValue; }
 
        T* operator->() { return nullableValue; }
 
        const T* operator->() const { return nullableValue; }
 

	
 
        T valueOr( T const& defaultValue ) const {
 
            return nullableValue ? *nullableValue : defaultValue;
 
        }
 

	
 
        bool some() const { return nullableValue != CATCH_NULL; }
 
        bool none() const { return nullableValue == CATCH_NULL; }
 

	
 
        bool operator !() const { return nullableValue == CATCH_NULL; }
 
        operator SafeBool::type() const {
 
            return SafeBool::makeSafe( some() );
 
        }
 

	
 
    private:
 
        T *nullableValue;
 
        union {
 
            char storage[sizeof(T)];
 

	
 
            // These are here to force alignment for the storage
 
            long double dummy1;
 
            void (*dummy2)();
 
            long double dummy3;
 
#ifdef CATCH_CONFIG_CPP11_LONG_LONG
 
            long long dummy4;
 
#endif
 
        };
 
    };
 

	
 
} // end namespace Catch
 

	
 
namespace Catch {
 

	
 
    struct ITagAliasRegistry {
 
        virtual ~ITagAliasRegistry();
 
        virtual Option<TagAlias> find( std::string const& alias ) const = 0;
 
        virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0;
 

	
 
        static ITagAliasRegistry const& get();
 
    };
 

	
 
} // end namespace Catch
 

	
 
// These files are included here so the single_include script doesn't put them
 
// in the conditionally compiled sections
 
// #included from: internal/catch_test_case_info.h
 
#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_H_INCLUDED
 

	
 
#include <string>
 
#include <set>
 

	
 
#ifdef __clang__
 
#pragma clang diagnostic push
 
#pragma clang diagnostic ignored "-Wpadded"
 
#endif
 

	
 
namespace Catch {
 

	
 
    struct ITestCase;
 

	
 
    struct TestCaseInfo {
 
        enum SpecialProperties{
 
            None = 0,
 
            IsHidden = 1 << 1,
 
            ShouldFail = 1 << 2,
 
            MayFail = 1 << 3,
 
            Throws = 1 << 4,
 
            NonPortable = 1 << 5
 
        };
 

	
 
        TestCaseInfo(   std::string const& _name,
 
                        std::string const& _className,
 
                        std::string const& _description,
 
                        std::set<std::string> const& _tags,
 
                        SourceLineInfo const& _lineInfo );
 

	
 
        TestCaseInfo( TestCaseInfo const& other );
 

	
 
        friend void setTags( TestCaseInfo& testCaseInfo, std::set<std::string> const& tags );
 

	
 
        bool isHidden() const;
 
        bool throws() const;
 
        bool okToFail() const;
 
        bool expectedToFail() const;
 

	
 
        std::string name;
 
        std::string className;
 
        std::string description;
 
        std::set<std::string> tags;
 
        std::set<std::string> lcaseTags;
 
        std::string tagsAsString;
 
        SourceLineInfo lineInfo;
 
        SpecialProperties properties;
 
    };
 

	
 
    class TestCase : public TestCaseInfo {
 
    public:
 

	
 
        TestCase( ITestCase* testCase, TestCaseInfo const& info );
 
        TestCase( TestCase const& other );
 

	
 
        TestCase withName( std::string const& _newName ) const;
 

	
 
        void invoke() const;
 

	
 
        TestCaseInfo const& getTestCaseInfo() const;
 

	
 
        void swap( TestCase& other );
 
        bool operator == ( TestCase const& other ) const;
 
        bool operator < ( TestCase const& other ) const;
 
        TestCase& operator = ( TestCase const& other );
 

	
 
    private:
 
        Ptr<ITestCase> test;
 
    };
 

	
 
    TestCase makeTestCase(  ITestCase* testCase,
 
                            std::string const& className,
 
                            std::string const& name,
 
                            std::string const& description,
 
                            SourceLineInfo const& lineInfo );
 
}
 

	
 
#ifdef __clang__
 
#pragma clang diagnostic pop
 
#endif
 

	
 

	
 
#ifdef __OBJC__
 
// #included from: internal/catch_objc.hpp
 
#define TWOBLUECUBES_CATCH_OBJC_HPP_INCLUDED
 

	
 
#import <objc/runtime.h>
 

	
 
#include <string>
 

	
 
// NB. Any general catch headers included here must be included
 
// in catch.hpp first to make sure they are included by the single
 
// header for non obj-usage
 

	
 
///////////////////////////////////////////////////////////////////////////////
 
// This protocol is really only here for (self) documenting purposes, since
 
// all its methods are optional.
 
@protocol OcFixture
 

	
 
@optional
 

	
 
-(void) setUp;
 
-(void) tearDown;
 

	
 
@end
 

	
 
namespace Catch {
 

	
 
    class OcMethod : public SharedImpl<ITestCase> {
 

	
 
    public:
 
        OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {}
 

	
 
        virtual void invoke() const {
 
            id obj = [[m_cls alloc] init];
 

	
 
            performOptionalSelector( obj, @selector(setUp)  );
 
            performOptionalSelector( obj, m_sel );
 
            performOptionalSelector( obj, @selector(tearDown)  );
 

	
 
            arcSafeRelease( obj );
 
        }
 
    private:
 
        virtual ~OcMethod() {}
 

	
 
        Class m_cls;
 
        SEL m_sel;
 
    };
 

	
 
    namespace Detail{
 

	
 
        inline std::string getAnnotation(   Class cls,
 
                                            std::string const& annotationName,
 
                                            std::string const& testCaseName ) {
 
            NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()];
 
            SEL sel = NSSelectorFromString( selStr );
 
            arcSafeRelease( selStr );
 
            id value = performOptionalSelector( cls, sel );
 
            if( value )
 
                return [(NSString*)value UTF8String];
 
            return "";
 
        }
 
    }
 

	
 
    inline size_t registerTestMethods() {
 
        size_t noTestMethods = 0;
 
        int noClasses = objc_getClassList( CATCH_NULL, 0 );
 

	
 
        Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses);
 
        objc_getClassList( classes, noClasses );
 

	
 
        for( int c = 0; c < noClasses; c++ ) {
 
            Class cls = classes[c];
 
            {
 
                u_int count;
 
                Method* methods = class_copyMethodList( cls, &count );
 
                for( u_int m = 0; m < count ; m++ ) {
 
                    SEL selector = method_getName(methods[m]);
 
                    std::string methodName = sel_getName(selector);
 
                    if( startsWith( methodName, "Catch_TestCase_" ) ) {
 
                        std::string testCaseName = methodName.substr( 15 );
 
                        std::string name = Detail::getAnnotation( cls, "Name", testCaseName );
 
                        std::string desc = Detail::getAnnotation( cls, "Description", testCaseName );
 
                        const char* className = class_getName( cls );
 

	
 
                        getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo() ) );
 
                        noTestMethods++;
 
                    }
 
                }
 
                free(methods);
 
            }
 
        }
 
        return noTestMethods;
 
    }
 

	
 
    namespace Matchers {
 
        namespace Impl {
 
        namespace NSStringMatchers {
 

	
 
            struct StringHolder : MatcherBase<NSString*>{
 
                StringHolder( NSString* substr ) : m_substr( [substr copy] ){}
 
                StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){}
 
                StringHolder() {
 
                    arcSafeRelease( m_substr );
 
                }
 

	
 
                virtual bool match( NSString* arg ) const CATCH_OVERRIDE {
 
                    return false;
 
                }
 

	
 
                NSString* m_substr;
 
            };
 

	
 
            struct Equals : StringHolder {
 
                Equals( NSString* substr ) : StringHolder( substr ){}
 

	
 
                virtual bool match( NSString* str ) const CATCH_OVERRIDE {
 
                    return  (str != nil || m_substr == nil ) &&
 
                            [str isEqualToString:m_substr];
 
                }
 

	
 
                virtual std::string describe() const CATCH_OVERRIDE {
 
                    return "equals string: " + Catch::toString( m_substr );
 
                }
 
            };
 

	
 
            struct Contains : StringHolder {
 
                Contains( NSString* substr ) : StringHolder( substr ){}
 

	
 
                virtual bool match( NSString* str ) const {
 
                    return  (str != nil || m_substr == nil ) &&
 
                            [str rangeOfString:m_substr].location != NSNotFound;
 
                }
 

	
 
                virtual std::string describe() const CATCH_OVERRIDE {
 
                    return "contains string: " + Catch::toString( m_substr );
 
                }
 
            };
 

	
 
            struct StartsWith : StringHolder {
 
                StartsWith( NSString* substr ) : StringHolder( substr ){}
 

	
 
                virtual bool match( NSString* str ) const {
 
                    return  (str != nil || m_substr == nil ) &&
 
                            [str rangeOfString:m_substr].location == 0;
 
                }
 

	
 
                virtual std::string describe() const CATCH_OVERRIDE {
 
                    return "starts with: " + Catch::toString( m_substr );
 
                }
 
            };
 
            struct EndsWith : StringHolder {
 
                EndsWith( NSString* substr ) : StringHolder( substr ){}
 

	
 
                virtual bool match( NSString* str ) const {
 
                    return  (str != nil || m_substr == nil ) &&
 
                            [str rangeOfString:m_substr].location == [str length] - [m_substr length];
 
                }
 

	
 
                virtual std::string describe() const CATCH_OVERRIDE {
 
                    return "ends with: " + Catch::toString( m_substr );
 
                }
 
            };
 

	
 
        } // namespace NSStringMatchers
 
        } // namespace Impl
 

	
 
        inline Impl::NSStringMatchers::Equals
 
            Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); }
 

	
 
        inline Impl::NSStringMatchers::Contains
 
            Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); }
 

	
 
        inline Impl::NSStringMatchers::StartsWith
 
            StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); }
 

	
 
        inline Impl::NSStringMatchers::EndsWith
 
            EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); }
 

	
 
    } // namespace Matchers
 

	
 
    using namespace Matchers;
 

	
 
} // namespace Catch
 

	
 
///////////////////////////////////////////////////////////////////////////////
 
#define OC_TEST_CASE( name, desc )\
 
+(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Name_test ) \
 
{\
 
return @ name; \
 
}\
 
+(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Description_test ) \
 
{ \
 
return @ desc; \
 
} \
 
-(void) INTERNAL_CATCH_UNIQUE_NAME( Catch_TestCase_test )
 

	
 
#endif
 

	
 
#ifdef CATCH_IMPL
 

	
 
// !TBD: Move the leak detector code into a separate header
 
#ifdef CATCH_CONFIG_WINDOWS_CRTDBG
 
#include <crtdbg.h>
 
class LeakDetector {
 
public:
 
    LeakDetector() {
 
        int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
 
        flag |= _CRTDBG_LEAK_CHECK_DF;
 
        flag |= _CRTDBG_ALLOC_MEM_DF;
 
        _CrtSetDbgFlag(flag);
 
        _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
 
        _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
 
        // Change this to leaking allocation's number to break there
 
        _CrtSetBreakAlloc(-1);
 
    }
 
};
 
#else
 
class LeakDetector {};
 
#endif
 

	
 
LeakDetector leakDetector;
 

	
 
// #included from: internal/catch_impl.hpp
 
#define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED
 

	
 
// Collect all the implementation files together here
 
// These are the equivalent of what would usually be cpp files
 

	
 
#ifdef __clang__
 
#pragma clang diagnostic push
 
#pragma clang diagnostic ignored "-Wweak-vtables"
 
#endif
 

	
 
// #included from: ../catch_session.hpp
 
#define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED
 

	
 
// #included from: internal/catch_commandline.hpp
 
#define TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED
 

	
 
// #included from: catch_config.hpp
 
#define TWOBLUECUBES_CATCH_CONFIG_HPP_INCLUDED
 

	
 
// #included from: catch_test_spec_parser.hpp
 
#define TWOBLUECUBES_CATCH_TEST_SPEC_PARSER_HPP_INCLUDED
 

	
 
#ifdef __clang__
 
#pragma clang diagnostic push
 
#pragma clang diagnostic ignored "-Wpadded"
 
#endif
 

	
 
// #included from: catch_test_spec.hpp
 
#define TWOBLUECUBES_CATCH_TEST_SPEC_HPP_INCLUDED
 

	
 
#ifdef __clang__
 
#pragma clang diagnostic push
 
#pragma clang diagnostic ignored "-Wpadded"
 
#endif
 

	
 
// #included from: catch_wildcard_pattern.hpp
 
#define TWOBLUECUBES_CATCH_WILDCARD_PATTERN_HPP_INCLUDED
 

	
 
#include <stdexcept>
 

	
 
namespace Catch
 
{
 
    class WildcardPattern {
 
        enum WildcardPosition {
 
            NoWildcard = 0,
 
            WildcardAtStart = 1,
 
            WildcardAtEnd = 2,
 
            WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd
 
        };
 

	
 
    public:
 

	
 
        WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity )
 
        :   m_caseSensitivity( caseSensitivity ),
 
            m_wildcard( NoWildcard ),
 
            m_pattern( adjustCase( pattern ) )
 
        {
 
            if( startsWith( m_pattern, '*' ) ) {
 
                m_pattern = m_pattern.substr( 1 );
 
                m_wildcard = WildcardAtStart;
 
            }
 
            if( endsWith( m_pattern, '*' ) ) {
 
                m_pattern = m_pattern.substr( 0, m_pattern.size()-1 );
 
                m_wildcard = static_cast<WildcardPosition>( m_wildcard | WildcardAtEnd );
 
            }
 
        }
 
        virtual ~WildcardPattern();
 
        virtual bool matches( std::string const& str ) const {
 
            switch( m_wildcard ) {
 
                case NoWildcard:
 
                    return m_pattern == adjustCase( str );
 
                case WildcardAtStart:
 
                    return endsWith( adjustCase( str ), m_pattern );
 
                case WildcardAtEnd:
 
                    return startsWith( adjustCase( str ), m_pattern );
 
                case WildcardAtBothEnds:
 
                    return contains( adjustCase( str ), m_pattern );
 
            }
 

	
 
#ifdef __clang__
 
#pragma clang diagnostic push
 
#pragma clang diagnostic ignored "-Wunreachable-code"
 
#endif
 
            throw std::logic_error( "Unknown enum" );
 
#ifdef __clang__
 
#pragma clang diagnostic pop
 
#endif
 
        }
 
    private:
 
        std::string adjustCase( std::string const& str ) const {
 
            return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str;
 
        }
 
        CaseSensitive::Choice m_caseSensitivity;
 
        WildcardPosition m_wildcard;
 
        std::string m_pattern;
 
    };
 
}
 

	
 
#include <string>
 
#include <vector>
 

	
 
namespace Catch {
 

	
 
    class TestSpec {
 
        struct Pattern : SharedImpl<> {
 
            virtual ~Pattern();
 
            virtual bool matches( TestCaseInfo const& testCase ) const = 0;
 
        };
 
        class NamePattern : public Pattern {
 
        public:
 
            NamePattern( std::string const& name )
 
            : m_wildcardPattern( toLower( name ), CaseSensitive::No )
 
            {}
 
            virtual ~NamePattern();
 
            virtual bool matches( TestCaseInfo const& testCase ) const {
 
                return m_wildcardPattern.matches( toLower( testCase.name ) );
 
            }
 
        private:
 
            WildcardPattern m_wildcardPattern;
 
        };
 

	
 
        class TagPattern : public Pattern {
 
        public:
 
            TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {}
 
            virtual ~TagPattern();
 
            virtual bool matches( TestCaseInfo const& testCase ) const {
 
                return testCase.lcaseTags.find( m_tag ) != testCase.lcaseTags.end();
 
            }
 
        private:
 
            std::string m_tag;
 
        };
 

	
 
        class ExcludedPattern : public Pattern {
 
        public:
 
            ExcludedPattern( Ptr<Pattern> const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {}
 
            virtual ~ExcludedPattern();
 
            virtual bool matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); }
 
        private:
 
            Ptr<Pattern> m_underlyingPattern;
 
        };
 

	
 
        struct Filter {
 
            std::vector<Ptr<Pattern> > m_patterns;
 

	
 
            bool matches( TestCaseInfo const& testCase ) const {
 
                // All patterns in a filter must match for the filter to be a match
 
                for( std::vector<Ptr<Pattern> >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it ) {
 
                    if( !(*it)->matches( testCase ) )
 
                        return false;
 
                }
 
                return true;
 
            }
 
        };
 

	
 
    public:
 
        bool hasFilters() const {
 
            return !m_filters.empty();
 
        }
 
        bool matches( TestCaseInfo const& testCase ) const {
 
            // A TestSpec matches if any filter matches
 
            for( std::vector<Filter>::const_iterator it = m_filters.begin(), itEnd = m_filters.end(); it != itEnd; ++it )
 
                if( it->matches( testCase ) )
 
                    return true;
 
            return false;
 
        }
 

	
 
    private:
 
        std::vector<Filter> m_filters;
 

	
 
        friend class TestSpecParser;
 
    };
 
}
 

	
 
#ifdef __clang__
 
#pragma clang diagnostic pop
 
#endif
 

	
 
namespace Catch {
 

	
 
    class TestSpecParser {
 
        enum Mode{ None, Name, QuotedName, Tag, EscapedName };
 
        Mode m_mode;
 
        bool m_exclusion;
 
        std::size_t m_start, m_pos;
 
        std::string m_arg;
 
        std::vector<std::size_t> m_escapeChars;
 
        TestSpec::Filter m_currentFilter;
 
        TestSpec m_testSpec;
 
        ITagAliasRegistry const* m_tagAliases;
 

	
 
    public:
 
        TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {}
 

	
 
        TestSpecParser& parse( std::string const& arg ) {
 
            m_mode = None;
 
            m_exclusion = false;
 
            m_start = std::string::npos;
 
            m_arg = m_tagAliases->expandAliases( arg );
 
            m_escapeChars.clear();
 
            for( m_pos = 0; m_pos < m_arg.size(); ++m_pos )
 
                visitChar( m_arg[m_pos] );
 
            if( m_mode == Name )
 
                addPattern<TestSpec::NamePattern>();
 
            return *this;
 
        }
 
        TestSpec testSpec() {
 
            addFilter();
 
            return m_testSpec;
 
        }
 
    private:
 
        void visitChar( char c ) {
 
            if( m_mode == None ) {
 
                switch( c ) {
 
                case ' ': return;
 
                case '~': m_exclusion = true; return;
 
                case '[': return startNewMode( Tag, ++m_pos );
 
                case '"': return startNewMode( QuotedName, ++m_pos );
 
                case '\\': return escape();
 
                default: startNewMode( Name, m_pos ); break;
 
                }
 
            }
 
            if( m_mode == Name ) {
 
                if( c == ',' ) {
 
                    addPattern<TestSpec::NamePattern>();
 
                    addFilter();
 
                }
 
                else if( c == '[' ) {
 
                    if( subString() == "exclude:" )
 
                        m_exclusion = true;
 
                    else
 
                        addPattern<TestSpec::NamePattern>();
 
                    startNewMode( Tag, ++m_pos );
 
                }
 
                else if( c == '\\' )
 
                    escape();
 
            }
 
            else if( m_mode == EscapedName )
 
                m_mode = Name;
 
            else if( m_mode == QuotedName && c == '"' )
 
                addPattern<TestSpec::NamePattern>();
 
            else if( m_mode == Tag && c == ']' )
 
                addPattern<TestSpec::TagPattern>();
 
        }
 
        void startNewMode( Mode mode, std::size_t start ) {
 
            m_mode = mode;
 
            m_start = start;
 
        }
 
        void escape() {
 
            if( m_mode == None )
 
                m_start = m_pos;
 
            m_mode = EscapedName;
 
            m_escapeChars.push_back( m_pos );
 
        }
 
        std::string subString() const { return m_arg.substr( m_start, m_pos - m_start ); }
 
        template<typename T>
 
        void addPattern() {
 
            std::string token = subString();
 
            for( size_t i = 0; i < m_escapeChars.size(); ++i )
 
                token = token.substr( 0, m_escapeChars[i]-m_start-i ) + token.substr( m_escapeChars[i]-m_start-i+1 );
 
            m_escapeChars.clear();
 
            if( startsWith( token, "exclude:" ) ) {
 
                m_exclusion = true;
 
                token = token.substr( 8 );
 
            }
 
            if( !token.empty() ) {
 
                Ptr<TestSpec::Pattern> pattern = new T( token );
 
                if( m_exclusion )
 
                    pattern = new TestSpec::ExcludedPattern( pattern );
 
                m_currentFilter.m_patterns.push_back( pattern );
 
            }
 
            m_exclusion = false;
 
            m_mode = None;
 
        }
 
        void addFilter() {
 
            if( !m_currentFilter.m_patterns.empty() ) {
 
                m_testSpec.m_filters.push_back( m_currentFilter );
 
                m_currentFilter = TestSpec::Filter();
 
            }
 
        }
 
    };
 
    inline TestSpec parseTestSpec( std::string const& arg ) {
 
        return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec();
 
    }
 

	
 
} // namespace Catch
 

	
 
#ifdef __clang__
 
#pragma clang diagnostic pop
 
#endif
 

	
 
// #included from: catch_interfaces_config.h
 
#define TWOBLUECUBES_CATCH_INTERFACES_CONFIG_H_INCLUDED
 

	
 
#include <iosfwd>
 
#include <string>
 
#include <vector>
 

	
 
namespace Catch {
 

	
 
    struct Verbosity { enum Level {
 
        NoOutput = 0,
 
        Quiet,
 
        Normal
 
    }; };
 

	
 
    struct WarnAbout { enum What {
 
        Nothing = 0x00,
 
        NoAssertions = 0x01
 
    }; };
 

	
 
    struct ShowDurations { enum OrNot {
 
        DefaultForReporter,
 
        Always,
 
        Never
 
    }; };
 
    struct RunTests { enum InWhatOrder {
 
        InDeclarationOrder,
 
        InLexicographicalOrder,
 
        InRandomOrder
 
    }; };
 
    struct UseColour { enum YesOrNo {
 
        Auto,
 
        Yes,
 
        No
 
    }; };
 

	
 
    class TestSpec;
 

	
 
    struct IConfig : IShared {
 

	
 
        virtual ~IConfig();
 

	
 
        virtual bool allowThrows() const = 0;
 
        virtual std::ostream& stream() const = 0;
 
        virtual std::string name() const = 0;
 
        virtual bool includeSuccessfulResults() const = 0;
 
        virtual bool shouldDebugBreak() const = 0;
 
        virtual bool warnAboutMissingAssertions() const = 0;
 
        virtual int abortAfter() const = 0;
 
        virtual bool showInvisibles() const = 0;
 
        virtual ShowDurations::OrNot showDurations() const = 0;
 
        virtual TestSpec const& testSpec() const = 0;
 
        virtual RunTests::InWhatOrder runOrder() const = 0;
 
        virtual unsigned int rngSeed() const = 0;
 
        virtual UseColour::YesOrNo useColour() const = 0;
 
        virtual std::vector<std::string> const& getSectionsToRun() const = 0;
 

	
 
    };
 
}
 

	
 
// #included from: catch_stream.h
 
#define TWOBLUECUBES_CATCH_STREAM_H_INCLUDED
 

	
 
// #included from: catch_streambuf.h
 
#define TWOBLUECUBES_CATCH_STREAMBUF_H_INCLUDED
 

	
 
#include <streambuf>
 

	
 
namespace Catch {
 

	
 
    class StreamBufBase : public std::streambuf {
 
    public:
 
        virtual ~StreamBufBase() CATCH_NOEXCEPT;
 
    };
 
}
 

	
 
#include <streambuf>
 
#include <ostream>
 
#include <fstream>
 
#include <memory>
 

	
 
namespace Catch {
 

	
 
    std::ostream& cout();
 
    std::ostream& cerr();
 

	
 
    struct IStream {
 
        virtual ~IStream() CATCH_NOEXCEPT;
 
        virtual std::ostream& stream() const = 0;
 
    };
 

	
 
    class FileStream : public IStream {
 
        mutable std::ofstream m_ofs;
 
    public:
 
        FileStream( std::string const& filename );
 
        virtual ~FileStream() CATCH_NOEXCEPT;
 
    public: // IStream
 
        virtual std::ostream& stream() const CATCH_OVERRIDE;
 
    };
 

	
 
    class CoutStream : public IStream {
 
        mutable std::ostream m_os;
 
    public:
 
        CoutStream();
 
        virtual ~CoutStream() CATCH_NOEXCEPT;
 

	
 
    public: // IStream
 
        virtual std::ostream& stream() const CATCH_OVERRIDE;
 
    };
 

	
 
    class DebugOutStream : public IStream {
 
        CATCH_AUTO_PTR( StreamBufBase ) m_streamBuf;
 
        mutable std::ostream m_os;
 
    public:
 
        DebugOutStream();
 
        virtual ~DebugOutStream() CATCH_NOEXCEPT;
 

	
 
    public: // IStream
 
        virtual std::ostream& stream() const CATCH_OVERRIDE;
 
    };
 
}
 

	
 
#include <memory>
 
#include <vector>
 
#include <string>
 
#include <stdexcept>
 

	
 
#ifndef CATCH_CONFIG_CONSOLE_WIDTH
 
#define CATCH_CONFIG_CONSOLE_WIDTH 80
 
#endif
 

	
 
namespace Catch {
 

	
 
    struct ConfigData {
 

	
 
        ConfigData()
 
        :   listTests( false ),
 
            listTags( false ),
 
            listReporters( false ),
 
            listTestNamesOnly( false ),
 
            listExtraInfo( false ),
 
            showSuccessfulTests( false ),
 
            shouldDebugBreak( false ),
 
            noThrow( false ),
 
            showHelp( false ),
 
            showInvisibles( false ),
 
            filenamesAsTags( false ),
 
            abortAfter( -1 ),
 
            rngSeed( 0 ),
 
            verbosity( Verbosity::Normal ),
 
            warnings( WarnAbout::Nothing ),
 
            showDurations( ShowDurations::DefaultForReporter ),
 
            runOrder( RunTests::InDeclarationOrder ),
 
            useColour( UseColour::Auto )
 
        {}
 

	
 
        bool listTests;
 
        bool listTags;
 
        bool listReporters;
 
        bool listTestNamesOnly;
 
        bool listExtraInfo;
 

	
 
        bool showSuccessfulTests;
 
        bool shouldDebugBreak;
 
        bool noThrow;
 
        bool showHelp;
 
        bool showInvisibles;
 
        bool filenamesAsTags;
 

	
 
        int abortAfter;
 
        unsigned int rngSeed;
 

	
 
        Verbosity::Level verbosity;
 
        WarnAbout::What warnings;
 
        ShowDurations::OrNot showDurations;
 
        RunTests::InWhatOrder runOrder;
 
        UseColour::YesOrNo useColour;
 

	
 
        std::string outputFilename;
 
        std::string name;
 
        std::string processName;
 

	
 
        std::vector<std::string> reporterNames;
 
        std::vector<std::string> testsOrTags;
 
        std::vector<std::string> sectionsToRun;
 
    };
 

	
 
    class Config : public SharedImpl<IConfig> {
 
    private:
 
        Config( Config const& other );
 
        Config& operator = ( Config const& other );
 
        virtual void dummy();
 
    public:
 

	
 
        Config()
 
        {}
 

	
 
        Config( ConfigData const& data )
 
        :   m_data( data ),
 
            m_stream( openStream() )
 
        {
 
            if( !data.testsOrTags.empty() ) {
 
                TestSpecParser parser( ITagAliasRegistry::get() );
 
                for( std::size_t i = 0; i < data.testsOrTags.size(); ++i )
 
                    parser.parse( data.testsOrTags[i] );
 
                m_testSpec = parser.testSpec();
 
            }
 
        }
 

	
 
        virtual ~Config() {}
 

	
 
        std::string const& getFilename() const {
 
            return m_data.outputFilename ;
 
        }
 

	
 
        bool listTests() const { return m_data.listTests; }
 
        bool listTestNamesOnly() const { return m_data.listTestNamesOnly; }
 
        bool listTags() const { return m_data.listTags; }
 
        bool listReporters() const { return m_data.listReporters; }
 
        bool listExtraInfo() const { return m_data.listExtraInfo; }
 

	
 
        std::string getProcessName() const { return m_data.processName; }
 

	
 
        std::vector<std::string> const& getReporterNames() const { return m_data.reporterNames; }
 
        std::vector<std::string> const& getSectionsToRun() const CATCH_OVERRIDE { return m_data.sectionsToRun; }
 

	
 
        virtual TestSpec const& testSpec() const CATCH_OVERRIDE { return m_testSpec; }
 

	
 
        bool showHelp() const { return m_data.showHelp; }
 

	
 
        // IConfig interface
 
        virtual bool allowThrows() const CATCH_OVERRIDE                 { return !m_data.noThrow; }
 
        virtual std::ostream& stream() const CATCH_OVERRIDE             { return m_stream->stream(); }
 
        virtual std::string name() const CATCH_OVERRIDE                 { return m_data.name.empty() ? m_data.processName : m_data.name; }
 
        virtual bool includeSuccessfulResults() const CATCH_OVERRIDE    { return m_data.showSuccessfulTests; }
 
        virtual bool warnAboutMissingAssertions() const CATCH_OVERRIDE  { return m_data.warnings & WarnAbout::NoAssertions; }
 
        virtual ShowDurations::OrNot showDurations() const CATCH_OVERRIDE { return m_data.showDurations; }
 
        virtual RunTests::InWhatOrder runOrder() const CATCH_OVERRIDE   { return m_data.runOrder; }
 
        virtual unsigned int rngSeed() const CATCH_OVERRIDE             { return m_data.rngSeed; }
 
        virtual UseColour::YesOrNo useColour() const CATCH_OVERRIDE     { return m_data.useColour; }
 
        virtual bool shouldDebugBreak() const CATCH_OVERRIDE { return m_data.shouldDebugBreak; }
 
        virtual int abortAfter() const CATCH_OVERRIDE { return m_data.abortAfter; }
 
        virtual bool showInvisibles() const CATCH_OVERRIDE { return m_data.showInvisibles; }
 

	
 
    private:
 

	
 
        IStream const* openStream() {
 
            if( m_data.outputFilename.empty() )
 
                return new CoutStream();
 
            else if( m_data.outputFilename[0] == '%' ) {
 
                if( m_data.outputFilename == "%debug" )
 
                    return new DebugOutStream();
 
                else
 
                    throw std::domain_error( "Unrecognised stream: " + m_data.outputFilename );
 
            }
 
            else
 
                return new FileStream( m_data.outputFilename );
 
        }
 
        ConfigData m_data;
 

	
 
        CATCH_AUTO_PTR( IStream const ) m_stream;
 
        TestSpec m_testSpec;
 
    };
 

	
 
} // end namespace Catch
 

	
 
// #included from: catch_clara.h
 
#define TWOBLUECUBES_CATCH_CLARA_H_INCLUDED
 

	
 
// Use Catch's value for console width (store Clara's off to the side, if present)
 
#ifdef CLARA_CONFIG_CONSOLE_WIDTH
 
#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH
 
#undef CLARA_CONFIG_CONSOLE_WIDTH
 
#endif
 
#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH
 

	
 
// Declare Clara inside the Catch namespace
 
#define STITCH_CLARA_OPEN_NAMESPACE namespace Catch {
 
// #included from: ../external/clara.h
 

	
 
// Version 0.0.2.4
 

	
 
// Only use header guard if we are not using an outer namespace
 
#if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE)
 

	
 
#ifndef STITCH_CLARA_OPEN_NAMESPACE
 
#define TWOBLUECUBES_CLARA_H_INCLUDED
 
#define STITCH_CLARA_OPEN_NAMESPACE
 
#define STITCH_CLARA_CLOSE_NAMESPACE
 
#else
 
#define STITCH_CLARA_CLOSE_NAMESPACE }
 
#endif
 

	
 
#define STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE
 

	
 
// ----------- #included from tbc_text_format.h -----------
 

	
 
// Only use header guard if we are not using an outer namespace
 
#if !defined(TBC_TEXT_FORMAT_H_INCLUDED) || defined(STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE)
 
#ifndef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE
 
#define TBC_TEXT_FORMAT_H_INCLUDED
 
#endif
 

	
 
#include <string>
 
#include <vector>
 
#include <sstream>
 
#include <algorithm>
 
#include <cctype>
 

	
 
// Use optional outer namespace
 
#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE
 
namespace STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE {
 
#endif
 

	
 
namespace Tbc {
 

	
 
#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH
 
    const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH;
 
#else
 
    const unsigned int consoleWidth = 80;
 
#endif
 

	
 
    struct TextAttributes {
 
        TextAttributes()
 
        :   initialIndent( std::string::npos ),
 
            indent( 0 ),
 
            width( consoleWidth-1 ),
 
            tabChar( '\t' )
 
        {}
 

	
 
        TextAttributes& setInitialIndent( std::size_t _value )  { initialIndent = _value; return *this; }
 
        TextAttributes& setIndent( std::size_t _value )         { indent = _value; return *this; }
 
        TextAttributes& setWidth( std::size_t _value )          { width = _value; return *this; }
 
        TextAttributes& setTabChar( char _value )               { tabChar = _value; return *this; }
 

	
 
        std::size_t initialIndent;  // indent of first line, or npos
 
        std::size_t indent;         // indent of subsequent lines, or all if initialIndent is npos
 
        std::size_t width;          // maximum width of text, including indent. Longer text will wrap
 
        char tabChar;               // If this char is seen the indent is changed to current pos
 
    };
 

	
 
    class Text {
 
    public:
 
        Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() )
 
        : attr( _attr )
 
        {
 
            std::string wrappableChars = " [({.,/|\\-";
 
            std::size_t indent = _attr.initialIndent != std::string::npos
 
                ? _attr.initialIndent
 
                : _attr.indent;
 
            std::string remainder = _str;
 

	
 
            while( !remainder.empty() ) {
 
                if( lines.size() >= 1000 ) {
 
                    lines.push_back( "... message truncated due to excessive size" );
 
                    return;
 
                }
 
                std::size_t tabPos = std::string::npos;
 
                std::size_t width = (std::min)( remainder.size(), _attr.width - indent );
 
                std::size_t pos = remainder.find_first_of( '\n' );
 
                if( pos <= width ) {
 
                    width = pos;
 
                }
 
                pos = remainder.find_last_of( _attr.tabChar, width );
 
                if( pos != std::string::npos ) {
 
                    tabPos = pos;
 
                    if( remainder[width] == '\n' )
 
                        width--;
 
                    remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 );
 
                }
 

	
 
                if( width == remainder.size() ) {
 
                    spliceLine( indent, remainder, width );
 
                }
 
                else if( remainder[width] == '\n' ) {
 
                    spliceLine( indent, remainder, width );
 
                    if( width <= 1 || remainder.size() != 1 )
 
                        remainder = remainder.substr( 1 );
 
                    indent = _attr.indent;
 
                }
 
                else {
 
                    pos = remainder.find_last_of( wrappableChars, width );
 
                    if( pos != std::string::npos && pos > 0 ) {
 
                        spliceLine( indent, remainder, pos );
 
                        if( remainder[0] == ' ' )
 
                            remainder = remainder.substr( 1 );
 
                    }
 
                    else {
 
                        spliceLine( indent, remainder, width-1 );
 
                        lines.back() += "-";
 
                    }
 
                    if( lines.size() == 1 )
 
                        indent = _attr.indent;
 
                    if( tabPos != std::string::npos )
 
                        indent += tabPos;
 
                }
 
            }
 
        }
 

	
 
        void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) {
 
            lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) );
 
            _remainder = _remainder.substr( _pos );
 
        }
 

	
 
        typedef std::vector<std::string>::const_iterator const_iterator;
 

	
 
        const_iterator begin() const { return lines.begin(); }
 
        const_iterator end() const { return lines.end(); }
 
        std::string const& last() const { return lines.back(); }
 
        std::size_t size() const { return lines.size(); }
 
        std::string const& operator[]( std::size_t _index ) const { return lines[_index]; }
 
        std::string toString() const {
 
            std::ostringstream oss;
 
            oss << *this;
 
            return oss.str();
 
        }
 

	
 
        inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) {
 
            for( Text::const_iterator it = _text.begin(), itEnd = _text.end();
 
                it != itEnd; ++it ) {
 
                if( it != _text.begin() )
 
                    _stream << "\n";
 
                _stream << *it;
 
            }
 
            return _stream;
 
        }
 

	
 
    private:
 
        std::string str;
 
        TextAttributes attr;
 
        std::vector<std::string> lines;
 
    };
 

	
 
} // end namespace Tbc
 

	
 
#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE
 
} // end outer namespace
 
#endif
 

	
 
#endif // TBC_TEXT_FORMAT_H_INCLUDED
 

	
 
// ----------- end of #include from tbc_text_format.h -----------
 
// ........... back in clara.h
 

	
 
#undef STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE
 

	
 
// ----------- #included from clara_compilers.h -----------
 

	
 
#ifndef TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED
 
#define TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED
 

	
 
// Detect a number of compiler features - mostly C++11/14 conformance - by compiler
 
// The following features are defined:
 
//
 
// CLARA_CONFIG_CPP11_NULLPTR : is nullptr supported?
 
// CLARA_CONFIG_CPP11_NOEXCEPT : is noexcept supported?
 
// CLARA_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods
 
// CLARA_CONFIG_CPP11_OVERRIDE : is override supported?
 
// CLARA_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr)
 

	
 
// CLARA_CONFIG_CPP11_OR_GREATER : Is C++11 supported?
 

	
 
// CLARA_CONFIG_VARIADIC_MACROS : are variadic macros supported?
 

	
 
// In general each macro has a _NO_<feature name> form
 
// (e.g. CLARA_CONFIG_CPP11_NO_NULLPTR) which disables the feature.
 
// Many features, at point of detection, define an _INTERNAL_ macro, so they
 
// can be combined, en-mass, with the _NO_ forms later.
 

	
 
// All the C++11 features can be disabled with CLARA_CONFIG_NO_CPP11
 

	
 
#ifdef __clang__
 

	
 
#if __has_feature(cxx_nullptr)
 
#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR
 
#endif
 

	
 
#if __has_feature(cxx_noexcept)
 
#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT
 
#endif
 

	
 
#endif // __clang__
 

	
 
////////////////////////////////////////////////////////////////////////////////
 
// GCC
 
#ifdef __GNUC__
 

	
 
#if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__)
 
#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR
 
#endif
 

	
 
// - otherwise more recent versions define __cplusplus >= 201103L
 
// and will get picked up below
 

	
 
#endif // __GNUC__
 

	
 
////////////////////////////////////////////////////////////////////////////////
 
// Visual C++
 
#ifdef _MSC_VER
 

	
 
#if (_MSC_VER >= 1600)
 
#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR
 
#define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR
 
#endif
 

	
 
#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015))
 
#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT
 
#define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS
 
#endif
 

	
 
#endif // _MSC_VER
 

	
 
////////////////////////////////////////////////////////////////////////////////
 
// C++ language feature support
 

	
 
// catch all support for C++11
 
#if defined(__cplusplus) && __cplusplus >= 201103L
 

	
 
#define CLARA_CPP11_OR_GREATER
 

	
 
#if !defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR)
 
#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR
 
#endif
 

	
 
#ifndef CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT
 
#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT
 
#endif
 

	
 
#ifndef CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS
 
#define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS
 
#endif
 

	
 
#if !defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE)
 
#define CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE
 
#endif
 
#if !defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR)
 
#define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR
 
#endif
 

	
 
#endif // __cplusplus >= 201103L
 

	
 
// Now set the actual defines based on the above + anything the user has configured
 
#if defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NO_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_NO_CPP11)
 
#define CLARA_CONFIG_CPP11_NULLPTR
 
#endif
 
#if defined(CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_NO_CPP11)
 
#define CLARA_CONFIG_CPP11_NOEXCEPT
 
#endif
 
#if defined(CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_NO_CPP11)
 
#define CLARA_CONFIG_CPP11_GENERATED_METHODS
 
#endif
 
#if defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_OVERRIDE) && !defined(CLARA_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_CPP11)
 
#define CLARA_CONFIG_CPP11_OVERRIDE
 
#endif
 
#if defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_UNIQUE_PTR) && !defined(CLARA_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_CPP11)
 
#define CLARA_CONFIG_CPP11_UNIQUE_PTR
 
#endif
 

	
 
// noexcept support:
 
#if defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_NOEXCEPT)
 
#define CLARA_NOEXCEPT noexcept
 
#  define CLARA_NOEXCEPT_IS(x) noexcept(x)
 
#else
 
#define CLARA_NOEXCEPT throw()
 
#  define CLARA_NOEXCEPT_IS(x)
 
#endif
 

	
 
// nullptr support
 
#ifdef CLARA_CONFIG_CPP11_NULLPTR
 
#define CLARA_NULL nullptr
 
#else
 
#define CLARA_NULL NULL
 
#endif
 

	
 
// override support
 
#ifdef CLARA_CONFIG_CPP11_OVERRIDE
 
#define CLARA_OVERRIDE override
 
#else
 
#define CLARA_OVERRIDE
 
#endif
 

	
 
// unique_ptr support
 
#ifdef CLARA_CONFIG_CPP11_UNIQUE_PTR
 
#   define CLARA_AUTO_PTR( T ) std::unique_ptr<T>
 
#else
 
#   define CLARA_AUTO_PTR( T ) std::auto_ptr<T>
 
#endif
 

	
 
#endif // TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED
 

	
 
// ----------- end of #include from clara_compilers.h -----------
 
// ........... back in clara.h
 

	
 
#include <map>
 
#include <stdexcept>
 
#include <memory>
 

	
 
#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER)
 
#define CLARA_PLATFORM_WINDOWS
 
#endif
 

	
 
// Use optional outer namespace
 
#ifdef STITCH_CLARA_OPEN_NAMESPACE
 
STITCH_CLARA_OPEN_NAMESPACE
 
#endif
 

	
 
namespace Clara {
 

	
 
    struct UnpositionalTag {};
 

	
 
    extern UnpositionalTag _;
 

	
 
#ifdef CLARA_CONFIG_MAIN
 
    UnpositionalTag _;
 
#endif
 

	
 
    namespace Detail {
 

	
 
#ifdef CLARA_CONSOLE_WIDTH
 
    const unsigned int consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH;
 
#else
 
    const unsigned int consoleWidth = 80;
 
#endif
 

	
 
        using namespace Tbc;
 

	
 
        inline bool startsWith( std::string const& str, std::string const& prefix ) {
 
            return str.size() >= prefix.size() && str.substr( 0, prefix.size() ) == prefix;
 
        }
 

	
 
        template<typename T> struct RemoveConstRef{ typedef T type; };
 
        template<typename T> struct RemoveConstRef<T&>{ typedef T type; };
 
        template<typename T> struct RemoveConstRef<T const&>{ typedef T type; };
 
        template<typename T> struct RemoveConstRef<T const>{ typedef T type; };
 

	
 
        template<typename T>    struct IsBool       { static const bool value = false; };
 
        template<>              struct IsBool<bool> { static const bool value = true; };
 

	
 
        template<typename T>
 
        void convertInto( std::string const& _source, T& _dest ) {
 
            std::stringstream ss;
 
            ss << _source;
 
            ss >> _dest;
 
            if( ss.fail() )
 
                throw std::runtime_error( "Unable to convert " + _source + " to destination type" );
 
        }
 
        inline void convertInto( std::string const& _source, std::string& _dest ) {
 
            _dest = _source;
 
        }
 
        char toLowerCh(char c) {
 
            return static_cast<char>( std::tolower( c ) );
 
        }
 
        inline void convertInto( std::string const& _source, bool& _dest ) {
 
            std::string sourceLC = _source;
 
            std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), toLowerCh );
 
            if( sourceLC == "y" || sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" )
 
                _dest = true;
 
            else if( sourceLC == "n" || sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" )
 
                _dest = false;
 
            else
 
                throw std::runtime_error( "Expected a boolean value but did not recognise:\n  '" + _source + "'" );
 
        }
 

	
 
        template<typename ConfigT>
 
        struct IArgFunction {
 
            virtual ~IArgFunction() {}
 
#ifdef CLARA_CONFIG_CPP11_GENERATED_METHODS
 
            IArgFunction()                      = default;
 
            IArgFunction( IArgFunction const& ) = default;
 
#endif
 
            virtual void set( ConfigT& config, std::string const& value ) const = 0;
 
            virtual bool takesArg() const = 0;
 
            virtual IArgFunction* clone() const = 0;
 
        };
 

	
 
        template<typename ConfigT>
 
        class BoundArgFunction {
 
        public:
 
            BoundArgFunction() : functionObj( CLARA_NULL ) {}
 
            BoundArgFunction( IArgFunction<ConfigT>* _functionObj ) : functionObj( _functionObj ) {}
 
            BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : CLARA_NULL ) {}
 
            BoundArgFunction& operator = ( BoundArgFunction const& other ) {
 
                IArgFunction<ConfigT>* newFunctionObj = other.functionObj ? other.functionObj->clone() : CLARA_NULL;
 
                delete functionObj;
 
                functionObj = newFunctionObj;
 
                return *this;
 
            }
 
            ~BoundArgFunction() { delete functionObj; }
 

	
 
            void set( ConfigT& config, std::string const& value ) const {
 
                functionObj->set( config, value );
 
            }
 
            bool takesArg() const { return functionObj->takesArg(); }
 

	
 
            bool isSet() const {
 
                return functionObj != CLARA_NULL;
 
            }
 
        private:
 
            IArgFunction<ConfigT>* functionObj;
 
        };
 

	
 
        template<typename C>
 
        struct NullBinder : IArgFunction<C>{
 
            virtual void set( C&, std::string const& ) const {}
 
            virtual bool takesArg() const { return true; }
 
            virtual IArgFunction<C>* clone() const { return new NullBinder( *this ); }
 
        };
 

	
 
        template<typename C, typename M>
 
        struct BoundDataMember : IArgFunction<C>{
 
            BoundDataMember( M C::* _member ) : member( _member ) {}
 
            virtual void set( C& p, std::string const& stringValue ) const {
 
                convertInto( stringValue, p.*member );
 
            }
 
            virtual bool takesArg() const { return !IsBool<M>::value; }
 
            virtual IArgFunction<C>* clone() const { return new BoundDataMember( *this ); }
 
            M C::* member;
 
        };
 
        template<typename C, typename M>
 
        struct BoundUnaryMethod : IArgFunction<C>{
 
            BoundUnaryMethod( void (C::*_member)( M ) ) : member( _member ) {}
 
            virtual void set( C& p, std::string const& stringValue ) const {
 
                typename RemoveConstRef<M>::type value;
 
                convertInto( stringValue, value );
 
                (p.*member)( value );
 
            }
 
            virtual bool takesArg() const { return !IsBool<M>::value; }
 
            virtual IArgFunction<C>* clone() const { return new BoundUnaryMethod( *this ); }
 
            void (C::*member)( M );
 
        };
 
        template<typename C>
 
        struct BoundNullaryMethod : IArgFunction<C>{
 
            BoundNullaryMethod( void (C::*_member)() ) : member( _member ) {}
 
            virtual void set( C& p, std::string const& stringValue ) const {
 
                bool value;
 
                convertInto( stringValue, value );
 
                if( value )
 
                    (p.*member)();
 
            }
 
            virtual bool takesArg() const { return false; }
 
            virtual IArgFunction<C>* clone() const { return new BoundNullaryMethod( *this ); }
 
            void (C::*member)();
 
        };
 

	
 
        template<typename C>
 
        struct BoundUnaryFunction : IArgFunction<C>{
 
            BoundUnaryFunction( void (*_function)( C& ) ) : function( _function ) {}
 
            virtual void set( C& obj, std::string const& stringValue ) const {
 
                bool value;
 
                convertInto( stringValue, value );
 
                if( value )
 
                    function( obj );
 
            }
 
            virtual bool takesArg() const { return false; }
 
            virtual IArgFunction<C>* clone() const { return new BoundUnaryFunction( *this ); }
 
            void (*function)( C& );
 
        };
 

	
 
        template<typename C, typename T>
 
        struct BoundBinaryFunction : IArgFunction<C>{
 
            BoundBinaryFunction( void (*_function)( C&, T ) ) : function( _function ) {}
 
            virtual void set( C& obj, std::string const& stringValue ) const {
 
                typename RemoveConstRef<T>::type value;
 
                convertInto( stringValue, value );
 
                function( obj, value );
 
            }
 
            virtual bool takesArg() const { return !IsBool<T>::value; }
 
            virtual IArgFunction<C>* clone() const { return new BoundBinaryFunction( *this ); }
 
            void (*function)( C&, T );
 
        };
 

	
 
    } // namespace Detail
 

	
 
    inline std::vector<std::string> argsToVector( int argc, char const* const* const argv ) {
 
        std::vector<std::string> args( static_cast<std::size_t>( argc ) );
 
        for( std::size_t i = 0; i < static_cast<std::size_t>( argc ); ++i )
 
            args[i] = argv[i];
 

	
 
        return args;
 
    }
 

	
 
    class Parser {
 
        enum Mode { None, MaybeShortOpt, SlashOpt, ShortOpt, LongOpt, Positional };
 
        Mode mode;
 
        std::size_t from;
 
        bool inQuotes;
 
    public:
 

	
 
        struct Token {
 
            enum Type { Positional, ShortOpt, LongOpt };
 
            Token( Type _type, std::string const& _data ) : type( _type ), data( _data ) {}
 
            Type type;
 
            std::string data;
 
        };
 

	
 
        Parser() : mode( None ), from( 0 ), inQuotes( false ){}
 

	
 
        void parseIntoTokens( std::vector<std::string> const& args, std::vector<Token>& tokens ) {
 
            const std::string doubleDash = "--";
 
            for( std::size_t i = 1; i < args.size() && args[i] != doubleDash; ++i )
 
                parseIntoTokens( args[i], tokens);
 
        }
 

	
 
        void parseIntoTokens( std::string const& arg, std::vector<Token>& tokens ) {
 
            for( std::size_t i = 0; i < arg.size(); ++i ) {
 
                char c = arg[i];
 
                if( c == '"' )
 
                    inQuotes = !inQuotes;
 
                mode = handleMode( i, c, arg, tokens );
 
            }
 
            mode = handleMode( arg.size(), '\0', arg, tokens );
 
        }
 
        Mode handleMode( std::size_t i, char c, std::string const& arg, std::vector<Token>& tokens ) {
 
            switch( mode ) {
 
                case None: return handleNone( i, c );
 
                case MaybeShortOpt: return handleMaybeShortOpt( i, c );
 
                case ShortOpt:
 
                case LongOpt:
 
                case SlashOpt: return handleOpt( i, c, arg, tokens );
 
                case Positional: return handlePositional( i, c, arg, tokens );
 
                default: throw std::logic_error( "Unknown mode" );
 
            }
 
        }
 

	
 
        Mode handleNone( std::size_t i, char c ) {
 
            if( inQuotes ) {
 
                from = i;
 
                return Positional;
 
            }
 
            switch( c ) {
 
                case '-': return MaybeShortOpt;
 
#ifdef CLARA_PLATFORM_WINDOWS
 
                case '/': from = i+1; return SlashOpt;
 
#endif
 
                default: from = i; return Positional;
 
            }
 
        }
 
        Mode handleMaybeShortOpt( std::size_t i, char c ) {
 
            switch( c ) {
 
                case '-': from = i+1; return LongOpt;
 
                default: from = i; return ShortOpt;
 
            }
 
        }
 

	
 
        Mode handleOpt( std::size_t i, char c, std::string const& arg, std::vector<Token>& tokens ) {
 
            if( std::string( ":=\0", 3 ).find( c ) == std::string::npos )
 
                return mode;
 

	
 
            std::string optName = arg.substr( from, i-from );
 
            if( mode == ShortOpt )
 
                for( std::size_t j = 0; j < optName.size(); ++j )
 
                    tokens.push_back( Token( Token::ShortOpt, optName.substr( j, 1 ) ) );
 
            else if( mode == SlashOpt && optName.size() == 1 )
 
                tokens.push_back( Token( Token::ShortOpt, optName ) );
 
            else
 
                tokens.push_back( Token( Token::LongOpt, optName ) );
 
            return None;
 
        }
 
        Mode handlePositional( std::size_t i, char c, std::string const& arg, std::vector<Token>& tokens ) {
 
            if( inQuotes || std::string( "\0", 1 ).find( c ) == std::string::npos )
 
                return mode;
 

	
 
            std::string data = arg.substr( from, i-from );
 
            tokens.push_back( Token( Token::Positional, data ) );
 
            return None;
 
        }
 
    };
 

	
 
    template<typename ConfigT>
 
    struct CommonArgProperties {
 
        CommonArgProperties() {}
 
        CommonArgProperties( Detail::BoundArgFunction<ConfigT> const& _boundField ) : boundField( _boundField ) {}
 

	
 
        Detail::BoundArgFunction<ConfigT> boundField;
 
        std::string description;
 
        std::string detail;
 
        std::string placeholder; // Only value if boundField takes an arg
 

	
 
        bool takesArg() const {
 
            return !placeholder.empty();
 
        }
 
        void validate() const {
 
            if( !boundField.isSet() )
 
                throw std::logic_error( "option not bound" );
 
        }
 
    };
 
    struct OptionArgProperties {
 
        std::vector<std::string> shortNames;
 
        std::string longName;
 

	
 
        bool hasShortName( std::string const& shortName ) const {
 
            return std::find( shortNames.begin(), shortNames.end(), shortName ) != shortNames.end();
 
        }
 
        bool hasLongName( std::string const& _longName ) const {
 
            return _longName == longName;
 
        }
 
    };
 
    struct PositionalArgProperties {
 
        PositionalArgProperties() : position( -1 ) {}
 
        int position; // -1 means non-positional (floating)
 

	
 
        bool isFixedPositional() const {
 
            return position != -1;
 
        }
 
    };
 

	
 
    template<typename ConfigT>
 
    class CommandLine {
 

	
 
        struct Arg : CommonArgProperties<ConfigT>, OptionArgProperties, PositionalArgProperties {
 
            Arg() {}
 
            Arg( Detail::BoundArgFunction<ConfigT> const& _boundField ) : CommonArgProperties<ConfigT>( _boundField ) {}
 

	
 
            using CommonArgProperties<ConfigT>::placeholder; // !TBD
 

	
 
            std::string dbgName() const {
 
                if( !longName.empty() )
 
                    return "--" + longName;
 
                if( !shortNames.empty() )
 
                    return "-" + shortNames[0];
 
                return "positional args";
 
            }
 
            std::string commands() const {
 
                std::ostringstream oss;
 
                bool first = true;
 
                std::vector<std::string>::const_iterator it = shortNames.begin(), itEnd = shortNames.end();
 
                for(; it != itEnd; ++it ) {
 
                    if( first )
 
                        first = false;
 
                    else
 
                        oss << ", ";
 
                    oss << "-" << *it;
 
                }
 
                if( !longName.empty() ) {
 
                    if( !first )
 
                        oss << ", ";
 
                    oss << "--" << longName;
 
                }
 
                if( !placeholder.empty() )
 
                    oss << " <" << placeholder << ">";
 
                return oss.str();
 
            }
 
        };
 

	
 
        typedef CLARA_AUTO_PTR( Arg ) ArgAutoPtr;
 

	
 
        friend void addOptName( Arg& arg, std::string const& optName )
 
        {
 
            if( optName.empty() )
 
                return;
 
            if( Detail::startsWith( optName, "--" ) ) {
 
                if( !arg.longName.empty() )
 
                    throw std::logic_error( "Only one long opt may be specified. '"
 
                        + arg.longName
 
                        + "' already specified, now attempting to add '"
 
                        + optName + "'" );
 
                arg.longName = optName.substr( 2 );
 
            }
 
            else if( Detail::startsWith( optName, "-" ) )
 
                arg.shortNames.push_back( optName.substr( 1 ) );
 
            else
 
                throw std::logic_error( "option must begin with - or --. Option was: '" + optName + "'" );
 
        }
 
        friend void setPositionalArg( Arg& arg, int position )
 
        {
 
            arg.position = position;
 
        }
 

	
 
        class ArgBuilder {
 
        public:
 
            ArgBuilder( Arg* arg ) : m_arg( arg ) {}
 

	
 
            // Bind a non-boolean data member (requires placeholder string)
 
            template<typename C, typename M>
 
            void bind( M C::* field, std::string const& placeholder ) {
 
                m_arg->boundField = new Detail::BoundDataMember<C,M>( field );
 
                m_arg->placeholder = placeholder;
 
            }
 
            // Bind a boolean data member (no placeholder required)
 
            template<typename C>
 
            void bind( bool C::* field ) {
 
                m_arg->boundField = new Detail::BoundDataMember<C,bool>( field );
 
            }
 

	
 
            // Bind a method taking a single, non-boolean argument (requires a placeholder string)
 
            template<typename C, typename M>
 
            void bind( void (C::* unaryMethod)( M ), std::string const& placeholder ) {
 
                m_arg->boundField = new Detail::BoundUnaryMethod<C,M>( unaryMethod );
 
                m_arg->placeholder = placeholder;
 
            }
 

	
 
            // Bind a method taking a single, boolean argument (no placeholder string required)
 
            template<typename C>
 
            void bind( void (C::* unaryMethod)( bool ) ) {
 
                m_arg->boundField = new Detail::BoundUnaryMethod<C,bool>( unaryMethod );
 
            }
 

	
 
            // Bind a method that takes no arguments (will be called if opt is present)
 
            template<typename C>
 
            void bind( void (C::* nullaryMethod)() ) {
 
                m_arg->boundField = new Detail::BoundNullaryMethod<C>( nullaryMethod );
 
            }
 

	
 
            // Bind a free function taking a single argument - the object to operate on (no placeholder string required)
 
            template<typename C>
 
            void bind( void (* unaryFunction)( C& ) ) {
 
                m_arg->boundField = new Detail::BoundUnaryFunction<C>( unaryFunction );
 
            }
 

	
 
            // Bind a free function taking a single argument - the object to operate on (requires a placeholder string)
 
            template<typename C, typename T>
 
            void bind( void (* binaryFunction)( C&, T ), std::string const& placeholder ) {
 
                m_arg->boundField = new Detail::BoundBinaryFunction<C, T>( binaryFunction );
 
                m_arg->placeholder = placeholder;
 
            }
 

	
 
            ArgBuilder& describe( std::string const& description ) {
 
                m_arg->description = description;
 
                return *this;
 
            }
 
            ArgBuilder& detail( std::string const& detail ) {
 
                m_arg->detail = detail;
 
                return *this;
 
            }
 

	
 
        protected:
 
            Arg* m_arg;
 
        };
 

	
 
        class OptBuilder : public ArgBuilder {
 
        public:
 
            OptBuilder( Arg* arg ) : ArgBuilder( arg ) {}
 
            OptBuilder( OptBuilder& other ) : ArgBuilder( other ) {}
 

	
 
            OptBuilder& operator[]( std::string const& optName ) {
 
                addOptName( *ArgBuilder::m_arg, optName );
 
                return *this;
 
            }
 
        };
 

	
 
    public:
 

	
 
        CommandLine()
 
        :   m_boundProcessName( new Detail::NullBinder<ConfigT>() ),
 
            m_highestSpecifiedArgPosition( 0 ),
 
            m_throwOnUnrecognisedTokens( false )
 
        {}
 
        CommandLine( CommandLine const& other )
 
        :   m_boundProcessName( other.m_boundProcessName ),
 
            m_options ( other.m_options ),
 
            m_positionalArgs( other.m_positionalArgs ),
 
            m_highestSpecifiedArgPosition( other.m_highestSpecifiedArgPosition ),
 
            m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens )
 
        {
 
            if( other.m_floatingArg.get() )
 
                m_floatingArg.reset( new Arg( *other.m_floatingArg ) );
 
        }
 

	
 
        CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) {
 
            m_throwOnUnrecognisedTokens = shouldThrow;
 
            return *this;
 
        }
 

	
 
        OptBuilder operator[]( std::string const& optName ) {
 
            m_options.push_back( Arg() );
 
            addOptName( m_options.back(), optName );
 
            OptBuilder builder( &m_options.back() );
 
            return builder;
 
        }
 

	
 
        ArgBuilder operator[]( int position ) {
 
            m_positionalArgs.insert( std::make_pair( position, Arg() ) );
 
            if( position > m_highestSpecifiedArgPosition )
 
                m_highestSpecifiedArgPosition = position;
 
            setPositionalArg( m_positionalArgs[position], position );
 
            ArgBuilder builder( &m_positionalArgs[position] );
 
            return builder;
 
        }
 

	
 
        // Invoke this with the _ instance
 
        ArgBuilder operator[]( UnpositionalTag ) {
 
            if( m_floatingArg.get() )
 
                throw std::logic_error( "Only one unpositional argument can be added" );
 
            m_floatingArg.reset( new Arg() );
 
            ArgBuilder builder( m_floatingArg.get() );
 
            return builder;
 
        }
 

	
 
        template<typename C, typename M>
 
        void bindProcessName( M C::* field ) {
 
            m_boundProcessName = new Detail::BoundDataMember<C,M>( field );
 
        }
 
        template<typename C, typename M>
 
        void bindProcessName( void (C::*_unaryMethod)( M ) ) {
 
            m_boundProcessName = new Detail::BoundUnaryMethod<C,M>( _unaryMethod );
 
        }
 

	
 
        void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = Detail::consoleWidth ) const {
 
            typename std::vector<Arg>::const_iterator itBegin = m_options.begin(), itEnd = m_options.end(), it;
 
            std::size_t maxWidth = 0;
 
            for( it = itBegin; it != itEnd; ++it )
 
                maxWidth = (std::max)( maxWidth, it->commands().size() );
 

	
 
            for( it = itBegin; it != itEnd; ++it ) {
 
                Detail::Text usage( it->commands(), Detail::TextAttributes()
 
                                                        .setWidth( maxWidth+indent )
 
                                                        .setIndent( indent ) );
 
                Detail::Text desc( it->description, Detail::TextAttributes()
 
                                                        .setWidth( width - maxWidth - 3 ) );
 

	
 
                for( std::size_t i = 0; i < (std::max)( usage.size(), desc.size() ); ++i ) {
 
                    std::string usageCol = i < usage.size() ? usage[i] : "";
 
                    os << usageCol;
 

	
 
                    if( i < desc.size() && !desc[i].empty() )
 
                        os  << std::string( indent + 2 + maxWidth - usageCol.size(), ' ' )
 
                            << desc[i];
 
                    os << "\n";
 
                }
 
            }
 
        }
 
        std::string optUsage() const {
 
            std::ostringstream oss;
 
            optUsage( oss );
 
            return oss.str();
 
        }
 

	
 
        void argSynopsis( std::ostream& os ) const {
 
            for( int i = 1; i <= m_highestSpecifiedArgPosition; ++i ) {
 
                if( i > 1 )
 
                    os << " ";
 
                typename std::map<int, Arg>::const_iterator it = m_positionalArgs.find( i );
 
                if( it != m_positionalArgs.end() )
 
                    os << "<" << it->second.placeholder << ">";
 
                else if( m_floatingArg.get() )
 
                    os << "<" << m_floatingArg->placeholder << ">";
 
                else
 
                    throw std::logic_error( "non consecutive positional arguments with no floating args" );
 
            }
 
            // !TBD No indication of mandatory args
 
            if( m_floatingArg.get() ) {
 
                if( m_highestSpecifiedArgPosition > 1 )
 
                    os << " ";
 
                os << "[<" << m_floatingArg->placeholder << "> ...]";
 
            }
 
        }
 
        std::string argSynopsis() const {
 
            std::ostringstream oss;
 
            argSynopsis( oss );
 
            return oss.str();
 
        }
 

	
 
        void usage( std::ostream& os, std::string const& procName ) const {
 
            validate();
 
            os << "usage:\n  " << procName << " ";
 
            argSynopsis( os );
 
            if( !m_options.empty() ) {
 
                os << " [options]\n\nwhere options are: \n";
 
                optUsage( os, 2 );
 
            }
 
            os << "\n";
 
        }
 
        std::string usage( std::string const& procName ) const {
 
            std::ostringstream oss;
 
            usage( oss, procName );
 
            return oss.str();
 
        }
 

	
 
        ConfigT parse( std::vector<std::string> const& args ) const {
 
            ConfigT config;
 
            parseInto( args, config );
 
            return config;
 
        }
 

	
 
        std::vector<Parser::Token> parseInto( std::vector<std::string> const& args, ConfigT& config ) const {
 
            std::string processName = args.empty() ? std::string() : args[0];
 
            std::size_t lastSlash = processName.find_last_of( "/\\" );
 
            if( lastSlash != std::string::npos )
 
                processName = processName.substr( lastSlash+1 );
 
            m_boundProcessName.set( config, processName );
 
            std::vector<Parser::Token> tokens;
 
            Parser parser;
 
            parser.parseIntoTokens( args, tokens );
 
            return populate( tokens, config );
 
        }
 

	
 
        std::vector<Parser::Token> populate( std::vector<Parser::Token> const& tokens, ConfigT& config ) const {
 
            validate();
 
            std::vector<Parser::Token> unusedTokens = populateOptions( tokens, config );
 
            unusedTokens = populateFixedArgs( unusedTokens, config );
 
            unusedTokens = populateFloatingArgs( unusedTokens, config );
 
            return unusedTokens;
 
        }
 

	
 
        std::vector<Parser::Token> populateOptions( std::vector<Parser::Token> const& tokens, ConfigT& config ) const {
 
            std::vector<Parser::Token> unusedTokens;
 
            std::vector<std::string> errors;
 
            for( std::size_t i = 0; i < tokens.size(); ++i ) {
 
                Parser::Token const& token = tokens[i];
 
                typename std::vector<Arg>::const_iterator it = m_options.begin(), itEnd = m_options.end();
 
                for(; it != itEnd; ++it ) {
 
                    Arg const& arg = *it;
 

	
 
                    try {
 
                        if( ( token.type == Parser::Token::ShortOpt && arg.hasShortName( token.data ) ) ||
 
                            ( token.type == Parser::Token::LongOpt && arg.hasLongName( token.data ) ) ) {
 
                            if( arg.takesArg() ) {
 
                                if( i == tokens.size()-1 || tokens[i+1].type != Parser::Token::Positional )
 
                                    errors.push_back( "Expected argument to option: " + token.data );
 
                                else
 
                                    arg.boundField.set( config, tokens[++i].data );
 
                            }
 
                            else {
 
                                arg.boundField.set( config, "true" );
 
                            }
 
                            break;
 
                        }
 
                    }
 
                    catch( std::exception& ex ) {
 
                        errors.push_back( std::string( ex.what() ) + "\n- while parsing: (" + arg.commands() + ")" );
 
                    }
 
                }
 
                if( it == itEnd ) {
 
                    if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens )
 
                        unusedTokens.push_back( token );
 
                    else if( errors.empty() && m_throwOnUnrecognisedTokens )
 
                        errors.push_back( "unrecognised option: " + token.data );
 
                }
 
            }
 
            if( !errors.empty() ) {
 
                std::ostringstream oss;
 
                for( std::vector<std::string>::const_iterator it = errors.begin(), itEnd = errors.end();
 
                        it != itEnd;
 
                        ++it ) {
 
                    if( it != errors.begin() )
 
                        oss << "\n";
 
                    oss << *it;
 
                }
 
                throw std::runtime_error( oss.str() );
 
            }
 
            return unusedTokens;
 
        }
 
        std::vector<Parser::Token> populateFixedArgs( std::vector<Parser::Token> const& tokens, ConfigT& config ) const {
 
            std::vector<Parser::Token> unusedTokens;
 
            int position = 1;
 
            for( std::size_t i = 0; i < tokens.size(); ++i ) {
 
                Parser::Token const& token = tokens[i];
 
                typename std::map<int, Arg>::const_iterator it = m_positionalArgs.find( position );
 
                if( it != m_positionalArgs.end() )
 
                    it->second.boundField.set( config, token.data );
 
                else
 
                    unusedTokens.push_back( token );
 
                if( token.type == Parser::Token::Positional )
 
                    position++;
 
            }
 
            return unusedTokens;
 
        }
 
        std::vector<Parser::Token> populateFloatingArgs( std::vector<Parser::Token> const& tokens, ConfigT& config ) const {
 
            if( !m_floatingArg.get() )
 
                return tokens;
 
            std::vector<Parser::Token> unusedTokens;
 
            for( std::size_t i = 0; i < tokens.size(); ++i ) {
 
                Parser::Token const& token = tokens[i];
 
                if( token.type == Parser::Token::Positional )
 
                    m_floatingArg->boundField.set( config, token.data );
 
                else
 
                    unusedTokens.push_back( token );
 
            }
 
            return unusedTokens;
 
        }
 

	
 
        void validate() const
 
        {
 
            if( m_options.empty() && m_positionalArgs.empty() && !m_floatingArg.get() )
 
                throw std::logic_error( "No options or arguments specified" );
 

	
 
            for( typename std::vector<Arg>::const_iterator  it = m_options.begin(),
 
                                                            itEnd = m_options.end();
 
                    it != itEnd; ++it )
 
                it->validate();
 
        }
 

	
 
    private:
 
        Detail::BoundArgFunction<ConfigT> m_boundProcessName;
 
        std::vector<Arg> m_options;
 
        std::map<int, Arg> m_positionalArgs;
 
        ArgAutoPtr m_floatingArg;
 
        int m_highestSpecifiedArgPosition;
 
        bool m_throwOnUnrecognisedTokens;
 
    };
 

	
 
} // end namespace Clara
 

	
 
STITCH_CLARA_CLOSE_NAMESPACE
 
#undef STITCH_CLARA_OPEN_NAMESPACE
 
#undef STITCH_CLARA_CLOSE_NAMESPACE
 

	
 
#endif // TWOBLUECUBES_CLARA_H_INCLUDED
 
#undef STITCH_CLARA_OPEN_NAMESPACE
 

	
 
// Restore Clara's value for console width, if present
 
#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH
 
#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH
 
#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH
 
#endif
 

	
 
#include <fstream>
 
#include <ctime>
 

	
 
namespace Catch {
 

	
 
    inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; }
 
    inline void abortAfterX( ConfigData& config, int x ) {
 
        if( x < 1 )
 
            throw std::runtime_error( "Value after -x or --abortAfter must be greater than zero" );
 
        config.abortAfter = x;
 
    }
 
    inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); }
 
    inline void addSectionToRun( ConfigData& config, std::string const& sectionName ) { config.sectionsToRun.push_back( sectionName ); }
 
    inline void addReporterName( ConfigData& config, std::string const& _reporterName ) { config.reporterNames.push_back( _reporterName ); }
 

	
 
    inline void addWarning( ConfigData& config, std::string const& _warning ) {
 
        if( _warning == "NoAssertions" )
 
            config.warnings = static_cast<WarnAbout::What>( config.warnings | WarnAbout::NoAssertions );
 
        else
 
            throw std::runtime_error( "Unrecognised warning: '" + _warning + '\'' );
 
    }
 
    inline void setOrder( ConfigData& config, std::string const& order ) {
 
        if( startsWith( "declared", order ) )
 
            config.runOrder = RunTests::InDeclarationOrder;
 
        else if( startsWith( "lexical", order ) )
 
            config.runOrder = RunTests::InLexicographicalOrder;
 
        else if( startsWith( "random", order ) )
 
            config.runOrder = RunTests::InRandomOrder;
 
        else
 
            throw std::runtime_error( "Unrecognised ordering: '" + order + '\'' );
 
    }
 
    inline void setRngSeed( ConfigData& config, std::string const& seed ) {
 
        if( seed == "time" ) {
 
            config.rngSeed = static_cast<unsigned int>( std::time(0) );
 
        }
 
        else {
 
            std::stringstream ss;
 
            ss << seed;
 
            ss >> config.rngSeed;
 
            if( ss.fail() )
 
                throw std::runtime_error( "Argument to --rng-seed should be the word 'time' or a number" );
 
        }
 
    }
 
    inline void setVerbosity( ConfigData& config, int level ) {
 
        // !TBD: accept strings?
 
        config.verbosity = static_cast<Verbosity::Level>( level );
 
    }
 
    inline void setShowDurations( ConfigData& config, bool _showDurations ) {
 
        config.showDurations = _showDurations
 
            ? ShowDurations::Always
 
            : ShowDurations::Never;
 
    }
 
    inline void setUseColour( ConfigData& config, std::string const& value ) {
 
        std::string mode = toLower( value );
 

	
 
        if( mode == "yes" )
 
            config.useColour = UseColour::Yes;
 
        else if( mode == "no" )
 
            config.useColour = UseColour::No;
 
        else if( mode == "auto" )
 
            config.useColour = UseColour::Auto;
 
        else
 
            throw std::runtime_error( "colour mode must be one of: auto, yes or no" );
 
    }
 
    inline void forceColour( ConfigData& config ) {
 
        config.useColour = UseColour::Yes;
 
    }
 
    inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) {
 
        std::ifstream f( _filename.c_str() );
 
        if( !f.is_open() )
 
            throw std::domain_error( "Unable to load input file: " + _filename );
 

	
 
        std::string line;
 
        while( std::getline( f, line ) ) {
 
            line = trim(line);
 
            if( !line.empty() && !startsWith( line, '#' ) ) {
 
                if( !startsWith( line, '"' ) )
 
                    line = '"' + line + '"';
 
                addTestOrTags( config, line + ',' );
 
            }
 
        }
 
    }
 

	
 
    inline Clara::CommandLine<ConfigData> makeCommandLineParser() {
 

	
 
        using namespace Clara;
 
        CommandLine<ConfigData> cli;
 

	
 
        cli.bindProcessName( &ConfigData::processName );
 

	
 
        cli["-?"]["-h"]["--help"]
 
            .describe( "display usage information" )
 
            .bind( &ConfigData::showHelp );
 

	
 
        cli["-l"]["--list-tests"]
 
            .describe( "list all/matching test cases" )
 
            .bind( &ConfigData::listTests );
 

	
 
        cli["-t"]["--list-tags"]
 
            .describe( "list all/matching tags" )
 
            .bind( &ConfigData::listTags );
 

	
 
        cli["-s"]["--success"]
 
            .describe( "include successful tests in output" )
 
            .bind( &ConfigData::showSuccessfulTests );
 

	
 
        cli["-b"]["--break"]
 
            .describe( "break into debugger on failure" )
 
            .bind( &ConfigData::shouldDebugBreak );
 

	
 
        cli["-e"]["--nothrow"]
 
            .describe( "skip exception tests" )
 
            .bind( &ConfigData::noThrow );
 

	
 
        cli["-i"]["--invisibles"]
 
            .describe( "show invisibles (tabs, newlines)" )
 
            .bind( &ConfigData::showInvisibles );
 

	
 
        cli["-o"]["--out"]
 
            .describe( "output filename" )
 
            .bind( &ConfigData::outputFilename, "filename" );
 

	
 
        cli["-r"]["--reporter"]
 
//            .placeholder( "name[:filename]" )
 
            .describe( "reporter to use (defaults to console)" )
 
            .bind( &addReporterName, "name" );
 

	
 
        cli["-n"]["--name"]
 
            .describe( "suite name" )
 
            .bind( &ConfigData::name, "name" );
 

	
 
        cli["-a"]["--abort"]
 
            .describe( "abort at first failure" )
 
            .bind( &abortAfterFirst );
 

	
 
        cli["-x"]["--abortx"]
 
            .describe( "abort after x failures" )
 
            .bind( &abortAfterX, "no. failures" );
 

	
 
        cli["-w"]["--warn"]
 
            .describe( "enable warnings" )
 
            .bind( &addWarning, "warning name" );
 

	
 
// - needs updating if reinstated
 
//        cli.into( &setVerbosity )
 
//            .describe( "level of verbosity (0=no output)" )
 
//            .shortOpt( "v")
 
//            .longOpt( "verbosity" )
 
//            .placeholder( "level" );
 

	
 
        cli[_]
 
            .describe( "which test or tests to use" )
 
            .bind( &addTestOrTags, "test name, pattern or tags" );
 

	
 
        cli["-d"]["--durations"]
 
            .describe( "show test durations" )
 
            .bind( &setShowDurations, "yes|no" );
 

	
 
        cli["-f"]["--input-file"]
 
            .describe( "load test names to run from a file" )
 
            .bind( &loadTestNamesFromFile, "filename" );
 

	
 
        cli["-#"]["--filenames-as-tags"]
 
            .describe( "adds a tag for the filename" )
 
            .bind( &ConfigData::filenamesAsTags );
 

	
 
        cli["-c"]["--section"]
 
                .describe( "specify section to run" )
 
                .bind( &addSectionToRun, "section name" );
 

	
 
        // Less common commands which don't have a short form
 
        cli["--list-test-names-only"]
 
            .describe( "list all/matching test cases names only" )
 
            .bind( &ConfigData::listTestNamesOnly );
 

	
 
        cli["--list-extra-info"]
 
            .describe( "list all/matching test cases with more info" )
 
            .bind( &ConfigData::listExtraInfo );
 

	
 
        cli["--list-reporters"]
 
            .describe( "list all reporters" )
 
            .bind( &ConfigData::listReporters );
 

	
 
        cli["--order"]
 
            .describe( "test case order (defaults to decl)" )
 
            .bind( &setOrder, "decl|lex|rand" );
 

	
 
        cli["--rng-seed"]
 
            .describe( "set a specific seed for random numbers" )
 
            .bind( &setRngSeed, "'time'|number" );
 

	
 
        cli["--force-colour"]
 
            .describe( "force colourised output (deprecated)" )
 
            .bind( &forceColour );
 

	
 
        cli["--use-colour"]
 
            .describe( "should output be colourised" )
 
            .bind( &setUseColour, "yes|no" );
 

	
 
        return cli;
 
    }
 

	
 
} // end namespace Catch
 

	
 
// #included from: internal/catch_list.hpp
 
#define TWOBLUECUBES_CATCH_LIST_HPP_INCLUDED
 

	
 
// #included from: catch_text.h
 
#define TWOBLUECUBES_CATCH_TEXT_H_INCLUDED
 

	
 
#define TBC_TEXT_FORMAT_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH
 

	
 
#define CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE Catch
 
// #included from: ../external/tbc_text_format.h
 
// Only use header guard if we are not using an outer namespace
 
#ifndef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE
 
# ifdef TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED
 
#  ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED
 
#   define TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED
 
#  endif
 
# else
 
#  define TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED
 
# endif
 
#endif
 
#ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED
 
#include <string>
 
#include <vector>
 
#include <sstream>
 

	
 
// Use optional outer namespace
 
#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE
 
namespace CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE {
 
#endif
 

	
 
namespace Tbc {
 

	
 
#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH
 
    const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH;
 
#else
 
    const unsigned int consoleWidth = 80;
 
#endif
 

	
 
    struct TextAttributes {
 
        TextAttributes()
 
        :   initialIndent( std::string::npos ),
 
            indent( 0 ),
 
            width( consoleWidth-1 )
 
        {}
 

	
 
        TextAttributes& setInitialIndent( std::size_t _value )  { initialIndent = _value; return *this; }
 
        TextAttributes& setIndent( std::size_t _value )         { indent = _value; return *this; }
 
        TextAttributes& setWidth( std::size_t _value )          { width = _value; return *this; }
 

	
 
        std::size_t initialIndent;  // indent of first line, or npos
 
        std::size_t indent;         // indent of subsequent lines, or all if initialIndent is npos
 
        std::size_t width;          // maximum width of text, including indent. Longer text will wrap
 
    };
 

	
 
    class Text {
 
    public:
 
        Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() )
 
        : attr( _attr )
 
        {
 
            const std::string wrappableBeforeChars = "[({<\t";
 
            const std::string wrappableAfterChars = "])}>-,./|\\";
 
            const std::string wrappableInsteadOfChars = " \n\r";
 
            std::string indent = _attr.initialIndent != std::string::npos
 
                ? std::string( _attr.initialIndent, ' ' )
 
                : std::string( _attr.indent, ' ' );
 

	
 
            typedef std::string::const_iterator iterator;
 
            iterator it = _str.begin();
 
            const iterator strEnd = _str.end();
 

	
 
            while( it != strEnd ) {
 

	
 
                if( lines.size() >= 1000 ) {
 
                    lines.push_back( "... message truncated due to excessive size" );
 
                    return;
 
                }
 

	
 
                std::string suffix;
 
                std::size_t width = (std::min)( static_cast<size_t>( strEnd-it ), _attr.width-static_cast<size_t>( indent.size() ) );
 
                iterator itEnd = it+width;
 
                iterator itNext = _str.end();
 

	
 
                iterator itNewLine = std::find( it, itEnd, '\n' );
 
                if( itNewLine != itEnd )
 
                    itEnd = itNewLine;
 

	
 
                if( itEnd != strEnd  ) {
 
                    bool foundWrapPoint = false;
 
                    iterator findIt = itEnd;
 
                    do {
 
                        if( wrappableAfterChars.find( *findIt ) != std::string::npos && findIt != itEnd ) {
 
                            itEnd = findIt+1;
 
                            itNext = findIt+1;
 
                            foundWrapPoint = true;
 
                        }
 
                        else if( findIt > it && wrappableBeforeChars.find( *findIt ) != std::string::npos ) {
 
                            itEnd = findIt;
 
                            itNext = findIt;
 
                            foundWrapPoint = true;
 
                        }
 
                        else if( wrappableInsteadOfChars.find( *findIt ) != std::string::npos ) {
 
                            itNext = findIt+1;
 
                            itEnd = findIt;
 
                            foundWrapPoint = true;
 
                        }
 
                        if( findIt == it )
 
                            break;
 
                        else
 
                            --findIt;
 
                    }
 
                    while( !foundWrapPoint );
 

	
 
                    if( !foundWrapPoint ) {
 
                        // No good wrap char, so we'll break mid word and add a hyphen
 
                        --itEnd;
 
                        itNext = itEnd;
 
                        suffix = "-";
 
                    }
 
                    else {
 
                        while( itEnd > it && wrappableInsteadOfChars.find( *(itEnd-1) ) != std::string::npos )
 
                            --itEnd;
 
                    }
 
                }
 
                lines.push_back( indent + std::string( it, itEnd ) + suffix );
 

	
 
                if( indent.size() != _attr.indent )
 
                    indent = std::string( _attr.indent, ' ' );
 
                it = itNext;
 
            }
 
        }
 

	
 
        typedef std::vector<std::string>::const_iterator const_iterator;
 

	
 
        const_iterator begin() const { return lines.begin(); }
 
        const_iterator end() const { return lines.end(); }
 
        std::string const& last() const { return lines.back(); }
 
        std::size_t size() const { return lines.size(); }
 
        std::string const& operator[]( std::size_t _index ) const { return lines[_index]; }
 
        std::string toString() const {
 
            std::ostringstream oss;
 
            oss << *this;
 
            return oss.str();
 
        }
 

	
 
        inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) {
 
            for( Text::const_iterator it = _text.begin(), itEnd = _text.end();
 
                it != itEnd; ++it ) {
 
                if( it != _text.begin() )
 
                    _stream << "\n";
 
                _stream << *it;
 
            }
 
            return _stream;
 
        }
 

	
 
    private:
 
        std::string str;
 
        TextAttributes attr;
 
        std::vector<std::string> lines;
 
    };
 

	
 
} // end namespace Tbc
 

	
 
#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE
 
} // end outer namespace
 
#endif
 

	
 
#endif // TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED
 
#undef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE
 

	
 
namespace Catch {
 
    using Tbc::Text;
 
    using Tbc::TextAttributes;
 
}
 

	
 
// #included from: catch_console_colour.hpp
 
#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_HPP_INCLUDED
 

	
 
namespace Catch {
 

	
 
    struct Colour {
 
        enum Code {
 
            None = 0,
 

	
 
            White,
 
            Red,
 
            Green,
 
            Blue,
 
            Cyan,
 
            Yellow,
 
            Grey,
 

	
 
            Bright = 0x10,
 

	
 
            BrightRed = Bright | Red,
 
            BrightGreen = Bright | Green,
 
            LightGrey = Bright | Grey,
 
            BrightWhite = Bright | White,
 

	
 
            // By intention
 
            FileName = LightGrey,
 
            Warning = Yellow,
 
            ResultError = BrightRed,
 
            ResultSuccess = BrightGreen,
 
            ResultExpectedFailure = Warning,
 

	
 
            Error = BrightRed,
 
            Success = Green,
 

	
 
            OriginalExpression = Cyan,
 
            ReconstructedExpression = Yellow,
 

	
 
            SecondaryText = LightGrey,
 
            Headers = White
 
        };
 

	
 
        // Use constructed object for RAII guard
 
        Colour( Code _colourCode );
 
        Colour( Colour const& other );
 
        ~Colour();
 

	
 
        // Use static method for one-shot changes
 
        static void use( Code _colourCode );
 

	
 
    private:
 
        bool m_moved;
 
    };
 

	
 
    inline std::ostream& operator << ( std::ostream& os, Colour const& ) { return os; }
 

	
 
} // end namespace Catch
 

	
 
// #included from: catch_interfaces_reporter.h
 
#define TWOBLUECUBES_CATCH_INTERFACES_REPORTER_H_INCLUDED
 

	
 
#include <string>
 
#include <ostream>
 
#include <map>
 

	
 
namespace Catch
 
{
 
    struct ReporterConfig {
 
        explicit ReporterConfig( Ptr<IConfig const> const& _fullConfig )
 
        :   m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {}
 

	
 
        ReporterConfig( Ptr<IConfig const> const& _fullConfig, std::ostream& _stream )
 
        :   m_stream( &_stream ), m_fullConfig( _fullConfig ) {}
 

	
 
        std::ostream& stream() const    { return *m_stream; }
 
        Ptr<IConfig const> fullConfig() const { return m_fullConfig; }
 

	
 
    private:
 
        std::ostream* m_stream;
 
        Ptr<IConfig const> m_fullConfig;
 
    };
 

	
 
    struct ReporterPreferences {
 
        ReporterPreferences()
 
        : shouldRedirectStdOut( false )
 
        {}
 

	
 
        bool shouldRedirectStdOut;
 
    };
 

	
 
    template<typename T>
 
    struct LazyStat : Option<T> {
 
        LazyStat() : used( false ) {}
 
        LazyStat& operator=( T const& _value ) {
 
            Option<T>::operator=( _value );
 
            used = false;
 
            return *this;
 
        }
 
        void reset() {
 
            Option<T>::reset();
 
            used = false;
 
        }
 
        bool used;
 
    };
 

	
 
    struct TestRunInfo {
 
        TestRunInfo( std::string const& _name ) : name( _name ) {}
 
        std::string name;
 
    };
 
    struct GroupInfo {
 
        GroupInfo(  std::string const& _name,
 
                    std::size_t _groupIndex,
 
                    std::size_t _groupsCount )
 
        :   name( _name ),
 
            groupIndex( _groupIndex ),
 
            groupsCounts( _groupsCount )
 
        {}
 

	
 
        std::string name;
 
        std::size_t groupIndex;
 
        std::size_t groupsCounts;
 
    };
 

	
 
    struct AssertionStats {
 
        AssertionStats( AssertionResult const& _assertionResult,
 
                        std::vector<MessageInfo> const& _infoMessages,
 
                        Totals const& _totals )
 
        :   assertionResult( _assertionResult ),
 
            infoMessages( _infoMessages ),
 
            totals( _totals )
 
        {
 
            if( assertionResult.hasMessage() ) {
 
                // Copy message into messages list.
 
                // !TBD This should have been done earlier, somewhere
 
                MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() );
 
                builder << assertionResult.getMessage();
 
                builder.m_info.message = builder.m_stream.str();
 

	
 
                infoMessages.push_back( builder.m_info );
 
            }
 
        }
 
        virtual ~AssertionStats();
 

	
 
#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
 
        AssertionStats( AssertionStats const& )              = default;
 
        AssertionStats( AssertionStats && )                  = default;
 
        AssertionStats& operator = ( AssertionStats const& ) = default;
 
        AssertionStats& operator = ( AssertionStats && )     = default;
 
#  endif
 

	
 
        AssertionResult assertionResult;
 
        std::vector<MessageInfo> infoMessages;
 
        Totals totals;
 
    };
 

	
 
    struct SectionStats {
 
        SectionStats(   SectionInfo const& _sectionInfo,
 
                        Counts const& _assertions,
 
                        double _durationInSeconds,
 
                        bool _missingAssertions )
 
        :   sectionInfo( _sectionInfo ),
 
            assertions( _assertions ),
 
            durationInSeconds( _durationInSeconds ),
 
            missingAssertions( _missingAssertions )
 
        {}
 
        virtual ~SectionStats();
 
#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
 
        SectionStats( SectionStats const& )              = default;
 
        SectionStats( SectionStats && )                  = default;
 
        SectionStats& operator = ( SectionStats const& ) = default;
 
        SectionStats& operator = ( SectionStats && )     = default;
 
#  endif
 

	
 
        SectionInfo sectionInfo;
 
        Counts assertions;
 
        double durationInSeconds;
 
        bool missingAssertions;
 
    };
 

	
 
    struct TestCaseStats {
 
        TestCaseStats(  TestCaseInfo const& _testInfo,
 
                        Totals const& _totals,
 
                        std::string const& _stdOut,
 
                        std::string const& _stdErr,
 
                        bool _aborting )
 
        : testInfo( _testInfo ),
 
            totals( _totals ),
 
            stdOut( _stdOut ),
 
            stdErr( _stdErr ),
 
            aborting( _aborting )
 
        {}
 
        virtual ~TestCaseStats();
 

	
 
#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
 
        TestCaseStats( TestCaseStats const& )              = default;
 
        TestCaseStats( TestCaseStats && )                  = default;
 
        TestCaseStats& operator = ( TestCaseStats const& ) = default;
 
        TestCaseStats& operator = ( TestCaseStats && )     = default;
 
#  endif
 

	
 
        TestCaseInfo testInfo;
 
        Totals totals;
 
        std::string stdOut;
 
        std::string stdErr;
 
        bool aborting;
 
    };
 

	
 
    struct TestGroupStats {
 
        TestGroupStats( GroupInfo const& _groupInfo,
 
                        Totals const& _totals,
 
                        bool _aborting )
 
        :   groupInfo( _groupInfo ),
 
            totals( _totals ),
 
            aborting( _aborting )
 
        {}
 
        TestGroupStats( GroupInfo const& _groupInfo )
 
        :   groupInfo( _groupInfo ),
 
            aborting( false )
 
        {}
 
        virtual ~TestGroupStats();
 

	
 
#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
 
        TestGroupStats( TestGroupStats const& )              = default;
 
        TestGroupStats( TestGroupStats && )                  = default;
 
        TestGroupStats& operator = ( TestGroupStats const& ) = default;
 
        TestGroupStats& operator = ( TestGroupStats && )     = default;
 
#  endif
 

	
 
        GroupInfo groupInfo;
 
        Totals totals;
 
        bool aborting;
 
    };
 

	
 
    struct TestRunStats {
 
        TestRunStats(   TestRunInfo const& _runInfo,
 
                        Totals const& _totals,
 
                        bool _aborting )
 
        :   runInfo( _runInfo ),
 
            totals( _totals ),
 
            aborting( _aborting )
 
        {}
 
        virtual ~TestRunStats();
 

	
 
#  ifndef CATCH_CONFIG_CPP11_GENERATED_METHODS
 
        TestRunStats( TestRunStats const& _other )
 
        :   runInfo( _other.runInfo ),
 
            totals( _other.totals ),
 
            aborting( _other.aborting )
 
        {}
 
#  else
 
        TestRunStats( TestRunStats const& )              = default;
 
        TestRunStats( TestRunStats && )                  = default;
 
        TestRunStats& operator = ( TestRunStats const& ) = default;
 
        TestRunStats& operator = ( TestRunStats && )     = default;
 
#  endif
 

	
 
        TestRunInfo runInfo;
 
        Totals totals;
 
        bool aborting;
 
    };
 

	
 
    class MultipleReporters;
 

	
 
    struct IStreamingReporter : IShared {
 
        virtual ~IStreamingReporter();
 

	
 
        // Implementing class must also provide the following static method:
 
        // static std::string getDescription();
 

	
 
        virtual ReporterPreferences getPreferences() const = 0;
 

	
 
        virtual void noMatchingTestCases( std::string const& spec ) = 0;
 

	
 
        virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0;
 
        virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0;
 

	
 
        virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0;
 
        virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0;
 

	
 
        virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0;
 

	
 
        // The return value indicates if the messages buffer should be cleared:
 
        virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0;
 

	
 
        virtual void sectionEnded( SectionStats const& sectionStats ) = 0;
 
        virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0;
 
        virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0;
 
        virtual void testRunEnded( TestRunStats const& testRunStats ) = 0;
 

	
 
        virtual void skipTest( TestCaseInfo const& testInfo ) = 0;
 

	
 
        virtual MultipleReporters* tryAsMulti() { return CATCH_NULL; }
 
    };
 

	
 
    struct IReporterFactory : IShared {
 
        virtual ~IReporterFactory();
 
        virtual IStreamingReporter* create( ReporterConfig const& config ) const = 0;
 
        virtual std::string getDescription() const = 0;
 
    };
 

	
 
    struct IReporterRegistry {
 
        typedef std::map<std::string, Ptr<IReporterFactory> > FactoryMap;
 
        typedef std::vector<Ptr<IReporterFactory> > Listeners;
 

	
 
        virtual ~IReporterRegistry();
 
        virtual IStreamingReporter* create( std::string const& name, Ptr<IConfig const> const& config ) const = 0;
 
        virtual FactoryMap const& getFactories() const = 0;
 
        virtual Listeners const& getListeners() const = 0;
 
    };
 

	
 
    Ptr<IStreamingReporter> addReporter( Ptr<IStreamingReporter> const& existingReporter, Ptr<IStreamingReporter> const& additionalReporter );
 

	
 
}
 

	
 
#include <limits>
 
#include <algorithm>
 

	
 
namespace Catch {
 

	
 
    inline std::size_t listTests( Config const& config ) {
 

	
 
        TestSpec testSpec = config.testSpec();
 
        if( config.testSpec().hasFilters() )
 
            Catch::cout() << "Matching test cases:\n";
 
        else {
 
            Catch::cout() << "All available test cases:\n";
 
            testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec();
 
        }
 

	
 
        std::size_t matchedTests = 0;
 
        TextAttributes nameAttr, descAttr, tagsAttr;
 
        nameAttr.setInitialIndent( 2 ).setIndent( 4 );
 
        descAttr.setIndent( 4 );
 
        tagsAttr.setIndent( 6 );
 

	
 
        std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config );
 
        for( std::vector<TestCase>::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end();
 
                it != itEnd;
 
                ++it ) {
 
            matchedTests++;
 
            TestCaseInfo const& testCaseInfo = it->getTestCaseInfo();
 
            Colour::Code colour = testCaseInfo.isHidden()
 
                ? Colour::SecondaryText
 
                : Colour::None;
 
            Colour colourGuard( colour );
 

	
 
            Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl;
 
            if( config.listExtraInfo() ) {
 
                Catch::cout() << "    " << testCaseInfo.lineInfo << std::endl;
 
                std::string description = testCaseInfo.description;
 
                if( description.empty() )
 
                    description = "(NO DESCRIPTION)";
 
                Catch::cout() << Text( description, descAttr ) << std::endl;
 
            }
 
            if( !testCaseInfo.tags.empty() )
 
                Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl;
 
        }
 

	
 
        if( !config.testSpec().hasFilters() )
 
            Catch::cout() << pluralise( matchedTests, "test case" ) << '\n' << std::endl;
 
        else
 
            Catch::cout() << pluralise( matchedTests, "matching test case" ) << '\n' << std::endl;
 
        return matchedTests;
 
    }
 

	
 
    inline std::size_t listTestsNamesOnly( Config const& config ) {
 
        TestSpec testSpec = config.testSpec();
 
        if( !config.testSpec().hasFilters() )
 
            testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec();
 
        std::size_t matchedTests = 0;
 
        std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config );
 
        for( std::vector<TestCase>::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end();
 
                it != itEnd;
 
                ++it ) {
 
            matchedTests++;
 
            TestCaseInfo const& testCaseInfo = it->getTestCaseInfo();
 
            if( startsWith( testCaseInfo.name, '#' ) )
 
               Catch::cout() << '"' << testCaseInfo.name << '"';
 
            else
 
               Catch::cout() << testCaseInfo.name;
 
            if ( config.listExtraInfo() )
 
                Catch::cout() << "\t@" << testCaseInfo.lineInfo;
 
            Catch::cout() << std::endl;
 
        }
 
        return matchedTests;
 
    }
 

	
 
    struct TagInfo {
 
        TagInfo() : count ( 0 ) {}
 
        void add( std::string const& spelling ) {
 
            ++count;
 
            spellings.insert( spelling );
 
        }
 
        std::string all() const {
 
            std::string out;
 
            for( std::set<std::string>::const_iterator it = spellings.begin(), itEnd = spellings.end();
 
                        it != itEnd;
 
                        ++it )
 
                out += "[" + *it + "]";
 
            return out;
 
        }
 
        std::set<std::string> spellings;
 
        std::size_t count;
 
    };
 

	
 
    inline std::size_t listTags( Config const& config ) {
 
        TestSpec testSpec = config.testSpec();
 
        if( config.testSpec().hasFilters() )
 
            Catch::cout() << "Tags for matching test cases:\n";
 
        else {
 
            Catch::cout() << "All available tags:\n";
 
            testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec();
 
        }
 

	
 
        std::map<std::string, TagInfo> tagCounts;
 

	
 
        std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config );
 
        for( std::vector<TestCase>::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end();
 
                it != itEnd;
 
                ++it ) {
 
            for( std::set<std::string>::const_iterator  tagIt = it->getTestCaseInfo().tags.begin(),
 
                                                        tagItEnd = it->getTestCaseInfo().tags.end();
 
                    tagIt != tagItEnd;
 
                    ++tagIt ) {
 
                std::string tagName = *tagIt;
 
                std::string lcaseTagName = toLower( tagName );
 
                std::map<std::string, TagInfo>::iterator countIt = tagCounts.find( lcaseTagName );
 
                if( countIt == tagCounts.end() )
 
                    countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first;
 
                countIt->second.add( tagName );
 
            }
 
        }
 

	
 
        for( std::map<std::string, TagInfo>::const_iterator countIt = tagCounts.begin(),
 
                                                            countItEnd = tagCounts.end();
 
                countIt != countItEnd;
 
                ++countIt ) {
 
            std::ostringstream oss;
 
            oss << "  " << std::setw(2) << countIt->second.count << "  ";
 
            Text wrapper( countIt->second.all(), TextAttributes()
 
                                                    .setInitialIndent( 0 )
 
                                                    .setIndent( oss.str().size() )
 
                                                    .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) );
 
            Catch::cout() << oss.str() << wrapper << '\n';
 
        }
 
        Catch::cout() << pluralise( tagCounts.size(), "tag" ) << '\n' << std::endl;
 
        return tagCounts.size();
 
    }
 

	
 
    inline std::size_t listReporters( Config const& /*config*/ ) {
 
        Catch::cout() << "Available reporters:\n";
 
        IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories();
 
        IReporterRegistry::FactoryMap::const_iterator itBegin = factories.begin(), itEnd = factories.end(), it;
 
        std::size_t maxNameLen = 0;
 
        for(it = itBegin; it != itEnd; ++it )
 
            maxNameLen = (std::max)( maxNameLen, it->first.size() );
 

	
 
        for(it = itBegin; it != itEnd; ++it ) {
 
            Text wrapper( it->second->getDescription(), TextAttributes()
 
                                                        .setInitialIndent( 0 )
 
                                                        .setIndent( 7+maxNameLen )
 
                                                        .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) );
 
            Catch::cout() << "  "
 
                    << it->first
 
                    << ':'
 
                    << std::string( maxNameLen - it->first.size() + 2, ' ' )
 
                    << wrapper << '\n';
 
        }
 
        Catch::cout() << std::endl;
 
        return factories.size();
 
    }
 

	
 
    inline Option<std::size_t> list( Config const& config ) {
 
        Option<std::size_t> listedCount;
 
        if( config.listTests() || ( config.listExtraInfo() && !config.listTestNamesOnly() ) )
 
            listedCount = listedCount.valueOr(0) + listTests( config );
 
        if( config.listTestNamesOnly() )
 
            listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config );
 
        if( config.listTags() )
 
            listedCount = listedCount.valueOr(0) + listTags( config );
 
        if( config.listReporters() )
 
            listedCount = listedCount.valueOr(0) + listReporters( config );
 
        return listedCount;
 
    }
 

	
 
} // end namespace Catch
 

	
 
// #included from: internal/catch_run_context.hpp
 
#define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED
 

	
 
// #included from: catch_test_case_tracker.hpp
 
#define TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED
 

	
 
#include <algorithm>
 
#include <string>
 
#include <assert.h>
 
#include <vector>
 
#include <stdexcept>
 

	
 
CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS
 

	
 
namespace Catch {
 
namespace TestCaseTracking {
 

	
 
    struct NameAndLocation {
 
        std::string name;
 
        SourceLineInfo location;
 

	
 
        NameAndLocation( std::string const& _name, SourceLineInfo const& _location )
 
        :   name( _name ),
 
            location( _location )
 
        {}
 
    };
 

	
 
    struct ITracker : SharedImpl<> {
 
        virtual ~ITracker();
 

	
 
        // static queries
 
        virtual NameAndLocation const& nameAndLocation() const = 0;
 

	
 
        // dynamic queries
 
        virtual bool isComplete() const = 0; // Successfully completed or failed
 
        virtual bool isSuccessfullyCompleted() const = 0;
 
        virtual bool isOpen() const = 0; // Started but not complete
 
        virtual bool hasChildren() const = 0;
 

	
 
        virtual ITracker& parent() = 0;
 

	
 
        // actions
 
        virtual void close() = 0; // Successfully complete
 
        virtual void fail() = 0;
 
        virtual void markAsNeedingAnotherRun() = 0;
 

	
 
        virtual void addChild( Ptr<ITracker> const& child ) = 0;
 
        virtual ITracker* findChild( NameAndLocation const& nameAndLocation ) = 0;
 
        virtual void openChild() = 0;
 

	
 
        // Debug/ checking
 
        virtual bool isSectionTracker() const = 0;
 
        virtual bool isIndexTracker() const = 0;
 
    };
 

	
 
    class  TrackerContext {
 

	
 
        enum RunState {
 
            NotStarted,
 
            Executing,
 
            CompletedCycle
 
        };
 

	
 
        Ptr<ITracker> m_rootTracker;
 
        ITracker* m_currentTracker;
 
        RunState m_runState;
 

	
 
    public:
 

	
 
        static TrackerContext& instance() {
 
            static TrackerContext s_instance;
 
            return s_instance;
 
        }
 

	
 
        TrackerContext()
 
        :   m_currentTracker( CATCH_NULL ),
 
            m_runState( NotStarted )
 
        {}
 

	
 
        ITracker& startRun();
 

	
 
        void endRun() {
 
            m_rootTracker.reset();
 
            m_currentTracker = CATCH_NULL;
 
            m_runState = NotStarted;
 
        }
 

	
 
        void startCycle() {
 
            m_currentTracker = m_rootTracker.get();
 
            m_runState = Executing;
 
        }
 
        void completeCycle() {
 
            m_runState = CompletedCycle;
 
        }
 

	
 
        bool completedCycle() const {
 
            return m_runState == CompletedCycle;
 
        }
 
        ITracker& currentTracker() {
 
            return *m_currentTracker;
 
        }
 
        void setCurrentTracker( ITracker* tracker ) {
 
            m_currentTracker = tracker;
 
        }
 
    };
 

	
 
    class TrackerBase : public ITracker {
 
    protected:
 
        enum CycleState {
 
            NotStarted,
 
            Executing,
 
            ExecutingChildren,
 
            NeedsAnotherRun,
 
            CompletedSuccessfully,
 
            Failed
 
        };
 
        class TrackerHasName {
 
            NameAndLocation m_nameAndLocation;
 
        public:
 
            TrackerHasName( NameAndLocation const& nameAndLocation ) : m_nameAndLocation( nameAndLocation ) {}
 
            bool operator ()( Ptr<ITracker> const& tracker ) {
 
                return
 
                    tracker->nameAndLocation().name == m_nameAndLocation.name &&
 
                    tracker->nameAndLocation().location == m_nameAndLocation.location;
 
            }
 
        };
 
        typedef std::vector<Ptr<ITracker> > Children;
 
        NameAndLocation m_nameAndLocation;
 
        TrackerContext& m_ctx;
 
        ITracker* m_parent;
 
        Children m_children;
 
        CycleState m_runState;
 
    public:
 
        TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent )
 
        :   m_nameAndLocation( nameAndLocation ),
 
            m_ctx( ctx ),
 
            m_parent( parent ),
 
            m_runState( NotStarted )
 
        {}
 
        virtual ~TrackerBase();
 

	
 
        virtual NameAndLocation const& nameAndLocation() const CATCH_OVERRIDE {
 
            return m_nameAndLocation;
 
        }
 
        virtual bool isComplete() const CATCH_OVERRIDE {
 
            return m_runState == CompletedSuccessfully || m_runState == Failed;
 
        }
 
        virtual bool isSuccessfullyCompleted() const CATCH_OVERRIDE {
 
            return m_runState == CompletedSuccessfully;
 
        }
 
        virtual bool isOpen() const CATCH_OVERRIDE {
 
            return m_runState != NotStarted && !isComplete();
 
        }
 
        virtual bool hasChildren() const CATCH_OVERRIDE {
 
            return !m_children.empty();
 
        }
 

	
 
        virtual void addChild( Ptr<ITracker> const& child ) CATCH_OVERRIDE {
 
            m_children.push_back( child );
 
        }
 

	
 
        virtual ITracker* findChild( NameAndLocation const& nameAndLocation ) CATCH_OVERRIDE {
 
            Children::const_iterator it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( nameAndLocation ) );
 
            return( it != m_children.end() )
 
                ? it->get()
 
                : CATCH_NULL;
 
        }
 
        virtual ITracker& parent() CATCH_OVERRIDE {
 
            assert( m_parent ); // Should always be non-null except for root
 
            return *m_parent;
 
        }
 

	
 
        virtual void openChild() CATCH_OVERRIDE {
 
            if( m_runState != ExecutingChildren ) {
 
                m_runState = ExecutingChildren;
 
                if( m_parent )
 
                    m_parent->openChild();
 
            }
 
        }
 

	
 
        virtual bool isSectionTracker() const CATCH_OVERRIDE { return false; }
 
        virtual bool isIndexTracker() const CATCH_OVERRIDE { return false; }
 

	
 
        void open() {
 
            m_runState = Executing;
 
            moveToThis();
 
            if( m_parent )
 
                m_parent->openChild();
 
        }
 

	
 
        virtual void close() CATCH_OVERRIDE {
 

	
 
            // Close any still open children (e.g. generators)
 
            while( &m_ctx.currentTracker() != this )
 
                m_ctx.currentTracker().close();
 

	
 
            switch( m_runState ) {
 
                case NotStarted:
 
                case CompletedSuccessfully:
 
                case Failed:
 
                    throw std::logic_error( "Illogical state" );
 

	
 
                case NeedsAnotherRun:
 
                    break;;
 

	
 
                case Executing:
 
                    m_runState = CompletedSuccessfully;
 
                    break;
 
                case ExecutingChildren:
 
                    if( m_children.empty() || m_children.back()->isComplete() )
 
                        m_runState = CompletedSuccessfully;
 
                    break;
 

	
 
                default:
 
                    throw std::logic_error( "Unexpected state" );
 
            }
 
            moveToParent();
 
            m_ctx.completeCycle();
 
        }
 
        virtual void fail() CATCH_OVERRIDE {
 
            m_runState = Failed;
 
            if( m_parent )
 
                m_parent->markAsNeedingAnotherRun();
 
            moveToParent();
 
            m_ctx.completeCycle();
 
        }
 
        virtual void markAsNeedingAnotherRun() CATCH_OVERRIDE {
 
            m_runState = NeedsAnotherRun;
 
        }
 
    private:
 
        void moveToParent() {
 
            assert( m_parent );
 
            m_ctx.setCurrentTracker( m_parent );
 
        }
 
        void moveToThis() {
 
            m_ctx.setCurrentTracker( this );
 
        }
 
    };
 

	
 
    class SectionTracker : public TrackerBase {
 
        std::vector<std::string> m_filters;
 
    public:
 
        SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent )
 
        :   TrackerBase( nameAndLocation, ctx, parent )
 
        {
 
            if( parent ) {
 
                while( !parent->isSectionTracker() )
 
                    parent = &parent->parent();
 

	
 
                SectionTracker& parentSection = static_cast<SectionTracker&>( *parent );
 
                addNextFilters( parentSection.m_filters );
 
            }
 
        }
 
        virtual ~SectionTracker();
 

	
 
        virtual bool isSectionTracker() const CATCH_OVERRIDE { return true; }
 

	
 
        static SectionTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) {
 
            SectionTracker* section = CATCH_NULL;
 

	
 
            ITracker& currentTracker = ctx.currentTracker();
 
            if( ITracker* childTracker = currentTracker.findChild( nameAndLocation ) ) {
 
                assert( childTracker );
 
                assert( childTracker->isSectionTracker() );
 
                section = static_cast<SectionTracker*>( childTracker );
 
            }
 
            else {
 
                section = new SectionTracker( nameAndLocation, ctx, &currentTracker );
 
                currentTracker.addChild( section );
 
            }
 
            if( !ctx.completedCycle() )
 
                section->tryOpen();
 
            return *section;
 
        }
 

	
 
        void tryOpen() {
 
            if( !isComplete() && (m_filters.empty() || m_filters[0].empty() ||  m_filters[0] == m_nameAndLocation.name ) )
 
                open();
 
        }
 

	
 
        void addInitialFilters( std::vector<std::string> const& filters ) {
 
            if( !filters.empty() ) {
 
                m_filters.push_back(""); // Root - should never be consulted
 
                m_filters.push_back(""); // Test Case - not a section filter
 
                m_filters.insert( m_filters.end(), filters.begin(), filters.end() );
 
            }
 
        }
 
        void addNextFilters( std::vector<std::string> const& filters ) {
 
            if( filters.size() > 1 )
 
                m_filters.insert( m_filters.end(), ++filters.begin(), filters.end() );
 
        }
 
    };
 

	
 
    class IndexTracker : public TrackerBase {
 
        int m_size;
 
        int m_index;
 
    public:
 
        IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size )
 
        :   TrackerBase( nameAndLocation, ctx, parent ),
 
            m_size( size ),
 
            m_index( -1 )
 
        {}
 
        virtual ~IndexTracker();
 

	
 
        virtual bool isIndexTracker() const CATCH_OVERRIDE { return true; }
 

	
 
        static IndexTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ) {
 
            IndexTracker* tracker = CATCH_NULL;
 

	
 
            ITracker& currentTracker = ctx.currentTracker();
 
            if( ITracker* childTracker = currentTracker.findChild( nameAndLocation ) ) {
 
                assert( childTracker );
 
                assert( childTracker->isIndexTracker() );
 
                tracker = static_cast<IndexTracker*>( childTracker );
 
            }
 
            else {
 
                tracker = new IndexTracker( nameAndLocation, ctx, &currentTracker, size );
 
                currentTracker.addChild( tracker );
 
            }
 

	
 
            if( !ctx.completedCycle() && !tracker->isComplete() ) {
 
                if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun )
 
                    tracker->moveNext();
 
                tracker->open();
 
            }
 

	
 
            return *tracker;
 
        }
 

	
 
        int index() const { return m_index; }
 

	
 
        void moveNext() {
 
            m_index++;
 
            m_children.clear();
 
        }
 

	
 
        virtual void close() CATCH_OVERRIDE {
 
            TrackerBase::close();
 
            if( m_runState == CompletedSuccessfully && m_index < m_size-1 )
 
                m_runState = Executing;
 
        }
 
    };
 

	
 
    inline ITracker& TrackerContext::startRun() {
 
        m_rootTracker = new SectionTracker( NameAndLocation( "{root}", CATCH_INTERNAL_LINEINFO ), *this, CATCH_NULL );
 
        m_currentTracker = CATCH_NULL;
 
        m_runState = Executing;
 
        return *m_rootTracker;
 
    }
 

	
 
} // namespace TestCaseTracking
 

	
 
using TestCaseTracking::ITracker;
 
using TestCaseTracking::TrackerContext;
 
using TestCaseTracking::SectionTracker;
 
using TestCaseTracking::IndexTracker;
 

	
 
} // namespace Catch
 

	
 
CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS
 

	
 
// #included from: catch_fatal_condition.hpp
 
#define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED
 

	
 
namespace Catch {
 

	
 
    // Report the error condition
 
    inline void reportFatal( std::string const& message ) {
 
        IContext& context = Catch::getCurrentContext();
 
        IResultCapture* resultCapture = context.getResultCapture();
 
        resultCapture->handleFatalErrorCondition( message );
 
    }
 

	
 
} // namespace Catch
 

	
 
#if defined ( CATCH_PLATFORM_WINDOWS ) /////////////////////////////////////////
 
// #included from: catch_windows_h_proxy.h
 

	
 
#define TWOBLUECUBES_CATCH_WINDOWS_H_PROXY_H_INCLUDED
 

	
 
#ifdef CATCH_DEFINES_NOMINMAX
 
#  define NOMINMAX
 
#endif
 
#ifdef CATCH_DEFINES_WIN32_LEAN_AND_MEAN
 
#  define WIN32_LEAN_AND_MEAN
 
#endif
 

	
 
#ifdef __AFXDLL
 
#include <AfxWin.h>
 
#else
 
#include <windows.h>
 
#endif
 

	
 
#ifdef CATCH_DEFINES_NOMINMAX
 
#  undef NOMINMAX
 
#endif
 
#ifdef CATCH_DEFINES_WIN32_LEAN_AND_MEAN
 
#  undef WIN32_LEAN_AND_MEAN
 
#endif
 

	
 

	
 
#  if !defined ( CATCH_CONFIG_WINDOWS_SEH )
 

	
 
namespace Catch {
 
    struct FatalConditionHandler {
 
        void reset() {}
 
    };
 
}
 

	
 
#  else // CATCH_CONFIG_WINDOWS_SEH is defined
 

	
 
namespace Catch {
 

	
 
    struct SignalDefs { DWORD id; const char* name; };
 
    extern SignalDefs signalDefs[];
 
    // There is no 1-1 mapping between signals and windows exceptions.
 
    // Windows can easily distinguish between SO and SigSegV,
 
    // but SigInt, SigTerm, etc are handled differently.
 
    SignalDefs signalDefs[] = {
 
        { EXCEPTION_ILLEGAL_INSTRUCTION,  "SIGILL - Illegal instruction signal" },
 
        { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" },
 
        { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" },
 
        { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" },
 
    };
 

	
 
    struct FatalConditionHandler {
 

	
 
        static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) {
 
            for (int i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) {
 
                if (ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) {
 
                    reportFatal(signalDefs[i].name);
 
                }
 
            }
 
            // If its not an exception we care about, pass it along.
 
            // This stops us from eating debugger breaks etc.
 
            return EXCEPTION_CONTINUE_SEARCH;
 
        }
 

	
 
        FatalConditionHandler() {
 
            isSet = true;
 
            // 32k seems enough for Catch to handle stack overflow,
 
            // but the value was found experimentally, so there is no strong guarantee
 
            guaranteeSize = 32 * 1024;
 
            exceptionHandlerHandle = CATCH_NULL;
 
            // Register as first handler in current chain
 
            exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException);
 
            // Pass in guarantee size to be filled
 
            SetThreadStackGuarantee(&guaranteeSize);
 
        }
 

	
 
        static void reset() {
 
            if (isSet) {
 
                // Unregister handler and restore the old guarantee
 
                RemoveVectoredExceptionHandler(exceptionHandlerHandle);
 
                SetThreadStackGuarantee(&guaranteeSize);
 
                exceptionHandlerHandle = CATCH_NULL;
 
                isSet = false;
 
            }
 
        }
 

	
 
        ~FatalConditionHandler() {
 
            reset();
 
        }
 
    private:
 
        static bool isSet;
 
        static ULONG guaranteeSize;
 
        static PVOID exceptionHandlerHandle;
 
    };
 

	
 
    bool FatalConditionHandler::isSet = false;
 
    ULONG FatalConditionHandler::guaranteeSize = 0;
 
    PVOID FatalConditionHandler::exceptionHandlerHandle = CATCH_NULL;
 

	
 
} // namespace Catch
 

	
 
#  endif // CATCH_CONFIG_WINDOWS_SEH
 

	
 
#else // Not Windows - assumed to be POSIX compatible //////////////////////////
 

	
 
#  if !defined(CATCH_CONFIG_POSIX_SIGNALS)
 

	
 
namespace Catch {
 
    struct FatalConditionHandler {
 
        void reset() {}
 
    };
 
}
 

	
 
#  else // CATCH_CONFIG_POSIX_SIGNALS is defined
 

	
 
#include <signal.h>
 

	
 
namespace Catch {
 

	
 
    struct SignalDefs {
 
        int id;
 
        const char* name;
 
    };
 
    extern SignalDefs signalDefs[];
 
    SignalDefs signalDefs[] = {
 
            { SIGINT,  "SIGINT - Terminal interrupt signal" },
 
            { SIGILL,  "SIGILL - Illegal instruction signal" },
 
            { SIGFPE,  "SIGFPE - Floating point error signal" },
 
            { SIGSEGV, "SIGSEGV - Segmentation violation signal" },
 
            { SIGTERM, "SIGTERM - Termination request signal" },
 
            { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" }
 
    };
 

	
 
    struct FatalConditionHandler {
 

	
 
        static bool isSet;
 
        static struct sigaction oldSigActions [sizeof(signalDefs)/sizeof(SignalDefs)];
 
        static stack_t oldSigStack;
 
        static char altStackMem[SIGSTKSZ];
 

	
 
        static void handleSignal( int sig ) {
 
            std::string name = "<unknown signal>";
 
            for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) {
 
                SignalDefs &def = signalDefs[i];
 
                if (sig == def.id) {
 
                    name = def.name;
 
                    break;
 
                }
 
            }
 
            reset();
 
            reportFatal(name);
 
            raise( sig );
 
        }
 

	
 
        FatalConditionHandler() {
 
            isSet = true;
 
            stack_t sigStack;
 
            sigStack.ss_sp = altStackMem;
 
            sigStack.ss_size = SIGSTKSZ;
 
            sigStack.ss_flags = 0;
 
            sigaltstack(&sigStack, &oldSigStack);
 
            struct sigaction sa = { 0 };
 

	
 
            sa.sa_handler = handleSignal;
 
            sa.sa_flags = SA_ONSTACK;
 
            for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) {
 
                sigaction(signalDefs[i].id, &sa, &oldSigActions[i]);
 
            }
 
        }
 

	
 
        ~FatalConditionHandler() {
 
            reset();
 
        }
 
        static void reset() {
 
            if( isSet ) {
 
                // Set signals back to previous values -- hopefully nobody overwrote them in the meantime
 
                for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) {
 
                    sigaction(signalDefs[i].id, &oldSigActions[i], CATCH_NULL);
 
                }
 
                // Return the old stack
 
                sigaltstack(&oldSigStack, CATCH_NULL);
 
                isSet = false;
 
            }
 
        }
 
    };
 

	
 
    bool FatalConditionHandler::isSet = false;
 
    struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {};
 
    stack_t FatalConditionHandler::oldSigStack = {};
 
    char FatalConditionHandler::altStackMem[SIGSTKSZ] = {};
 

	
 
} // namespace Catch
 

	
 
#  endif // CATCH_CONFIG_POSIX_SIGNALS
 

	
 
#endif // not Windows
 

	
 
#include <set>
 
#include <string>
 

	
 
namespace Catch {
 

	
 
    class StreamRedirect {
 

	
 
    public:
 
        StreamRedirect( std::ostream& stream, std::string& targetString )
 
        :   m_stream( stream ),
 
            m_prevBuf( stream.rdbuf() ),
 
            m_targetString( targetString )
 
        {
 
            stream.rdbuf( m_oss.rdbuf() );
 
        }
 

	
 
        ~StreamRedirect() {
 
            m_targetString += m_oss.str();
 
            m_stream.rdbuf( m_prevBuf );
 
        }
 

	
 
    private:
 
        std::ostream& m_stream;
 
        std::streambuf* m_prevBuf;
 
        std::ostringstream m_oss;
 
        std::string& m_targetString;
 
    };
 

	
 
    ///////////////////////////////////////////////////////////////////////////
 

	
 
    class RunContext : public IResultCapture, public IRunner {
 

	
 
        RunContext( RunContext const& );
 
        void operator =( RunContext const& );
 

	
 
    public:
 

	
 
        explicit RunContext( Ptr<IConfig const> const& _config, Ptr<IStreamingReporter> const& reporter )
 
        :   m_runInfo( _config->name() ),
 
            m_context( getCurrentMutableContext() ),
 
            m_activeTestCase( CATCH_NULL ),
 
            m_config( _config ),
 
            m_reporter( reporter ),
 
            m_shouldReportUnexpected ( true )
 
        {
 
            m_context.setRunner( this );
 
            m_context.setConfig( m_config );
 
            m_context.setResultCapture( this );
 
            m_reporter->testRunStarting( m_runInfo );
 
        }
 

	
 
        virtual ~RunContext() {
 
            m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, aborting() ) );
 
        }
 

	
 
        void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) {
 
            m_reporter->testGroupStarting( GroupInfo( testSpec, groupIndex, groupsCount ) );
 
        }
 
        void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ) {
 
            m_reporter->testGroupEnded( TestGroupStats( GroupInfo( testSpec, groupIndex, groupsCount ), totals, aborting() ) );
 
        }
 

	
 
        Totals runTest( TestCase const& testCase ) {
 
            Totals prevTotals = m_totals;
 

	
 
            std::string redirectedCout;
 
            std::string redirectedCerr;
 

	
 
            TestCaseInfo testInfo = testCase.getTestCaseInfo();
 

	
 
            m_reporter->testCaseStarting( testInfo );
 

	
 
            m_activeTestCase = &testCase;
 

	
 
            do {
 
                ITracker& rootTracker = m_trackerContext.startRun();
 
                assert( rootTracker.isSectionTracker() );
 
                static_cast<SectionTracker&>( rootTracker ).addInitialFilters( m_config->getSectionsToRun() );
 
                do {
 
                    m_trackerContext.startCycle();
 
                    m_testCaseTracker = &SectionTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( testInfo.name, testInfo.lineInfo ) );
 
                    runCurrentTest( redirectedCout, redirectedCerr );
 
                }
 
                while( !m_testCaseTracker->isSuccessfullyCompleted() && !aborting() );
 
            }
 
            // !TBD: deprecated - this will be replaced by indexed trackers
 
            while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() );
 

	
 
            Totals deltaTotals = m_totals.delta( prevTotals );
 
            if( testInfo.expectedToFail() && deltaTotals.testCases.passed > 0 ) {
 
                deltaTotals.assertions.failed++;
 
                deltaTotals.testCases.passed--;
 
                deltaTotals.testCases.failed++;
 
            }
 
            m_totals.testCases += deltaTotals.testCases;
 
            m_reporter->testCaseEnded( TestCaseStats(   testInfo,
 
                                                        deltaTotals,
 
                                                        redirectedCout,
 
                                                        redirectedCerr,
 
                                                        aborting() ) );
 

	
 
            m_activeTestCase = CATCH_NULL;
 
            m_testCaseTracker = CATCH_NULL;
 

	
 
            return deltaTotals;
 
        }
 

	
 
        Ptr<IConfig const> config() const {
 
            return m_config;
 
        }
 

	
 
    private: // IResultCapture
 

	
 
        virtual void assertionEnded( AssertionResult const& result ) {
 
            if( result.getResultType() == ResultWas::Ok ) {
 
                m_totals.assertions.passed++;
 
            }
 
            else if( !result.isOk() ) {
 
                m_totals.assertions.failed++;
 
            }
 

	
 
            // We have no use for the return value (whether messages should be cleared), because messages were made scoped
 
            // and should be let to clear themselves out.
 
            static_cast<void>(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals)));
 

	
 
            // Reset working state
 
            m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition );
 
            m_lastResult = result;
 
        }
 

	
 
        virtual bool sectionStarted (
 
            SectionInfo const& sectionInfo,
 
            Counts& assertions
 
        )
 
        {
 
            ITracker& sectionTracker = SectionTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( sectionInfo.name, sectionInfo.lineInfo ) );
 
            if( !sectionTracker.isOpen() )
 
                return false;
 
            m_activeSections.push_back( &sectionTracker );
 

	
 
            m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo;
 

	
 
            m_reporter->sectionStarting( sectionInfo );
 

	
 
            assertions = m_totals.assertions;
 

	
 
            return true;
 
        }
 
        bool testForMissingAssertions( Counts& assertions ) {
 
            if( assertions.total() != 0 )
 
                return false;
 
            if( !m_config->warnAboutMissingAssertions() )
 
                return false;
 
            if( m_trackerContext.currentTracker().hasChildren() )
 
                return false;
 
            m_totals.assertions.failed++;
 
            assertions.failed++;
 
            return true;
 
        }
 

	
 
        virtual void sectionEnded( SectionEndInfo const& endInfo ) {
 
            Counts assertions = m_totals.assertions - endInfo.prevAssertions;
 
            bool missingAssertions = testForMissingAssertions( assertions );
 

	
 
            if( !m_activeSections.empty() ) {
 
                m_activeSections.back()->close();
 
                m_activeSections.pop_back();
 
            }
 

	
 
            m_reporter->sectionEnded( SectionStats( endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions ) );
 
            m_messages.clear();
 
        }
 

	
 
        virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) {
 
            if( m_unfinishedSections.empty() )
 
                m_activeSections.back()->fail();
 
            else
 
                m_activeSections.back()->close();
 
            m_activeSections.pop_back();
 

	
 
            m_unfinishedSections.push_back( endInfo );
 
        }
 

	
 
        virtual void pushScopedMessage( MessageInfo const& message ) {
 
            m_messages.push_back( message );
 
        }
 

	
 
        virtual void popScopedMessage( MessageInfo const& message ) {
 
            m_messages.erase( std::remove( m_messages.begin(), m_messages.end(), message ), m_messages.end() );
 
        }
 

	
 
        virtual std::string getCurrentTestName() const {
 
            return m_activeTestCase
 
                ? m_activeTestCase->getTestCaseInfo().name
 
                : std::string();
 
        }
 

	
 
        virtual const AssertionResult* getLastResult() const {
 
            return &m_lastResult;
 
        }
 

	
 
        virtual void exceptionEarlyReported() {
 
            m_shouldReportUnexpected = false;
 
        }
 

	
 
        virtual void handleFatalErrorCondition( std::string const& message ) {
 
            // Don't rebuild the result -- the stringification itself can cause more fatal errors
 
            // Instead, fake a result data.
 
            AssertionResultData tempResult;
 
            tempResult.resultType = ResultWas::FatalErrorCondition;
 
            tempResult.message = message;
 
            AssertionResult result(m_lastAssertionInfo, tempResult);
 

	
 
            getResultCapture().assertionEnded(result);
 

	
 
            handleUnfinishedSections();
 

	
 
            // Recreate section for test case (as we will lose the one that was in scope)
 
            TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo();
 
            SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description );
 

	
 
            Counts assertions;
 
            assertions.failed = 1;
 
            SectionStats testCaseSectionStats( testCaseSection, assertions, 0, false );
 
            m_reporter->sectionEnded( testCaseSectionStats );
 

	
 
            TestCaseInfo testInfo = m_activeTestCase->getTestCaseInfo();
 

	
 
            Totals deltaTotals;
 
            deltaTotals.testCases.failed = 1;
 
            m_reporter->testCaseEnded( TestCaseStats(   testInfo,
 
                                                        deltaTotals,
 
                                                        std::string(),
 
                                                        std::string(),
 
                                                        false ) );
 
            m_totals.testCases.failed++;
 
            testGroupEnded( std::string(), m_totals, 1, 1 );
 
            m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, false ) );
 
        }
 

	
 
    public:
 
        // !TBD We need to do this another way!
 
        bool aborting() const {
 
            return m_totals.assertions.failed == static_cast<std::size_t>( m_config->abortAfter() );
 
        }
 

	
 
    private:
 

	
 
        void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ) {
 
            TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo();
 
            SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description );
 
            m_reporter->sectionStarting( testCaseSection );
 
            Counts prevAssertions = m_totals.assertions;
 
            double duration = 0;
 
            m_shouldReportUnexpected = true;
 
            try {
 
                m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal );
 

	
 
                seedRng( *m_config );
 

	
 
                Timer timer;
 
                timer.start();
 
                if( m_reporter->getPreferences().shouldRedirectStdOut ) {
 
                    StreamRedirect coutRedir( Catch::cout(), redirectedCout );
 
                    StreamRedirect cerrRedir( Catch::cerr(), redirectedCerr );
 
                    invokeActiveTestCase();
 
                }
 
                else {
 
                    invokeActiveTestCase();
 
                }
 
                duration = timer.getElapsedSeconds();
 
            }
 
            catch( TestFailureException& ) {
 
                // This just means the test was aborted due to failure
 
            }
 
            catch(...) {
 
                // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions
 
                // are reported without translation at the point of origin.
 
                if (m_shouldReportUnexpected) {
 
                    makeUnexpectedResultBuilder().useActiveException();
 
                }
 
            }
 
            m_testCaseTracker->close();
 
            handleUnfinishedSections();
 
            m_messages.clear();
 

	
 
            Counts assertions = m_totals.assertions - prevAssertions;
 
            bool missingAssertions = testForMissingAssertions( assertions );
 

	
 
            if( testCaseInfo.okToFail() ) {
 
                std::swap( assertions.failedButOk, assertions.failed );
 
                m_totals.assertions.failed -= assertions.failedButOk;
 
                m_totals.assertions.failedButOk += assertions.failedButOk;
 
            }
 

	
 
            SectionStats testCaseSectionStats( testCaseSection, assertions, duration, missingAssertions );
 
            m_reporter->sectionEnded( testCaseSectionStats );
 
        }
 

	
 
        void invokeActiveTestCase() {
 
            FatalConditionHandler fatalConditionHandler; // Handle signals
 
            m_activeTestCase->invoke();
 
            fatalConditionHandler.reset();
 
        }
 

	
 
    private:
 

	
 
        ResultBuilder makeUnexpectedResultBuilder() const {
 
            return ResultBuilder(   m_lastAssertionInfo.macroName,
 
                                    m_lastAssertionInfo.lineInfo,
 
                                    m_lastAssertionInfo.capturedExpression,
 
                                    m_lastAssertionInfo.resultDisposition );
 
        }
 

	
 
        void handleUnfinishedSections() {
 
            // If sections ended prematurely due to an exception we stored their
 
            // infos here so we can tear them down outside the unwind process.
 
            for( std::vector<SectionEndInfo>::const_reverse_iterator it = m_unfinishedSections.rbegin(),
 
                        itEnd = m_unfinishedSections.rend();
 
                    it != itEnd;
 
                    ++it )
 
                sectionEnded( *it );
 
            m_unfinishedSections.clear();
 
        }
 

	
 
        TestRunInfo m_runInfo;
 
        IMutableContext& m_context;
 
        TestCase const* m_activeTestCase;
 
        ITracker* m_testCaseTracker;
 
        ITracker* m_currentSectionTracker;
 
        AssertionResult m_lastResult;
 

	
 
        Ptr<IConfig const> m_config;
 
        Totals m_totals;
 
        Ptr<IStreamingReporter> m_reporter;
 
        std::vector<MessageInfo> m_messages;
 
        AssertionInfo m_lastAssertionInfo;
 
        std::vector<SectionEndInfo> m_unfinishedSections;
 
        std::vector<ITracker*> m_activeSections;
 
        TrackerContext m_trackerContext;
 
        bool m_shouldReportUnexpected;
 
    };
 

	
 
    IResultCapture& getResultCapture() {
 
        if( IResultCapture* capture = getCurrentContext().getResultCapture() )
 
            return *capture;
 
        else
 
            throw std::logic_error( "No result capture instance" );
 
    }
 

	
 
} // end namespace Catch
 

	
 
// #included from: internal/catch_version.h
 
#define TWOBLUECUBES_CATCH_VERSION_H_INCLUDED
 

	
 
namespace Catch {
 

	
 
    // Versioning information
 
    struct Version {
 
        Version(    unsigned int _majorVersion,
 
                    unsigned int _minorVersion,
 
                    unsigned int _patchNumber,
 
                    char const * const _branchName,
 
                    unsigned int _buildNumber );
 

	
 
        unsigned int const majorVersion;
 
        unsigned int const minorVersion;
 
        unsigned int const patchNumber;
 

	
 
        // buildNumber is only used if branchName is not null
 
        char const * const branchName;
 
        unsigned int const buildNumber;
 

	
 
        friend std::ostream& operator << ( std::ostream& os, Version const& version );
 

	
 
    private:
 
        void operator=( Version const& );
 
    };
 

	
 
    inline Version libraryVersion();
 
}
 

	
 
#include <fstream>
 
#include <stdlib.h>
 
#include <limits>
 

	
 
namespace Catch {
 

	
 
    Ptr<IStreamingReporter> createReporter( std::string const& reporterName, Ptr<Config> const& config ) {
 
        Ptr<IStreamingReporter> reporter = getRegistryHub().getReporterRegistry().create( reporterName, config.get() );
 
        if( !reporter ) {
 
            std::ostringstream oss;
 
            oss << "No reporter registered with name: '" << reporterName << "'";
 
            throw std::domain_error( oss.str() );
 
        }
 
        return reporter;
 
    }
 

	
 
    Ptr<IStreamingReporter> makeReporter( Ptr<Config> const& config ) {
 
        std::vector<std::string> reporters = config->getReporterNames();
 
        if( reporters.empty() )
 
            reporters.push_back( "console" );
 

	
 
        Ptr<IStreamingReporter> reporter;
 
        for( std::vector<std::string>::const_iterator it = reporters.begin(), itEnd = reporters.end();
 
                it != itEnd;
 
                ++it )
 
            reporter = addReporter( reporter, createReporter( *it, config ) );
 
        return reporter;
 
    }
 
    Ptr<IStreamingReporter> addListeners( Ptr<IConfig const> const& config, Ptr<IStreamingReporter> reporters ) {
 
        IReporterRegistry::Listeners listeners = getRegistryHub().getReporterRegistry().getListeners();
 
        for( IReporterRegistry::Listeners::const_iterator it = listeners.begin(), itEnd = listeners.end();
 
                it != itEnd;
 
                ++it )
 
            reporters = addReporter(reporters, (*it)->create( ReporterConfig( config ) ) );
 
        return reporters;
 
    }
 

	
 
    Totals runTests( Ptr<Config> const& config ) {
 

	
 
        Ptr<IConfig const> iconfig = config.get();
 

	
 
        Ptr<IStreamingReporter> reporter = makeReporter( config );
 
        reporter = addListeners( iconfig, reporter );
 

	
 
        RunContext context( iconfig, reporter );
 

	
 
        Totals totals;
 

	
 
        context.testGroupStarting( config->name(), 1, 1 );
 

	
 
        TestSpec testSpec = config->testSpec();
 
        if( !testSpec.hasFilters() )
 
            testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests
 

	
 
        std::vector<TestCase> const& allTestCases = getAllTestCasesSorted( *iconfig );
 
        for( std::vector<TestCase>::const_iterator it = allTestCases.begin(), itEnd = allTestCases.end();
 
                it != itEnd;
 
                ++it ) {
 
            if( !context.aborting() && matchTest( *it, testSpec, *iconfig ) )
 
                totals += context.runTest( *it );
 
            else
 
                reporter->skipTest( *it );
 
        }
 

	
 
        context.testGroupEnded( iconfig->name(), totals, 1, 1 );
 
        return totals;
 
    }
 

	
 
    void applyFilenamesAsTags( IConfig const& config ) {
 
        std::vector<TestCase> const& tests = getAllTestCasesSorted( config );
 
        for(std::size_t i = 0; i < tests.size(); ++i ) {
 
            TestCase& test = const_cast<TestCase&>( tests[i] );
 
            std::set<std::string> tags = test.tags;
 

	
 
            std::string filename = test.lineInfo.file;
 
            std::string::size_type lastSlash = filename.find_last_of( "\\/" );
 
            if( lastSlash != std::string::npos )
 
                filename = filename.substr( lastSlash+1 );
 

	
 
            std::string::size_type lastDot = filename.find_last_of( "." );
 
            if( lastDot != std::string::npos )
 
                filename = filename.substr( 0, lastDot );
 

	
 
            tags.insert( "#" + filename );
 
            setTags( test, tags );
 
        }
 
    }
 

	
 
    class Session : NonCopyable {
 
        static bool alreadyInstantiated;
 

	
 
    public:
 

	
 
        struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; };
 

	
 
        Session()
 
        : m_cli( makeCommandLineParser() ) {
 
            if( alreadyInstantiated ) {
 
                std::string msg = "Only one instance of Catch::Session can ever be used";
 
                Catch::cerr() << msg << std::endl;
 
                throw std::logic_error( msg );
 
            }
 
            alreadyInstantiated = true;
 
        }
 
        ~Session() {
 
            Catch::cleanUp();
 
        }
 

	
 
        void showHelp( std::string const& processName ) {
 
            Catch::cout() << "\nCatch v" << libraryVersion() << "\n";
 

	
 
            m_cli.usage( Catch::cout(), processName );
 
            Catch::cout() << "For more detail usage please see the project docs\n" << std::endl;
 
        }
 

	
 
        int applyCommandLine( int argc, char const* const* const argv, OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) {
 
            try {
 
                m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail );
 
                m_unusedTokens = m_cli.parseInto( Clara::argsToVector( argc, argv ), m_configData );
 
                if( m_configData.showHelp )
 
                    showHelp( m_configData.processName );
 
                m_config.reset();
 
            }
 
            catch( std::exception& ex ) {
 
                {
 
                    Colour colourGuard( Colour::Red );
 
                    Catch::cerr()
 
                        << "\nError(s) in input:\n"
 
                        << Text( ex.what(), TextAttributes().setIndent(2) )
 
                        << "\n\n";
 
                }
 
                m_cli.usage( Catch::cout(), m_configData.processName );
 
                return (std::numeric_limits<int>::max)();
 
            }
 
            return 0;
 
        }
 

	
 
        void useConfigData( ConfigData const& _configData ) {
 
            m_configData = _configData;
 
            m_config.reset();
 
        }
 

	
 
        int run( int argc, char const* const* const argv ) {
 

	
 
            int returnCode = applyCommandLine( argc, argv );
 
            if( returnCode == 0 )
 
                returnCode = run();
 
            return returnCode;
 
        }
 

	
 
    #if defined(WIN32) && defined(UNICODE)
 
        int run( int argc, wchar_t const* const* const argv ) {
 

	
 
            char **utf8Argv = new char *[ argc ];
 

	
 
            for ( int i = 0; i < argc; ++i ) {
 
                int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL );
 

	
 
                utf8Argv[ i ] = new char[ bufSize ];
 

	
 
                WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL );
 
            }
 

	
 
            int returnCode = applyCommandLine( argc, utf8Argv );
 
            if( returnCode == 0 )
 
                returnCode = run();
 

	
 
            for ( int i = 0; i < argc; ++i )
 
                delete [] utf8Argv[ i ];
 

	
 
            delete [] utf8Argv;
 

	
 
            return returnCode;
 
        }
 
    #endif
 

	
 
        int run() {
 
            if( m_configData.showHelp )
 
                return 0;
 

	
 
            try
 
            {
 
                config(); // Force config to be constructed
 

	
 
                seedRng( *m_config );
 

	
 
                if( m_configData.filenamesAsTags )
 
                    applyFilenamesAsTags( *m_config );
 

	
 
                // Handle list request
 
                if( Option<std::size_t> listed = list( config() ) )
 
                    return static_cast<int>( *listed );
 

	
 
                return static_cast<int>( runTests( m_config ).assertions.failed );
 
            }
 
            catch( std::exception& ex ) {
 
                Catch::cerr() << ex.what() << std::endl;
 
                return (std::numeric_limits<int>::max)();
 
            }
 
        }
 

	
 
        Clara::CommandLine<ConfigData> const& cli() const {
 
            return m_cli;
 
        }
 
        std::vector<Clara::Parser::Token> const& unusedTokens() const {
 
            return m_unusedTokens;
 
        }
 
        ConfigData& configData() {
 
            return m_configData;
 
        }
 
        Config& config() {
 
            if( !m_config )
 
                m_config = new Config( m_configData );
 
            return *m_config;
 
        }
 
    private:
 
        Clara::CommandLine<ConfigData> m_cli;
 
        std::vector<Clara::Parser::Token> m_unusedTokens;
 
        ConfigData m_configData;
 
        Ptr<Config> m_config;
 
    };
 

	
 
    bool Session::alreadyInstantiated = false;
 

	
 
} // end namespace Catch
 

	
 
// #included from: catch_registry_hub.hpp
 
#define TWOBLUECUBES_CATCH_REGISTRY_HUB_HPP_INCLUDED
 

	
 
// #included from: catch_test_case_registry_impl.hpp
 
#define TWOBLUECUBES_CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED
 

	
 
#include <vector>
 
#include <set>
 
#include <sstream>
 
#include <algorithm>
 

	
 
namespace Catch {
 

	
 
    struct RandomNumberGenerator {
 
        typedef std::ptrdiff_t result_type;
 

	
 
        result_type operator()( result_type n ) const { return std::rand() % n; }
 

	
 
#ifdef CATCH_CONFIG_CPP11_SHUFFLE
 
        static constexpr result_type min() { return 0; }
 
        static constexpr result_type max() { return 1000000; }
 
        result_type operator()() const { return std::rand() % max(); }
 
#endif
 
        template<typename V>
 
        static void shuffle( V& vector ) {
 
            RandomNumberGenerator rng;
 
#ifdef CATCH_CONFIG_CPP11_SHUFFLE
 
            std::shuffle( vector.begin(), vector.end(), rng );
 
#else
 
            std::random_shuffle( vector.begin(), vector.end(), rng );
 
#endif
 
        }
 
    };
 

	
 
    inline std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases ) {
 

	
 
        std::vector<TestCase> sorted = unsortedTestCases;
 

	
 
        switch( config.runOrder() ) {
 
            case RunTests::InLexicographicalOrder:
 
                std::sort( sorted.begin(), sorted.end() );
 
                break;
 
            case RunTests::InRandomOrder:
 
                {
 
                    seedRng( config );
 
                    RandomNumberGenerator::shuffle( sorted );
 
                }
 
                break;
 
            case RunTests::InDeclarationOrder:
 
                // already in declaration order
 
                break;
 
        }
 
        return sorted;
 
    }
 
    bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) {
 
        return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() );
 
    }
 

	
 
    void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions ) {
 
        std::set<TestCase> seenFunctions;
 
        for( std::vector<TestCase>::const_iterator it = functions.begin(), itEnd = functions.end();
 
            it != itEnd;
 
            ++it ) {
 
            std::pair<std::set<TestCase>::const_iterator, bool> prev = seenFunctions.insert( *it );
 
            if( !prev.second ) {
 
                std::ostringstream ss;
 

	
 
                ss  << Colour( Colour::Red )
 
                    << "error: TEST_CASE( \"" << it->name << "\" ) already defined.\n"
 
                    << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << '\n'
 
                    << "\tRedefined at " << it->getTestCaseInfo().lineInfo << std::endl;
 

	
 
                throw std::runtime_error(ss.str());
 
            }
 
        }
 
    }
 

	
 
    std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ) {
 
        std::vector<TestCase> filtered;
 
        filtered.reserve( testCases.size() );
 
        for( std::vector<TestCase>::const_iterator it = testCases.begin(), itEnd = testCases.end();
 
                it != itEnd;
 
                ++it )
 
            if( matchTest( *it, testSpec, config ) )
 
                filtered.push_back( *it );
 
        return filtered;
 
    }
 
    std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ) {
 
        return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config );
 
    }
 

	
 
    class TestRegistry : public ITestCaseRegistry {
 
    public:
 
        TestRegistry()
 
        :   m_currentSortOrder( RunTests::InDeclarationOrder ),
 
            m_unnamedCount( 0 )
 
        {}
 
        virtual ~TestRegistry();
 

	
 
        virtual void registerTest( TestCase const& testCase ) {
 
            std::string name = testCase.getTestCaseInfo().name;
 
            if( name.empty() ) {
 
                std::ostringstream oss;
 
                oss << "Anonymous test case " << ++m_unnamedCount;
 
                return registerTest( testCase.withName( oss.str() ) );
 
            }
 
            m_functions.push_back( testCase );
 
        }
 

	
 
        virtual std::vector<TestCase> const& getAllTests() const {
 
            return m_functions;
 
        }
 
        virtual std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const {
 
            if( m_sortedFunctions.empty() )
 
                enforceNoDuplicateTestCases( m_functions );
 

	
 
            if(  m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) {
 
                m_sortedFunctions = sortTests( config, m_functions );
 
                m_currentSortOrder = config.runOrder();
 
            }
 
            return m_sortedFunctions;
 
        }
 

	
 
    private:
 
        std::vector<TestCase> m_functions;
 
        mutable RunTests::InWhatOrder m_currentSortOrder;
 
        mutable std::vector<TestCase> m_sortedFunctions;
 
        size_t m_unnamedCount;
 
        std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised
 
    };
 

	
 
    ///////////////////////////////////////////////////////////////////////////
 

	
 
    class FreeFunctionTestCase : public SharedImpl<ITestCase> {
 
    public:
 

	
 
        FreeFunctionTestCase( TestFunction fun ) : m_fun( fun ) {}
 

	
 
        virtual void invoke() const {
 
            m_fun();
 
        }
 

	
 
    private:
 
        virtual ~FreeFunctionTestCase();
 

	
 
        TestFunction m_fun;
 
    };
 

	
 
    inline std::string extractClassName( std::string const& classOrQualifiedMethodName ) {
 
        std::string className = classOrQualifiedMethodName;
 
        if( startsWith( className, '&' ) )
 
        {
 
            std::size_t lastColons = className.rfind( "::" );
 
            std::size_t penultimateColons = className.rfind( "::", lastColons-1 );
 
            if( penultimateColons == std::string::npos )
 
                penultimateColons = 1;
 
            className = className.substr( penultimateColons, lastColons-penultimateColons );
 
        }
 
        return className;
 
    }
 

	
 
    void registerTestCase
 
        (   ITestCase* testCase,
 
            char const* classOrQualifiedMethodName,
 
            NameAndDesc const& nameAndDesc,
 
            SourceLineInfo const& lineInfo ) {
 

	
 
        getMutableRegistryHub().registerTest
 
            ( makeTestCase
 
                (   testCase,
 
                    extractClassName( classOrQualifiedMethodName ),
 
                    nameAndDesc.name,
 
                    nameAndDesc.description,
 
                    lineInfo ) );
 
    }
 
    void registerTestCaseFunction
 
        (   TestFunction function,
 
            SourceLineInfo const& lineInfo,
 
            NameAndDesc const& nameAndDesc ) {
 
        registerTestCase( new FreeFunctionTestCase( function ), "", nameAndDesc, lineInfo );
 
    }
 

	
 
    ///////////////////////////////////////////////////////////////////////////
 

	
 
    AutoReg::AutoReg
 
        (   TestFunction function,
 
            SourceLineInfo const& lineInfo,
 
            NameAndDesc const& nameAndDesc ) {
 
        registerTestCaseFunction( function, lineInfo, nameAndDesc );
 
    }
 

	
 
    AutoReg::~AutoReg() {}
 

	
 
} // end namespace Catch
 

	
 
// #included from: catch_reporter_registry.hpp
 
#define TWOBLUECUBES_CATCH_REPORTER_REGISTRY_HPP_INCLUDED
 

	
 
#include <map>
 

	
 
namespace Catch {
 

	
 
    class ReporterRegistry : public IReporterRegistry {
 

	
 
    public:
 

	
 
        virtual ~ReporterRegistry() CATCH_OVERRIDE {}
 

	
 
        virtual IStreamingReporter* create( std::string const& name, Ptr<IConfig const> const& config ) const CATCH_OVERRIDE {
 
            FactoryMap::const_iterator it =  m_factories.find( name );
 
            if( it == m_factories.end() )
 
                return CATCH_NULL;
 
            return it->second->create( ReporterConfig( config ) );
 
        }
 

	
 
        void registerReporter( std::string const& name, Ptr<IReporterFactory> const& factory ) {
 
            m_factories.insert( std::make_pair( name, factory ) );
 
        }
 
        void registerListener( Ptr<IReporterFactory> const& factory ) {
 
            m_listeners.push_back( factory );
 
        }
 

	
 
        virtual FactoryMap const& getFactories() const CATCH_OVERRIDE {
 
            return m_factories;
 
        }
 
        virtual Listeners const& getListeners() const CATCH_OVERRIDE {
 
            return m_listeners;
 
        }
 

	
 
    private:
 
        FactoryMap m_factories;
 
        Listeners m_listeners;
 
    };
 
}
 

	
 
// #included from: catch_exception_translator_registry.hpp
 
#define TWOBLUECUBES_CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED
 

	
 
#ifdef __OBJC__
 
#import "Foundation/Foundation.h"
 
#endif
 

	
 
namespace Catch {
 

	
 
    class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry {
 
    public:
 
        ~ExceptionTranslatorRegistry() {
 
            deleteAll( m_translators );
 
        }
 

	
 
        virtual void registerTranslator( const IExceptionTranslator* translator ) {
 
            m_translators.push_back( translator );
 
        }
 

	
 
        virtual std::string translateActiveException() const {
 
            try {
 
#ifdef __OBJC__
 
                // In Objective-C try objective-c exceptions first
 
                @try {
 
                    return tryTranslators();
 
                }
 
                @catch (NSException *exception) {
 
                    return Catch::toString( [exception description] );
 
                }
 
#else
 
                return tryTranslators();
 
#endif
 
            }
 
            catch( TestFailureException& ) {
 
                throw;
 
            }
 
            catch( std::exception& ex ) {
 
                return ex.what();
 
            }
 
            catch( std::string& msg ) {
 
                return msg;
 
            }
 
            catch( const char* msg ) {
 
                return msg;
 
            }
 
            catch(...) {
 
                return "Unknown exception";
 
            }
 
        }
 

	
 
        std::string tryTranslators() const {
 
            if( m_translators.empty() )
 
                throw;
 
            else
 
                return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() );
 
        }
 

	
 
    private:
 
        std::vector<const IExceptionTranslator*> m_translators;
 
    };
 
}
 

	
 
// #included from: catch_tag_alias_registry.h
 
#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED
 

	
 
#include <map>
 

	
 
namespace Catch {
 

	
 
    class TagAliasRegistry : public ITagAliasRegistry {
 
    public:
 
        virtual ~TagAliasRegistry();
 
        virtual Option<TagAlias> find( std::string const& alias ) const;
 
        virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const;
 
        void add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo );
 

	
 
    private:
 
        std::map<std::string, TagAlias> m_registry;
 
    };
 

	
 
} // end namespace Catch
 

	
 
namespace Catch {
 

	
 
    namespace {
 

	
 
        class RegistryHub : public IRegistryHub, public IMutableRegistryHub {
 

	
 
            RegistryHub( RegistryHub const& );
 
            void operator=( RegistryHub const& );
 

	
 
        public: // IRegistryHub
 
            RegistryHub() {
 
            }
 
            virtual IReporterRegistry const& getReporterRegistry() const CATCH_OVERRIDE {
 
                return m_reporterRegistry;
 
            }
 
            virtual ITestCaseRegistry const& getTestCaseRegistry() const CATCH_OVERRIDE {
 
                return m_testCaseRegistry;
 
            }
 
            virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() CATCH_OVERRIDE {
 
                return m_exceptionTranslatorRegistry;
 
            }
 
            virtual ITagAliasRegistry const& getTagAliasRegistry() const CATCH_OVERRIDE {
 
                return m_tagAliasRegistry;
 
            }
 

	
 
        public: // IMutableRegistryHub
 
            virtual void registerReporter( std::string const& name, Ptr<IReporterFactory> const& factory ) CATCH_OVERRIDE {
 
                m_reporterRegistry.registerReporter( name, factory );
 
            }
 
            virtual void registerListener( Ptr<IReporterFactory> const& factory ) CATCH_OVERRIDE {
 
                m_reporterRegistry.registerListener( factory );
 
            }
 
            virtual void registerTest( TestCase const& testInfo ) CATCH_OVERRIDE {
 
                m_testCaseRegistry.registerTest( testInfo );
 
            }
 
            virtual void registerTranslator( const IExceptionTranslator* translator ) CATCH_OVERRIDE {
 
                m_exceptionTranslatorRegistry.registerTranslator( translator );
 
            }
 
            virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) CATCH_OVERRIDE {
 
                m_tagAliasRegistry.add( alias, tag, lineInfo );
 
            }
 

	
 
        private:
 
            TestRegistry m_testCaseRegistry;
 
            ReporterRegistry m_reporterRegistry;
 
            ExceptionTranslatorRegistry m_exceptionTranslatorRegistry;
 
            TagAliasRegistry m_tagAliasRegistry;
 
        };
 

	
 
        // Single, global, instance
 
        inline RegistryHub*& getTheRegistryHub() {
 
            static RegistryHub* theRegistryHub = CATCH_NULL;
 
            if( !theRegistryHub )
 
                theRegistryHub = new RegistryHub();
 
            return theRegistryHub;
 
        }
 
    }
 

	
 
    IRegistryHub& getRegistryHub() {
 
        return *getTheRegistryHub();
 
    }
 
    IMutableRegistryHub& getMutableRegistryHub() {
 
        return *getTheRegistryHub();
 
    }
 
    void cleanUp() {
 
        delete getTheRegistryHub();
 
        getTheRegistryHub() = CATCH_NULL;
 
        cleanUpContext();
 
    }
 
    std::string translateActiveException() {
 
        return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException();
 
    }
 

	
 
} // end namespace Catch
 

	
 
// #included from: catch_notimplemented_exception.hpp
 
#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_HPP_INCLUDED
 

	
 
#include <sstream>
 

	
 
namespace Catch {
 

	
 
    NotImplementedException::NotImplementedException( SourceLineInfo const& lineInfo )
 
    :   m_lineInfo( lineInfo ) {
 
        std::ostringstream oss;
 
        oss << lineInfo << ": function ";
 
        oss << "not implemented";
 
        m_what = oss.str();
 
    }
 

	
 
    const char* NotImplementedException::what() const CATCH_NOEXCEPT {
 
        return m_what.c_str();
 
    }
 

	
 
} // end namespace Catch
 

	
 
// #included from: catch_context_impl.hpp
 
#define TWOBLUECUBES_CATCH_CONTEXT_IMPL_HPP_INCLUDED
 

	
 
// #included from: catch_stream.hpp
 
#define TWOBLUECUBES_CATCH_STREAM_HPP_INCLUDED
 

	
 
#include <stdexcept>
 
#include <cstdio>
 
#include <iostream>
 

	
 
namespace Catch {
 

	
 
    template<typename WriterF, size_t bufferSize=256>
 
    class StreamBufImpl : public StreamBufBase {
 
        char data[bufferSize];
 
        WriterF m_writer;
 

	
 
    public:
 
        StreamBufImpl() {
 
            setp( data, data + sizeof(data) );
 
        }
 

	
 
        ~StreamBufImpl() CATCH_NOEXCEPT {
 
            sync();
 
        }
 

	
 
    private:
 
        int overflow( int c ) {
 
            sync();
 

	
 
            if( c != EOF ) {
 
                if( pbase() == epptr() )
 
                    m_writer( std::string( 1, static_cast<char>( c ) ) );
 
                else
 
                    sputc( static_cast<char>( c ) );
 
            }
 
            return 0;
 
        }
 

	
 
        int sync() {
 
            if( pbase() != pptr() ) {
 
                m_writer( std::string( pbase(), static_cast<std::string::size_type>( pptr() - pbase() ) ) );
 
                setp( pbase(), epptr() );
 
            }
 
            return 0;
 
        }
 
    };
 

	
 
    ///////////////////////////////////////////////////////////////////////////
 

	
 
    FileStream::FileStream( std::string const& filename ) {
 
        m_ofs.open( filename.c_str() );
 
        if( m_ofs.fail() ) {
 
            std::ostringstream oss;
 
            oss << "Unable to open file: '" << filename << '\'';
 
            throw std::domain_error( oss.str() );
 
        }
 
    }
 

	
 
    std::ostream& FileStream::stream() const {
 
        return m_ofs;
 
    }
 

	
 
    struct OutputDebugWriter {
 

	
 
        void operator()( std::string const&str ) {
 
            writeToDebugConsole( str );
 
        }
 
    };
 

	
 
    DebugOutStream::DebugOutStream()
 
    :   m_streamBuf( new StreamBufImpl<OutputDebugWriter>() ),
 
        m_os( m_streamBuf.get() )
 
    {}
 

	
 
    std::ostream& DebugOutStream::stream() const {
 
        return m_os;
 
    }
 

	
 
    // Store the streambuf from cout up-front because
 
    // cout may get redirected when running tests
 
    CoutStream::CoutStream()
 
    :   m_os( Catch::cout().rdbuf() )
 
    {}
 

	
 
    std::ostream& CoutStream::stream() const {
 
        return m_os;
 
    }
 

	
 
#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions
 
    std::ostream& cout() {
 
        return std::cout;
 
    }
 
    std::ostream& cerr() {
 
        return std::cerr;
 
    }
 
#endif
 
}
 

	
 
namespace Catch {
 

	
 
    class Context : public IMutableContext {
 

	
 
        Context() : m_config( CATCH_NULL ), m_runner( CATCH_NULL ), m_resultCapture( CATCH_NULL ) {}
 
        Context( Context const& );
 
        void operator=( Context const& );
 

	
 
    public:
 
        virtual ~Context() {
 
            deleteAllValues( m_generatorsByTestName );
 
        }
 

	
 
    public: // IContext
 
        virtual IResultCapture* getResultCapture() {
 
            return m_resultCapture;
 
        }
 
        virtual IRunner* getRunner() {
 
            return m_runner;
 
        }
 
        virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) {
 
            return getGeneratorsForCurrentTest()
 
            .getGeneratorInfo( fileInfo, totalSize )
 
            .getCurrentIndex();
 
        }
 
        virtual bool advanceGeneratorsForCurrentTest() {
 
            IGeneratorsForTest* generators = findGeneratorsForCurrentTest();
 
            return generators && generators->moveNext();
 
        }
 

	
 
        virtual Ptr<IConfig const> getConfig() const {
 
            return m_config;
 
        }
 

	
 
    public: // IMutableContext
 
        virtual void setResultCapture( IResultCapture* resultCapture ) {
 
            m_resultCapture = resultCapture;
 
        }
 
        virtual void setRunner( IRunner* runner ) {
 
            m_runner = runner;
 
        }
 
        virtual void setConfig( Ptr<IConfig const> const& config ) {
 
            m_config = config;
 
        }
 

	
 
        friend IMutableContext& getCurrentMutableContext();
 

	
 
    private:
 
        IGeneratorsForTest* findGeneratorsForCurrentTest() {
 
            std::string testName = getResultCapture()->getCurrentTestName();
 

	
 
            std::map<std::string, IGeneratorsForTest*>::const_iterator it =
 
                m_generatorsByTestName.find( testName );
 
            return it != m_generatorsByTestName.end()
 
                ? it->second
 
                : CATCH_NULL;
 
        }
 

	
 
        IGeneratorsForTest& getGeneratorsForCurrentTest() {
 
            IGeneratorsForTest* generators = findGeneratorsForCurrentTest();
 
            if( !generators ) {
 
                std::string testName = getResultCapture()->getCurrentTestName();
 
                generators = createGeneratorsForTest();
 
                m_generatorsByTestName.insert( std::make_pair( testName, generators ) );
 
            }
 
            return *generators;
 
        }
 

	
 
    private:
 
        Ptr<IConfig const> m_config;
 
        IRunner* m_runner;
 
        IResultCapture* m_resultCapture;
 
        std::map<std::string, IGeneratorsForTest*> m_generatorsByTestName;
 
    };
 

	
 
    namespace {
 
        Context* currentContext = CATCH_NULL;
 
    }
 
    IMutableContext& getCurrentMutableContext() {
 
        if( !currentContext )
 
            currentContext = new Context();
 
        return *currentContext;
 
    }
 
    IContext& getCurrentContext() {
 
        return getCurrentMutableContext();
 
    }
 

	
 
    void cleanUpContext() {
 
        delete currentContext;
 
        currentContext = CATCH_NULL;
 
    }
 
}
 

	
 
// #included from: catch_console_colour_impl.hpp
 
#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED
 

	
 
// #included from: catch_errno_guard.hpp
 
#define TWOBLUECUBES_CATCH_ERRNO_GUARD_HPP_INCLUDED
 

	
 
#include <cerrno>
 

	
 
namespace Catch {
 

	
 
    class ErrnoGuard {
 
    public:
 
        ErrnoGuard():m_oldErrno(errno){}
 
        ~ErrnoGuard() { errno = m_oldErrno; }
 
    private:
 
        int m_oldErrno;
 
    };
 

	
 
}
 

	
 
namespace Catch {
 
    namespace {
 

	
 
        struct IColourImpl {
 
            virtual ~IColourImpl() {}
 
            virtual void use( Colour::Code _colourCode ) = 0;
 
        };
 

	
 
        struct NoColourImpl : IColourImpl {
 
            void use( Colour::Code ) {}
 

	
 
            static IColourImpl* instance() {
 
                static NoColourImpl s_instance;
 
                return &s_instance;
 
            }
 
        };
 

	
 
    } // anon namespace
 
} // namespace Catch
 

	
 
#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI )
 
#   ifdef CATCH_PLATFORM_WINDOWS
 
#       define CATCH_CONFIG_COLOUR_WINDOWS
 
#   else
 
#       define CATCH_CONFIG_COLOUR_ANSI
 
#   endif
 
#endif
 

	
 
#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) /////////////////////////////////////////
 

	
 
namespace Catch {
 
namespace {
 

	
 
    class Win32ColourImpl : public IColourImpl {
 
    public:
 
        Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) )
 
        {
 
            CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
 
            GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo );
 
            originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY );
 
            originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY );
 
        }
 

	
 
        virtual void use( Colour::Code _colourCode ) {
 
            switch( _colourCode ) {
 
                case Colour::None:      return setTextAttribute( originalForegroundAttributes );
 
                case Colour::White:     return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE );
 
                case Colour::Red:       return setTextAttribute( FOREGROUND_RED );
 
                case Colour::Green:     return setTextAttribute( FOREGROUND_GREEN );
 
                case Colour::Blue:      return setTextAttribute( FOREGROUND_BLUE );
 
                case Colour::Cyan:      return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN );
 
                case Colour::Yellow:    return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN );
 
                case Colour::Grey:      return setTextAttribute( 0 );
 

	
 
                case Colour::LightGrey:     return setTextAttribute( FOREGROUND_INTENSITY );
 
                case Colour::BrightRed:     return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED );
 
                case Colour::BrightGreen:   return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN );
 
                case Colour::BrightWhite:   return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE );
 

	
 
                case Colour::Bright: throw std::logic_error( "not a colour" );
 
            }
 
        }
 

	
 
    private:
 
        void setTextAttribute( WORD _textAttribute ) {
 
            SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes );
 
        }
 
        HANDLE stdoutHandle;
 
        WORD originalForegroundAttributes;
 
        WORD originalBackgroundAttributes;
 
    };
 

	
 
    IColourImpl* platformColourInstance() {
 
        static Win32ColourImpl s_instance;
 

	
 
        Ptr<IConfig const> config = getCurrentContext().getConfig();
 
        UseColour::YesOrNo colourMode = config
 
            ? config->useColour()
 
            : UseColour::Auto;
 
        if( colourMode == UseColour::Auto )
 
            colourMode = !isDebuggerActive()
 
                ? UseColour::Yes
 
                : UseColour::No;
 
        return colourMode == UseColour::Yes
 
            ? &s_instance
 
            : NoColourImpl::instance();
 
    }
 

	
 
} // end anon namespace
 
} // end namespace Catch
 

	
 
#elif defined( CATCH_CONFIG_COLOUR_ANSI ) //////////////////////////////////////
 

	
 
#include <unistd.h>
 

	
 
namespace Catch {
 
namespace {
 

	
 
    // use POSIX/ ANSI console terminal codes
 
    // Thanks to Adam Strzelecki for original contribution
 
    // (http://github.com/nanoant)
 
    // https://github.com/philsquared/Catch/pull/131
 
    class PosixColourImpl : public IColourImpl {
 
    public:
 
        virtual void use( Colour::Code _colourCode ) {
 
            switch( _colourCode ) {
 
                case Colour::None:
 
                case Colour::White:     return setColour( "[0m" );
 
                case Colour::Red:       return setColour( "[0;31m" );
 
                case Colour::Green:     return setColour( "[0;32m" );
 
                case Colour::Blue:      return setColour( "[0;34m" );
 
                case Colour::Cyan:      return setColour( "[0;36m" );
 
                case Colour::Yellow:    return setColour( "[0;33m" );
 
                case Colour::Grey:      return setColour( "[1;30m" );
 

	
 
                case Colour::LightGrey:     return setColour( "[0;37m" );
 
                case Colour::BrightRed:     return setColour( "[1;31m" );
 
                case Colour::BrightGreen:   return setColour( "[1;32m" );
 
                case Colour::BrightWhite:   return setColour( "[1;37m" );
 

	
 
                case Colour::Bright: throw std::logic_error( "not a colour" );
 
            }
 
        }
 
        static IColourImpl* instance() {
 
            static PosixColourImpl s_instance;
 
            return &s_instance;
 
        }
 

	
 
    private:
 
        void setColour( const char* _escapeCode ) {
 
            Catch::cout() << '\033' << _escapeCode;
 
        }
 
    };
 

	
 
    IColourImpl* platformColourInstance() {
 
        ErrnoGuard guard;
 
        Ptr<IConfig const> config = getCurrentContext().getConfig();
 
        UseColour::YesOrNo colourMode = config
 
            ? config->useColour()
 
            : UseColour::Auto;
 
        if( colourMode == UseColour::Auto )
 
            colourMode = (!isDebuggerActive() && isatty(STDOUT_FILENO) )
 
                ? UseColour::Yes
 
                : UseColour::No;
 
        return colourMode == UseColour::Yes
 
            ? PosixColourImpl::instance()
 
            : NoColourImpl::instance();
 
    }
 

	
 
} // end anon namespace
 
} // end namespace Catch
 

	
 
#else  // not Windows or ANSI ///////////////////////////////////////////////
 

	
 
namespace Catch {
 

	
 
    static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); }
 

	
 
} // end namespace Catch
 

	
 
#endif // Windows/ ANSI/ None
 

	
 
namespace Catch {
 

	
 
    Colour::Colour( Code _colourCode ) : m_moved( false ) { use( _colourCode ); }
 
    Colour::Colour( Colour const& _other ) : m_moved( false ) { const_cast<Colour&>( _other ).m_moved = true; }
 
    Colour::~Colour(){ if( !m_moved ) use( None ); }
 

	
 
    void Colour::use( Code _colourCode ) {
 
        static IColourImpl* impl = platformColourInstance();
 
        impl->use( _colourCode );
 
    }
 

	
 
} // end namespace Catch
 

	
 
// #included from: catch_generators_impl.hpp
 
#define TWOBLUECUBES_CATCH_GENERATORS_IMPL_HPP_INCLUDED
 

	
 
#include <vector>
 
#include <string>
 
#include <map>
 

	
 
namespace Catch {
 

	
 
    struct GeneratorInfo : IGeneratorInfo {
 

	
 
        GeneratorInfo( std::size_t size )
 
        :   m_size( size ),
 
            m_currentIndex( 0 )
 
        {}
 

	
 
        bool moveNext() {
 
            if( ++m_currentIndex == m_size ) {
 
                m_currentIndex = 0;
 
                return false;
 
            }
 
            return true;
 
        }
 

	
 
        std::size_t getCurrentIndex() const {
 
            return m_currentIndex;
 
        }
 

	
 
        std::size_t m_size;
 
        std::size_t m_currentIndex;
 
    };
 

	
 
    ///////////////////////////////////////////////////////////////////////////
 

	
 
    class GeneratorsForTest : public IGeneratorsForTest {
 

	
 
    public:
 
        ~GeneratorsForTest() {
 
            deleteAll( m_generatorsInOrder );
 
        }
 

	
 
        IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) {
 
            std::map<std::string, IGeneratorInfo*>::const_iterator it = m_generatorsByName.find( fileInfo );
 
            if( it == m_generatorsByName.end() ) {
 
                IGeneratorInfo* info = new GeneratorInfo( size );
 
                m_generatorsByName.insert( std::make_pair( fileInfo, info ) );
 
                m_generatorsInOrder.push_back( info );
 
                return *info;
 
            }
 
            return *it->second;
 
        }
 

	
 
        bool moveNext() {
 
            std::vector<IGeneratorInfo*>::const_iterator it = m_generatorsInOrder.begin();
 
            std::vector<IGeneratorInfo*>::const_iterator itEnd = m_generatorsInOrder.end();
 
            for(; it != itEnd; ++it ) {
 
                if( (*it)->moveNext() )
 
                    return true;
 
            }
 
            return false;
 
        }
 

	
 
    private:
 
        std::map<std::string, IGeneratorInfo*> m_generatorsByName;
 
        std::vector<IGeneratorInfo*> m_generatorsInOrder;
 
    };
 

	
 
    IGeneratorsForTest* createGeneratorsForTest()
 
    {
 
        return new GeneratorsForTest();
 
    }
 

	
 
} // end namespace Catch
 

	
 
// #included from: catch_assertionresult.hpp
 
#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_HPP_INCLUDED
 

	
 
namespace Catch {
 

	
 
    AssertionInfo::AssertionInfo(   char const * _macroName,
 
                                    SourceLineInfo const& _lineInfo,
 
                                    char const * _capturedExpression,
 
                                    ResultDisposition::Flags _resultDisposition,
 
                                    char const * _secondArg)
 
    :   macroName( _macroName ),
 
        lineInfo( _lineInfo ),
 
        capturedExpression( _capturedExpression ),
 
        resultDisposition( _resultDisposition ),
 
        secondArg( _secondArg )
 
    {}
 

	
 
    AssertionResult::AssertionResult() {}
 

	
 
    AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data )
 
    :   m_info( info ),
 
        m_resultData( data )
 
    {}
 

	
 
    AssertionResult::~AssertionResult() {}
 

	
 
    // Result was a success
 
    bool AssertionResult::succeeded() const {
 
        return Catch::isOk( m_resultData.resultType );
 
    }
 

	
 
    // Result was a success, or failure is suppressed
 
    bool AssertionResult::isOk() const {
 
        return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition );
 
    }
 

	
 
    ResultWas::OfType AssertionResult::getResultType() const {
 
        return m_resultData.resultType;
 
    }
 

	
 
    bool AssertionResult::hasExpression() const {
 
        return m_info.capturedExpression[0] != 0;
 
    }
 

	
 
    bool AssertionResult::hasMessage() const {
 
        return !m_resultData.message.empty();
 
    }
 

	
 
    std::string capturedExpressionWithSecondArgument( char const * capturedExpression, char const * secondArg ) {
 
        return (secondArg[0] == 0 || secondArg[0] == '"' && secondArg[1] == '"')
 
            ? capturedExpression
 
            : std::string(capturedExpression) + ", " + secondArg;
 
    }
 

	
 
    std::string AssertionResult::getExpression() const {
 
        if( isFalseTest( m_info.resultDisposition ) )
 
            return '!' + capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg);
 
        else
 
            return capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg);
 
    }
 
    std::string AssertionResult::getExpressionInMacro() const {
 
        if( m_info.macroName[0] == 0 )
 
            return capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg);
 
        else
 
            return std::string(m_info.macroName) + "( " + capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg) + " )";
 
    }
 

	
 
    bool AssertionResult::hasExpandedExpression() const {
 
        return hasExpression() && getExpandedExpression() != getExpression();
 
    }
 

	
 
    std::string AssertionResult::getExpandedExpression() const {
 
        return m_resultData.reconstructExpression();
 
    }
 

	
 
    std::string AssertionResult::getMessage() const {
 
        return m_resultData.message;
 
    }
 
    SourceLineInfo AssertionResult::getSourceInfo() const {
 
        return m_info.lineInfo;
 
    }
 

	
 
    std::string AssertionResult::getTestMacroName() const {
 
        return m_info.macroName;
 
    }
 

	
 
    void AssertionResult::discardDecomposedExpression() const {
 
        m_resultData.decomposedExpression = CATCH_NULL;
 
    }
 

	
 
    void AssertionResult::expandDecomposedExpression() const {
 
        m_resultData.reconstructExpression();
 
    }
 

	
 
} // end namespace Catch
 

	
 
// #included from: catch_test_case_info.hpp
 
#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_HPP_INCLUDED
 

	
 
#include <cctype>
 

	
 
namespace Catch {
 

	
 
    inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) {
 
        if( startsWith( tag, '.' ) ||
 
            tag == "hide" ||
 
            tag == "!hide" )
 
            return TestCaseInfo::IsHidden;
 
        else if( tag == "!throws" )
 
            return TestCaseInfo::Throws;
 
        else if( tag == "!shouldfail" )
 
            return TestCaseInfo::ShouldFail;
 
        else if( tag == "!mayfail" )
 
            return TestCaseInfo::MayFail;
 
        else if( tag == "!nonportable" )
 
            return TestCaseInfo::NonPortable;
 
        else
 
            return TestCaseInfo::None;
 
    }
 
    inline bool isReservedTag( std::string const& tag ) {
 
        return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( tag[0] );
 
    }
 
    inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) {
 
        if( isReservedTag( tag ) ) {
 
            std::ostringstream ss;
 
            ss << Colour(Colour::Red)
 
               << "Tag name [" << tag << "] not allowed.\n"
 
               << "Tag names starting with non alpha-numeric characters are reserved\n"
 
               << Colour(Colour::FileName)
 
               << _lineInfo << '\n';
 
            throw std::runtime_error(ss.str());
 
        }
 
    }
 

	
 
    TestCase makeTestCase(  ITestCase* _testCase,
 
                            std::string const& _className,
 
                            std::string const& _name,
 
                            std::string const& _descOrTags,
 
                            SourceLineInfo const& _lineInfo )
 
    {
 
        bool isHidden( startsWith( _name, "./" ) ); // Legacy support
 

	
 
        // Parse out tags
 
        std::set<std::string> tags;
 
        std::string desc, tag;
 
        bool inTag = false;
 
        for( std::size_t i = 0; i < _descOrTags.size(); ++i ) {
 
            char c = _descOrTags[i];
 
            if( !inTag ) {
 
                if( c == '[' )
 
                    inTag = true;
 
                else
 
                    desc += c;
 
            }
 
            else {
 
                if( c == ']' ) {
 
                    TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag );
 
                    if( prop == TestCaseInfo::IsHidden )
 
                        isHidden = true;
 
                    else if( prop == TestCaseInfo::None )
 
                        enforceNotReservedTag( tag, _lineInfo );
 

	
 
                    tags.insert( tag );
 
                    tag.clear();
 
                    inTag = false;
 
                }
 
                else
 
                    tag += c;
 
            }
 
        }
 
        if( isHidden ) {
 
            tags.insert( "hide" );
 
            tags.insert( "." );
 
        }
 

	
 
        TestCaseInfo info( _name, _className, desc, tags, _lineInfo );
 
        return TestCase( _testCase, info );
 
    }
 

	
 
    void setTags( TestCaseInfo& testCaseInfo, std::set<std::string> const& tags )
 
    {
 
        testCaseInfo.tags = tags;
 
        testCaseInfo.lcaseTags.clear();
 

	
 
        std::ostringstream oss;
 
        for( std::set<std::string>::const_iterator it = tags.begin(), itEnd = tags.end(); it != itEnd; ++it ) {
 
            oss << '[' << *it << ']';
 
            std::string lcaseTag = toLower( *it );
 
            testCaseInfo.properties = static_cast<TestCaseInfo::SpecialProperties>( testCaseInfo.properties | parseSpecialTag( lcaseTag ) );
 
            testCaseInfo.lcaseTags.insert( lcaseTag );
 
        }
 
        testCaseInfo.tagsAsString = oss.str();
 
    }
 

	
 
    TestCaseInfo::TestCaseInfo( std::string const& _name,
 
                                std::string const& _className,
 
                                std::string const& _description,
 
                                std::set<std::string> const& _tags,
 
                                SourceLineInfo const& _lineInfo )
 
    :   name( _name ),
 
        className( _className ),
 
        description( _description ),
 
        lineInfo( _lineInfo ),
 
        properties( None )
 
    {
 
        setTags( *this, _tags );
 
    }
 

	
 
    TestCaseInfo::TestCaseInfo( TestCaseInfo const& other )
 
    :   name( other.name ),
 
        className( other.className ),
 
        description( other.description ),
 
        tags( other.tags ),
 
        lcaseTags( other.lcaseTags ),
 
        tagsAsString( other.tagsAsString ),
 
        lineInfo( other.lineInfo ),
 
        properties( other.properties )
 
    {}
 

	
 
    bool TestCaseInfo::isHidden() const {
 
        return ( properties & IsHidden ) != 0;
 
    }
 
    bool TestCaseInfo::throws() const {
 
        return ( properties & Throws ) != 0;
 
    }
 
    bool TestCaseInfo::okToFail() const {
 
        return ( properties & (ShouldFail | MayFail ) ) != 0;
 
    }
 
    bool TestCaseInfo::expectedToFail() const {
 
        return ( properties & (ShouldFail ) ) != 0;
 
    }
 

	
 
    TestCase::TestCase( ITestCase* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {}
 

	
 
    TestCase::TestCase( TestCase const& other )
 
    :   TestCaseInfo( other ),
 
        test( other.test )
 
    {}
 

	
 
    TestCase TestCase::withName( std::string const& _newName ) const {
 
        TestCase other( *this );
 
        other.name = _newName;
 
        return other;
 
    }
 

	
 
    void TestCase::swap( TestCase& other ) {
 
        test.swap( other.test );
 
        name.swap( other.name );
 
        className.swap( other.className );
 
        description.swap( other.description );
 
        tags.swap( other.tags );
 
        lcaseTags.swap( other.lcaseTags );
 
        tagsAsString.swap( other.tagsAsString );
 
        std::swap( TestCaseInfo::properties, static_cast<TestCaseInfo&>( other ).properties );
 
        std::swap( lineInfo, other.lineInfo );
 
    }
 

	
 
    void TestCase::invoke() const {
 
        test->invoke();
 
    }
 

	
 
    bool TestCase::operator == ( TestCase const& other ) const {
 
        return  test.get() == other.test.get() &&
 
                name == other.name &&
 
                className == other.className;
 
    }
 

	
 
    bool TestCase::operator < ( TestCase const& other ) const {
 
        return name < other.name;
 
    }
 
    TestCase& TestCase::operator = ( TestCase const& other ) {
 
        TestCase temp( other );
 
        swap( temp );
 
        return *this;
 
    }
 

	
 
    TestCaseInfo const& TestCase::getTestCaseInfo() const
 
    {
 
        return *this;
 
    }
 

	
 
} // end namespace Catch
 

	
 
// #included from: catch_version.hpp
 
#define TWOBLUECUBES_CATCH_VERSION_HPP_INCLUDED
 

	
 
namespace Catch {
 

	
 
    Version::Version
 
        (   unsigned int _majorVersion,
 
            unsigned int _minorVersion,
 
            unsigned int _patchNumber,
 
            char const * const _branchName,
 
            unsigned int _buildNumber )
 
    :   majorVersion( _majorVersion ),
 
        minorVersion( _minorVersion ),
 
        patchNumber( _patchNumber ),
 
        branchName( _branchName ),
 
        buildNumber( _buildNumber )
 
    {}
 

	
 
    std::ostream& operator << ( std::ostream& os, Version const& version ) {
 
        os  << version.majorVersion << '.'
 
            << version.minorVersion << '.'
 
            << version.patchNumber;
 
        // branchName is never null -> 0th char is \0 if it is empty
 
        if (version.branchName[0]) {
 
            os << '-' << version.branchName
 
               << '.' << version.buildNumber;
 
        }
 
        return os;
 
    }
 

	
 
    inline Version libraryVersion() {
 
        static Version version( 1, 9, 6, "", 0 );
 
        return version;
 
    }
 

	
 
}
 

	
 
// #included from: catch_message.hpp
 
#define TWOBLUECUBES_CATCH_MESSAGE_HPP_INCLUDED
 

	
 
namespace Catch {
 

	
 
    MessageInfo::MessageInfo(   std::string const& _macroName,
 
                                SourceLineInfo const& _lineInfo,
 
                                ResultWas::OfType _type )
 
    :   macroName( _macroName ),
 
        lineInfo( _lineInfo ),
 
        type( _type ),
 
        sequence( ++globalCount )
 
    {}
 

	
 
    // This may need protecting if threading support is added
 
    unsigned int MessageInfo::globalCount = 0;
 

	
 
    ////////////////////////////////////////////////////////////////////////////
 

	
 
    ScopedMessage::ScopedMessage( MessageBuilder const& builder )
 
    : m_info( builder.m_info )
 
    {
 
        m_info.message = builder.m_stream.str();
 
        getResultCapture().pushScopedMessage( m_info );
 
    }
 
    ScopedMessage::ScopedMessage( ScopedMessage const& other )
 
    : m_info( other.m_info )
 
    {}
 

	
 
    ScopedMessage::~ScopedMessage() {
 
        if ( !std::uncaught_exception() ){
 
            getResultCapture().popScopedMessage(m_info);
 
        }
 
    }
 

	
 
} // end namespace Catch
 

	
 
// #included from: catch_legacy_reporter_adapter.hpp
 
#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_HPP_INCLUDED
 

	
 
// #included from: catch_legacy_reporter_adapter.h
 
#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_H_INCLUDED
 

	
 
namespace Catch
 
{
 
    // Deprecated
 
    struct IReporter : IShared {
 
        virtual ~IReporter();
 

	
 
        virtual bool shouldRedirectStdout() const = 0;
 

	
 
        virtual void StartTesting() = 0;
 
        virtual void EndTesting( Totals const& totals ) = 0;
 
        virtual void StartGroup( std::string const& groupName ) = 0;
 
        virtual void EndGroup( std::string const& groupName, Totals const& totals ) = 0;
 
        virtual void StartTestCase( TestCaseInfo const& testInfo ) = 0;
 
        virtual void EndTestCase( TestCaseInfo const& testInfo, Totals const& totals, std::string const& stdOut, std::string const& stdErr ) = 0;
 
        virtual void StartSection( std::string const& sectionName, std::string const& description ) = 0;
 
        virtual void EndSection( std::string const& sectionName, Counts const& assertions ) = 0;
 
        virtual void NoAssertionsInSection( std::string const& sectionName ) = 0;
 
        virtual void NoAssertionsInTestCase( std::string const& testName ) = 0;
 
        virtual void Aborted() = 0;
 
        virtual void Result( AssertionResult const& result ) = 0;
 
    };
 

	
 
    class LegacyReporterAdapter : public SharedImpl<IStreamingReporter>
 
    {
 
    public:
 
        LegacyReporterAdapter( Ptr<IReporter> const& legacyReporter );
 
        virtual ~LegacyReporterAdapter();
 

	
 
        virtual ReporterPreferences getPreferences() const;
 
        virtual void noMatchingTestCases( std::string const& );
 
        virtual void testRunStarting( TestRunInfo const& );
 
        virtual void testGroupStarting( GroupInfo const& groupInfo );
 
        virtual void testCaseStarting( TestCaseInfo const& testInfo );
 
        virtual void sectionStarting( SectionInfo const& sectionInfo );
 
        virtual void assertionStarting( AssertionInfo const& );
 
        virtual bool assertionEnded( AssertionStats const& assertionStats );
 
        virtual void sectionEnded( SectionStats const& sectionStats );
 
        virtual void testCaseEnded( TestCaseStats const& testCaseStats );
 
        virtual void testGroupEnded( TestGroupStats const& testGroupStats );
 
        virtual void testRunEnded( TestRunStats const& testRunStats );
 
        virtual void skipTest( TestCaseInfo const& );
 

	
 
    private:
 
        Ptr<IReporter> m_legacyReporter;
 
    };
 
}
 

	
 
namespace Catch
 
{
 
    LegacyReporterAdapter::LegacyReporterAdapter( Ptr<IReporter> const& legacyReporter )
 
    :   m_legacyReporter( legacyReporter )
 
    {}
 
    LegacyReporterAdapter::~LegacyReporterAdapter() {}
 

	
 
    ReporterPreferences LegacyReporterAdapter::getPreferences() const {
 
        ReporterPreferences prefs;
 
        prefs.shouldRedirectStdOut = m_legacyReporter->shouldRedirectStdout();
 
        return prefs;
 
    }
 

	
 
    void LegacyReporterAdapter::noMatchingTestCases( std::string const& ) {}
 
    void LegacyReporterAdapter::testRunStarting( TestRunInfo const& ) {
 
        m_legacyReporter->StartTesting();
 
    }
 
    void LegacyReporterAdapter::testGroupStarting( GroupInfo const& groupInfo ) {
 
        m_legacyReporter->StartGroup( groupInfo.name );
 
    }
 
    void LegacyReporterAdapter::testCaseStarting( TestCaseInfo const& testInfo ) {
 
        m_legacyReporter->StartTestCase( testInfo );
 
    }
 
    void LegacyReporterAdapter::sectionStarting( SectionInfo const& sectionInfo ) {
 
        m_legacyReporter->StartSection( sectionInfo.name, sectionInfo.description );
 
    }
 
    void LegacyReporterAdapter::assertionStarting( AssertionInfo const& ) {
 
        // Not on legacy interface
 
    }
 

	
 
    bool LegacyReporterAdapter::assertionEnded( AssertionStats const& assertionStats ) {
 
        if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) {
 
            for( std::vector<MessageInfo>::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end();
 
                    it != itEnd;
 
                    ++it ) {
 
                if( it->type == ResultWas::Info ) {
 
                    ResultBuilder rb( it->macroName.c_str(), it->lineInfo, "", ResultDisposition::Normal );
 
                    rb << it->message;
 
                    rb.setResultType( ResultWas::Info );
 
                    AssertionResult result = rb.build();
 
                    m_legacyReporter->Result( result );
 
                }
 
            }
 
        }
 
        m_legacyReporter->Result( assertionStats.assertionResult );
 
        return true;
 
    }
 
    void LegacyReporterAdapter::sectionEnded( SectionStats const& sectionStats ) {
 
        if( sectionStats.missingAssertions )
 
            m_legacyReporter->NoAssertionsInSection( sectionStats.sectionInfo.name );
 
        m_legacyReporter->EndSection( sectionStats.sectionInfo.name, sectionStats.assertions );
 
    }
 
    void LegacyReporterAdapter::testCaseEnded( TestCaseStats const& testCaseStats ) {
 
        m_legacyReporter->EndTestCase
 
            (   testCaseStats.testInfo,
 
                testCaseStats.totals,
 
                testCaseStats.stdOut,
 
                testCaseStats.stdErr );
 
    }
 
    void LegacyReporterAdapter::testGroupEnded( TestGroupStats const& testGroupStats ) {
 
        if( testGroupStats.aborting )
 
            m_legacyReporter->Aborted();
 
        m_legacyReporter->EndGroup( testGroupStats.groupInfo.name, testGroupStats.totals );
 
    }
 
    void LegacyReporterAdapter::testRunEnded( TestRunStats const& testRunStats ) {
 
        m_legacyReporter->EndTesting( testRunStats.totals );
 
    }
 
    void LegacyReporterAdapter::skipTest( TestCaseInfo const& ) {
 
    }
 
}
 

	
 
// #included from: catch_timer.hpp
 

	
 
#ifdef __clang__
 
#pragma clang diagnostic push
 
#pragma clang diagnostic ignored "-Wc++11-long-long"
 
#endif
 

	
 
#ifdef CATCH_PLATFORM_WINDOWS
 

	
 
#else
 

	
 
#include <sys/time.h>
 

	
 
#endif
 

	
 
namespace Catch {
 

	
 
    namespace {
 
#ifdef CATCH_PLATFORM_WINDOWS
 
        UInt64 getCurrentTicks() {
 
            static UInt64 hz=0, hzo=0;
 
            if (!hz) {
 
                QueryPerformanceFrequency( reinterpret_cast<LARGE_INTEGER*>( &hz ) );
 
                QueryPerformanceCounter( reinterpret_cast<LARGE_INTEGER*>( &hzo ) );
 
            }
 
            UInt64 t;
 
            QueryPerformanceCounter( reinterpret_cast<LARGE_INTEGER*>( &t ) );
 
            return ((t-hzo)*1000000)/hz;
 
        }
 
#else
 
        UInt64 getCurrentTicks() {
 
            timeval t;
 
            gettimeofday(&t,CATCH_NULL);
 
            return static_cast<UInt64>( t.tv_sec ) * 1000000ull + static_cast<UInt64>( t.tv_usec );
 
        }
 
#endif
 
    }
 

	
 
    void Timer::start() {
 
        m_ticks = getCurrentTicks();
 
    }
 
    unsigned int Timer::getElapsedMicroseconds() const {
 
        return static_cast<unsigned int>(getCurrentTicks() - m_ticks);
 
    }
 
    unsigned int Timer::getElapsedMilliseconds() const {
 
        return static_cast<unsigned int>(getElapsedMicroseconds()/1000);
 
    }
 
    double Timer::getElapsedSeconds() const {
 
        return getElapsedMicroseconds()/1000000.0;
 
    }
 

	
 
} // namespace Catch
 

	
 
#ifdef __clang__
 
#pragma clang diagnostic pop
 
#endif
 
// #included from: catch_common.hpp
 
#define TWOBLUECUBES_CATCH_COMMON_HPP_INCLUDED
 

	
 
#include <cstring>
 
#include <cctype>
 

	
 
namespace Catch {
 

	
 
    bool startsWith( std::string const& s, std::string const& prefix ) {
 
        return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin());
 
    }
 
    bool startsWith( std::string const& s, char prefix ) {
 
        return !s.empty() && s[0] == prefix;
 
    }
 
    bool endsWith( std::string const& s, std::string const& suffix ) {
 
        return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin());
 
    }
 
    bool endsWith( std::string const& s, char suffix ) {
 
        return !s.empty() && s[s.size()-1] == suffix;
 
    }
 
    bool contains( std::string const& s, std::string const& infix ) {
 
        return s.find( infix ) != std::string::npos;
 
    }
 
    char toLowerCh(char c) {
 
        return static_cast<char>( std::tolower( c ) );
 
    }
 
    void toLowerInPlace( std::string& s ) {
 
        std::transform( s.begin(), s.end(), s.begin(), toLowerCh );
 
    }
 
    std::string toLower( std::string const& s ) {
 
        std::string lc = s;
 
        toLowerInPlace( lc );
 
        return lc;
 
    }
 
    std::string trim( std::string const& str ) {
 
        static char const* whitespaceChars = "\n\r\t ";
 
        std::string::size_type start = str.find_first_not_of( whitespaceChars );
 
        std::string::size_type end = str.find_last_not_of( whitespaceChars );
 

	
 
        return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string();
 
    }
 

	
 
    bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) {
 
        bool replaced = false;
 
        std::size_t i = str.find( replaceThis );
 
        while( i != std::string::npos ) {
 
            replaced = true;
 
            str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() );
 
            if( i < str.size()-withThis.size() )
 
                i = str.find( replaceThis, i+withThis.size() );
 
            else
 
                i = std::string::npos;
 
        }
 
        return replaced;
 
    }
 

	
 
    pluralise::pluralise( std::size_t count, std::string const& label )
 
    :   m_count( count ),
 
        m_label( label )
 
    {}
 

	
 
    std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) {
 
        os << pluraliser.m_count << ' ' << pluraliser.m_label;
 
        if( pluraliser.m_count != 1 )
 
            os << 's';
 
        return os;
 
    }
 

	
 
    SourceLineInfo::SourceLineInfo() : file(""), line( 0 ){}
 
    SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line )
 
    :   file( _file ),
 
        line( _line )
 
    {}
 
    bool SourceLineInfo::empty() const {
 
        return file[0] == '\0';
 
    }
 
    bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const {
 
        return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0);
 
    }
 
    bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const {
 
        return line < other.line || ( line == other.line && (std::strcmp(file, other.file) < 0));
 
    }
 

	
 
    void seedRng( IConfig const& config ) {
 
        if( config.rngSeed() != 0 )
 
            std::srand( config.rngSeed() );
 
    }
 
    unsigned int rngSeed() {
 
        return getCurrentContext().getConfig()->rngSeed();
 
    }
 

	
 
    std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) {
 
#ifndef __GNUG__
 
        os << info.file << '(' << info.line << ')';
 
#else
 
        os << info.file << ':' << info.line;
 
#endif
 
        return os;
 
    }
 

	
 
    void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ) {
 
        std::ostringstream oss;
 
        oss << locationInfo << ": Internal Catch error: '" << message << '\'';
 
        if( alwaysTrue() )
 
            throw std::logic_error( oss.str() );
 
    }
 
}
 

	
 
// #included from: catch_section.hpp
 
#define TWOBLUECUBES_CATCH_SECTION_HPP_INCLUDED
 

	
 
namespace Catch {
 

	
 
    SectionInfo::SectionInfo
 
        (   SourceLineInfo const& _lineInfo,
 
            std::string const& _name,
 
            std::string const& _description )
 
    :   name( _name ),
 
        description( _description ),
 
        lineInfo( _lineInfo )
 
    {}
 

	
 
    Section::Section( SectionInfo const& info )
 
    :   m_info( info ),
 
        m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) )
 
    {
 
        m_timer.start();
 
    }
 

	
 
#if defined(_MSC_VER)
 
#pragma warning(push)
 
#pragma warning(disable:4996) // std::uncaught_exception is deprecated in C++17
 
#endif
 
    Section::~Section() {
 
        if( m_sectionIncluded ) {
 
            SectionEndInfo endInfo( m_info, m_assertions, m_timer.getElapsedSeconds() );
 
            if( std::uncaught_exception() )
 
                getResultCapture().sectionEndedEarly( endInfo );
 
            else
 
                getResultCapture().sectionEnded( endInfo );
 
        }
 
    }
 
#if defined(_MSC_VER)
 
#pragma warning(pop)
 
#endif
 

	
 
    // This indicates whether the section should be executed or not
 
    Section::operator bool() const {
 
        return m_sectionIncluded;
 
    }
 

	
 
} // end namespace Catch
 

	
 
// #included from: catch_debugger.hpp
 
#define TWOBLUECUBES_CATCH_DEBUGGER_HPP_INCLUDED
 

	
 
#ifdef CATCH_PLATFORM_MAC
 

	
 
    #include <assert.h>
 
    #include <stdbool.h>
 
    #include <sys/types.h>
 
    #include <unistd.h>
 
    #include <sys/sysctl.h>
 

	
 
    namespace Catch{
 

	
 
        // The following function is taken directly from the following technical note:
 
        // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html
 

	
 
        // Returns true if the current process is being debugged (either
 
        // running under the debugger or has a debugger attached post facto).
 
        bool isDebuggerActive(){
 

	
 
            int                 mib[4];
 
            struct kinfo_proc   info;
 
            size_t              size;
 

	
 
            // Initialize the flags so that, if sysctl fails for some bizarre
 
            // reason, we get a predictable result.
 

	
 
            info.kp_proc.p_flag = 0;
 

	
 
            // Initialize mib, which tells sysctl the info we want, in this case
 
            // we're looking for information about a specific process ID.
 

	
 
            mib[0] = CTL_KERN;
 
            mib[1] = KERN_PROC;
 
            mib[2] = KERN_PROC_PID;
 
            mib[3] = getpid();
 

	
 
            // Call sysctl.
 

	
 
            size = sizeof(info);
 
            if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, CATCH_NULL, 0) != 0 ) {
 
                Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl;
 
                return false;
 
            }
 

	
 
            // We're being debugged if the P_TRACED flag is set.
 

	
 
            return ( (info.kp_proc.p_flag & P_TRACED) != 0 );
 
        }
 
    } // namespace Catch
 

	
 
#elif defined(CATCH_PLATFORM_LINUX)
 
    #include <fstream>
 
    #include <string>
 

	
 
    namespace Catch{
 
        // The standard POSIX way of detecting a debugger is to attempt to
 
        // ptrace() the process, but this needs to be done from a child and not
 
        // this process itself to still allow attaching to this process later
 
        // if wanted, so is rather heavy. Under Linux we have the PID of the
 
        // "debugger" (which doesn't need to be gdb, of course, it could also
 
        // be strace, for example) in /proc/$PID/status, so just get it from
 
        // there instead.
 
        bool isDebuggerActive(){
 
            // Libstdc++ has a bug, where std::ifstream sets errno to 0
 
            // This way our users can properly assert over errno values
 
            ErrnoGuard guard;
 
            std::ifstream in("/proc/self/status");
 
            for( std::string line; std::getline(in, line); ) {
 
                static const int PREFIX_LEN = 11;
 
                if( line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0 ) {
 
                    // We're traced if the PID is not 0 and no other PID starts
 
                    // with 0 digit, so it's enough to check for just a single
 
                    // character.
 
                    return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0';
 
                }
 
            }
 

	
 
            return false;
 
        }
 
    } // namespace Catch
 
#elif defined(_MSC_VER)
 
    extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent();
 
    namespace Catch {
 
        bool isDebuggerActive() {
 
            return IsDebuggerPresent() != 0;
 
        }
 
    }
 
#elif defined(__MINGW32__)
 
    extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent();
 
    namespace Catch {
 
        bool isDebuggerActive() {
 
            return IsDebuggerPresent() != 0;
 
        }
 
    }
 
#else
 
    namespace Catch {
 
       inline bool isDebuggerActive() { return false; }
 
    }
 
#endif // Platform
 

	
 
#ifdef CATCH_PLATFORM_WINDOWS
 

	
 
    namespace Catch {
 
        void writeToDebugConsole( std::string const& text ) {
 
            ::OutputDebugStringA( text.c_str() );
 
        }
 
    }
 
#else
 
    namespace Catch {
 
        void writeToDebugConsole( std::string const& text ) {
 
            // !TBD: Need a version for Mac/ XCode and other IDEs
 
            Catch::cout() << text;
 
        }
 
    }
 
#endif // Platform
 

	
 
// #included from: catch_tostring.hpp
 
#define TWOBLUECUBES_CATCH_TOSTRING_HPP_INCLUDED
 

	
 
namespace Catch {
 

	
 
namespace Detail {
 

	
 
    const std::string unprintableString = "{?}";
 

	
 
    namespace {
 
        const int hexThreshold = 255;
 

	
 
        struct Endianness {
 
            enum Arch { Big, Little };
 

	
 
            static Arch which() {
 
                union _{
 
                    int asInt;
 
                    char asChar[sizeof (int)];
 
                } u;
 

	
 
                u.asInt = 1;
 
                return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little;
 
            }
 
        };
 
    }
 

	
 
    std::string rawMemoryToString( const void *object, std::size_t size )
 
    {
 
        // Reverse order for little endian architectures
 
        int i = 0, end = static_cast<int>( size ), inc = 1;
 
        if( Endianness::which() == Endianness::Little ) {
 
            i = end-1;
 
            end = inc = -1;
 
        }
 

	
 
        unsigned char const *bytes = static_cast<unsigned char const *>(object);
 
        std::ostringstream os;
 
        os << "0x" << std::setfill('0') << std::hex;
 
        for( ; i != end; i += inc )
 
             os << std::setw(2) << static_cast<unsigned>(bytes[i]);
 
       return os.str();
 
    }
 
}
 

	
 
std::string toString( std::string const& value ) {
 
    std::string s = value;
 
    if( getCurrentContext().getConfig()->showInvisibles() ) {
 
        for(size_t i = 0; i < s.size(); ++i ) {
 
            std::string subs;
 
            switch( s[i] ) {
 
            case '\n': subs = "\\n"; break;
 
            case '\t': subs = "\\t"; break;
 
            default: break;
 
            }
 
            if( !subs.empty() ) {
 
                s = s.substr( 0, i ) + subs + s.substr( i+1 );
 
                ++i;
 
            }
 
        }
 
    }
 
    return '"' + s + '"';
 
}
 
std::string toString( std::wstring const& value ) {
 

	
 
    std::string s;
 
    s.reserve( value.size() );
 
    for(size_t i = 0; i < value.size(); ++i )
 
        s += value[i] <= 0xff ? static_cast<char>( value[i] ) : '?';
 
    return Catch::toString( s );
 
}
 

	
 
std::string toString( const char* const value ) {
 
    return value ? Catch::toString( std::string( value ) ) : std::string( "{null string}" );
 
}
 

	
 
std::string toString( char* const value ) {
 
    return Catch::toString( static_cast<const char*>( value ) );
 
}
 

	
 
std::string toString( const wchar_t* const value )
 
{
 
    return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" );
 
}
 

	
 
std::string toString( wchar_t* const value )
 
{
 
    return Catch::toString( static_cast<const wchar_t*>( value ) );
 
}
 

	
 
std::string toString( int value ) {
 
    std::ostringstream oss;
 
    oss << value;
 
    if( value > Detail::hexThreshold )
 
        oss << " (0x" << std::hex << value << ')';
 
    return oss.str();
 
}
 

	
 
std::string toString( unsigned long value ) {
 
    std::ostringstream oss;
 
    oss << value;
 
    if( value > Detail::hexThreshold )
 
        oss << " (0x" << std::hex << value << ')';
 
    return oss.str();
 
}
 

	
 
std::string toString( unsigned int value ) {
 
    return Catch::toString( static_cast<unsigned long>( value ) );
 
}
 

	
 
template<typename T>
 
std::string fpToString( T value, int precision ) {
 
    std::ostringstream oss;
 
    oss << std::setprecision( precision )
 
        << std::fixed
 
        << value;
 
    std::string d = oss.str();
 
    std::size_t i = d.find_last_not_of( '0' );
 
    if( i != std::string::npos && i != d.size()-1 ) {
 
        if( d[i] == '.' )
 
            i++;
 
        d = d.substr( 0, i+1 );
 
    }
 
    return d;
 
}
 

	
 
std::string toString( const double value ) {
 
    return fpToString( value, 10 );
 
}
 
std::string toString( const float value ) {
 
    return fpToString( value, 5 ) + 'f';
 
}
 

	
 
std::string toString( bool value ) {
 
    return value ? "true" : "false";
 
}
 

	
 
std::string toString( char value ) {
 
    if ( value == '\r' )
 
        return "'\\r'";
 
    if ( value == '\f' )
 
        return "'\\f'";
 
    if ( value == '\n' )
 
        return "'\\n'";
 
    if ( value == '\t' )
 
        return "'\\t'";
 
    if ( '\0' <= value && value < ' ' )
 
        return toString( static_cast<unsigned int>( value ) );
 
    char chstr[] = "' '";
 
    chstr[1] = value;
 
    return chstr;
 
}
 

	
 
std::string toString( signed char value ) {
 
    return toString( static_cast<char>( value ) );
 
}
 

	
 
std::string toString( unsigned char value ) {
 
    return toString( static_cast<char>( value ) );
 
}
 

	
 
#ifdef CATCH_CONFIG_CPP11_LONG_LONG
 
std::string toString( long long value ) {
 
    std::ostringstream oss;
 
    oss << value;
 
    if( value > Detail::hexThreshold )
 
        oss << " (0x" << std::hex << value << ')';
 
    return oss.str();
 
}
 
std::string toString( unsigned long long value ) {
 
    std::ostringstream oss;
 
    oss << value;
 
    if( value > Detail::hexThreshold )
 
        oss << " (0x" << std::hex << value << ')';
 
    return oss.str();
 
}
 
#endif
 

	
 
#ifdef CATCH_CONFIG_CPP11_NULLPTR
 
std::string toString( std::nullptr_t ) {
 
    return "nullptr";
 
}
 
#endif
 

	
 
#ifdef __OBJC__
 
    std::string toString( NSString const * const& nsstring ) {
 
        if( !nsstring )
 
            return "nil";
 
        return "@" + toString([nsstring UTF8String]);
 
    }
 
    std::string toString( NSString * CATCH_ARC_STRONG & nsstring ) {
 
        if( !nsstring )
 
            return "nil";
 
        return "@" + toString([nsstring UTF8String]);
 
    }
 
    std::string toString( NSObject* const& nsObject ) {
 
        return toString( [nsObject description] );
 
    }
 
#endif
 

	
 
} // end namespace Catch
 

	
 
// #included from: catch_result_builder.hpp
 
#define TWOBLUECUBES_CATCH_RESULT_BUILDER_HPP_INCLUDED
 

	
 
namespace Catch {
 

	
 
    ResultBuilder::ResultBuilder(   char const* macroName,
 
                                    SourceLineInfo const& lineInfo,
 
                                    char const* capturedExpression,
 
                                    ResultDisposition::Flags resultDisposition,
 
                                    char const* secondArg )
 
    :   m_assertionInfo( macroName, lineInfo, capturedExpression, resultDisposition, secondArg ),
 
        m_shouldDebugBreak( false ),
 
        m_shouldThrow( false ),
 
        m_guardException( false )
 
    {
 
        m_stream().oss.str("");
 
    }
 

	
 
    ResultBuilder::~ResultBuilder() {
 
#if defined(CATCH_CONFIG_FAST_COMPILE)
 
        if ( m_guardException ) {
 
            m_stream().oss << "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE";
 
            captureResult( ResultWas::ThrewException );
 
            getCurrentContext().getResultCapture()->exceptionEarlyReported();
 
        }
 
#endif
 
    }
 

	
 
    ResultBuilder& ResultBuilder::setResultType( ResultWas::OfType result ) {
 
        m_data.resultType = result;
 
        return *this;
 
    }
 
    ResultBuilder& ResultBuilder::setResultType( bool result ) {
 
        m_data.resultType = result ? ResultWas::Ok : ResultWas::ExpressionFailed;
 
        return *this;
 
    }
 

	
 
    void ResultBuilder::endExpression( DecomposedExpression const& expr ) {
 
        AssertionResult result = build( expr );
 
        handleResult( result );
 
    }
 

	
 
    void ResultBuilder::useActiveException( ResultDisposition::Flags resultDisposition ) {
 
        m_assertionInfo.resultDisposition = resultDisposition;
 
        m_stream().oss << Catch::translateActiveException();
 
        captureResult( ResultWas::ThrewException );
 
    }
 

	
 
    void ResultBuilder::captureResult( ResultWas::OfType resultType ) {
 
        setResultType( resultType );
 
        captureExpression();
 
    }
 

	
 
    void ResultBuilder::captureExpectedException( std::string const& expectedMessage ) {
 
        if( expectedMessage.empty() )
 
            captureExpectedException( Matchers::Impl::MatchAllOf<std::string>() );
 
        else
 
            captureExpectedException( Matchers::Equals( expectedMessage ) );
 
    }
 

	
 
    void ResultBuilder::captureExpectedException( Matchers::Impl::MatcherBase<std::string> const& matcher ) {
 

	
 
        assert( !isFalseTest( m_assertionInfo.resultDisposition ) );
 
        AssertionResultData data = m_data;
 
        data.resultType = ResultWas::Ok;
 
        data.reconstructedExpression = capturedExpressionWithSecondArgument(m_assertionInfo.capturedExpression, m_assertionInfo.secondArg);
 

	
 
        std::string actualMessage = Catch::translateActiveException();
 
        if( !matcher.match( actualMessage ) ) {
 
            data.resultType = ResultWas::ExpressionFailed;
 
            data.reconstructedExpression = actualMessage;
 
        }
 
        AssertionResult result( m_assertionInfo, data );
 
        handleResult( result );
 
    }
 

	
 
    void ResultBuilder::captureExpression() {
 
        AssertionResult result = build();
 
        handleResult( result );
 
    }
 

	
 
    void ResultBuilder::handleResult( AssertionResult const& result )
 
    {
 
        getResultCapture().assertionEnded( result );
 

	
 
        if( !result.isOk() ) {
 
            if( getCurrentContext().getConfig()->shouldDebugBreak() )
 
                m_shouldDebugBreak = true;
 
            if( getCurrentContext().getRunner()->aborting() || (m_assertionInfo.resultDisposition & ResultDisposition::Normal) )
 
                m_shouldThrow = true;
 
        }
 
    }
 

	
 
    void ResultBuilder::react() {
 
#if defined(CATCH_CONFIG_FAST_COMPILE)
 
        if (m_shouldDebugBreak) {
 
            ///////////////////////////////////////////////////////////////////
 
            // To inspect the state during test, you need to go one level up the callstack
 
            // To go back to the test and change execution, jump over the throw statement
 
            ///////////////////////////////////////////////////////////////////
 
            CATCH_BREAK_INTO_DEBUGGER();
 
        }
 
#endif
 
        if( m_shouldThrow )
 
            throw Catch::TestFailureException();
 
    }
 

	
 
    bool ResultBuilder::shouldDebugBreak() const { return m_shouldDebugBreak; }
 
    bool ResultBuilder::allowThrows() const { return getCurrentContext().getConfig()->allowThrows(); }
 

	
 
    AssertionResult ResultBuilder::build() const
 
    {
 
        return build( *this );
 
    }
 

	
 
    // CAVEAT: The returned AssertionResult stores a pointer to the argument expr,
 
    //         a temporary DecomposedExpression, which in turn holds references to
 
    //         operands, possibly temporary as well.
 
    //         It should immediately be passed to handleResult; if the expression
 
    //         needs to be reported, its string expansion must be composed before
 
    //         the temporaries are destroyed.
 
    AssertionResult ResultBuilder::build( DecomposedExpression const& expr ) const
 
    {
 
        assert( m_data.resultType != ResultWas::Unknown );
 
        AssertionResultData data = m_data;
 

	
 
        // Flip bool results if FalseTest flag is set
 
        if( isFalseTest( m_assertionInfo.resultDisposition ) ) {
 
            data.negate( expr.isBinaryExpression() );
 
        }
 

	
 
        data.message = m_stream().oss.str();
 
        data.decomposedExpression = &expr; // for lazy reconstruction
 
        return AssertionResult( m_assertionInfo, data );
 
    }
 

	
 
    void ResultBuilder::reconstructExpression( std::string& dest ) const {
 
        dest = capturedExpressionWithSecondArgument(m_assertionInfo.capturedExpression, m_assertionInfo.secondArg);
 
    }
 

	
 
    void ResultBuilder::setExceptionGuard() {
 
        m_guardException = true;
 
    }
 
    void ResultBuilder::unsetExceptionGuard() {
 
        m_guardException = false;
 
    }
 

	
 
} // end namespace Catch
 

	
 
// #included from: catch_tag_alias_registry.hpp
 
#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED
 

	
 
namespace Catch {
 

	
 
    TagAliasRegistry::~TagAliasRegistry() {}
 

	
 
    Option<TagAlias> TagAliasRegistry::find( std::string const& alias ) const {
 
        std::map<std::string, TagAlias>::const_iterator it = m_registry.find( alias );
 
        if( it != m_registry.end() )
 
            return it->second;
 
        else
 
            return Option<TagAlias>();
 
    }
 

	
 
    std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const {
 
        std::string expandedTestSpec = unexpandedTestSpec;
 
        for( std::map<std::string, TagAlias>::const_iterator it = m_registry.begin(), itEnd = m_registry.end();
 
                it != itEnd;
 
                ++it ) {
 
            std::size_t pos = expandedTestSpec.find( it->first );
 
            if( pos != std::string::npos ) {
 
                expandedTestSpec =  expandedTestSpec.substr( 0, pos ) +
 
                                    it->second.tag +
 
                                    expandedTestSpec.substr( pos + it->first.size() );
 
            }
 
        }
 
        return expandedTestSpec;
 
    }
 

	
 
    void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) {
 

	
 
        if( !startsWith( alias, "[@" ) || !endsWith( alias, ']' ) ) {
 
            std::ostringstream oss;
 
            oss << Colour( Colour::Red )
 
                << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n"
 
                << Colour( Colour::FileName )
 
                << lineInfo << '\n';
 
            throw std::domain_error( oss.str().c_str() );
 
        }
 
        if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) {
 
            std::ostringstream oss;
 
            oss << Colour( Colour::Red )
 
                << "error: tag alias, \"" << alias << "\" already registered.\n"
 
                << "\tFirst seen at "
 
                << Colour( Colour::Red ) << find(alias)->lineInfo << '\n'
 
                << Colour( Colour::Red ) << "\tRedefined at "
 
                << Colour( Colour::FileName) << lineInfo << '\n';
 
            throw std::domain_error( oss.str().c_str() );
 
        }
 
    }
 

	
 
    ITagAliasRegistry::~ITagAliasRegistry() {}
 

	
 
    ITagAliasRegistry const& ITagAliasRegistry::get() {
 
        return getRegistryHub().getTagAliasRegistry();
 
    }
 

	
 
    RegistrarForTagAliases::RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) {
 
        getMutableRegistryHub().registerTagAlias( alias, tag, lineInfo );
 
    }
 

	
 
} // end namespace Catch
 

	
 
// #included from: catch_matchers_string.hpp
 

	
 
namespace Catch {
 
namespace Matchers {
 

	
 
    namespace StdString {
 

	
 
        CasedString::CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity )
 
        :   m_caseSensitivity( caseSensitivity ),
 
            m_str( adjustString( str ) )
 
        {}
 
        std::string CasedString::adjustString( std::string const& str ) const {
 
            return m_caseSensitivity == CaseSensitive::No
 
                   ? toLower( str )
 
                   : str;
 
        }
 
        std::string CasedString::caseSensitivitySuffix() const {
 
            return m_caseSensitivity == CaseSensitive::No
 
                   ? " (case insensitive)"
 
                   : std::string();
 
        }
 

	
 
        StringMatcherBase::StringMatcherBase( std::string const& operation, CasedString const& comparator )
 
        : m_comparator( comparator ),
 
          m_operation( operation ) {
 
        }
 

	
 
        std::string StringMatcherBase::describe() const {
 
            std::string description;
 
            description.reserve(5 + m_operation.size() + m_comparator.m_str.size() +
 
                                        m_comparator.caseSensitivitySuffix().size());
 
            description += m_operation;
 
            description += ": \"";
 
            description += m_comparator.m_str;
 
            description += "\"";
 
            description += m_comparator.caseSensitivitySuffix();
 
            return description;
 
        }
 

	
 
        EqualsMatcher::EqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals", comparator ) {}
 

	
 
        bool EqualsMatcher::match( std::string const& source ) const {
 
            return m_comparator.adjustString( source ) == m_comparator.m_str;
 
        }
 

	
 
        ContainsMatcher::ContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains", comparator ) {}
 

	
 
        bool ContainsMatcher::match( std::string const& source ) const {
 
            return contains( m_comparator.adjustString( source ), m_comparator.m_str );
 
        }
 

	
 
        StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with", comparator ) {}
 

	
 
        bool StartsWithMatcher::match( std::string const& source ) const {
 
            return startsWith( m_comparator.adjustString( source ), m_comparator.m_str );
 
        }
 

	
 
        EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with", comparator ) {}
 

	
 
        bool EndsWithMatcher::match( std::string const& source ) const {
 
            return endsWith( m_comparator.adjustString( source ), m_comparator.m_str );
 
        }
 

	
 
    } // namespace StdString
 

	
 
    StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity ) {
 
        return StdString::EqualsMatcher( StdString::CasedString( str, caseSensitivity) );
 
    }
 
    StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity ) {
 
        return StdString::ContainsMatcher( StdString::CasedString( str, caseSensitivity) );
 
    }
 
    StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) {
 
        return StdString::EndsWithMatcher( StdString::CasedString( str, caseSensitivity) );
 
    }
 
    StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) {
 
        return StdString::StartsWithMatcher( StdString::CasedString( str, caseSensitivity) );
 
    }
 

	
 
} // namespace Matchers
 
} // namespace Catch
 
// #included from: ../reporters/catch_reporter_multi.hpp
 
#define TWOBLUECUBES_CATCH_REPORTER_MULTI_HPP_INCLUDED
 

	
 
namespace Catch {
 

	
 
class MultipleReporters : public SharedImpl<IStreamingReporter> {
 
    typedef std::vector<Ptr<IStreamingReporter> > Reporters;
 
    Reporters m_reporters;
 

	
 
public:
 
    void add( Ptr<IStreamingReporter> const& reporter ) {
 
        m_reporters.push_back( reporter );
 
    }
 

	
 
public: // IStreamingReporter
 

	
 
    virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE {
 
        return m_reporters[0]->getPreferences();
 
    }
 

	
 
    virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE {
 
        for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
 
                it != itEnd;
 
                ++it )
 
            (*it)->noMatchingTestCases( spec );
 
    }
 

	
 
    virtual void testRunStarting( TestRunInfo const& testRunInfo ) CATCH_OVERRIDE {
 
        for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
 
                it != itEnd;
 
                ++it )
 
            (*it)->testRunStarting( testRunInfo );
 
    }
 

	
 
    virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE {
 
        for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
 
                it != itEnd;
 
                ++it )
 
            (*it)->testGroupStarting( groupInfo );
 
    }
 

	
 
    virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE {
 
        for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
 
                it != itEnd;
 
                ++it )
 
            (*it)->testCaseStarting( testInfo );
 
    }
 

	
 
    virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE {
 
        for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
 
                it != itEnd;
 
                ++it )
 
            (*it)->sectionStarting( sectionInfo );
 
    }
 

	
 
    virtual void assertionStarting( AssertionInfo const& assertionInfo ) CATCH_OVERRIDE {
 
        for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
 
                it != itEnd;
 
                ++it )
 
            (*it)->assertionStarting( assertionInfo );
 
    }
 

	
 
    // The return value indicates if the messages buffer should be cleared:
 
    virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE {
 
        bool clearBuffer = false;
 
        for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
 
                it != itEnd;
 
                ++it )
 
            clearBuffer |= (*it)->assertionEnded( assertionStats );
 
        return clearBuffer;
 
    }
 

	
 
    virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE {
 
        for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
 
                it != itEnd;
 
                ++it )
 
            (*it)->sectionEnded( sectionStats );
 
    }
 

	
 
    virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE {
 
        for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
 
                it != itEnd;
 
                ++it )
 
            (*it)->testCaseEnded( testCaseStats );
 
    }
 

	
 
    virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE {
 
        for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
 
                it != itEnd;
 
                ++it )
 
            (*it)->testGroupEnded( testGroupStats );
 
    }
 

	
 
    virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE {
 
        for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
 
                it != itEnd;
 
                ++it )
 
            (*it)->testRunEnded( testRunStats );
 
    }
 

	
 
    virtual void skipTest( TestCaseInfo const& testInfo ) CATCH_OVERRIDE {
 
        for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
 
                it != itEnd;
 
                ++it )
 
            (*it)->skipTest( testInfo );
 
    }
 

	
 
    virtual MultipleReporters* tryAsMulti() CATCH_OVERRIDE {
 
        return this;
 
    }
 

	
 
};
 

	
 
Ptr<IStreamingReporter> addReporter( Ptr<IStreamingReporter> const& existingReporter, Ptr<IStreamingReporter> const& additionalReporter ) {
 
    Ptr<IStreamingReporter> resultingReporter;
 

	
 
    if( existingReporter ) {
 
        MultipleReporters* multi = existingReporter->tryAsMulti();
 
        if( !multi ) {
 
            multi = new MultipleReporters;
 
            resultingReporter = Ptr<IStreamingReporter>( multi );
 
            if( existingReporter )
 
                multi->add( existingReporter );
 
        }
 
        else
 
            resultingReporter = existingReporter;
 
        multi->add( additionalReporter );
 
    }
 
    else
 
        resultingReporter = additionalReporter;
 

	
 
    return resultingReporter;
 
}
 

	
 
} // end namespace Catch
 

	
 
// #included from: ../reporters/catch_reporter_xml.hpp
 
#define TWOBLUECUBES_CATCH_REPORTER_XML_HPP_INCLUDED
 

	
 
// #included from: catch_reporter_bases.hpp
 
#define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED
 

	
 
#include <cstring>
 
#include <cfloat>
 
#include <cstdio>
 
#include <assert.h>
 

	
 
namespace Catch {
 

	
 
    namespace {
 
        // Because formatting using c++ streams is stateful, drop down to C is required
 
        // Alternatively we could use stringstream, but its performance is... not good.
 
        std::string getFormattedDuration( double duration ) {
 
            // Max exponent + 1 is required to represent the whole part
 
            // + 1 for decimal point
 
            // + 3 for the 3 decimal places
 
            // + 1 for null terminator
 
            const size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1;
 
            char buffer[maxDoubleSize];
 

	
 
            // Save previous errno, to prevent sprintf from overwriting it
 
            ErrnoGuard guard;
 
#ifdef _MSC_VER
 
            sprintf_s(buffer, "%.3f", duration);
 
#else
 
            sprintf(buffer, "%.3f", duration);
 
#endif
 
            return std::string(buffer);
 
        }
 
    }
 

	
 
    struct StreamingReporterBase : SharedImpl<IStreamingReporter> {
 

	
 
        StreamingReporterBase( ReporterConfig const& _config )
 
        :   m_config( _config.fullConfig() ),
 
            stream( _config.stream() )
 
        {
 
            m_reporterPrefs.shouldRedirectStdOut = false;
 
        }
 

	
 
        virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE {
 
            return m_reporterPrefs;
 
        }
 

	
 
        virtual ~StreamingReporterBase() CATCH_OVERRIDE;
 

	
 
        virtual void noMatchingTestCases( std::string const& ) CATCH_OVERRIDE {}
 

	
 
        virtual void testRunStarting( TestRunInfo const& _testRunInfo ) CATCH_OVERRIDE {
 
            currentTestRunInfo = _testRunInfo;
 
        }
 
        virtual void testGroupStarting( GroupInfo const& _groupInfo ) CATCH_OVERRIDE {
 
            currentGroupInfo = _groupInfo;
 
        }
 

	
 
        virtual void testCaseStarting( TestCaseInfo const& _testInfo ) CATCH_OVERRIDE {
 
            currentTestCaseInfo = _testInfo;
 
        }
 
        virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE {
 
            m_sectionStack.push_back( _sectionInfo );
 
        }
 

	
 
        virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) CATCH_OVERRIDE {
 
            m_sectionStack.pop_back();
 
        }
 
        virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) CATCH_OVERRIDE {
 
            currentTestCaseInfo.reset();
 
        }
 
        virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) CATCH_OVERRIDE {
 
            currentGroupInfo.reset();
 
        }
 
        virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) CATCH_OVERRIDE {
 
            currentTestCaseInfo.reset();
 
            currentGroupInfo.reset();
 
            currentTestRunInfo.reset();
 
        }
 

	
 
        virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE {
 
            // Don't do anything with this by default.
 
            // It can optionally be overridden in the derived class.
 
        }
 

	
 
        Ptr<IConfig const> m_config;
 
        std::ostream& stream;
 

	
 
        LazyStat<TestRunInfo> currentTestRunInfo;
 
        LazyStat<GroupInfo> currentGroupInfo;
 
        LazyStat<TestCaseInfo> currentTestCaseInfo;
 

	
 
        std::vector<SectionInfo> m_sectionStack;
 
        ReporterPreferences m_reporterPrefs;
 
    };
 

	
 
    struct CumulativeReporterBase : SharedImpl<IStreamingReporter> {
 
        template<typename T, typename ChildNodeT>
 
        struct Node : SharedImpl<> {
 
            explicit Node( T const& _value ) : value( _value ) {}
 
            virtual ~Node() {}
 

	
 
            typedef std::vector<Ptr<ChildNodeT> > ChildNodes;
 
            T value;
 
            ChildNodes children;
 
        };
 
        struct SectionNode : SharedImpl<> {
 
            explicit SectionNode( SectionStats const& _stats ) : stats( _stats ) {}
 
            virtual ~SectionNode();
 

	
 
            bool operator == ( SectionNode const& other ) const {
 
                return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo;
 
            }
 
            bool operator == ( Ptr<SectionNode> const& other ) const {
 
                return operator==( *other );
 
            }
 

	
 
            SectionStats stats;
 
            typedef std::vector<Ptr<SectionNode> > ChildSections;
 
            typedef std::vector<AssertionStats> Assertions;
 
            ChildSections childSections;
 
            Assertions assertions;
 
            std::string stdOut;
 
            std::string stdErr;
 
        };
 

	
 
        struct BySectionInfo {
 
            BySectionInfo( SectionInfo const& other ) : m_other( other ) {}
 
            BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {}
 
            bool operator() ( Ptr<SectionNode> const& node ) const {
 
                return node->stats.sectionInfo.lineInfo == m_other.lineInfo;
 
            }
 
        private:
 
            void operator=( BySectionInfo const& );
 
            SectionInfo const& m_other;
 
        };
 

	
 
        typedef Node<TestCaseStats, SectionNode> TestCaseNode;
 
        typedef Node<TestGroupStats, TestCaseNode> TestGroupNode;
 
        typedef Node<TestRunStats, TestGroupNode> TestRunNode;
 

	
 
        CumulativeReporterBase( ReporterConfig const& _config )
 
        :   m_config( _config.fullConfig() ),
 
            stream( _config.stream() )
 
        {
 
            m_reporterPrefs.shouldRedirectStdOut = false;
 
        }
 
        ~CumulativeReporterBase();
 

	
 
        virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE {
 
            return m_reporterPrefs;
 
        }
 

	
 
        virtual void testRunStarting( TestRunInfo const& ) CATCH_OVERRIDE {}
 
        virtual void testGroupStarting( GroupInfo const& ) CATCH_OVERRIDE {}
 

	
 
        virtual void testCaseStarting( TestCaseInfo const& ) CATCH_OVERRIDE {}
 

	
 
        virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE {
 
            SectionStats incompleteStats( sectionInfo, Counts(), 0, false );
 
            Ptr<SectionNode> node;
 
            if( m_sectionStack.empty() ) {
 
                if( !m_rootSection )
 
                    m_rootSection = new SectionNode( incompleteStats );
 
                node = m_rootSection;
 
            }
 
            else {
 
                SectionNode& parentNode = *m_sectionStack.back();
 
                SectionNode::ChildSections::const_iterator it =
 
                    std::find_if(   parentNode.childSections.begin(),
 
                                    parentNode.childSections.end(),
 
                                    BySectionInfo( sectionInfo ) );
 
                if( it == parentNode.childSections.end() ) {
 
                    node = new SectionNode( incompleteStats );
 
                    parentNode.childSections.push_back( node );
 
                }
 
                else
 
                    node = *it;
 
            }
 
            m_sectionStack.push_back( node );
 
            m_deepestSection = node;
 
        }
 

	
 
        virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {}
 

	
 
        virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE {
 
            assert( !m_sectionStack.empty() );
 
            SectionNode& sectionNode = *m_sectionStack.back();
 
            sectionNode.assertions.push_back( assertionStats );
 
            // AssertionResult holds a pointer to a temporary DecomposedExpression,
 
            // which getExpandedExpression() calls to build the expression string.
 
            // Our section stack copy of the assertionResult will likely outlive the
 
            // temporary, so it must be expanded or discarded now to avoid calling
 
            // a destroyed object later.
 
            prepareExpandedExpression( sectionNode.assertions.back().assertionResult );
 
            return true;
 
        }
 
        virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE {
 
            assert( !m_sectionStack.empty() );
 
            SectionNode& node = *m_sectionStack.back();
 
            node.stats = sectionStats;
 
            m_sectionStack.pop_back();
 
        }
 
        virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE {
 
            Ptr<TestCaseNode> node = new TestCaseNode( testCaseStats );
 
            assert( m_sectionStack.size() == 0 );
 
            node->children.push_back( m_rootSection );
 
            m_testCases.push_back( node );
 
            m_rootSection.reset();
 

	
 
            assert( m_deepestSection );
 
            m_deepestSection->stdOut = testCaseStats.stdOut;
 
            m_deepestSection->stdErr = testCaseStats.stdErr;
 
        }
 
        virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE {
 
            Ptr<TestGroupNode> node = new TestGroupNode( testGroupStats );
 
            node->children.swap( m_testCases );
 
            m_testGroups.push_back( node );
 
        }
 
        virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE {
 
            Ptr<TestRunNode> node = new TestRunNode( testRunStats );
 
            node->children.swap( m_testGroups );
 
            m_testRuns.push_back( node );
 
            testRunEndedCumulative();
 
        }
 
        virtual void testRunEndedCumulative() = 0;
 

	
 
        virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE {}
 

	
 
        virtual void prepareExpandedExpression( AssertionResult& result ) const {
 
            if( result.isOk() )
 
                result.discardDecomposedExpression();
 
            else
 
                result.expandDecomposedExpression();
 
        }
 

	
 
        Ptr<IConfig const> m_config;
 
        std::ostream& stream;
 
        std::vector<AssertionStats> m_assertions;
 
        std::vector<std::vector<Ptr<SectionNode> > > m_sections;
 
        std::vector<Ptr<TestCaseNode> > m_testCases;
 
        std::vector<Ptr<TestGroupNode> > m_testGroups;
 

	
 
        std::vector<Ptr<TestRunNode> > m_testRuns;
 

	
 
        Ptr<SectionNode> m_rootSection;
 
        Ptr<SectionNode> m_deepestSection;
 
        std::vector<Ptr<SectionNode> > m_sectionStack;
 
        ReporterPreferences m_reporterPrefs;
 

	
 
    };
 

	
 
    template<char C>
 
    char const* getLineOfChars() {
 
        static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0};
 
        if( !*line ) {
 
            std::memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 );
 
            line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0;
 
        }
 
        return line;
 
    }
 

	
 
    struct TestEventListenerBase : StreamingReporterBase {
 
        TestEventListenerBase( ReporterConfig const& _config )
 
        :   StreamingReporterBase( _config )
 
        {}
 

	
 
        virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {}
 
        virtual bool assertionEnded( AssertionStats const& ) CATCH_OVERRIDE {
 
            return false;
 
        }
 
    };
 

	
 
} // end namespace Catch
 

	
 
// #included from: ../internal/catch_reporter_registrars.hpp
 
#define TWOBLUECUBES_CATCH_REPORTER_REGISTRARS_HPP_INCLUDED
 

	
 
namespace Catch {
 

	
 
    template<typename T>
 
    class LegacyReporterRegistrar {
 

	
 
        class ReporterFactory : public IReporterFactory {
 
            virtual IStreamingReporter* create( ReporterConfig const& config ) const {
 
                return new LegacyReporterAdapter( new T( config ) );
 
            }
 

	
 
            virtual std::string getDescription() const {
 
                return T::getDescription();
 
            }
 
        };
 

	
 
    public:
 

	
 
        LegacyReporterRegistrar( std::string const& name ) {
 
            getMutableRegistryHub().registerReporter( name, new ReporterFactory() );
 
        }
 
    };
 

	
 
    template<typename T>
 
    class ReporterRegistrar {
 

	
 
        class ReporterFactory : public SharedImpl<IReporterFactory> {
 

	
 
            // *** Please Note ***:
 
            // - If you end up here looking at a compiler error because it's trying to register
 
            // your custom reporter class be aware that the native reporter interface has changed
 
            // to IStreamingReporter. The "legacy" interface, IReporter, is still supported via
 
            // an adapter. Just use REGISTER_LEGACY_REPORTER to take advantage of the adapter.
 
            // However please consider updating to the new interface as the old one is now
 
            // deprecated and will probably be removed quite soon!
 
            // Please contact me via github if you have any questions at all about this.
 
            // In fact, ideally, please contact me anyway to let me know you've hit this - as I have
 
            // no idea who is actually using custom reporters at all (possibly no-one!).
 
            // The new interface is designed to minimise exposure to interface changes in the future.
 
            virtual IStreamingReporter* create( ReporterConfig const& config ) const {
 
                return new T( config );
 
            }
 

	
 
            virtual std::string getDescription() const {
 
                return T::getDescription();
 
            }
 
        };
 

	
 
    public:
 

	
 
        ReporterRegistrar( std::string const& name ) {
 
            getMutableRegistryHub().registerReporter( name, new ReporterFactory() );
 
        }
 
    };
 

	
 
    template<typename T>
 
    class ListenerRegistrar {
 

	
 
        class ListenerFactory : public SharedImpl<IReporterFactory> {
 

	
 
            virtual IStreamingReporter* create( ReporterConfig const& config ) const {
 
                return new T( config );
 
            }
 
            virtual std::string getDescription() const {
 
                return std::string();
 
            }
 
        };
 

	
 
    public:
 

	
 
        ListenerRegistrar() {
 
            getMutableRegistryHub().registerListener( new ListenerFactory() );
 
        }
 
    };
 
}
 

	
 
#define INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) \
 
    namespace{ Catch::LegacyReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); }
 

	
 
#define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \
 
    namespace{ Catch::ReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); }
 

	
 
// Deprecated - use the form without INTERNAL_
 
#define INTERNAL_CATCH_REGISTER_LISTENER( listenerType ) \
 
    namespace{ Catch::ListenerRegistrar<listenerType> catch_internal_RegistrarFor##listenerType; }
 

	
 
#define CATCH_REGISTER_LISTENER( listenerType ) \
 
    namespace{ Catch::ListenerRegistrar<listenerType> catch_internal_RegistrarFor##listenerType; }
 

	
 
// #included from: ../internal/catch_xmlwriter.hpp
 
#define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED
 

	
 
#include <sstream>
 
#include <string>
 
#include <vector>
 
#include <iomanip>
 

	
 
namespace Catch {
 

	
 
    class XmlEncode {
 
    public:
 
        enum ForWhat { ForTextNodes, ForAttributes };
 

	
 
        XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes )
 
        :   m_str( str ),
 
            m_forWhat( forWhat )
 
        {}
 

	
 
        void encodeTo( std::ostream& os ) const {
 

	
 
            // Apostrophe escaping not necessary if we always use " to write attributes
 
            // (see: http://www.w3.org/TR/xml/#syntax)
 

	
 
            for( std::size_t i = 0; i < m_str.size(); ++ i ) {
 
                char c = m_str[i];
 
                switch( c ) {
 
                    case '<':   os << "&lt;"; break;
 
                    case '&':   os << "&amp;"; break;
 

	
 
                    case '>':
 
                        // See: http://www.w3.org/TR/xml/#syntax
 
                        if( i > 2 && m_str[i-1] == ']' && m_str[i-2] == ']' )
 
                            os << "&gt;";
 
                        else
 
                            os << c;
 
                        break;
 

	
 
                    case '\"':
 
                        if( m_forWhat == ForAttributes )
 
                            os << "&quot;";
 
                        else
 
                            os << c;
 
                        break;
 

	
 
                    default:
 
                        // Escape control chars - based on contribution by @espenalb in PR #465 and
 
                        // by @mrpi PR #588
 
                        if ( ( c >= 0 && c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' ) {
 
                            // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0
 
                            os << "\\x" << std::uppercase << std::hex << std::setfill('0') << std::setw(2)
 
                               << static_cast<int>( c );
 
                        }
 
                        else
 
                            os << c;
 
                }
 
            }
 
        }
 

	
 
        friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) {
 
            xmlEncode.encodeTo( os );
 
            return os;
 
        }
 

	
 
    private:
 
        std::string m_str;
 
        ForWhat m_forWhat;
 
    };
 

	
 
    class XmlWriter {
 
    public:
 

	
 
        class ScopedElement {
 
        public:
 
            ScopedElement( XmlWriter* writer )
 
            :   m_writer( writer )
 
            {}
 

	
 
            ScopedElement( ScopedElement const& other )
 
            :   m_writer( other.m_writer ){
 
                other.m_writer = CATCH_NULL;
 
            }
 

	
 
            ~ScopedElement() {
 
                if( m_writer )
 
                    m_writer->endElement();
 
            }
 

	
 
            ScopedElement& writeText( std::string const& text, bool indent = true ) {
 
                m_writer->writeText( text, indent );
 
                return *this;
 
            }
 

	
 
            template<typename T>
 
            ScopedElement& writeAttribute( std::string const& name, T const& attribute ) {
 
                m_writer->writeAttribute( name, attribute );
 
                return *this;
 
            }
 

	
 
        private:
 
            mutable XmlWriter* m_writer;
 
        };
 

	
 
        XmlWriter()
 
        :   m_tagIsOpen( false ),
 
            m_needsNewline( false ),
 
            m_os( Catch::cout() )
 
        {
 
            writeDeclaration();
 
        }
 

	
 
        XmlWriter( std::ostream& os )
 
        :   m_tagIsOpen( false ),
 
            m_needsNewline( false ),
 
            m_os( os )
 
        {
 
            writeDeclaration();
 
        }
 

	
 
        ~XmlWriter() {
 
            while( !m_tags.empty() )
 
                endElement();
 
        }
 

	
 
        XmlWriter& startElement( std::string const& name ) {
 
            ensureTagClosed();
 
            newlineIfNecessary();
 
            m_os << m_indent << '<' << name;
 
            m_tags.push_back( name );
 
            m_indent += "  ";
 
            m_tagIsOpen = true;
 
            return *this;
 
        }
 

	
 
        ScopedElement scopedElement( std::string const& name ) {
 
            ScopedElement scoped( this );
 
            startElement( name );
 
            return scoped;
 
        }
 

	
 
        XmlWriter& endElement() {
 
            newlineIfNecessary();
 
            m_indent = m_indent.substr( 0, m_indent.size()-2 );
 
            if( m_tagIsOpen ) {
 
                m_os << "/>";
 
                m_tagIsOpen = false;
 
            }
 
            else {
 
                m_os << m_indent << "</" << m_tags.back() << ">";
 
            }
 
            m_os << std::endl;
 
            m_tags.pop_back();
 
            return *this;
 
        }
 

	
 
        XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) {
 
            if( !name.empty() && !attribute.empty() )
 
                m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"';
 
            return *this;
 
        }
 

	
 
        XmlWriter& writeAttribute( std::string const& name, bool attribute ) {
 
            m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"';
 
            return *this;
 
        }
 

	
 
        template<typename T>
 
        XmlWriter& writeAttribute( std::string const& name, T const& attribute ) {
 
            std::ostringstream oss;
 
            oss << attribute;
 
            return writeAttribute( name, oss.str() );
 
        }
 

	
 
        XmlWriter& writeText( std::string const& text, bool indent = true ) {
 
            if( !text.empty() ){
 
                bool tagWasOpen = m_tagIsOpen;
 
                ensureTagClosed();
 
                if( tagWasOpen && indent )
 
                    m_os << m_indent;
 
                m_os << XmlEncode( text );
 
                m_needsNewline = true;
 
            }
 
            return *this;
 
        }
 

	
 
        XmlWriter& writeComment( std::string const& text ) {
 
            ensureTagClosed();
 
            m_os << m_indent << "<!--" << text << "-->";
 
            m_needsNewline = true;
 
            return *this;
 
        }
 

	
 
        void writeStylesheetRef( std::string const& url ) {
 
            m_os << "<?xml-stylesheet type=\"text/xsl\" href=\"" << url << "\"?>\n";
 
        }
 

	
 
        XmlWriter& writeBlankLine() {
 
            ensureTagClosed();
 
            m_os << '\n';
 
            return *this;
 
        }
 

	
 
        void ensureTagClosed() {
 
            if( m_tagIsOpen ) {
 
                m_os << ">" << std::endl;
 
                m_tagIsOpen = false;
 
            }
 
        }
 

	
 
    private:
 
        XmlWriter( XmlWriter const& );
 
        void operator=( XmlWriter const& );
 

	
 
        void writeDeclaration() {
 
            m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
 
        }
 

	
 
        void newlineIfNecessary() {
 
            if( m_needsNewline ) {
 
                m_os << std::endl;
 
                m_needsNewline = false;
 
            }
 
        }
 

	
 
        bool m_tagIsOpen;
 
        bool m_needsNewline;
 
        std::vector<std::string> m_tags;
 
        std::string m_indent;
 
        std::ostream& m_os;
 
    };
 

	
 
}
 

	
 
namespace Catch {
 
    class XmlReporter : public StreamingReporterBase {
 
    public:
 
        XmlReporter( ReporterConfig const& _config )
 
        :   StreamingReporterBase( _config ),
 
            m_xml(_config.stream()),
 
            m_sectionDepth( 0 )
 
        {
 
            m_reporterPrefs.shouldRedirectStdOut = true;
 
        }
 

	
 
        virtual ~XmlReporter() CATCH_OVERRIDE;
 

	
 
        static std::string getDescription() {
 
            return "Reports test results as an XML document";
 
        }
 

	
 
        virtual std::string getStylesheetRef() const {
 
            return std::string();
 
        }
 

	
 
        void writeSourceInfo( SourceLineInfo const& sourceInfo ) {
 
            m_xml
 
                .writeAttribute( "filename", sourceInfo.file )
 
                .writeAttribute( "line", sourceInfo.line );
 
        }
 

	
 
    public: // StreamingReporterBase
 

	
 
        virtual void noMatchingTestCases( std::string const& s ) CATCH_OVERRIDE {
 
            StreamingReporterBase::noMatchingTestCases( s );
 
        }
 

	
 
        virtual void testRunStarting( TestRunInfo const& testInfo ) CATCH_OVERRIDE {
 
            StreamingReporterBase::testRunStarting( testInfo );
 
            std::string stylesheetRef = getStylesheetRef();
 
            if( !stylesheetRef.empty() )
 
                m_xml.writeStylesheetRef( stylesheetRef );
 
            m_xml.startElement( "Catch" );
 
            if( !m_config->name().empty() )
 
                m_xml.writeAttribute( "name", m_config->name() );
 
        }
 

	
 
        virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE {
 
            StreamingReporterBase::testGroupStarting( groupInfo );
 
            m_xml.startElement( "Group" )
 
                .writeAttribute( "name", groupInfo.name );
 
        }
 

	
 
        virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE {
 
            StreamingReporterBase::testCaseStarting(testInfo);
 
            m_xml.startElement( "TestCase" )
 
                .writeAttribute( "name", trim( testInfo.name ) )
 
                .writeAttribute( "description", testInfo.description )
 
                .writeAttribute( "tags", testInfo.tagsAsString );
 

	
 
            writeSourceInfo( testInfo.lineInfo );
 

	
 
            if ( m_config->showDurations() == ShowDurations::Always )
 
                m_testCaseTimer.start();
 
            m_xml.ensureTagClosed();
 
        }
 

	
 
        virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE {
 
            StreamingReporterBase::sectionStarting( sectionInfo );
 
            if( m_sectionDepth++ > 0 ) {
 
                m_xml.startElement( "Section" )
 
                    .writeAttribute( "name", trim( sectionInfo.name ) )
 
                    .writeAttribute( "description", sectionInfo.description );
 
                writeSourceInfo( sectionInfo.lineInfo );
 
                m_xml.ensureTagClosed();
 
            }
 
        }
 

	
 
        virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { }
 

	
 
        virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE {
 

	
 
            AssertionResult const& result = assertionStats.assertionResult;
 

	
 
            bool includeResults = m_config->includeSuccessfulResults() || !result.isOk();
 

	
 
            if( includeResults ) {
 
                // Print any info messages in <Info> tags.
 
                for( std::vector<MessageInfo>::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end();
 
                     it != itEnd;
 
                     ++it ) {
 
                    if( it->type == ResultWas::Info ) {
 
                        m_xml.scopedElement( "Info" )
 
                                .writeText( it->message );
 
                    } else if ( it->type == ResultWas::Warning ) {
 
                        m_xml.scopedElement( "Warning" )
 
                                .writeText( it->message );
 
                    }
 
                }
 
            }
 

	
 
            // Drop out if result was successful but we're not printing them.
 
            if( !includeResults && result.getResultType() != ResultWas::Warning )
 
                return true;
 

	
 
            // Print the expression if there is one.
 
            if( result.hasExpression() ) {
 
                m_xml.startElement( "Expression" )
 
                    .writeAttribute( "success", result.succeeded() )
 
                    .writeAttribute( "type", result.getTestMacroName() );
 

	
 
                writeSourceInfo( result.getSourceInfo() );
 

	
 
                m_xml.scopedElement( "Original" )
 
                    .writeText( result.getExpression() );
 
                m_xml.scopedElement( "Expanded" )
 
                    .writeText( result.getExpandedExpression() );
 
            }
 

	
 
            // And... Print a result applicable to each result type.
 
            switch( result.getResultType() ) {
 
                case ResultWas::ThrewException:
 
                    m_xml.startElement( "Exception" );
 
                    writeSourceInfo( result.getSourceInfo() );
 
                    m_xml.writeText( result.getMessage() );
 
                    m_xml.endElement();
 
                    break;
 
                case ResultWas::FatalErrorCondition:
 
                    m_xml.startElement( "FatalErrorCondition" );
 
                    writeSourceInfo( result.getSourceInfo() );
 
                    m_xml.writeText( result.getMessage() );
 
                    m_xml.endElement();
 
                    break;
 
                case ResultWas::Info:
 
                    m_xml.scopedElement( "Info" )
 
                        .writeText( result.getMessage() );
 
                    break;
 
                case ResultWas::Warning:
 
                    // Warning will already have been written
 
                    break;
 
                case ResultWas::ExplicitFailure:
 
                    m_xml.startElement( "Failure" );
 
                    writeSourceInfo( result.getSourceInfo() );
 
                    m_xml.writeText( result.getMessage() );
 
                    m_xml.endElement();
 
                    break;
 
                default:
 
                    break;
 
            }
 

	
 
            if( result.hasExpression() )
 
                m_xml.endElement();
 

	
 
            return true;
 
        }
 

	
 
        virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE {
 
            StreamingReporterBase::sectionEnded( sectionStats );
 
            if( --m_sectionDepth > 0 ) {
 
                XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" );
 
                e.writeAttribute( "successes", sectionStats.assertions.passed );
 
                e.writeAttribute( "failures", sectionStats.assertions.failed );
 
                e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk );
 

	
 
                if ( m_config->showDurations() == ShowDurations::Always )
 
                    e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds );
 

	
 
                m_xml.endElement();
 
            }
 
        }
 

	
 
        virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE {
 
            StreamingReporterBase::testCaseEnded( testCaseStats );
 
            XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" );
 
            e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() );
 

	
 
            if ( m_config->showDurations() == ShowDurations::Always )
 
                e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() );
 

	
 
            if( !testCaseStats.stdOut.empty() )
 
                m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), false );
 
            if( !testCaseStats.stdErr.empty() )
 
                m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), false );
 

	
 
            m_xml.endElement();
 
        }
 

	
 
        virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE {
 
            StreamingReporterBase::testGroupEnded( testGroupStats );
 
            // TODO: Check testGroupStats.aborting and act accordingly.
 
            m_xml.scopedElement( "OverallResults" )
 
                .writeAttribute( "successes", testGroupStats.totals.assertions.passed )
 
                .writeAttribute( "failures", testGroupStats.totals.assertions.failed )
 
                .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk );
 
            m_xml.endElement();
 
        }
 

	
 
        virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE {
 
            StreamingReporterBase::testRunEnded( testRunStats );
 
            m_xml.scopedElement( "OverallResults" )
 
                .writeAttribute( "successes", testRunStats.totals.assertions.passed )
 
                .writeAttribute( "failures", testRunStats.totals.assertions.failed )
 
                .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk );
 
            m_xml.endElement();
 
        }
 

	
 
    private:
 
        Timer m_testCaseTimer;
 
        XmlWriter m_xml;
 
        int m_sectionDepth;
 
    };
 

	
 
     INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter )
 

	
 
} // end namespace Catch
 

	
 
// #included from: ../reporters/catch_reporter_junit.hpp
 
#define TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED
 

	
 
#include <assert.h>
 

	
 
namespace Catch {
 

	
 
    namespace {
 
        std::string getCurrentTimestamp() {
 
            // Beware, this is not reentrant because of backward compatibility issues
 
            // Also, UTC only, again because of backward compatibility (%z is C++11)
 
            time_t rawtime;
 
            std::time(&rawtime);
 
            const size_t timeStampSize = sizeof("2017-01-16T17:06:45Z");
 

	
 
#ifdef _MSC_VER
 
            std::tm timeInfo = {};
 
            gmtime_s(&timeInfo, &rawtime);
 
#else
 
            std::tm* timeInfo;
 
            timeInfo = std::gmtime(&rawtime);
 
#endif
 

	
 
            char timeStamp[timeStampSize];
 
            const char * const fmt = "%Y-%m-%dT%H:%M:%SZ";
 

	
 
#ifdef _MSC_VER
 
            std::strftime(timeStamp, timeStampSize, fmt, &timeInfo);
 
#else
 
            std::strftime(timeStamp, timeStampSize, fmt, timeInfo);
 
#endif
 
            return std::string(timeStamp);
 
        }
 

	
 
    }
 

	
 
    class JunitReporter : public CumulativeReporterBase {
 
    public:
 
        JunitReporter( ReporterConfig const& _config )
 
        :   CumulativeReporterBase( _config ),
 
            xml( _config.stream() ),
 
            m_okToFail( false )
 
        {
 
            m_reporterPrefs.shouldRedirectStdOut = true;
 
        }
 

	
 
        virtual ~JunitReporter() CATCH_OVERRIDE;
 

	
 
        static std::string getDescription() {
 
            return "Reports test results in an XML format that looks like Ant's junitreport target";
 
        }
 

	
 
        virtual void noMatchingTestCases( std::string const& /*spec*/ ) CATCH_OVERRIDE {}
 

	
 
        virtual void testRunStarting( TestRunInfo const& runInfo ) CATCH_OVERRIDE {
 
            CumulativeReporterBase::testRunStarting( runInfo );
 
            xml.startElement( "testsuites" );
 
        }
 

	
 
        virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE {
 
            suiteTimer.start();
 
            stdOutForSuite.str("");
 
            stdErrForSuite.str("");
 
            unexpectedExceptions = 0;
 
            CumulativeReporterBase::testGroupStarting( groupInfo );
 
        }
 

	
 
        virtual void testCaseStarting( TestCaseInfo const& testCaseInfo ) CATCH_OVERRIDE {
 
            m_okToFail = testCaseInfo.okToFail();
 
        }
 
        virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE {
 
            if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail )
 
                unexpectedExceptions++;
 
            return CumulativeReporterBase::assertionEnded( assertionStats );
 
        }
 

	
 
        virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE {
 
            stdOutForSuite << testCaseStats.stdOut;
 
            stdErrForSuite << testCaseStats.stdErr;
 
            CumulativeReporterBase::testCaseEnded( testCaseStats );
 
        }
 

	
 
        virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE {
 
            double suiteTime = suiteTimer.getElapsedSeconds();
 
            CumulativeReporterBase::testGroupEnded( testGroupStats );
 
            writeGroup( *m_testGroups.back(), suiteTime );
 
        }
 

	
 
        virtual void testRunEndedCumulative() CATCH_OVERRIDE {
 
            xml.endElement();
 
        }
 

	
 
        void writeGroup( TestGroupNode const& groupNode, double suiteTime ) {
 
            XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" );
 
            TestGroupStats const& stats = groupNode.value;
 
            xml.writeAttribute( "name", stats.groupInfo.name );
 
            xml.writeAttribute( "errors", unexpectedExceptions );
 
            xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions );
 
            xml.writeAttribute( "tests", stats.totals.assertions.total() );
 
            xml.writeAttribute( "hostname", "tbd" ); // !TBD
 
            if( m_config->showDurations() == ShowDurations::Never )
 
                xml.writeAttribute( "time", "" );
 
            else
 
                xml.writeAttribute( "time", suiteTime );
 
            xml.writeAttribute( "timestamp", getCurrentTimestamp() );
 

	
 
            // Write test cases
 
            for( TestGroupNode::ChildNodes::const_iterator
 
                    it = groupNode.children.begin(), itEnd = groupNode.children.end();
 
                    it != itEnd;
 
                    ++it )
 
                writeTestCase( **it );
 

	
 
            xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false );
 
            xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false );
 
        }
 

	
 
        void writeTestCase( TestCaseNode const& testCaseNode ) {
 
            TestCaseStats const& stats = testCaseNode.value;
 

	
 
            // All test cases have exactly one section - which represents the
 
            // test case itself. That section may have 0-n nested sections
 
            assert( testCaseNode.children.size() == 1 );
 
            SectionNode const& rootSection = *testCaseNode.children.front();
 

	
 
            std::string className = stats.testInfo.className;
 

	
 
            if( className.empty() ) {
 
                if( rootSection.childSections.empty() )
 
                    className = "global";
 
            }
 
            writeSection( className, "", rootSection );
 
        }
 

	
 
        void writeSection(  std::string const& className,
 
                            std::string const& rootName,
 
                            SectionNode const& sectionNode ) {
 
            std::string name = trim( sectionNode.stats.sectionInfo.name );
 
            if( !rootName.empty() )
 
                name = rootName + '/' + name;
 

	
 
            if( !sectionNode.assertions.empty() ||
 
                !sectionNode.stdOut.empty() ||
 
                !sectionNode.stdErr.empty() ) {
 
                XmlWriter::ScopedElement e = xml.scopedElement( "testcase" );
 
                if( className.empty() ) {
 
                    xml.writeAttribute( "classname", name );
 
                    xml.writeAttribute( "name", "root" );
 
                }
 
                else {
 
                    xml.writeAttribute( "classname", className );
 
                    xml.writeAttribute( "name", name );
 
                }
 
                xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) );
 

	
 
                writeAssertions( sectionNode );
 

	
 
                if( !sectionNode.stdOut.empty() )
 
                    xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false );
 
                if( !sectionNode.stdErr.empty() )
 
                    xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false );
 
            }
 
            for( SectionNode::ChildSections::const_iterator
 
                    it = sectionNode.childSections.begin(),
 
                    itEnd = sectionNode.childSections.end();
 
                    it != itEnd;
 
                    ++it )
 
                if( className.empty() )
 
                    writeSection( name, "", **it );
 
                else
 
                    writeSection( className, name, **it );
 
        }
 

	
 
        void writeAssertions( SectionNode const& sectionNode ) {
 
            for( SectionNode::Assertions::const_iterator
 
                    it = sectionNode.assertions.begin(), itEnd = sectionNode.assertions.end();
 
                    it != itEnd;
 
                    ++it )
 
                writeAssertion( *it );
 
        }
 
        void writeAssertion( AssertionStats const& stats ) {
 
            AssertionResult const& result = stats.assertionResult;
 
            if( !result.isOk() ) {
 
                std::string elementName;
 
                switch( result.getResultType() ) {
 
                    case ResultWas::ThrewException:
 
                    case ResultWas::FatalErrorCondition:
 
                        elementName = "error";
 
                        break;
 
                    case ResultWas::ExplicitFailure:
 
                        elementName = "failure";
 
                        break;
 
                    case ResultWas::ExpressionFailed:
 
                        elementName = "failure";
 
                        break;
 
                    case ResultWas::DidntThrowException:
 
                        elementName = "failure";
 
                        break;
 

	
 
                    // We should never see these here:
 
                    case ResultWas::Info:
 
                    case ResultWas::Warning:
 
                    case ResultWas::Ok:
 
                    case ResultWas::Unknown:
 
                    case ResultWas::FailureBit:
 
                    case ResultWas::Exception:
 
                        elementName = "internalError";
 
                        break;
 
                }
 

	
 
                XmlWriter::ScopedElement e = xml.scopedElement( elementName );
 

	
 
                xml.writeAttribute( "message", result.getExpandedExpression() );
 
                xml.writeAttribute( "type", result.getTestMacroName() );
 

	
 
                std::ostringstream oss;
 
                if( !result.getMessage().empty() )
 
                    oss << result.getMessage() << '\n';
 
                for( std::vector<MessageInfo>::const_iterator
 
                        it = stats.infoMessages.begin(),
 
                        itEnd = stats.infoMessages.end();
 
                            it != itEnd;
 
                            ++it )
 
                    if( it->type == ResultWas::Info )
 
                        oss << it->message << '\n';
 

	
 
                oss << "at " << result.getSourceInfo();
 
                xml.writeText( oss.str(), false );
 
            }
 
        }
 

	
 
        XmlWriter xml;
 
        Timer suiteTimer;
 
        std::ostringstream stdOutForSuite;
 
        std::ostringstream stdErrForSuite;
 
        unsigned int unexpectedExceptions;
 
        bool m_okToFail;
 
    };
 

	
 
    INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter )
 

	
 
} // end namespace Catch
 

	
 
// #included from: ../reporters/catch_reporter_console.hpp
 
#define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED
 

	
 
#include <cfloat>
 
#include <cstdio>
 

	
 
namespace Catch {
 

	
 
    struct ConsoleReporter : StreamingReporterBase {
 
        ConsoleReporter( ReporterConfig const& _config )
 
        :   StreamingReporterBase( _config ),
 
            m_headerPrinted( false )
 
        {}
 

	
 
        virtual ~ConsoleReporter() CATCH_OVERRIDE;
 
        static std::string getDescription() {
 
            return "Reports test results as plain lines of text";
 
        }
 

	
 
        virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE {
 
            stream << "No test cases matched '" << spec << '\'' << std::endl;
 
        }
 

	
 
        virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {
 
        }
 

	
 
        virtual bool assertionEnded( AssertionStats const& _assertionStats ) CATCH_OVERRIDE {
 
            AssertionResult const& result = _assertionStats.assertionResult;
 

	
 
            bool includeResults = m_config->includeSuccessfulResults() || !result.isOk();
 

	
 
            // Drop out if result was successful but we're not printing them.
 
            if( !includeResults && result.getResultType() != ResultWas::Warning )
 
                return false;
 

	
 
            lazyPrint();
 

	
 
            AssertionPrinter printer( stream, _assertionStats, includeResults );
 
            printer.print();
 
            stream << std::endl;
 
            return true;
 
        }
 

	
 
        virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE {
 
            m_headerPrinted = false;
 
            StreamingReporterBase::sectionStarting( _sectionInfo );
 
        }
 
        virtual void sectionEnded( SectionStats const& _sectionStats ) CATCH_OVERRIDE {
 
            if( _sectionStats.missingAssertions ) {
 
                lazyPrint();
 
                Colour colour( Colour::ResultError );
 
                if( m_sectionStack.size() > 1 )
 
                    stream << "\nNo assertions in section";
 
                else
 
                    stream << "\nNo assertions in test case";
 
                stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl;
 
            }
 
            if( m_config->showDurations() == ShowDurations::Always ) {
 
                stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl;
 
            }
 
            if( m_headerPrinted ) {
 
                m_headerPrinted = false;
 
            }
 
            StreamingReporterBase::sectionEnded( _sectionStats );
 
        }
 

	
 
        virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) CATCH_OVERRIDE {
 
            StreamingReporterBase::testCaseEnded( _testCaseStats );
 
            m_headerPrinted = false;
 
        }
 
        virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) CATCH_OVERRIDE {
 
            if( currentGroupInfo.used ) {
 
                printSummaryDivider();
 
                stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n";
 
                printTotals( _testGroupStats.totals );
 
                stream << '\n' << std::endl;
 
            }
 
            StreamingReporterBase::testGroupEnded( _testGroupStats );
 
        }
 
        virtual void testRunEnded( TestRunStats const& _testRunStats ) CATCH_OVERRIDE {
 
            printTotalsDivider( _testRunStats.totals );
 
            printTotals( _testRunStats.totals );
 
            stream << std::endl;
 
            StreamingReporterBase::testRunEnded( _testRunStats );
 
        }
 

	
 
    private:
 

	
 
        class AssertionPrinter {
 
            void operator= ( AssertionPrinter const& );
 
        public:
 
            AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages )
 
            :   stream( _stream ),
 
                stats( _stats ),
 
                result( _stats.assertionResult ),
 
                colour( Colour::None ),
 
                message( result.getMessage() ),
 
                messages( _stats.infoMessages ),
 
                printInfoMessages( _printInfoMessages )
 
            {
 
                switch( result.getResultType() ) {
 
                    case ResultWas::Ok:
 
                        colour = Colour::Success;
 
                        passOrFail = "PASSED";
 
                        //if( result.hasMessage() )
 
                        if( _stats.infoMessages.size() == 1 )
 
                            messageLabel = "with message";
 
                        if( _stats.infoMessages.size() > 1 )
 
                            messageLabel = "with messages";
 
                        break;
 
                    case ResultWas::ExpressionFailed:
 
                        if( result.isOk() ) {
 
                            colour = Colour::Success;
 
                            passOrFail = "FAILED - but was ok";
 
                        }
 
                        else {
 
                            colour = Colour::Error;
 
                            passOrFail = "FAILED";
 
                        }
 
                        if( _stats.infoMessages.size() == 1 )
 
                            messageLabel = "with message";
 
                        if( _stats.infoMessages.size() > 1 )
 
                            messageLabel = "with messages";
 
                        break;
 
                    case ResultWas::ThrewException:
 
                        colour = Colour::Error;
 
                        passOrFail = "FAILED";
 
                        messageLabel = "due to unexpected exception with ";
 
                        if (_stats.infoMessages.size() == 1)
 
                            messageLabel += "message";
 
                        if (_stats.infoMessages.size() > 1)
 
                            messageLabel += "messages";
 
                        break;
 
                    case ResultWas::FatalErrorCondition:
 
                        colour = Colour::Error;
 
                        passOrFail = "FAILED";
 
                        messageLabel = "due to a fatal error condition";
 
                        break;
 
                    case ResultWas::DidntThrowException:
 
                        colour = Colour::Error;
 
                        passOrFail = "FAILED";
 
                        messageLabel = "because no exception was thrown where one was expected";
 
                        break;
 
                    case ResultWas::Info:
 
                        messageLabel = "info";
 
                        break;
 
                    case ResultWas::Warning:
 
                        messageLabel = "warning";
 
                        break;
 
                    case ResultWas::ExplicitFailure:
 
                        passOrFail = "FAILED";
 
                        colour = Colour::Error;
 
                        if( _stats.infoMessages.size() == 1 )
 
                            messageLabel = "explicitly with message";
 
                        if( _stats.infoMessages.size() > 1 )
 
                            messageLabel = "explicitly with messages";
 
                        break;
 
                    // These cases are here to prevent compiler warnings
 
                    case ResultWas::Unknown:
 
                    case ResultWas::FailureBit:
 
                    case ResultWas::Exception:
 
                        passOrFail = "** internal error **";
 
                        colour = Colour::Error;
 
                        break;
 
                }
 
            }
 

	
 
            void print() const {
 
                printSourceInfo();
 
                if( stats.totals.assertions.total() > 0 ) {
 
                    if( result.isOk() )
 
                        stream << '\n';
 
                    printResultType();
 
                    printOriginalExpression();
 
                    printReconstructedExpression();
 
                }
 
                else {
 
                    stream << '\n';
 
                }
 
                printMessage();
 
            }
 

	
 
        private:
 
            void printResultType() const {
 
                if( !passOrFail.empty() ) {
 
                    Colour colourGuard( colour );
 
                    stream << passOrFail << ":\n";
 
                }
 
            }
 
            void printOriginalExpression() const {
 
                if( result.hasExpression() ) {
 
                    Colour colourGuard( Colour::OriginalExpression );
 
                    stream  << "  ";
 
                    stream << result.getExpressionInMacro();
 
                    stream << '\n';
 
                }
 
            }
 
            void printReconstructedExpression() const {
 
                if( result.hasExpandedExpression() ) {
 
                    stream << "with expansion:\n";
 
                    Colour colourGuard( Colour::ReconstructedExpression );
 
                    stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << '\n';
 
                }
 
            }
 
            void printMessage() const {
 
                if( !messageLabel.empty() )
 
                    stream << messageLabel << ':' << '\n';
 
                for( std::vector<MessageInfo>::const_iterator it = messages.begin(), itEnd = messages.end();
 
                        it != itEnd;
 
                        ++it ) {
 
                    // If this assertion is a warning ignore any INFO messages
 
                    if( printInfoMessages || it->type != ResultWas::Info )
 
                        stream << Text( it->message, TextAttributes().setIndent(2) ) << '\n';
 
                }
 
            }
 
            void printSourceInfo() const {
 
                Colour colourGuard( Colour::FileName );
 
                stream << result.getSourceInfo() << ": ";
 
            }
 

	
 
            std::ostream& stream;
 
            AssertionStats const& stats;
 
            AssertionResult const& result;
 
            Colour::Code colour;
 
            std::string passOrFail;
 
            std::string messageLabel;
 
            std::string message;
 
            std::vector<MessageInfo> messages;
 
            bool printInfoMessages;
 
        };
 

	
 
        void lazyPrint() {
 

	
 
            if( !currentTestRunInfo.used )
 
                lazyPrintRunInfo();
 
            if( !currentGroupInfo.used )
 
                lazyPrintGroupInfo();
 

	
 
            if( !m_headerPrinted ) {
 
                printTestCaseAndSectionHeader();
 
                m_headerPrinted = true;
 
            }
 
        }
 
        void lazyPrintRunInfo() {
 
            stream  << '\n' << getLineOfChars<'~'>() << '\n';
 
            Colour colour( Colour::SecondaryText );
 
            stream  << currentTestRunInfo->name
 
                    << " is a Catch v"  << libraryVersion() << " host application.\n"
 
                    << "Run with -? for options\n\n";
 

	
 
            if( m_config->rngSeed() != 0 )
 
                stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n";
 

	
 
            currentTestRunInfo.used = true;
 
        }
 
        void lazyPrintGroupInfo() {
 
            if( !currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1 ) {
 
                printClosedHeader( "Group: " + currentGroupInfo->name );
 
                currentGroupInfo.used = true;
 
            }
 
        }
 
        void printTestCaseAndSectionHeader() {
 
            assert( !m_sectionStack.empty() );
 
            printOpenHeader( currentTestCaseInfo->name );
 

	
 
            if( m_sectionStack.size() > 1 ) {
 
                Colour colourGuard( Colour::Headers );
 

	
 
                std::vector<SectionInfo>::const_iterator
 
                    it = m_sectionStack.begin()+1, // Skip first section (test case)
 
                    itEnd = m_sectionStack.end();
 
                for( ; it != itEnd; ++it )
 
                    printHeaderString( it->name, 2 );
 
            }
 

	
 
            SourceLineInfo lineInfo = m_sectionStack.back().lineInfo;
 

	
 
            if( !lineInfo.empty() ){
 
                stream << getLineOfChars<'-'>() << '\n';
 
                Colour colourGuard( Colour::FileName );
 
                stream << lineInfo << '\n';
 
            }
 
            stream << getLineOfChars<'.'>() << '\n' << std::endl;
 
        }
 

	
 
        void printClosedHeader( std::string const& _name ) {
 
            printOpenHeader( _name );
 
            stream << getLineOfChars<'.'>() << '\n';
 
        }
 
        void printOpenHeader( std::string const& _name ) {
 
            stream  << getLineOfChars<'-'>() << '\n';
 
            {
 
                Colour colourGuard( Colour::Headers );
 
                printHeaderString( _name );
 
            }
 
        }
 

	
 
        // if string has a : in first line will set indent to follow it on
 
        // subsequent lines
 
        void printHeaderString( std::string const& _string, std::size_t indent = 0 ) {
 
            std::size_t i = _string.find( ": " );
 
            if( i != std::string::npos )
 
                i+=2;
 
            else
 
                i = 0;
 
            stream << Text( _string, TextAttributes()
 
                                        .setIndent( indent+i)
 
                                        .setInitialIndent( indent ) ) << '\n';
 
        }
 

	
 
        struct SummaryColumn {
 

	
 
            SummaryColumn( std::string const& _label, Colour::Code _colour )
 
            :   label( _label ),
 
                colour( _colour )
 
            {}
 
            SummaryColumn addRow( std::size_t count ) {
 
                std::ostringstream oss;
 
                oss << count;
 
                std::string row = oss.str();
 
                for( std::vector<std::string>::iterator it = rows.begin(); it != rows.end(); ++it ) {
 
                    while( it->size() < row.size() )
 
                        *it = ' ' + *it;
 
                    while( it->size() > row.size() )
 
                        row = ' ' + row;
 
                }
 
                rows.push_back( row );
 
                return *this;
 
            }
 

	
 
            std::string label;
 
            Colour::Code colour;
 
            std::vector<std::string> rows;
 

	
 
        };
 

	
 
        void printTotals( Totals const& totals ) {
 
            if( totals.testCases.total() == 0 ) {
 
                stream << Colour( Colour::Warning ) << "No tests ran\n";
 
            }
 
            else if( totals.assertions.total() > 0 && totals.testCases.allPassed() ) {
 
                stream << Colour( Colour::ResultSuccess ) << "All tests passed";
 
                stream << " ("
 
                        << pluralise( totals.assertions.passed, "assertion" ) << " in "
 
                        << pluralise( totals.testCases.passed, "test case" ) << ')'
 
                        << '\n';
 
            }
 
            else {
 

	
 
                std::vector<SummaryColumn> columns;
 
                columns.push_back( SummaryColumn( "", Colour::None )
 
                                        .addRow( totals.testCases.total() )
 
                                        .addRow( totals.assertions.total() ) );
 
                columns.push_back( SummaryColumn( "passed", Colour::Success )
 
                                        .addRow( totals.testCases.passed )
 
                                        .addRow( totals.assertions.passed ) );
 
                columns.push_back( SummaryColumn( "failed", Colour::ResultError )
 
                                        .addRow( totals.testCases.failed )
 
                                        .addRow( totals.assertions.failed ) );
 
                columns.push_back( SummaryColumn( "failed as expected", Colour::ResultExpectedFailure )
 
                                        .addRow( totals.testCases.failedButOk )
 
                                        .addRow( totals.assertions.failedButOk ) );
 

	
 
                printSummaryRow( "test cases", columns, 0 );
 
                printSummaryRow( "assertions", columns, 1 );
 
            }
 
        }
 
        void printSummaryRow( std::string const& label, std::vector<SummaryColumn> const& cols, std::size_t row ) {
 
            for( std::vector<SummaryColumn>::const_iterator it = cols.begin(); it != cols.end(); ++it ) {
 
                std::string value = it->rows[row];
 
                if( it->label.empty() ) {
 
                    stream << label << ": ";
 
                    if( value != "0" )
 
                        stream << value;
 
                    else
 
                        stream << Colour( Colour::Warning ) << "- none -";
 
                }
 
                else if( value != "0" ) {
 
                    stream  << Colour( Colour::LightGrey ) << " | ";
 
                    stream  << Colour( it->colour )
 
                            << value << ' ' << it->label;
 
                }
 
            }
 
            stream << '\n';
 
        }
 

	
 
        static std::size_t makeRatio( std::size_t number, std::size_t total ) {
 
            std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number/ total : 0;
 
            return ( ratio == 0 && number > 0 ) ? 1 : ratio;
 
        }
 
        static std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) {
 
            if( i > j && i > k )
 
                return i;
 
            else if( j > k )
 
                return j;
 
            else
 
                return k;
 
        }
 

	
 
        void printTotalsDivider( Totals const& totals ) {
 
            if( totals.testCases.total() > 0 ) {
 
                std::size_t failedRatio = makeRatio( totals.testCases.failed, totals.testCases.total() );
 
                std::size_t failedButOkRatio = makeRatio( totals.testCases.failedButOk, totals.testCases.total() );
 
                std::size_t passedRatio = makeRatio( totals.testCases.passed, totals.testCases.total() );
 
                while( failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH-1 )
 
                    findMax( failedRatio, failedButOkRatio, passedRatio )++;
 
                while( failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH-1 )
 
                    findMax( failedRatio, failedButOkRatio, passedRatio )--;
 

	
 
                stream << Colour( Colour::Error ) << std::string( failedRatio, '=' );
 
                stream << Colour( Colour::ResultExpectedFailure ) << std::string( failedButOkRatio, '=' );
 
                if( totals.testCases.allPassed() )
 
                    stream << Colour( Colour::ResultSuccess ) << std::string( passedRatio, '=' );
 
                else
 
                    stream << Colour( Colour::Success ) << std::string( passedRatio, '=' );
 
            }
 
            else {
 
                stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' );
 
            }
 
            stream << '\n';
 
        }
 
        void printSummaryDivider() {
 
            stream << getLineOfChars<'-'>() << '\n';
 
        }
 

	
 
    private:
 
        bool m_headerPrinted;
 
    };
 

	
 
    INTERNAL_CATCH_REGISTER_REPORTER( "console", ConsoleReporter )
 

	
 
} // end namespace Catch
 

	
 
// #included from: ../reporters/catch_reporter_compact.hpp
 
#define TWOBLUECUBES_CATCH_REPORTER_COMPACT_HPP_INCLUDED
 

	
 
namespace Catch {
 

	
 
    struct CompactReporter : StreamingReporterBase {
 

	
 
        CompactReporter( ReporterConfig const& _config )
 
        : StreamingReporterBase( _config )
 
        {}
 

	
 
        virtual ~CompactReporter();
 

	
 
        static std::string getDescription() {
 
            return "Reports test results on a single line, suitable for IDEs";
 
        }
 

	
 
        virtual ReporterPreferences getPreferences() const {
 
            ReporterPreferences prefs;
 
            prefs.shouldRedirectStdOut = false;
 
            return prefs;
 
        }
 

	
 
        virtual void noMatchingTestCases( std::string const& spec ) {
 
            stream << "No test cases matched '" << spec << '\'' << std::endl;
 
        }
 

	
 
        virtual void assertionStarting( AssertionInfo const& ) {}
 

	
 
        virtual bool assertionEnded( AssertionStats const& _assertionStats ) {
 
            AssertionResult const& result = _assertionStats.assertionResult;
 

	
 
            bool printInfoMessages = true;
 

	
 
            // Drop out if result was successful and we're not printing those
 
            if( !m_config->includeSuccessfulResults() && result.isOk() ) {
 
                if( result.getResultType() != ResultWas::Warning )
 
                    return false;
 
                printInfoMessages = false;
 
            }
 

	
 
            AssertionPrinter printer( stream, _assertionStats, printInfoMessages );
 
            printer.print();
 

	
 
            stream << std::endl;
 
            return true;
 
        }
 

	
 
        virtual void sectionEnded(SectionStats const& _sectionStats) CATCH_OVERRIDE {
 
            if (m_config->showDurations() == ShowDurations::Always) {
 
                stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl;
 
            }
 
        }
 

	
 
        virtual void testRunEnded( TestRunStats const& _testRunStats ) {
 
            printTotals( _testRunStats.totals );
 
            stream << '\n' << std::endl;
 
            StreamingReporterBase::testRunEnded( _testRunStats );
 
        }
 

	
 
    private:
 
        class AssertionPrinter {
 
            void operator= ( AssertionPrinter const& );
 
        public:
 
            AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages )
 
            : stream( _stream )
 
            , stats( _stats )
 
            , result( _stats.assertionResult )
 
            , messages( _stats.infoMessages )
 
            , itMessage( _stats.infoMessages.begin() )
 
            , printInfoMessages( _printInfoMessages )
 
            {}
 

	
 
            void print() {
 
                printSourceInfo();
 

	
 
                itMessage = messages.begin();
 

	
 
                switch( result.getResultType() ) {
 
                    case ResultWas::Ok:
 
                        printResultType( Colour::ResultSuccess, passedString() );
 
                        printOriginalExpression();
 
                        printReconstructedExpression();
 
                        if ( ! result.hasExpression() )
 
                            printRemainingMessages( Colour::None );
 
                        else
 
                            printRemainingMessages();
 
                        break;
 
                    case ResultWas::ExpressionFailed:
 
                        if( result.isOk() )
 
                            printResultType( Colour::ResultSuccess, failedString() + std::string( " - but was ok" ) );
 
                        else
 
                            printResultType( Colour::Error, failedString() );
 
                        printOriginalExpression();
 
                        printReconstructedExpression();
 
                        printRemainingMessages();
 
                        break;
 
                    case ResultWas::ThrewException:
 
                        printResultType( Colour::Error, failedString() );
 
                        printIssue( "unexpected exception with message:" );
 
                        printMessage();
 
                        printExpressionWas();
 
                        printRemainingMessages();
 
                        break;
 
                    case ResultWas::FatalErrorCondition:
 
                        printResultType( Colour::Error, failedString() );
 
                        printIssue( "fatal error condition with message:" );
 
                        printMessage();
 
                        printExpressionWas();
 
                        printRemainingMessages();
 
                        break;
 
                    case ResultWas::DidntThrowException:
 
                        printResultType( Colour::Error, failedString() );
 
                        printIssue( "expected exception, got none" );
 
                        printExpressionWas();
 
                        printRemainingMessages();
 
                        break;
 
                    case ResultWas::Info:
 
                        printResultType( Colour::None, "info" );
 
                        printMessage();
 
                        printRemainingMessages();
 
                        break;
 
                    case ResultWas::Warning:
 
                        printResultType( Colour::None, "warning" );
 
                        printMessage();
 
                        printRemainingMessages();
 
                        break;
 
                    case ResultWas::ExplicitFailure:
 
                        printResultType( Colour::Error, failedString() );
 
                        printIssue( "explicitly" );
 
                        printRemainingMessages( Colour::None );
 
                        break;
 
                    // These cases are here to prevent compiler warnings
 
                    case ResultWas::Unknown:
 
                    case ResultWas::FailureBit:
 
                    case ResultWas::Exception:
 
                        printResultType( Colour::Error, "** internal error **" );
 
                        break;
 
                }
 
            }
 

	
 
        private:
 
            // Colour::LightGrey
 

	
 
            static Colour::Code dimColour() { return Colour::FileName; }
 

	
 
#ifdef CATCH_PLATFORM_MAC
 
            static const char* failedString() { return "FAILED"; }
 
            static const char* passedString() { return "PASSED"; }
 
#else
 
            static const char* failedString() { return "failed"; }
 
            static const char* passedString() { return "passed"; }
 
#endif
 

	
 
            void printSourceInfo() const {
 
                Colour colourGuard( Colour::FileName );
 
                stream << result.getSourceInfo() << ':';
 
            }
 

	
 
            void printResultType( Colour::Code colour, std::string const& passOrFail ) const {
 
                if( !passOrFail.empty() ) {
 
                    {
 
                        Colour colourGuard( colour );
 
                        stream << ' ' << passOrFail;
 
                    }
 
                    stream << ':';
 
                }
 
            }
 

	
 
            void printIssue( std::string const& issue ) const {
 
                stream << ' ' << issue;
 
            }
 

	
 
            void printExpressionWas() {
 
                if( result.hasExpression() ) {
 
                    stream << ';';
 
                    {
 
                        Colour colour( dimColour() );
 
                        stream << " expression was:";
 
                    }
 
                    printOriginalExpression();
 
                }
 
            }
 

	
 
            void printOriginalExpression() const {
 
                if( result.hasExpression() ) {
 
                    stream << ' ' << result.getExpression();
 
                }
 
            }
 

	
 
            void printReconstructedExpression() const {
 
                if( result.hasExpandedExpression() ) {
 
                    {
 
                        Colour colour( dimColour() );
 
                        stream << " for: ";
 
                    }
 
                    stream << result.getExpandedExpression();
 
                }
 
            }
 

	
 
            void printMessage() {
 
                if ( itMessage != messages.end() ) {
 
                    stream << " '" << itMessage->message << '\'';
 
                    ++itMessage;
 
                }
 
            }
 

	
 
            void printRemainingMessages( Colour::Code colour = dimColour() ) {
 
                if ( itMessage == messages.end() )
 
                    return;
 

	
 
                // using messages.end() directly yields compilation error:
 
                std::vector<MessageInfo>::const_iterator itEnd = messages.end();
 
                const std::size_t N = static_cast<std::size_t>( std::distance( itMessage, itEnd ) );
 

	
 
                {
 
                    Colour colourGuard( colour );
 
                    stream << " with " << pluralise( N, "message" ) << ':';
 
                }
 

	
 
                for(; itMessage != itEnd; ) {
 
                    // If this assertion is a warning ignore any INFO messages
 
                    if( printInfoMessages || itMessage->type != ResultWas::Info ) {
 
                        stream << " '" << itMessage->message << '\'';
 
                        if ( ++itMessage != itEnd ) {
 
                            Colour colourGuard( dimColour() );
 
                            stream << " and";
 
                        }
 
                    }
 
                }
 
            }
 

	
 
        private:
 
            std::ostream& stream;
 
            AssertionStats const& stats;
 
            AssertionResult const& result;
 
            std::vector<MessageInfo> messages;
 
            std::vector<MessageInfo>::const_iterator itMessage;
 
            bool printInfoMessages;
 
        };
 

	
 
        // Colour, message variants:
 
        // - white: No tests ran.
 
        // -   red: Failed [both/all] N test cases, failed [both/all] M assertions.
 
        // - white: Passed [both/all] N test cases (no assertions).
 
        // -   red: Failed N tests cases, failed M assertions.
 
        // - green: Passed [both/all] N tests cases with M assertions.
 

	
 
        std::string bothOrAll( std::size_t count ) const {
 
            return count == 1 ? std::string() : count == 2 ? "both " : "all " ;
 
        }
 

	
 
        void printTotals( const Totals& totals ) const {
 
            if( totals.testCases.total() == 0 ) {
 
                stream << "No tests ran.";
 
            }
 
            else if( totals.testCases.failed == totals.testCases.total() ) {
 
                Colour colour( Colour::ResultError );
 
                const std::string qualify_assertions_failed =
 
                    totals.assertions.failed == totals.assertions.total() ?
 
                        bothOrAll( totals.assertions.failed ) : std::string();
 
                stream <<
 
                    "Failed " << bothOrAll( totals.testCases.failed )
 
                              << pluralise( totals.testCases.failed, "test case"  ) << ", "
 
                    "failed " << qualify_assertions_failed <<
 
                                 pluralise( totals.assertions.failed, "assertion" ) << '.';
 
            }
 
            else if( totals.assertions.total() == 0 ) {
 
                stream <<
 
                    "Passed " << bothOrAll( totals.testCases.total() )
 
                              << pluralise( totals.testCases.total(), "test case" )
 
                              << " (no assertions).";
 
            }
 
            else if( totals.assertions.failed ) {
 
                Colour colour( Colour::ResultError );
 
                stream <<
 
                    "Failed " << pluralise( totals.testCases.failed, "test case"  ) << ", "
 
                    "failed " << pluralise( totals.assertions.failed, "assertion" ) << '.';
 
            }
 
            else {
 
                Colour colour( Colour::ResultSuccess );
 
                stream <<
 
                    "Passed " << bothOrAll( totals.testCases.passed )
 
                              << pluralise( totals.testCases.passed, "test case"  ) <<
 
                    " with "  << pluralise( totals.assertions.passed, "assertion" ) << '.';
 
            }
 
        }
 
    };
 

	
 
    INTERNAL_CATCH_REGISTER_REPORTER( "compact", CompactReporter )
 

	
 
} // end namespace Catch
 

	
 
namespace Catch {
 
    // These are all here to avoid warnings about not having any out of line
 
    // virtual methods
 
    NonCopyable::~NonCopyable() {}
 
    IShared::~IShared() {}
 
    IStream::~IStream() CATCH_NOEXCEPT {}
 
    FileStream::~FileStream() CATCH_NOEXCEPT {}
 
    CoutStream::~CoutStream() CATCH_NOEXCEPT {}
 
    DebugOutStream::~DebugOutStream() CATCH_NOEXCEPT {}
 
    StreamBufBase::~StreamBufBase() CATCH_NOEXCEPT {}
 
    IContext::~IContext() {}
 
    IResultCapture::~IResultCapture() {}
 
    ITestCase::~ITestCase() {}
 
    ITestCaseRegistry::~ITestCaseRegistry() {}
 
    IRegistryHub::~IRegistryHub() {}
 
    IMutableRegistryHub::~IMutableRegistryHub() {}
 
    IExceptionTranslator::~IExceptionTranslator() {}
 
    IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() {}
 
    IReporter::~IReporter() {}
 
    IReporterFactory::~IReporterFactory() {}
 
    IReporterRegistry::~IReporterRegistry() {}
 
    IStreamingReporter::~IStreamingReporter() {}
 
    AssertionStats::~AssertionStats() {}
 
    SectionStats::~SectionStats() {}
 
    TestCaseStats::~TestCaseStats() {}
 
    TestGroupStats::~TestGroupStats() {}
 
    TestRunStats::~TestRunStats() {}
 
    CumulativeReporterBase::SectionNode::~SectionNode() {}
 
    CumulativeReporterBase::~CumulativeReporterBase() {}
 

	
 
    StreamingReporterBase::~StreamingReporterBase() {}
 
    ConsoleReporter::~ConsoleReporter() {}
 
    CompactReporter::~CompactReporter() {}
 
    IRunner::~IRunner() {}
 
    IMutableContext::~IMutableContext() {}
 
    IConfig::~IConfig() {}
 
    XmlReporter::~XmlReporter() {}
 
    JunitReporter::~JunitReporter() {}
 
    TestRegistry::~TestRegistry() {}
 
    FreeFunctionTestCase::~FreeFunctionTestCase() {}
 
    IGeneratorInfo::~IGeneratorInfo() {}
 
    IGeneratorsForTest::~IGeneratorsForTest() {}
 
    WildcardPattern::~WildcardPattern() {}
 
    TestSpec::Pattern::~Pattern() {}
 
    TestSpec::NamePattern::~NamePattern() {}
 
    TestSpec::TagPattern::~TagPattern() {}
 
    TestSpec::ExcludedPattern::~ExcludedPattern() {}
 
    Matchers::Impl::MatcherUntypedBase::~MatcherUntypedBase() {}
 

	
 
    void Config::dummy() {}
 

	
 
    namespace TestCaseTracking {
 
        ITracker::~ITracker() {}
 
        TrackerBase::~TrackerBase() {}
 
        SectionTracker::~SectionTracker() {}
 
        IndexTracker::~IndexTracker() {}
 
    }
 
}
 

	
 
#ifdef __clang__
 
#pragma clang diagnostic pop
 
#endif
 

	
 
#endif
 

	
 
#ifdef CATCH_CONFIG_MAIN
 
// #included from: internal/catch_default_main.hpp
 
#define TWOBLUECUBES_CATCH_DEFAULT_MAIN_HPP_INCLUDED
 

	
 
#ifndef __OBJC__
 

	
 
#if defined(WIN32) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN)
 
// Standard C/C++ Win32 Unicode wmain entry point
 
extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) {
 
#else
 
// Standard C/C++ main entry point
 
int main (int argc, char * argv[]) {
 
#endif
 

	
 
    int result = Catch::Session().run( argc, argv );
 
    return ( result < 0xff ? result : 0xff );
 
}
 

	
 
#else // __OBJC__
 

	
 
// Objective-C entry point
 
int main (int argc, char * const argv[]) {
 
#if !CATCH_ARC_ENABLED
 
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
 
#endif
 

	
 
    Catch::registerTestMethods();
 
    int result = Catch::Session().run( argc, (char* const*)argv );
 

	
 
#if !CATCH_ARC_ENABLED
 
    [pool drain];
 
#endif
 

	
 
    return ( result < 0xff ? result : 0xff );
 
}
 

	
 
#endif // __OBJC__
 

	
 
#endif
 

	
 
#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED
 
#  undef CLARA_CONFIG_MAIN
 
#endif
 

	
 
//////
 

	
 
// If this config identifier is defined then all CATCH macros are prefixed with CATCH_
 
#ifdef CATCH_CONFIG_PREFIX_ALL
 

	
 
#if defined(CATCH_CONFIG_FAST_COMPILE)
 
#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, expr )
 
#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr )
 
#else
 
#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, expr )
 
#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr  )
 
#endif
 

	
 
#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", expr )
 
#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr )
 
#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr )
 
#define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, expr )
 

	
 
#define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, expr )
 
#define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, expr )
 
#define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, expr )
 
#define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, expr )
 
#define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, expr )
 

	
 
#define CATCH_CHECK_THROWS( expr )  INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", expr )
 
#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr )
 
#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr )
 
#define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, expr )
 

	
 
#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg )
 

	
 
#if defined(CATCH_CONFIG_FAST_COMPILE)
 
#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT_NO_TRY( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg )
 
#else
 
#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg )
 
#endif
 

	
 
#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg )
 
#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg )
 
#define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg )
 
#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CATCH_CAPTURE", #msg " := " << Catch::toString(msg) )
 
#define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CATCH_CAPTURE", #msg " := " << Catch::toString(msg) )
 

	
 
#ifdef CATCH_CONFIG_VARIADIC_MACROS
 
    #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )
 
    #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ )
 
    #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )
 
    #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )
 
    #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )
 
    #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ )
 
    #define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
 
    #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
 
#else
 
    #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description )
 
    #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description )
 
    #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description )
 
    #define CATCH_REGISTER_TEST_CASE( function, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( function, name, description )
 
    #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description )
 
    #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, msg )
 
    #define CATCH_FAIL_CHECK( msg ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, msg )
 
    #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, msg )
 
#endif
 
#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" )
 

	
 
#define CATCH_REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType )
 
#define CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType )
 

	
 
#define CATCH_GENERATE( expr) INTERNAL_CATCH_GENERATE( expr )
 

	
 
// "BDD-style" convenience wrappers
 
#ifdef CATCH_CONFIG_VARIADIC_MACROS
 
#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ )
 
#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ )
 
#else
 
#define CATCH_SCENARIO( name, tags ) CATCH_TEST_CASE( "Scenario: " name, tags )
 
#define CATCH_SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags )
 
#endif
 
#define CATCH_GIVEN( desc )    CATCH_SECTION( std::string( "Given: ") + desc, "" )
 
#define CATCH_WHEN( desc )     CATCH_SECTION( std::string( " When: ") + desc, "" )
 
#define CATCH_AND_WHEN( desc ) CATCH_SECTION( std::string( "  And: ") + desc, "" )
 
#define CATCH_THEN( desc )     CATCH_SECTION( std::string( " Then: ") + desc, "" )
 
#define CATCH_AND_THEN( desc ) CATCH_SECTION( std::string( "  And: ") + desc, "" )
 

	
 
// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required
 
#else
 

	
 
#if defined(CATCH_CONFIG_FAST_COMPILE)
 
#define REQUIRE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "REQUIRE", Catch::ResultDisposition::Normal, expr )
 
#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr )
 

	
 
#else
 
#define REQUIRE( expr ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, expr  )
 
#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr )
 
#endif
 

	
 
#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", expr )
 
#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr )
 
#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr )
 
#define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, expr )
 

	
 
#define CHECK( expr ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, expr )
 
#define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, expr )
 
#define CHECKED_IF( expr ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, expr )
 
#define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, expr )
 
#define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, expr )
 

	
 
#define CHECK_THROWS( expr )  INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", expr )
 
#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr )
 
#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr )
 
#define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, expr )
 

	
 
#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg )
 

	
 
#if defined(CATCH_CONFIG_FAST_COMPILE)
 
#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT_NO_TRY( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg )
 
#else
 
#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg )
 
#endif
 

	
 
#define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg )
 
#define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg )
 
#define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg )
 
#define CAPTURE( msg ) INTERNAL_CATCH_INFO( "CAPTURE", #msg " := " << Catch::toString(msg) )
 
#define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CAPTURE", #msg " := " << Catch::toString(msg) )
 

	
 
#ifdef CATCH_CONFIG_VARIADIC_MACROS
 
#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )
 
#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ )
 
#define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )
 
#define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )
 
#define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )
 
#define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ )
 
#define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
 
#define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
 
#else
 
#define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description )
 
    #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description )
 
    #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description )
 
    #define REGISTER_TEST_CASE( method, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( method, name, description )
 
    #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description )
 
    #define FAIL( msg ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, msg )
 
    #define FAIL_CHECK( msg ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, msg )
 
    #define SUCCEED( msg ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, msg )
 
#endif
 
#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" )
 

	
 
#define REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType )
 
#define REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType )
 

	
 
#define GENERATE( expr) INTERNAL_CATCH_GENERATE( expr )
 

	
 
#endif
 

	
 
#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature )
 

	
 
// "BDD-style" convenience wrappers
 
#ifdef CATCH_CONFIG_VARIADIC_MACROS
 
#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ )
 
#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ )
 
#else
 
#define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags )
 
#define SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags )
 
#endif
 
#define GIVEN( desc )    SECTION( std::string("   Given: ") + desc, "" )
 
#define WHEN( desc )     SECTION( std::string("    When: ") + desc, "" )
 
#define AND_WHEN( desc ) SECTION( std::string("And when: ") + desc, "" )
 
#define THEN( desc )     SECTION( std::string("    Then: ") + desc, "" )
 
#define AND_THEN( desc ) SECTION( std::string("     And: ") + desc, "" )
 

	
 
using Catch::Detail::Approx;
 

	
 
// #included from: internal/catch_reenable_warnings.h
 

	
 
#define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED
 

	
 
#ifdef __clang__
 
#    ifdef __ICC // icpc defines the __clang__ macro
 
#        pragma warning(pop)
 
#    else
 
#        pragma clang diagnostic pop
 
#    endif
 
#elif defined __GNUC__
 
#    pragma GCC diagnostic pop
 
#endif
 

	
 
#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
 

	
tests/test.cpp
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <time.h>
 
#define CATCH_CONFIG_MAIN  // This tells Catch to provide a main() - only do this in one cpp file
 
#include "catch.hpp"
 

	
 
#include "samplepack.h"
 
#include "source.h"
 
#include "indexbuffer.h"
 
#include "linindexbuffer.h"
 
#include "ringbuffer.h"
 
#include "readonlybuffer.h"
 
#include "datachunk.h"
 
#include "chunkedbuffer.h"
 

	
 
#include "test_helpers.h"
 

	
 
TEST_CASE("samplepack with no X", "[memory]")
 
{
 
    SamplePack pack(100, 3, false);
 

	
 
    REQUIRE_FALSE(pack.hasX());
 
    REQUIRE(pack.numChannels() == 3);
 
    REQUIRE(pack.numSamples() == 100);
 

	
 
    double* chan0 = pack.data(0);
 
    double* chan1 = pack.data(1);
 
    double* chan2 = pack.data(2);
 

	
 
    REQUIRE(chan0 == chan1 - 100);
 
    REQUIRE(chan1 == chan2 - 100);
 
}
 

	
 
TEST_CASE("samplepack with X", "[memory]")
 
{
 
    SamplePack pack(100, 3, true);
 

	
 
    REQUIRE(pack.hasX());
 
    REQUIRE(pack.numChannels() == 3);
 
    REQUIRE(pack.numSamples() == 100);
 
    REQUIRE(pack.xData() != nullptr);
 
}
 

	
 
TEST_CASE("samplepack copy", "[memory]")
 
{
 
    SamplePack pack(10, 3, true);
 

	
 
    // fill test data
 
    for (int i = 0; i < 10; i++)
 
    {
 
        pack.xData()[i] = i;
 
        pack.data(0)[i] = i+5;
 
        pack.data(1)[i] = i*2;
 
        pack.data(2)[i] = i*3;
 
    }
 

	
 
    SamplePack other = pack;
 
    // compare
 
    for (int i = 0; i < 10; i++)
 
    {
 
        REQUIRE(other.xData()[i] == i);
 
        REQUIRE(other.data(0)[i] == i+5);
 
        REQUIRE(other.data(1)[i] == i*2);
 
        REQUIRE(other.data(2)[i] == i*3);
 
    }
 
}
 

	
 
TEST_CASE("sink", "[memory, stream]")
 
{
 
    TestSink sink;
 
    SamplePack pack(100, 3, false);
 

	
 
    sink.setNumChannels(3, false);
 
    REQUIRE(sink.numChannels() == 3);
 

	
 
    sink.feedIn(pack);
 
    REQUIRE(sink.totalFed == 100);
 
    sink.feedIn(pack);
 
    REQUIRE(sink.totalFed == 200);
 

	
 
    TestSink follower;
 

	
 
    sink.connectFollower(&follower);
 
    REQUIRE(follower.numChannels() == 3);
 
    REQUIRE(follower.hasX() == false);
 

	
 
    sink.feedIn(pack);
 
    REQUIRE(sink.totalFed == 300);
 
    REQUIRE(follower.totalFed == 100);
 

	
 
    sink.setNumChannels(2, true);
 
    REQUIRE(follower.numChannels() == 2);
 
    REQUIRE(follower.hasX() == true);
 
}
 

	
 
TEST_CASE("sink must be created unconnected", "[memory, stream]")
 
{
 
    TestSink sink;
 
    REQUIRE(sink.connectedSource() == NULL);
 
}
 

	
 
TEST_CASE("source", "[memory, stream]")
 
{
 
    TestSink sink;
 

	
 
    TestSource source(3, false);
 

	
 
    REQUIRE(source.numChannels() == 3);
 
    REQUIRE(source.hasX() == false);
 

	
 
    source.connectSink(&sink);
 
    REQUIRE(sink.numChannels() == 3);
 
    REQUIRE(sink.hasX() == false);
 

	
 
    source._setNumChannels(5, true);
 
    REQUIRE(sink.numChannels() == 5);
 
    REQUIRE(sink.hasX() == true);
 

	
 
    SamplePack pack(100, 5, true);
 
    source._feed(pack);
 
    REQUIRE(sink.totalFed == 100);
 

	
 
    source.disconnect(&sink);
 
    source._feed(pack);
 
    REQUIRE(sink.totalFed == 100);
 
}
 

	
 
TEST_CASE("source must set/unset sink 'source'", "[memory, stream]")
 
{
 
    TestSink sink;
 
    TestSource source(3, false);
 

	
 
    source.connectSink(&sink);
 
    REQUIRE(sink.connectedSource() == &source);
 

	
 
    source.disconnect(&sink);
 
    REQUIRE(sink.connectedSource() == NULL);
 
}
 

	
 
TEST_CASE("source disconnect all sinks", "[memory, stream]")
 
{
 
    TestSink sinks[3];
 
    TestSource source(3, false);
 

	
 
    // connect sinks
 
    for (int i = 0; i < 3; i++)
 
    {
 
        source.connectSink(&sinks[i]);
 
    }
 

	
 
    source.disconnectSinks();
 
    for (int i = 0; i < 3; i++)
 
    {
 
        REQUIRE(sinks[i].connectedSource() == NULL);
 
    }
 
}
 

	
 
TEST_CASE("IndexBuffer", "[memory, buffer]")
 
{
 
    IndexBuffer buf(10);
 

	
 
    REQUIRE(buf.size() == 10);
 
    for (unsigned i = 0; i < 10; i++)
 
    {
 
        REQUIRE(buf.sample(i) == i);
 
    }
 
    auto l = buf.limits();
 
    REQUIRE(l.start == 0);
 
    REQUIRE(l.end == 9);
 

	
 
    buf.resize(20);
 
    REQUIRE(buf.size() == 20);
 
    REQUIRE(buf.sample(15) == 15);
 
    l = buf.limits();
 
    REQUIRE(l.start == 0);
 
    REQUIRE(l.end == 19);
 
}
 

	
 
TEST_CASE("LinIndexBuffer", "[memory, buffer]")
 
{
 
    LinIndexBuffer buf(10, 0., 3.0);
 

	
 
    REQUIRE(buf.size() == 10);
 
    REQUIRE(buf.sample(0) == 0.);
 
    REQUIRE(buf.sample(9) == 3.0);
 
    REQUIRE(buf.sample(4) == Approx(1+1/3.));
 

	
 
    auto l = buf.limits();
 
    REQUIRE(l.start == 0.);
 
    REQUIRE(l.end == 3.);
 

	
 
    buf.resize(20);
 
    REQUIRE(buf.size() == 20);
 
    REQUIRE(buf.sample(0) == 0.);
 
    REQUIRE(buf.sample(9) == Approx(9.*3./19.));
 
    REQUIRE(buf.sample(4) == Approx(4.*3./19.));
 
    REQUIRE(buf.sample(19) == 3.0);
 

	
 
    l = buf.limits();
 
    REQUIRE(l.start == 0.);
 
    REQUIRE(l.end == 3.0);
 

	
 
    buf.setLimits({-5., 5.});
 
    l = buf.limits();
 
    REQUIRE(l.start == -5.0);
 
    REQUIRE(l.end == 5.0);
 

	
 
    REQUIRE(buf.sample(0) == -5.0);
 
    REQUIRE(buf.sample(19) == 5.0);
 
}
 

	
 
TEST_CASE("RingBuffer sizing", "[memory, buffer]")
 
{
 
    RingBuffer buf(10);
 

	
 
    REQUIRE(buf.size() == 10);
 

	
 
    buf.resize(5);
 
    REQUIRE(buf.size() == 5);
 

	
 
    buf.resize(15);
 
    REQUIRE(buf.size() == 15);
 
}
 

	
 
TEST_CASE("RingBuffer initial values should be 0", "[memory, buffer]")
 
{
 
    RingBuffer buf(10);
 

	
 
    for (unsigned i = 0; i < 10; i++)
 
    {
 
        REQUIRE(buf.sample(i) == 0.);
 
    }
 
}
 

	
 
TEST_CASE("RingBuffer data access", "[memory, buffer]")
 
{
 
    RingBuffer buf(10);
 
    double values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 

	
 
    buf.addSamples(values, 10);
 

	
 
    REQUIRE(buf.size() == 10);
 
    for (unsigned i = 0; i < 10; i++)
 
    {
 
        REQUIRE(buf.sample(i) == values[i]);
 
    }
 

	
 
    buf.addSamples(values, 5);
 

	
 
    REQUIRE(buf.size() == 10);
 
    for (unsigned i = 0; i < 5; i++)
 
    {
 
        REQUIRE(buf.sample(i) == values[i+5]);
 
    }
 
    for (unsigned i = 5; i < 10; i++)
 
    {
 
        REQUIRE(buf.sample(i) == values[i-5]);
 
    }
 
}
 

	
 
TEST_CASE("making RingBuffer bigger should keep end values", "[memory, buffer]")
 
{
 
    RingBuffer buf(5);
 
    double values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 

	
 
    buf.addSamples(values, 5);
 
    buf.resize(10);
 

	
 
    REQUIRE(buf.size() == 10);
 
    for (unsigned i = 0; i < 5; i++)
 
    {
 
        REQUIRE(buf.sample(i) == 0);
 
    }
 
    for (unsigned i = 5; i < 10; i++)
 
    {
 
        REQUIRE(buf.sample(i) == values[i-5]);
 
    }
 
}
 

	
 
TEST_CASE("making RingBuffer smaller should keep end values", "[memory, buffer]")
 
{
 
    RingBuffer buf(10);
 
    double values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 

	
 
    buf.addSamples(values, 10);
 
    buf.resize(5);
 

	
 
    REQUIRE(buf.size() == 5);
 
    for (unsigned i = 0; i < 5; i++)
 
    {
 
        REQUIRE(buf.sample(i) == values[i+5]);
 
    }
 
}
 

	
 
TEST_CASE("RingBuffer limits", "[memory, buffer]")
 
{
 
    RingBuffer buf(10);
 
    double values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 

	
 
    auto lim = buf.limits();
 
    REQUIRE(lim.start == 0.);
 
    REQUIRE(lim.end == 0.);
 

	
 
    buf.addSamples(values, 10);
 
    lim = buf.limits();
 
    REQUIRE(lim.start == 1.);
 
    REQUIRE(lim.end == 10.);
 

	
 
    buf.addSamples(&values[9], 1);
 
    lim = buf.limits();
 
    REQUIRE(lim.start == 2.);
 
    REQUIRE(lim.end == 10.);
 

	
 
    buf.addSamples(values, 9);
 
    buf.addSamples(values, 1);
 
    lim = buf.limits();
 
    REQUIRE(lim.start == 1.);
 
    REQUIRE(lim.end == 9.);
 
}
 

	
 
TEST_CASE("RingBuffer clear", "[memory, buffer]")
 
{
 
    RingBuffer buf(10);
 
    double values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 

	
 
    buf.addSamples(values, 10);
 
    buf.clear();
 

	
 
    REQUIRE(buf.size() == 10);
 
    for (unsigned i = 0; i < 10; i++)
 
    {
 
        REQUIRE(buf.sample(i) == 0.);
 
    }
 
    auto lim = buf.limits();
 
    REQUIRE(lim.start == 0.);
 
    REQUIRE(lim.end == 0.);
 
}
 

	
 
TEST_CASE("ReadOnlyBuffer", "[memory, buffer]")
 
{
 
    IndexBuffer source(10);
 

	
 
    ReadOnlyBuffer buf(&source);
 

	
 
    REQUIRE(buf.size() == 10);
 
    auto lim = buf.limits();
 
    REQUIRE(lim.start == 0.);
 
    REQUIRE(lim.end == 9.);
 
    for (unsigned i = 0; i < 10; i++)
 
    {
 
        REQUIRE(buf.sample(i) == i);
 
    }
 
}
 

	
 
TEST_CASE("ReadOnlyBuffer sliced constructor", "[memory, buffer]")
 
{
 
    IndexBuffer source(10);
 

	
 
    ReadOnlyBuffer buf(&source, 5, 4);
 

	
 
    REQUIRE(buf.size() == 4);
 
    auto lim = buf.limits();
 
    REQUIRE(lim.start == 5.);
 
    REQUIRE(lim.end == 8.);
 
    for (unsigned i = 0; i < 4; i++)
 
    {
 
        REQUIRE(buf.sample(i) == (i + 5));
 
    }
 
}
 

	
 
TEST_CASE("DataChunk created empty", "[memory]")
 
{
 
    DataChunk c(0, 1000);
 
    REQUIRE(c.size() == 0);
 
    REQUIRE(c.capacity() == 1000);
 
    REQUIRE(c.start() == 0);
 
    REQUIRE(c.end() == 0);
 
    REQUIRE_FALSE(c.isFull());
 
    REQUIRE(c.left() == 1000);
 
}
 

	
 
TEST_CASE("adding data to DataChunk", "[memory]")
 
{
 
    DataChunk c(0, 1000);
 
    double samples[10] = {1,2,3,4,5,6,7,8,9,10};
 
    c.addSamples(samples, 10);
 

	
 
    REQUIRE(c.size() == 10);
 
    REQUIRE(c.capacity() == 1000);
 
    REQUIRE(c.start() == 0);
 
    REQUIRE(c.end() == 10);
 
    REQUIRE(c.left() == 990);
 

	
 
    for (int i = 0; i < 10; i++)
 
    {
 
        REQUIRE(c.sample(i) == samples[i]);
 
    }
 

	
 
    REQUIRE(c.min() == 1);
 
    REQUIRE(c.max() == 10);
 
    REQUIRE(c.avg() == Approx(5.5));
 
    REQUIRE(c.meanSquare() == Approx(38.5));
 
}
 

	
 
TEST_CASE("filling data chunk", "[memory]")
 
{
 
    DataChunk c(0, 1000);
 

	
 
    for (int i = 0; i < 1000; i++)
 
    {
 
        double sample = i + 1;
 
        c.addSamples(&sample, 1);
 
    }
 

	
 
    REQUIRE(c.size() == 1000);
 
    REQUIRE(c.capacity() == 1000);
 
    REQUIRE(c.start() == 0);
 
    REQUIRE(c.end() == 1000);
 
    REQUIRE(c.left() == 0);
 

	
 
    REQUIRE(c.min() == 1);
 
    REQUIRE(c.max() == 1000);
 
    REQUIRE(c.avg() == Approx(500.5));
 
    REQUIRE(c.meanSquare() == Approx(333833.5));
 
}
 

	
 
TEST_CASE("ChunkedBuffer created empty", "[memory]")
 
{
 
    ChunkedBuffer b;
 

	
 
    REQUIRE(b.size() == 0);
 
    REQUIRE(b.boundingRect() == QRectF(0,0,0,0));
 
}
 

	
 
TEST_CASE("ChunkedBuffer adding data and clearing", "[memory]")
 
{
 
    ChunkedBuffer b;
 

	
 
    // add some small data
 
    const int N = 10;
 
    double samples[N] = {1,2,3,4,5,6,7,8,9,10};
 
    b.addSamples(samples, N);
 

	
 
    REQUIRE(b.size() == N);
 
    REQUIRE(b.boundingRect() == QRectF(0,10,N,9));
 

	
 
    // add data to fill the chunk
 
    double samples2[CHUNK_SIZE-N] = {0};
 
    b.addSamples(samples2, CHUNK_SIZE-N);
 
    REQUIRE(b.size() == CHUNK_SIZE);
 
    REQUIRE(b.boundingRect() == QRectF(0,10,CHUNK_SIZE,10));
 

	
 
    // add data for second chunk
 
    b.addSamples(samples, N);
 
    REQUIRE(b.size() == CHUNK_SIZE+N);
 
    REQUIRE(b.boundingRect() == QRectF(0,10,CHUNK_SIZE+N,10));
 

	
 
    // add more data to make it 4 chunks
 
    double samples3[CHUNK_SIZE*3-N] = {0};
 
    b.addSamples(samples3, CHUNK_SIZE*3-N);
 
    REQUIRE(b.size() == CHUNK_SIZE*4);
 
    REQUIRE(b.boundingRect() == QRectF(0,10,CHUNK_SIZE*4,10));
 

	
 
    // clear
 
    b.clear();
 
    REQUIRE(b.size() == 0);
 
}
 

	
 
TEST_CASE("ChunkedBuffer accessing data", "[memory]")
 
{
 
    ChunkedBuffer b;
 

	
 
    // prepare data
 
    double samples[CHUNK_SIZE*3];
 
    samples[0] = 10;
 
    samples[10] = 20;
 
    samples[CHUNK_SIZE-1] = 30;
 
    samples[CHUNK_SIZE] = 40;
 
    samples[CHUNK_SIZE+1] = 50;
 
    samples[CHUNK_SIZE*2-1] = 60;
 
    samples[CHUNK_SIZE*3-1] = 70;
 

	
 
    // test
 
    b.addSamples(samples, CHUNK_SIZE*3);
 

	
 
    REQUIRE(b.size() == CHUNK_SIZE*3);
 
    REQUIRE(b.sample(0) == 10);
 
    REQUIRE(b.sample(10) == 20);
 
    REQUIRE(b.sample(CHUNK_SIZE-1) == 30);
 
    REQUIRE(b.sample(CHUNK_SIZE) == 40);
 
    REQUIRE(b.sample(CHUNK_SIZE+1) == 50);
 
    REQUIRE(b.sample(CHUNK_SIZE*2-1) == 60);
 
    REQUIRE(b.sample(CHUNK_SIZE*3-1) == 70);
 
}
 

	
 
TEST_CASE("ChunkedBuffer time measurement", "[.][timing][memory]")
 
{
 
    const int N = CHUNK_SIZE*10;
 
    clock_t start, end;
 
    double samples[N];
 
    ChunkedBuffer b;
 
    start = clock();
 
    b.addSamples(samples, N);
 
    end = clock();
 
    REQUIRE(b.size() == N);
 
    WARN("addSamples(" << N << ") took: " << ((end-start) / ((double) CLOCKS_PER_SEC)));
 

	
 
    // access
 
    start = clock();
 
    for (int i =0; i < N; i++)
 
    {
 
        samples[i] = b.sample(i);
 
    }
 
    end = clock();
 
    WARN("sample()*"<< N <<" took: " << ((end-start) / ((double) CLOCKS_PER_SEC)));
 
}
tests/test_helpers.h
Show inline comments
 
new file 100644
 
#ifndef TEST_HELPERS_H
 
#define TEST_HELPERS_H
 

	
 
#include "source.h"
 
#include "sink.h"
 

	
 
class TestSink : public Sink
 
{
 
public:
 
    int totalFed;
 
    int _numChannels;
 
    bool _hasX;
 

	
 
    TestSink()
 
        {
 
            totalFed = 0;
 
            _numChannels = 0;
 
            _hasX = false;
 
        };
 

	
 
    void feedIn(const SamplePack& data)
 
        {
 
            REQUIRE(data.numChannels() == numChannels());
 

	
 
            totalFed += data.numSamples();
 

	
 
            Sink::feedIn(data);
 
        };
 

	
 
    void setNumChannels(unsigned nc, bool x)
 
        {
 
            _numChannels = nc;
 
            _hasX = x;
 

	
 
            Sink::setNumChannels(nc, x);
 
        };
 

	
 
    virtual unsigned numChannels() const
 
        {
 
            return _numChannels;
 
        };
 

	
 
    virtual bool hasX() const
 
        {
 
            return _hasX;
 
        };
 
};
 

	
 
class TestSource : public Source
 
{
 
public:
 
    int _numChannels;
 
    bool _hasX;
 

	
 
    TestSource(unsigned nc, bool x)
 
        {
 
            _numChannels = nc;
 
            _hasX = x;
 
        };
 

	
 
    virtual unsigned numChannels() const
 
        {
 
            return _numChannels;
 
        };
 

	
 
    virtual bool hasX() const
 
        {
 
            return _hasX;
 
        };
 

	
 
    void _feed(const SamplePack& data) const
 
        {
 
            feedOut(data);
 
        };
 

	
 
    void _setNumChannels(unsigned nc, bool x)
 
        {
 
            _numChannels = nc;
 
            _hasX = x;
 

	
 
            updateNumChannels();
 
        };
 
};
 

	
 

	
 
#endif // TEST_HELPERS_H
tests/test_readers.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
// This tells Catch to provide a main() - only do this in one cpp file per executable
 
#define CATCH_CONFIG_RUNNER
 
#include "catch.hpp"
 

	
 
#include <QSignalSpy>
 
#include <QBuffer>
 
#include "binarystreamreader.h"
 
#include "asciireader.h"
 
#include "framedreader.h"
 
#include "demoreader.h"
 

	
 
#include "test_helpers.h"
 

	
 
static const int READYREAD_TIMEOUT = 10; // milliseconds
 

	
 
TEST_CASE("reading data with BinaryStreamReader", "[reader]")
 
{
 
    QBuffer bufferDev;
 
    BinaryStreamReader bs(&bufferDev);
 
    bs.enable(true);
 

	
 
    TestSink sink;
 
    bs.connectSink(&sink);
 

	
 
    REQUIRE(sink._numChannels == 1);
 
    REQUIRE(sink._hasX == false);
 

	
 
    bufferDev.open(QIODevice::ReadWrite);
 
    const char data[] = {0x01, 0x02, 0x03, 0x04};
 
    bufferDev.write(data, 4);
 
    bufferDev.seek(0);
 

	
 
    QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
 
    REQUIRE(spy.wait(READYREAD_TIMEOUT));
 
    REQUIRE(sink.totalFed == 4);
 
}
 

	
 
TEST_CASE("disabled BinaryStreamReader shouldn't read", "[reader]")
 
{
 
    QBuffer bufferDev;
 
    BinaryStreamReader bs(&bufferDev); // disabled by default
 

	
 
    TestSink sink;
 
    bs.connectSink(&sink);
 

	
 
    REQUIRE(sink._numChannels == 1);
 
    REQUIRE(sink._hasX == false);
 

	
 
    bufferDev.open(QIODevice::ReadWrite);
 
    const char data[] = {0x01, 0x02, 0x03, 0x04};
 
    bufferDev.write(data, 4);
 
    bufferDev.seek(0);
 

	
 
    QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
 
    // readyRead isn't signaled because there are no connections to it
 
    REQUIRE_FALSE(spy.wait(READYREAD_TIMEOUT));
 
    REQUIRE(sink.totalFed == 0);
 
}
 

	
 
TEST_CASE("reading data with AsciiReader", "[reader, ascii]")
 
{
 
    QBuffer bufferDev;
 
    AsciiReader reader(&bufferDev);
 
    reader.enable(true);
 

	
 
    TestSink sink;
 
    reader.connectSink(&sink);
 

	
 
    REQUIRE(sink._numChannels == 1);
 
    REQUIRE(sink._hasX == false);
 

	
 
    // inject data to the buffer
 
    bufferDev.open(QIODevice::ReadWrite);
 
    bufferDev.write("0,1,3\n0,1,3\n0,1,3\n0,1,3\n");
 
    bufferDev.seek(0);
 

	
 
    QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
 
    REQUIRE(spy.wait(READYREAD_TIMEOUT));
 
    REQUIRE(sink._numChannels == 3);
 
    REQUIRE(sink._hasX == false);
 
    REQUIRE(sink.totalFed == 3);
 
}
 

	
 
TEST_CASE("AsciiReader shouldn't read when disabled", "[reader, ascii]")
 
{
 
    QBuffer bufferDev;
 
    AsciiReader reader(&bufferDev); // disabled by default
 

	
 
    TestSink sink;
 
    reader.connectSink(&sink);
 

	
 
    REQUIRE(sink._numChannels == 1);
 
    REQUIRE(sink._hasX == false);
 

	
 
    // inject data to the buffer
 
    bufferDev.open(QIODevice::ReadWrite);
 
    bufferDev.write("0,1,3\n0,1,3\n0,1,3\n0,1,3\n");
 
    bufferDev.seek(0);
 

	
 
    QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
 
    REQUIRE_FALSE(spy.wait(READYREAD_TIMEOUT));
 
    REQUIRE(sink._numChannels == 1);
 
    REQUIRE(sink._hasX == false);
 
    REQUIRE(sink.totalFed == 0);
 
}
 

	
 
TEST_CASE("reading data with FramedReader", "[reader]")
 
{
 
    QBuffer bufferDev;
 
    FramedReader reader(&bufferDev);
 
    reader.enable(true);
 

	
 
    TestSink sink;
 
    reader.connectSink(&sink);
 

	
 
    REQUIRE(sink._numChannels == 1);
 
    REQUIRE(sink._hasX == false);
 

	
 
    bufferDev.open(QIODevice::ReadWrite);
 
    const uint8_t data[] = {0xAA, 0xBB, 4, 0x01, 0x02, 0x03, 0x04};
 
    bufferDev.write((const char*) data, 7);
 
    bufferDev.seek(0);
 

	
 
    QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
 
    REQUIRE(spy.wait(READYREAD_TIMEOUT));
 
    REQUIRE(sink.totalFed == 4);
 
}
 

	
 
TEST_CASE("FramedReader shouldn't read when disabled", "[reader]")
 
{
 
    QBuffer bufferDev;
 
    FramedReader reader(&bufferDev);
 

	
 
    TestSink sink;
 
    reader.connectSink(&sink);
 

	
 
    REQUIRE(sink._numChannels == 1);
 
    REQUIRE(sink._hasX == false);
 

	
 
    bufferDev.open(QIODevice::ReadWrite);
 
    const uint8_t data[] = {0xAA, 0xBB, 4, 0x01, 0x02, 0x03, 0x04};
 
    bufferDev.write((const char*) data, 7);
 
    bufferDev.seek(0);
 

	
 
    QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
 
    REQUIRE_FALSE(spy.wait(READYREAD_TIMEOUT));
 
    REQUIRE(sink.totalFed == 0);
 
}
 

	
 
TEST_CASE("Generating data with DemoReader", "[reader, demo]")
 
{
 
    QBuffer bufferDev;          // not actually used
 
    DemoReader demoReader(&bufferDev);
 
    demoReader.enable(true);
 

	
 
    TestSink sink;
 
    demoReader.connectSink(&sink);
 
    REQUIRE(sink._numChannels == 1);
 
    REQUIRE(sink._hasX == false);
 

	
 
    // we need to wait somehow, we are not actually looking for signals
 
    QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
 
    REQUIRE_FALSE(spy.wait(1000)); // we need some time for demoreader to produce data
 
    REQUIRE(sink.totalFed >= 9);
 
}
 

	
 
TEST_CASE("DemoReader shouldn't generate data when paused", "[reader, demo]")
 
{
 
    QBuffer bufferDev;          // not actually used
 
    DemoReader demoReader(&bufferDev); // paused by default
 

	
 
    TestSink sink;
 
    demoReader.connectSink(&sink);
 
    REQUIRE(sink._numChannels == 1);
 

	
 
    // we need to wait somehow, we are not actually looking for signals
 
    QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
 
    REQUIRE_FALSE(spy.wait(1000)); // we need some time for demoreader to produce data
 
    REQUIRE(sink.totalFed == 0);
 
}
 

	
 
// Note: this is added because `QApplication` must be created for widgets
 
#include <QApplication>
 
int main(int argc, char* argv[])
 
{
 
    QApplication a(argc, argv);
 

	
 
    int result = Catch::Session().run( argc, argv );
 

	
 
    return result;
 
}
tests/test_recorder.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#define CATCH_CONFIG_MAIN  // This tells Catch to provide a main() - only do this in one cpp file
 
#include "catch.hpp"
 

	
 
#include <QDir>
 
#include "datarecorder.h"
 
#include "test_helpers.h"
 

	
 
#define TEST_FILE_NAME   "sp_test_recording.csv"
 

	
 
TEST_CASE("test recording single channel", "[recorder]")
 
{
 
    DataRecorder rec;
 
    TestSource source(1, false);
 

	
 
    // temporary file, remove if exists
 
    auto fileName = QDir::tempPath() + QString("/" TEST_FILE_NAME);
 
    if (QFile::exists(fileName)) QFile::remove(fileName);
 

	
 
    // connect source → sink
 
    source.connectSink(&rec);
 

	
 
    // prepare data
 
    QStringList channelNames({"Channel 1"});
 
    SamplePack samples(5, 1);
 
    for (int i = 0; i < 5; i++)
 
    {
 
        samples.data(0)[i] = i+1;
 
    }
 

	
 
    // test
 
    rec.startRecording(fileName, ",", channelNames, false);
 
    source._feed(samples);
 
    rec.stopRecording();
 

	
 
    // read file contents back
 
    QFile recordFile(fileName);
 
    REQUIRE(recordFile.open(QIODevice::ReadOnly | QIODevice::Text));
 
    // NOTE: mind the extra parantheses, otherwise 'catch' macros fail to compile
 
    REQUIRE((recordFile.readLine() == "Channel 1\n"));
 
    for (int i = 0; i < 5; i++)
 
        REQUIRE((recordFile.readLine() == QString("%1\n").arg(i+1)));
 

	
 
    // cleanup
 
    if (QFile::exists(fileName)) QFile::remove(fileName);
 
}
 

	
 
TEST_CASE("test recording multiple channels", "[recorder]")
 
{
 
    DataRecorder rec;
 
    TestSource source(3, false);
 

	
 
    // temporary file, remove if exists
 
    auto fileName = QDir::tempPath() + QString("/" TEST_FILE_NAME);
 
    if (QFile::exists(fileName)) QFile::remove(fileName);
 

	
 
    // connect source → sink
 
    source.connectSink(&rec);
 

	
 
    // prepare data
 
    QStringList channelNames({"Channel 1", "Channel 2", "Channel 3"});
 
    SamplePack samples(5, 3);
 
    for (int ci = 0; ci < 3; ci++)
 
    {
 
        for (int i = 0; i < 5; i++)
 
        {
 
            samples.data(ci)[i] = (ci+1)*(i+1);
 
        }
 
    }
 

	
 
    // test
 
    rec.startRecording(fileName, ",", channelNames, false);
 
    source._feed(samples);
 
    rec.stopRecording();
 

	
 
    // read file contents back
 
    QFile recordFile(fileName);
 
    REQUIRE(recordFile.open(QIODevice::ReadOnly | QIODevice::Text));
 
    // NOTE: mind the extra parantheses, otherwise 'catch' macros fail to compile
 
    REQUIRE((recordFile.readLine() == "Channel 1,Channel 2,Channel 3\n"));
 
    REQUIRE((recordFile.readLine() == "1,2,3\n"));
 
    REQUIRE((recordFile.readLine() == "2,4,6\n"));
 
    REQUIRE((recordFile.readLine() == "3,6,9\n"));
 
    REQUIRE((recordFile.readLine() == "4,8,12\n"));
 
    REQUIRE((recordFile.readLine() == "5,10,15\n"));
 

	
 
    // cleanup
 
    if (QFile::exists(fileName)) QFile::remove(fileName);
 
}
tests/test_stream.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include "stream.h"
 

	
 
#include "catch.hpp"
 
#include "test_helpers.h"
 

	
 
TEST_CASE("construction of stream with default values", "[memory, stream]")
 
{
 
    // default values are an empty stream with no channels
 
    Stream s;
 

	
 
    REQUIRE(s.numChannels() == 0);
 
    REQUIRE(!s.hasX());
 
    REQUIRE(s.numSamples() == 0);
 
}
 

	
 
TEST_CASE("construction of stream with parameters", "[memory, stream]")
 
{
 
    Stream s(4, true, 100);
 

	
 
    REQUIRE(s.numChannels() == 4);
 
    REQUIRE(s.hasX());
 
    REQUIRE(s.numSamples() == 100);
 

	
 
    for (unsigned i = 0; i < 4; i++)
 
    {
 
        const StreamChannel* c = s.channel(i);
 
        REQUIRE(c != NULL);
 
        REQUIRE(c->index() == i);
 
    }
 
}
 

	
 
TEST_CASE("changing stream number of channels via sink", "[memory, stream, sink]")
 
{
 
    Stream s;
 
    TestSource so(3, false);
 
    so.connectSink(&s);
 

	
 
    // nc=3, x= false
 
    REQUIRE(s.numChannels() == 3);
 
    REQUIRE(!s.hasX());
 
    for (unsigned i = 0; i < 3; i++)
 
    {
 
        const StreamChannel* c = s.channel(i);
 
        REQUIRE(c != NULL);
 
        REQUIRE(c->index() == i);
 
    }
 

	
 
    // increase nc value, add X
 
    so._setNumChannels(5, true);
 

	
 
    REQUIRE(s.numChannels() == 5);
 
    REQUIRE(s.hasX());
 

	
 
    for (unsigned i = 0; i < 5; i++)
 
    {
 
        const StreamChannel* c = s.channel(i);
 
        REQUIRE(c != NULL);
 
        REQUIRE(c->index() == i);
 
    }
 

	
 
    // reduce nc value, remove X
 
    so._setNumChannels(1, false);
 

	
 
    REQUIRE(s.numChannels() == 1);
 
    REQUIRE(!s.hasX());
 

	
 
    for (unsigned i = 0; i < 1; i++)
 
    {
 
        const StreamChannel* c = s.channel(i);
 
        REQUIRE(c != NULL);
 
        REQUIRE(c->index() == i);
 
    }
 
}
 

	
 
TEST_CASE("adding data to a stream with no X", "[memory, stream, data, sink]")
 
{
 
    Stream s(3, false, 10);
 

	
 
    // prepare data
 
    SamplePack pack(5, 3, false);
 
    for (unsigned ci = 0; ci < 3; ci++)
 
    {
 
        for (unsigned i = 0; i < 5; i++)
 
        {
 
            pack.data(ci)[i] = i;
 
        }
 
    }
 

	
 
    TestSource so(3, false);
 
    so.connectSink(&s);
 

	
 
    // test
 
    so._feed(pack);
 

	
 
    for (unsigned ci = 0; ci < 3; ci++)
 
    {
 
        const StreamChannel* c = s.channel(ci);
 
        const FrameBuffer* y = c->yData();
 

	
 
        for (unsigned i = 0; i < 5; i++)
 
        {
 
            REQUIRE(y->sample(i) == 0);
 
        }
 
        for (unsigned i = 5; i < 10; i++)
 
        {
 
            REQUIRE(y->sample(i) == i-5);
 
        }
 
    }
 
}
 

	
 
TEST_CASE("adding data to a stream with X", "[memory, stream, data, sink]")
 
{
 
    Stream s(3, true, 10);
 

	
 
    // prepare data
 
    SamplePack pack(5, 3, true);
 
    for (unsigned ci = 0; ci < 3; ci++)
 
    {
 
        for (unsigned i = 0; i < 5; i++)
 
        {
 
            pack.data(ci)[i] = i;
 
        }
 
    }
 
    // x data
 
    for (unsigned i = 0; i < 5; i++)
 
    {
 
        pack.xData()[i] = i+10;
 
    }
 

	
 
    TestSource so(3, true);
 
    so.connectSink(&s);
 

	
 
    // test
 
    so._feed(pack);
 

	
 
    for (unsigned ci = 0; ci < 3; ci++)
 
    {
 
        const StreamChannel* c = s.channel(ci);
 
        const FrameBuffer* y = c->yData();
 

	
 
        for (unsigned i = 0; i < 5; i++)
 
        {
 
            REQUIRE(y->sample(i) == 0);
 
        }
 
        for (unsigned i = 5; i < 10; i++)
 
        {
 
            REQUIRE(y->sample(i) == i-5);
 
        }
 
    }
 

	
 
    // check x
 
    const FrameBuffer* x = s.channel(0)->xData();
 
    for (unsigned i = 0; i < 5; i++)
 
    {
 
        REQUIRE(x->sample(i) == 0);
 
    }
 
    for (unsigned i = 5; i < 10; i++)
 
    {
 
        REQUIRE(x->sample(i) == (i-5)+10);
 
    }
 
}
 

	
 
TEST_CASE("paused stream shouldn't store data", "[memory, stream, pause]")
 
{
 
    Stream s(3, false, 10);
 

	
 
    // prepare data
 
    SamplePack pack(5, 3, false);
 
    for (unsigned ci = 0; ci < 3; ci++)
 
    {
 
        for (unsigned i = 0; i < 5; i++)
 
        {
 
            pack.data(ci)[i] = i;
 
        }
 
    }
 

	
 
    TestSource so(3, false);
 
    so.connectSink(&s);
 

	
 
    // test
 
    s.pause(true);
 
    so._feed(pack);
 

	
 
    for (unsigned ci = 0; ci < 3; ci++)
 
    {
 
        const StreamChannel* c = s.channel(ci);
 
        const FrameBuffer* y = c->yData();
 

	
 
        for (unsigned i = 0; i < 10; i++)
 
        {
 
            REQUIRE(y->sample(i) == 0);
 
        }
 
    }
 
}
 

	
 
TEST_CASE("clear stream data", "[memory, stream, pause]")
 
{
 
    Stream s(3, false, 10);
 

	
 
    // prepare data
 
    SamplePack pack(5, 3, false);
 
    for (unsigned ci = 0; ci < 3; ci++)
 
    {
 
        for (unsigned i = 0; i < 5; i++)
 
        {
 
            pack.data(ci)[i] = i;
 
        }
 
    }
 

	
 
    TestSource so(3, false);
 
    so.connectSink(&s);
 

	
 
    // test
 
    so._feed(pack);
 
    s.clear();
 

	
 
    for (unsigned ci = 0; ci < 3; ci++)
 
    {
 
        const StreamChannel* c = s.channel(ci);
 
        const FrameBuffer* y = c->yData();
 

	
 
        for (unsigned i = 0; i < 10; i++)
 
        {
 
            REQUIRE(y->sample(i) == 0);
 
        }
 
    }
 
}
0 comments (0 inline, 0 general)