[pulseview] 01/06: Import Upstream version 0.2.0
Zoltan Gyarmati
zgyarmati-guest at moszumanska.debian.org
Wed Mar 29 21:43:19 UTC 2017
This is an automated email from the git hooks/post-receive script.
zgyarmati-guest pushed a commit to branch master
in repository pulseview.
commit 02adf9a7b0913f54d43d467d0781d319a895400c
Author: Zoltan Gyarmati <Zoltan Gyarmati mr.zoltan.gyarmati at gmail.com>
Date: Wed Mar 1 02:09:53 2017 +0100
Import Upstream version 0.2.0
---
CMake/cotire.cmake | 3185 ++++++++++++++++++++++++++++++++++++
CMake/memaccess.cmake | 41 +
CMakeLists.txt | 373 +++++
COPYING | 674 ++++++++
HACKING | 79 +
INSTALL | 53 +
NEWS | 101 ++
README | 69 +
config.h.in | 37 +
contrib/pulseview.desktop | 9 +
contrib/pulseview_cross.nsi | 252 +++
doc/pulseview.1 | 59 +
extdef.h | 29 +
icons/application-exit.png | Bin 0 -> 1084 bytes
icons/configure.png | Bin 0 -> 1151 bytes
icons/decoder-delete.svg | 17 +
icons/decoder-hidden.svg | 33 +
icons/decoder-shown.svg | 47 +
icons/document-open.png | Bin 0 -> 919 bytes
icons/document-save-as.png | Bin 0 -> 841 bytes
icons/probes.svg | 72 +
icons/sigrok-logo-notext.ico | Bin 0 -> 9662 bytes
icons/sigrok-logo-notext.png | Bin 0 -> 3782 bytes
icons/status-green.svg | 12 +
icons/status-grey.svg | 12 +
icons/status-red.svg | 12 +
icons/trigger-change.svg | 76 +
icons/trigger-falling.svg | 36 +
icons/trigger-high.svg | 36 +
icons/trigger-low.svg | 36 +
icons/trigger-none.svg | 35 +
icons/trigger-rising.svg | 36 +
icons/zoom-fit.png | Bin 0 -> 729 bytes
icons/zoom-in.png | Bin 0 -> 727 bytes
icons/zoom-original.png | Bin 0 -> 766 bytes
icons/zoom-out.png | Bin 0 -> 651 bytes
main.cpp | 181 ++
pulseview.qrc | 26 +
pulseviewico.rc | 1 +
pv/data/analog.cpp | 64 +
pv/data/analog.h | 56 +
pv/data/analogsnapshot.cpp | 215 +++
pv/data/analogsnapshot.h | 96 ++
pv/data/decode/annotation.cpp | 73 +
pv/data/decode/annotation.h | 55 +
pv/data/decode/decoder.cpp | 159 ++
pv/data/decode/decoder.h | 90 +
pv/data/decode/row.cpp | 72 +
pv/data/decode/row.h | 59 +
pv/data/decode/rowdata.cpp | 58 +
pv/data/decode/rowdata.h | 57 +
pv/data/decoderstack.cpp | 450 +++++
pv/data/decoderstack.h | 165 ++
pv/data/logic.cpp | 72 +
pv/data/logic.h | 59 +
pv/data/logicsnapshot.cpp | 468 ++++++
pv/data/logicsnapshot.h | 115 ++
pv/data/signaldata.cpp | 49 +
pv/data/signaldata.h | 52 +
pv/data/snapshot.cpp | 95 ++
pv/data/snapshot.h | 83 +
pv/device/device.cpp | 104 ++
pv/device/device.h | 51 +
pv/device/devinst.cpp | 139 ++
pv/device/devinst.h | 95 ++
pv/device/file.cpp | 66 +
pv/device/file.h | 49 +
pv/device/inputfile.cpp | 143 ++
pv/device/inputfile.h | 68 +
pv/device/sessionfile.cpp | 75 +
pv/device/sessionfile.h | 47 +
pv/devicemanager.cpp | 150 ++
pv/devicemanager.h | 75 +
pv/dialogs/about.cpp | 121 ++
pv/dialogs/about.h | 53 +
pv/dialogs/about.ui | 96 ++
pv/dialogs/connect.cpp | 241 +++
pv/dialogs/connect.h | 95 ++
pv/dialogs/storeprogress.cpp | 84 +
pv/dialogs/storeprogress.h | 65 +
pv/mainwindow.cpp | 456 ++++++
pv/mainwindow.h | 122 ++
pv/popups/deviceoptions.cpp | 53 +
pv/popups/deviceoptions.h | 54 +
pv/popups/probes.cpp | 258 +++
pv/popups/probes.h | 104 ++
pv/prop/binding/binding.cpp | 94 ++
pv/prop/binding/binding.h | 64 +
pv/prop/binding/decoderoptions.cpp | 151 ++
pv/prop/binding/decoderoptions.h | 66 +
pv/prop/binding/deviceoptions.cpp | 192 +++
pv/prop/binding/deviceoptions.h | 72 +
pv/prop/bool.cpp | 83 +
pv/prop/bool.h | 55 +
pv/prop/double.cpp | 96 ++
pv/prop/double.h | 67 +
pv/prop/enum.cpp | 98 ++
pv/prop/enum.h | 60 +
pv/prop/int.cpp | 186 +++
pv/prop/int.h | 64 +
pv/prop/property.cpp | 44 +
pv/prop/property.h | 67 +
pv/prop/string.cpp | 77 +
pv/prop/string.h | 52 +
pv/sigsession.cpp | 650 ++++++++
pv/sigsession.h | 204 +++
pv/storesession.cpp | 196 +++
pv/storesession.h | 83 +
pv/toolbars/samplingbar.cpp | 450 +++++
pv/toolbars/samplingbar.h | 121 ++
pv/view/analogsignal.cpp | 202 +++
pv/view/analogsignal.h | 92 ++
pv/view/cursor.cpp | 159 ++
pv/view/cursor.h | 86 +
pv/view/cursorpair.cpp | 155 ++
pv/view/cursorpair.h | 81 +
pv/view/decodetrace.cpp | 725 ++++++++
pv/view/decodetrace.h | 191 +++
pv/view/header.cpp | 304 ++++
pv/view/header.h | 94 ++
pv/view/logicsignal.cpp | 353 ++++
pv/view/logicsignal.h | 119 ++
pv/view/marginwidget.cpp | 35 +
pv/view/marginwidget.h | 53 +
pv/view/ruler.cpp | 275 ++++
pv/view/ruler.h | 84 +
pv/view/selectableitem.cpp | 66 +
pv/view/selectableitem.h | 81 +
pv/view/signal.cpp | 136 ++
pv/view/signal.h | 92 ++
pv/view/timemarker.cpp | 104 ++
pv/view/timemarker.h | 113 ++
pv/view/trace.cpp | 292 ++++
pv/view/trace.h | 203 +++
pv/view/tracepalette.cpp | 70 +
pv/view/tracepalette.h | 41 +
pv/view/view.cpp | 539 ++++++
pv/view/view.h | 214 +++
pv/view/viewport.cpp | 161 ++
pv/view/viewport.h | 68 +
pv/widgets/colourbutton.cpp | 109 ++
pv/widgets/colourbutton.h | 68 +
pv/widgets/colourpopup.cpp | 51 +
pv/widgets/colourpopup.h | 55 +
pv/widgets/decodergroupbox.cpp | 77 +
pv/widgets/decodergroupbox.h | 56 +
pv/widgets/decodermenu.cpp | 72 +
pv/widgets/decodermenu.h | 56 +
pv/widgets/popup.cpp | 269 +++
pv/widgets/popup.h | 93 ++
pv/widgets/popuptoolbutton.cpp | 58 +
pv/widgets/popuptoolbutton.h | 52 +
pv/widgets/sweeptimingwidget.cpp | 184 +++
pv/widgets/sweeptimingwidget.h | 75 +
pv/widgets/wellarray.cpp | 290 ++++
pv/widgets/wellarray.h | 133 ++
signalhandler.cpp | 95 ++
signalhandler.h | 55 +
test/CMakeLists.txt | 176 ++
test/data/analogsnapshot.cpp | 114 ++
test/data/decoderstack.cpp | 86 +
test/data/logicsnapshot.cpp | 534 ++++++
test/test.cpp | 22 +
163 files changed, 22415 insertions(+)
diff --git a/CMake/cotire.cmake b/CMake/cotire.cmake
new file mode 100644
index 0000000..a6e3141
--- /dev/null
+++ b/CMake/cotire.cmake
@@ -0,0 +1,3185 @@
+# - cotire (compile time reducer)
+#
+# See the cotire manual for usage hints.
+#
+#=============================================================================
+# Copyright 2012-2013 Sascha Kratky
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#=============================================================================
+
+if(__COTIRE_INCLUDED)
+ return()
+endif()
+set(__COTIRE_INCLUDED TRUE)
+
+# call cmake_minimum_required, but prevent modification of the CMake policy stack in include mode
+# cmake_minimum_required also sets the policy version as a side effect, which we have to avoid
+if (NOT CMAKE_SCRIPT_MODE_FILE)
+ cmake_policy(PUSH)
+endif()
+# we need the CMake variables CMAKE_SCRIPT_MODE_FILE and CMAKE_ARGV available since 2.8.5
+# we need APPEND_STRING option for set_property available since 2.8.6
+cmake_minimum_required(VERSION 2.8.6)
+if (NOT CMAKE_SCRIPT_MODE_FILE)
+ cmake_policy(POP)
+endif()
+
+set (COTIRE_CMAKE_MODULE_FILE "${CMAKE_CURRENT_LIST_FILE}")
+set (COTIRE_CMAKE_MODULE_VERSION "1.4.1")
+
+include(CMakeParseArguments)
+include(ProcessorCount)
+
+function (cotire_determine_compiler_version _language _versionPrefix)
+ if (NOT ${_versionPrefix}_VERSION)
+ # use CMake's predefined compiler version variable (available since CMake 2.8.8)
+ if (DEFINED CMAKE_${_language}_COMPILER_VERSION)
+ set (${_versionPrefix}_VERSION "${CMAKE_${_language}_COMPILER_VERSION}")
+ elseif (WIN32)
+ # cl.exe messes with the output streams unless the environment variable VS_UNICODE_OUTPUT is cleared
+ unset (ENV{VS_UNICODE_OUTPUT})
+ string (STRIP "${CMAKE_${_language}_COMPILER_ARG1}" _compilerArg1)
+ execute_process (COMMAND ${CMAKE_${_language}_COMPILER} ${_compilerArg1}
+ ERROR_VARIABLE _versionLine OUTPUT_QUIET TIMEOUT 10)
+ string (REGEX REPLACE ".*Version *([0-9]+(\\.[0-9]+)*).*" "\\1" ${_versionPrefix}_VERSION "${_versionLine}")
+ else()
+ # assume GCC like command line interface
+ string (STRIP "${CMAKE_${_language}_COMPILER_ARG1}" _compilerArg1)
+ execute_process (COMMAND ${CMAKE_${_language}_COMPILER} ${_compilerArg1} "-dumpversion"
+ OUTPUT_VARIABLE ${_versionPrefix}_VERSION
+ RESULT_VARIABLE _result
+ OUTPUT_STRIP_TRAILING_WHITESPACE TIMEOUT 10)
+ if (_result)
+ set (${_versionPrefix}_VERSION "")
+ endif()
+ endif()
+ if (${_versionPrefix}_VERSION)
+ set (${_versionPrefix}_VERSION "${${_versionPrefix}_VERSION}" CACHE INTERNAL "${_language} compiler version")
+ endif()
+ set (${_versionPrefix}_VERSION "${${_versionPrefix}_VERSION}" PARENT_SCOPE)
+ if (COTIRE_DEBUG)
+ message (STATUS "${CMAKE_${_language}_COMPILER} version ${${_versionPrefix}_VERSION}")
+ endif()
+ endif()
+endfunction()
+
+function (cotire_get_source_file_extension _sourceFile _extVar)
+ # get_filename_component returns extension from first occurrence of . in file name
+ # this function computes the extension from last occurrence of . in file name
+ string (FIND "${_sourceFile}" "." _index REVERSE)
+ if (_index GREATER -1)
+ math (EXPR _index "${_index} + 1")
+ string (SUBSTRING "${_sourceFile}" ${_index} -1 _sourceExt)
+ else()
+ set (_sourceExt "")
+ endif()
+ set (${_extVar} "${_sourceExt}" PARENT_SCOPE)
+endfunction()
+
+macro (cotire_check_is_path_relative_to _path _isRelativeVar)
+ set (${_isRelativeVar} FALSE)
+ if (IS_ABSOLUTE "${_path}")
+ foreach (_dir ${ARGN})
+ file (RELATIVE_PATH _relPath "${_dir}" "${_path}")
+ if (NOT _relPath OR (NOT IS_ABSOLUTE "${_relPath}" AND NOT "${_relPath}" MATCHES "^\\.\\."))
+ set (${_isRelativeVar} TRUE)
+ break()
+ endif()
+ endforeach()
+ endif()
+endmacro()
+
+function (cotire_filter_language_source_files _language _sourceFilesVar _excludedSourceFilesVar _cotiredSourceFilesVar)
+ set (_sourceFiles "")
+ set (_excludedSourceFiles "")
+ set (_cotiredSourceFiles "")
+ if (CMAKE_${_language}_SOURCE_FILE_EXTENSIONS)
+ set (_languageExtensions "${CMAKE_${_language}_SOURCE_FILE_EXTENSIONS}")
+ else()
+ set (_languageExtensions "")
+ endif()
+ if (CMAKE_${_language}_IGNORE_EXTENSIONS)
+ set (_ignoreExtensions "${CMAKE_${_language}_IGNORE_EXTENSIONS}")
+ else()
+ set (_ignoreExtensions "")
+ endif()
+ if (COTIRE_UNITY_SOURCE_EXCLUDE_EXTENSIONS)
+ set (_excludeExtensions "${COTIRE_UNITY_SOURCE_EXCLUDE_EXTENSIONS}")
+ else()
+ set (_excludeExtensions "")
+ endif()
+ if (COTIRE_DEBUG)
+ message (STATUS "${_language} source file extensions: ${_languageExtensions}")
+ message (STATUS "${_language} ignore extensions: ${_ignoreExtensions}")
+ message (STATUS "${_language} exclude extensions: ${_excludeExtensions}")
+ endif()
+ foreach (_sourceFile ${ARGN})
+ get_source_file_property(_sourceIsHeaderOnly "${_sourceFile}" HEADER_FILE_ONLY)
+ get_source_file_property(_sourceIsExternal "${_sourceFile}" EXTERNAL_OBJECT)
+ get_source_file_property(_sourceIsSymbolic "${_sourceFile}" SYMBOLIC)
+ get_source_file_property(_sourceLanguage "${_sourceFile}" LANGUAGE)
+ set (_sourceIsFiltered FALSE)
+ if (NOT _sourceIsHeaderOnly AND NOT _sourceIsExternal AND NOT _sourceIsSymbolic)
+ cotire_get_source_file_extension("${_sourceFile}" _sourceExt)
+ if (_sourceExt)
+ list (FIND _ignoreExtensions "${_sourceExt}" _ignoreIndex)
+ if (_ignoreIndex LESS 0)
+ list (FIND _excludeExtensions "${_sourceExt}" _excludeIndex)
+ if (_excludeIndex GREATER -1)
+ list (APPEND _excludedSourceFiles "${_sourceFile}")
+ else()
+ list (FIND _languageExtensions "${_sourceExt}" _sourceIndex)
+ if (_sourceIndex GREATER -1)
+ set (_sourceIsFiltered TRUE)
+ elseif ("${_sourceLanguage}" STREQUAL "${_language}")
+ # add to excluded sources, if file is not ignored and has correct language without having the correct extension
+ list (APPEND _excludedSourceFiles "${_sourceFile}")
+ endif()
+ endif()
+ endif()
+ endif()
+ endif()
+ if (COTIRE_DEBUG)
+ message (STATUS "${_sourceFile} filtered=${_sourceIsFiltered} language=${_sourceLanguage} header=${_sourceIsHeaderOnly}")
+ endif()
+ if (_sourceIsFiltered)
+ get_source_file_property(_sourceIsExcluded "${_sourceFile}" COTIRE_EXCLUDED)
+ get_source_file_property(_sourceIsCotired "${_sourceFile}" COTIRE_TARGET)
+ get_source_file_property(_sourceCompileFlags "${_sourceFile}" COMPILE_FLAGS)
+ if (COTIRE_DEBUG)
+ message (STATUS "${_sourceFile} excluded=${_sourceIsExcluded} cotired=${_sourceIsCotired}")
+ endif()
+ if (_sourceIsCotired)
+ list (APPEND _cotiredSourceFiles "${_sourceFile}")
+ elseif (_sourceIsExcluded OR _sourceCompileFlags)
+ list (APPEND _excludedSourceFiles "${_sourceFile}")
+ else()
+ list (APPEND _sourceFiles "${_sourceFile}")
+ endif()
+ endif()
+ endforeach()
+ if (COTIRE_DEBUG)
+ message (STATUS "All: ${ARGN}")
+ message (STATUS "${_language}: ${_sourceFiles}")
+ message (STATUS "Excluded: ${_excludedSourceFiles}")
+ message (STATUS "Cotired: ${_cotiredSourceFiles}")
+ endif()
+ set (${_sourceFilesVar} ${_sourceFiles} PARENT_SCOPE)
+ set (${_excludedSourceFilesVar} ${_excludedSourceFiles} PARENT_SCOPE)
+ set (${_cotiredSourceFilesVar} ${_cotiredSourceFiles} PARENT_SCOPE)
+endfunction()
+
+function (cotire_get_objects_with_property_on _filteredObjectsVar _property _type)
+ set (_filteredObjects "")
+ foreach (_object ${ARGN})
+ get_property(_isSet ${_type} "${_object}" PROPERTY ${_property} SET)
+ if (_isSet)
+ get_property(_propertyValue ${_type} "${_object}" PROPERTY ${_property})
+ if (_propertyValue)
+ list (APPEND _filteredObjects "${_object}")
+ endif()
+ endif()
+ endforeach()
+ set (${_filteredObjectsVar} ${_filteredObjects} PARENT_SCOPE)
+endfunction()
+
+function (cotire_get_objects_with_property_off _filteredObjectsVar _property _type)
+ set (_filteredObjects "")
+ foreach (_object ${ARGN})
+ get_property(_isSet ${_type} "${_object}" PROPERTY ${_property} SET)
+ if (_isSet)
+ get_property(_propertyValue ${_type} "${_object}" PROPERTY ${_property})
+ if (NOT _propertyValue)
+ list (APPEND _filteredObjects "${_object}")
+ endif()
+ endif()
+ endforeach()
+ set (${_filteredObjectsVar} ${_filteredObjects} PARENT_SCOPE)
+endfunction()
+
+function (cotire_get_source_file_property_values _valuesVar _property)
+ set (_values "")
+ foreach (_sourceFile ${ARGN})
+ get_source_file_property(_propertyValue "${_sourceFile}" ${_property})
+ if (_propertyValue)
+ list (APPEND _values "${_propertyValue}")
+ endif()
+ endforeach()
+ set (${_valuesVar} ${_values} PARENT_SCOPE)
+endfunction()
+
+function (cotrie_resolve_config_properites _configurations _propertiesVar)
+ set (_properties "")
+ foreach (_property ${ARGN})
+ if ("${_property}" MATCHES "<CONFIG>")
+ foreach (_config ${_configurations})
+ string (TOUPPER "${_config}" _upperConfig)
+ string (REPLACE "<CONFIG>" "${_upperConfig}" _configProperty "${_property}")
+ list (APPEND _properties ${_configProperty})
+ endforeach()
+ else()
+ list (APPEND _properties ${_property})
+ endif()
+ endforeach()
+ set (${_propertiesVar} ${_properties} PARENT_SCOPE)
+endfunction()
+
+function (cotrie_copy_set_properites _configurations _type _source _target)
+ cotrie_resolve_config_properites("${_configurations}" _properties ${ARGN})
+ foreach (_property ${_properties})
+ get_property(_isSet ${_type} ${_source} PROPERTY ${_property} SET)
+ if (_isSet)
+ get_property(_propertyValue ${_type} ${_source} PROPERTY ${_property})
+ set_property(${_type} ${_target} PROPERTY ${_property} "${_propertyValue}")
+ endif()
+ endforeach()
+endfunction()
+
+function (cotire_filter_compile_flags _language _flagFilter _matchedOptionsVar _unmatchedOptionsVar)
+ if (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel")
+ set (_flagPrefix "[/-]")
+ else()
+ set (_flagPrefix "--?")
+ endif()
+ set (_optionFlag "")
+ set (_matchedOptions "")
+ set (_unmatchedOptions "")
+ foreach (_compileFlag ${ARGN})
+ if (_compileFlag)
+ if (_optionFlag AND NOT "${_compileFlag}" MATCHES "^${_flagPrefix}")
+ # option with separate argument
+ list (APPEND _matchedOptions "${_compileFlag}")
+ set (_optionFlag "")
+ elseif ("${_compileFlag}" MATCHES "^(${_flagPrefix})(${_flagFilter})$")
+ # remember option
+ set (_optionFlag "${CMAKE_MATCH_2}")
+ elseif ("${_compileFlag}" MATCHES "^(${_flagPrefix})(${_flagFilter})(.+)$")
+ # option with joined argument
+ list (APPEND _matchedOptions "${CMAKE_MATCH_3}")
+ set (_optionFlag "")
+ else()
+ # flush remembered option
+ if (_optionFlag)
+ list (APPEND _matchedOptions "${_optionFlag}")
+ set (_optionFlag "")
+ endif()
+ # add to unfiltered options
+ list (APPEND _unmatchedOptions "${_compileFlag}")
+ endif()
+ endif()
+ endforeach()
+ if (_optionFlag)
+ list (APPEND _matchedOptions "${_optionFlag}")
+ endif()
+ if (COTIRE_DEBUG)
+ message (STATUS "Filter ${_flagFilter}")
+ if (_matchedOptions)
+ message (STATUS "Matched ${_matchedOptions}")
+ endif()
+ if (_unmatchedOptions)
+ message (STATUS "Unmatched ${_unmatchedOptions}")
+ endif()
+ endif()
+ set (${_matchedOptionsVar} ${_matchedOptions} PARENT_SCOPE)
+ set (${_unmatchedOptionsVar} ${_unmatchedOptions} PARENT_SCOPE)
+endfunction()
+
+function (cotire_get_target_compile_flags _config _language _directory _target _flagsVar)
+ string (TOUPPER "${_config}" _upperConfig)
+ # collect options from CMake language variables
+ set (_compileFlags "")
+ if (CMAKE_${_language}_FLAGS)
+ set (_compileFlags "${_compileFlags} ${CMAKE_${_language}_FLAGS}")
+ endif()
+ if (CMAKE_${_language}_FLAGS_${_upperConfig})
+ set (_compileFlags "${_compileFlags} ${CMAKE_${_language}_FLAGS_${_upperConfig}}")
+ endif()
+ if (_target)
+ # add option from CMake target type variable
+ get_target_property(_targetType ${_target} TYPE)
+ if (POLICY CMP0018)
+ # handle POSITION_INDEPENDENT_CODE property introduced with CMake 2.8.9 if policy CMP0018 is turned on
+ cmake_policy(GET CMP0018 _PIC_Policy)
+ else()
+ # default to old behavior
+ set (_PIC_Policy "OLD")
+ endif()
+ if (COTIRE_DEBUG)
+ message(STATUS "CMP0018=${_PIC_Policy}")
+ endif()
+ if (_PIC_Policy STREQUAL "NEW")
+ # NEW behavior: honor the POSITION_INDEPENDENT_CODE target property
+ get_target_property(_targetPIC ${_target} POSITION_INDEPENDENT_CODE)
+ if (_targetPIC)
+ if (_targetType STREQUAL "EXECUTABLE" AND CMAKE_${_language}_COMPILE_OPTIONS_PIE)
+ set (_compileFlags "${_compileFlags} ${CMAKE_${_language}_COMPILE_OPTIONS_PIE}")
+ elseif (CMAKE_${_language}_COMPILE_OPTIONS_PIC)
+ set (_compileFlags "${_compileFlags} ${CMAKE_${_language}_COMPILE_OPTIONS_PIC}")
+ endif()
+ endif()
+ else()
+ # OLD behavior or policy not set: use the value of CMAKE_SHARED_LIBRARY_<Lang>_FLAGS
+ if (_targetType STREQUAL "MODULE_LIBRARY")
+ # flags variable for module library uses different name SHARED_MODULE
+ # (e.g., CMAKE_SHARED_MODULE_C_FLAGS)
+ set (_targetType SHARED_MODULE)
+ endif()
+ if (CMAKE_${_targetType}_${_language}_FLAGS)
+ set (_compileFlags "${_compileFlags} ${CMAKE_${_targetType}_${_language}_FLAGS}")
+ endif()
+ endif()
+ endif()
+ if (_directory)
+ # add_definitions may have been used to add flags to the compiler command
+ get_directory_property(_dirDefinitions DIRECTORY "${_directory}" DEFINITIONS)
+ if (_dirDefinitions)
+ set (_compileFlags "${_compileFlags} ${_dirDefinitions}")
+ endif()
+ endif()
+ if (_target)
+ # add target compile options
+ get_target_property(_targetflags ${_target} COMPILE_FLAGS)
+ if (_targetflags)
+ set (_compileFlags "${_compileFlags} ${_targetflags}")
+ endif()
+ endif()
+ if (UNIX)
+ separate_arguments(_compileFlags UNIX_COMMAND "${_compileFlags}")
+ elseif(WIN32)
+ separate_arguments(_compileFlags WINDOWS_COMMAND "${_compileFlags}")
+ else()
+ separate_arguments(_compileFlags)
+ endif()
+ # platform specific flags
+ if (APPLE)
+ get_target_property(_architectures ${_target} OSX_ARCHITECTURES_${_upperConfig})
+ if (NOT _architectures)
+ get_target_property(_architectures ${_target} OSX_ARCHITECTURES)
+ endif()
+ foreach (_arch ${_architectures})
+ list (APPEND _compileFlags "-arch" "${_arch}")
+ endforeach()
+ if (CMAKE_OSX_SYSROOT AND CMAKE_OSX_SYSROOT_DEFAULT AND CMAKE_${_language}_HAS_ISYSROOT)
+ if (NOT "${CMAKE_OSX_SYSROOT}" STREQUAL "${CMAKE_OSX_SYSROOT_DEFAULT}")
+ list (APPEND _compileFlags "-isysroot" "${CMAKE_OSX_SYSROOT}")
+ endif()
+ endif()
+ if (CMAKE_OSX_DEPLOYMENT_TARGET AND CMAKE_${_language}_OSX_DEPLOYMENT_TARGET_FLAG)
+ list (APPEND _compileFlags "${CMAKE_${_language}_OSX_DEPLOYMENT_TARGET_FLAG}${CMAKE_OSX_DEPLOYMENT_TARGET}")
+ endif()
+ endif()
+ if (COTIRE_DEBUG AND _compileFlags)
+ message (STATUS "Target ${_target} compile flags ${_compileFlags}")
+ endif()
+ set (${_flagsVar} ${_compileFlags} PARENT_SCOPE)
+endfunction()
+
+function (cotire_get_target_include_directories _config _language _targetSourceDir _targetBinaryDir _target _includeDirsVar)
+ set (_includeDirs "")
+ # default include dirs
+ if (CMAKE_INCLUDE_CURRENT_DIR)
+ list (APPEND _includeDirs "${_targetBinaryDir}")
+ list (APPEND _includeDirs "${_targetSourceDir}")
+ endif()
+ # parse additional include directories from target compile flags
+ set (_targetFlags "")
+ cotire_get_target_compile_flags("${_config}" "${_language}" "${_targetSourceDir}" "${_target}" _targetFlags)
+ cotire_filter_compile_flags("${_language}" "I" _dirs _ignore ${_targetFlags})
+ if (_dirs)
+ list (APPEND _includeDirs ${_dirs})
+ endif()
+ # target include directories
+ get_directory_property(_dirs DIRECTORY "${_targetSourceDir}" INCLUDE_DIRECTORIES)
+ if (_target)
+ get_target_property(_targetDirs ${_target} INCLUDE_DIRECTORIES)
+ if (_targetDirs)
+ list (APPEND _dirs ${_targetDirs})
+ list (REMOVE_DUPLICATES _dirs)
+ endif()
+ endif()
+ list (LENGTH _includeDirs _projectInsertIndex)
+ foreach (_dir ${_dirs})
+ if (CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE)
+ cotire_check_is_path_relative_to("${_dir}" _isRelative "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}")
+ if (_isRelative)
+ list (LENGTH _includeDirs _len)
+ if (_len EQUAL _projectInsertIndex)
+ list (APPEND _includeDirs "${_dir}")
+ else()
+ list (INSERT _includeDirs _projectInsertIndex "${_dir}")
+ endif()
+ math (EXPR _projectInsertIndex "${_projectInsertIndex} + 1")
+ else()
+ list (APPEND _includeDirs "${_dir}")
+ endif()
+ else()
+ list (APPEND _includeDirs "${_dir}")
+ endif()
+ endforeach()
+ list (REMOVE_DUPLICATES _includeDirs)
+ if (CMAKE_${_language}_IMPLICIT_INCLUDE_DIRECTORIES)
+ list (REMOVE_ITEM _includeDirs ${CMAKE_${_language}_IMPLICIT_INCLUDE_DIRECTORIES})
+ endif()
+ if (COTIRE_DEBUG AND _includeDirs)
+ message (STATUS "Target ${_target} include dirs ${_includeDirs}")
+ endif()
+ set (${_includeDirsVar} ${_includeDirs} PARENT_SCOPE)
+endfunction()
+
+macro (cotire_make_C_identifier _identifierVar _str)
+ # mimic CMake SystemTools::MakeCindentifier behavior
+ if ("${_str}" MATCHES "^[0-9].+$")
+ set (_str "_${str}")
+ endif()
+ string (REGEX REPLACE "[^a-zA-Z0-9]" "_" ${_identifierVar} "${_str}")
+endmacro()
+
+function (cotire_get_target_export_symbol _target _exportSymbolVar)
+ set (_exportSymbol "")
+ get_target_property(_targetType ${_target} TYPE)
+ get_target_property(_enableExports ${_target} ENABLE_EXPORTS)
+ if (_targetType MATCHES "(SHARED|MODULE)_LIBRARY" OR
+ (_targetType STREQUAL "EXECUTABLE" AND _enableExports))
+ get_target_property(_exportSymbol ${_target} DEFINE_SYMBOL)
+ if (NOT _exportSymbol)
+ set (_exportSymbol "${_target}_EXPORTS")
+ endif()
+ cotire_make_C_identifier(_exportSymbol "${_exportSymbol}")
+ endif()
+ set (${_exportSymbolVar} ${_exportSymbol} PARENT_SCOPE)
+endfunction()
+
+function (cotire_get_target_compile_definitions _config _language _directory _target _definitionsVar)
+ string (TOUPPER "${_config}" _upperConfig)
+ set (_configDefinitions "")
+ # CMAKE_INTDIR for multi-configuration build systems
+ if (NOT "${CMAKE_CFG_INTDIR}" STREQUAL ".")
+ list (APPEND _configDefinitions "CMAKE_INTDIR=\"${_config}\"")
+ endif()
+ # target export define symbol
+ cotire_get_target_export_symbol("${_target}" _defineSymbol)
+ if (_defineSymbol)
+ list (APPEND _configDefinitions "${_defineSymbol}")
+ endif()
+ # directory compile definitions
+ get_directory_property(_definitions DIRECTORY "${_directory}" COMPILE_DEFINITIONS)
+ if (_definitions)
+ list (APPEND _configDefinitions ${_definitions})
+ endif()
+ get_directory_property(_definitions DIRECTORY "${_directory}" COMPILE_DEFINITIONS_${_upperConfig})
+ if (_definitions)
+ list (APPEND _configDefinitions ${_definitions})
+ endif()
+ # target compile definitions
+ get_target_property(_definitions ${_target} COMPILE_DEFINITIONS)
+ if (_definitions)
+ list (APPEND _configDefinitions ${_definitions})
+ endif()
+ get_target_property(_definitions ${_target} COMPILE_DEFINITIONS_${_upperConfig})
+ if (_definitions)
+ list (APPEND _configDefinitions ${_definitions})
+ endif()
+ # parse additional compile definitions from target compile flags
+ # and don't look at directory compile definitions, which we already handled
+ set (_targetFlags "")
+ cotire_get_target_compile_flags("${_config}" "${_language}" "" "${_target}" _targetFlags)
+ cotire_filter_compile_flags("${_language}" "D" _definitions _ignore ${_targetFlags})
+ if (_definitions)
+ list (APPEND _configDefinitions ${_definitions})
+ endif()
+ list (REMOVE_DUPLICATES _configDefinitions)
+ if (COTIRE_DEBUG AND _configDefinitions)
+ message (STATUS "Target ${_target} compile definitions ${_configDefinitions}")
+ endif()
+ set (${_definitionsVar} ${_configDefinitions} PARENT_SCOPE)
+endfunction()
+
+function (cotire_get_target_compiler_flags _config _language _directory _target _compilerFlagsVar)
+ # parse target compile flags omitting compile definitions and include directives
+ set (_targetFlags "")
+ cotire_get_target_compile_flags("${_config}" "${_language}" "${_directory}" "${_target}" _targetFlags)
+ set (_compilerFlags "")
+ cotire_filter_compile_flags("${_language}" "[ID]" _ignore _compilerFlags ${_targetFlags})
+ if (COTIRE_DEBUG AND _compilerFlags)
+ message (STATUS "Target ${_target} compiler flags ${_compilerFlags}")
+ endif()
+ set (${_compilerFlagsVar} ${_compilerFlags} PARENT_SCOPE)
+endfunction()
+
+function (cotire_add_sys_root_paths _pathsVar)
+ if (APPLE)
+ if (CMAKE_OSX_SYSROOT AND CMAKE_${_language}_HAS_ISYSROOT)
+ foreach (_path IN LISTS ${_pathsVar})
+ if (IS_ABSOLUTE "${_path}")
+ get_filename_component(_path "${CMAKE_OSX_SYSROOT}/${_path}" ABSOLUTE)
+ if (EXISTS "${_path}")
+ list (APPEND ${_pathsVar} "${_path}")
+ endif()
+ endif()
+ endforeach()
+ endif()
+ endif()
+ set (${_pathsVar} ${${_pathsVar}} PARENT_SCOPE)
+ if (COTIRE_DEBUG)
+ message (STATUS "${_pathsVar}=${${_pathsVar}}")
+ endif()
+endfunction()
+
+function (cotire_get_source_extra_properties _sourceFile _pattern _resultVar)
+ set (_extraProperties ${ARGN})
+ set (_result "")
+ if (_extraProperties)
+ list (FIND _extraProperties "${_sourceFile}" _index)
+ if (_index GREATER -1)
+ math (EXPR _index "${_index} + 1")
+ list (LENGTH _extraProperties _len)
+ math (EXPR _len "${_len} - 1")
+ foreach (_index RANGE ${_index} ${_len})
+ list (GET _extraProperties ${_index} _value)
+ if ("${_value}" MATCHES "${_pattern}")
+ list (APPEND _result "${_value}")
+ else()
+ break()
+ endif()
+ endforeach()
+ endif()
+ endif()
+ set (${_resultVar} ${_result} PARENT_SCOPE)
+endfunction()
+
+function (cotire_get_source_compile_definitions _config _language _sourceFile _definitionsVar)
+ set (_compileDefinitions "")
+ if (NOT CMAKE_SCRIPT_MODE_FILE)
+ string (TOUPPER "${_config}" _upperConfig)
+ get_source_file_property(_definitions "${_sourceFile}" COMPILE_DEFINITIONS)
+ if (_definitions)
+ list (APPEND _compileDefinitions ${_definitions})
+ endif()
+ get_source_file_property(_definitions "${_sourceFile}" COMPILE_DEFINITIONS_${_upperConfig})
+ if (_definitions)
+ list (APPEND _compileDefinitions ${_definitions})
+ endif()
+ endif()
+ cotire_get_source_extra_properties("${_sourceFile}" "^[a-zA-Z0-9_]+(=.*)?$" _definitions ${ARGN})
+ if (_definitions)
+ list (APPEND _compileDefinitions ${_definitions})
+ endif()
+ if (COTIRE_DEBUG AND _compileDefinitions)
+ message (STATUS "Source ${_sourceFile} compile definitions ${_compileDefinitions}")
+ endif()
+ set (${_definitionsVar} ${_compileDefinitions} PARENT_SCOPE)
+endfunction()
+
+function (cotire_get_source_files_compile_definitions _config _language _definitionsVar)
+ set (_configDefinitions "")
+ foreach (_sourceFile ${ARGN})
+ cotire_get_source_compile_definitions("${_config}" "${_language}" "${_sourceFile}" _sourceDefinitions)
+ if (_sourceDefinitions)
+ list (APPEND _configDefinitions "${_sourceFile}" ${_sourceDefinitions} "-")
+ endif()
+ endforeach()
+ set (${_definitionsVar} ${_configDefinitions} PARENT_SCOPE)
+endfunction()
+
+function (cotire_get_source_undefs _sourceFile _property _sourceUndefsVar)
+ set (_sourceUndefs "")
+ if (NOT CMAKE_SCRIPT_MODE_FILE)
+ get_source_file_property(_undefs "${_sourceFile}" ${_property})
+ if (_undefs)
+ list (APPEND _sourceUndefs ${_undefs})
+ endif()
+ endif()
+ cotire_get_source_extra_properties("${_sourceFile}" "^[a-zA-Z0-9_]+$" _undefs ${ARGN})
+ if (_undefs)
+ list (APPEND _sourceUndefs ${_undefs})
+ endif()
+ if (COTIRE_DEBUG AND _sourceUndefs)
+ message (STATUS "Source ${_sourceFile} ${_property} undefs ${_sourceUndefs}")
+ endif()
+ set (${_sourceUndefsVar} ${_sourceUndefs} PARENT_SCOPE)
+endfunction()
+
+function (cotire_get_source_files_undefs _property _sourceUndefsVar)
+ set (_sourceUndefs "")
+ foreach (_sourceFile ${ARGN})
+ cotire_get_source_undefs("${_sourceFile}" ${_property} _undefs)
+ if (_undefs)
+ list (APPEND _sourceUndefs "${_sourceFile}" ${_undefs} "-")
+ endif()
+ endforeach()
+ set (${_sourceUndefsVar} ${_sourceUndefs} PARENT_SCOPE)
+endfunction()
+
+macro (cotire_set_cmd_to_prologue _cmdVar)
+ set (${_cmdVar} "${CMAKE_COMMAND}")
+ if (COTIRE_DEBUG)
+ list (APPEND ${_cmdVar} "--warn-uninitialized")
+ endif()
+ list (APPEND ${_cmdVar} "-DCOTIRE_BUILD_TYPE:STRING=$<CONFIGURATION>")
+ if (COTIRE_VERBOSE)
+ list (APPEND ${_cmdVar} "-DCOTIRE_VERBOSE:BOOL=ON")
+ elseif("${CMAKE_GENERATOR}" MATCHES "Makefiles")
+ list (APPEND ${_cmdVar} "-DCOTIRE_VERBOSE:BOOL=$(VERBOSE)")
+ endif()
+endmacro()
+
+function (cotire_init_compile_cmd _cmdVar _language _compilerExe _compilerArg1)
+ if (NOT _compilerExe)
+ set (_compilerExe "${CMAKE_${_language}_COMPILER}")
+ endif()
+ if (NOT _compilerArg1)
+ set (_compilerArg1 ${CMAKE_${_language}_COMPILER_ARG1})
+ endif()
+ string (STRIP "${_compilerArg1}" _compilerArg1)
+ set (${_cmdVar} "${_compilerExe}" ${_compilerArg1} PARENT_SCOPE)
+endfunction()
+
+macro (cotire_add_definitions_to_cmd _cmdVar _language)
+ foreach (_definition ${ARGN})
+ if (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel")
+ list (APPEND ${_cmdVar} "/D${_definition}")
+ else()
+ list (APPEND ${_cmdVar} "-D${_definition}")
+ endif()
+ endforeach()
+endmacro()
+
+macro (cotire_add_includes_to_cmd _cmdVar _language)
+ foreach (_include ${ARGN})
+ if (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel")
+ file (TO_NATIVE_PATH "${_include}" _include)
+ list (APPEND ${_cmdVar} "/I${_include}")
+ else()
+ list (APPEND ${_cmdVar} "-I${_include}")
+ endif()
+ endforeach()
+endmacro()
+
+macro (cotire_add_compile_flags_to_cmd _cmdVar)
+ foreach (_flag ${ARGN})
+ list (APPEND ${_cmdVar} "${_flag}")
+ endforeach()
+endmacro()
+
+function (cotire_check_file_up_to_date _fileIsUpToDateVar _file)
+ set (${_fileIsUpToDateVar} FALSE PARENT_SCOPE)
+ set (_triggerFile "")
+ foreach (_dependencyFile ${ARGN})
+ if (EXISTS "${_dependencyFile}" AND "${_dependencyFile}" IS_NEWER_THAN "${_file}")
+ set (_triggerFile "${_dependencyFile}")
+ break()
+ endif()
+ endforeach()
+ get_filename_component(_fileName "${_file}" NAME)
+ if (EXISTS "${_file}")
+ if (_triggerFile)
+ if (COTIRE_VERBOSE)
+ message (STATUS "${_fileName} update triggered by ${_triggerFile} change.")
+ endif()
+ else()
+ if (COTIRE_VERBOSE)
+ message (STATUS "${_fileName} is up-to-date.")
+ endif()
+ set (${_fileIsUpToDateVar} TRUE PARENT_SCOPE)
+ endif()
+ else()
+ if (COTIRE_VERBOSE)
+ message (STATUS "${_fileName} does not exist yet.")
+ endif()
+ endif()
+endfunction()
+
+macro (cotire_find_closest_relative_path _headerFile _includeDirs _relPathVar)
+ set (${_relPathVar} "")
+ foreach (_includeDir ${_includeDirs})
+ if (IS_DIRECTORY "${_includeDir}")
+ file (RELATIVE_PATH _relPath "${_includeDir}" "${_headerFile}")
+ if (NOT IS_ABSOLUTE "${_relPath}" AND NOT "${_relPath}" MATCHES "^\\.\\.")
+ string (LENGTH "${${_relPathVar}}" _closestLen)
+ string (LENGTH "${_relPath}" _relLen)
+ if (_closestLen EQUAL 0 OR _relLen LESS _closestLen)
+ set (${_relPathVar} "${_relPath}")
+ endif()
+ endif()
+ elseif ("${_includeDir}" STREQUAL "${_headerFile}")
+ # if path matches exactly, return short non-empty string
+ set (${_relPathVar} "1")
+ break()
+ endif()
+ endforeach()
+endmacro()
+
+macro (cotire_check_header_file_location _headerFile _insideIncudeDirs _outsideIncudeDirs _headerIsInside)
+ # check header path against ignored and honored include directories
+ cotire_find_closest_relative_path("${_headerFile}" "${_insideIncudeDirs}" _insideRelPath)
+ if (_insideRelPath)
+ # header is inside, but could be become outside if there is a shorter outside match
+ cotire_find_closest_relative_path("${_headerFile}" "${_outsideIncudeDirs}" _outsideRelPath)
+ if (_outsideRelPath)
+ string (LENGTH "${_insideRelPath}" _insideRelPathLen)
+ string (LENGTH "${_outsideRelPath}" _outsideRelPathLen)
+ if (_outsideRelPathLen LESS _insideRelPathLen)
+ set (${_headerIsInside} FALSE)
+ else()
+ set (${_headerIsInside} TRUE)
+ endif()
+ else()
+ set (${_headerIsInside} TRUE)
+ endif()
+ else()
+ # header is outside
+ set (${_headerIsInside} FALSE)
+ endif()
+endmacro()
+
+macro (cotire_check_ignore_header_file_path _headerFile _headerIsIgnoredVar)
+ if (NOT EXISTS "${_headerFile}")
+ set (${_headerIsIgnoredVar} TRUE)
+ elseif (IS_DIRECTORY "${_headerFile}")
+ set (${_headerIsIgnoredVar} TRUE)
+ elseif ("${_headerFile}" MATCHES "\\.\\.|[_-]fixed" AND "${_headerFile}" MATCHES "\\.h$")
+ # heuristic: ignore C headers with embedded parent directory references or "-fixed" or "_fixed" in path
+ # these often stem from using GCC #include_next tricks, which may break the precompiled header compilation
+ # with the error message "error: no include path in which to search for header.h"
+ set (${_headerIsIgnoredVar} TRUE)
+ else()
+ set (${_headerIsIgnoredVar} FALSE)
+ endif()
+endmacro()
+
+macro (cotire_check_ignore_header_file_ext _headerFile _ignoreExtensionsVar _headerIsIgnoredVar)
+ # check header file extension
+ cotire_get_source_file_extension("${_headerFile}" _headerFileExt)
+ set (${_headerIsIgnoredVar} FALSE)
+ if (_headerFileExt)
+ list (FIND ${_ignoreExtensionsVar} "${_headerFileExt}" _index)
+ if (_index GREATER -1)
+ set (${_headerIsIgnoredVar} TRUE)
+ endif()
+ endif()
+endmacro()
+
+macro (cotire_parse_line _line _headerFileVar _headerDepthVar)
+ if (MSVC)
+ # cl.exe /showIncludes output looks different depending on the language pack used, e.g.:
+ # English: "Note: including file: C:\directory\file"
+ # German: "Hinweis: Einlesen der Datei: C:\directory\file"
+ # We use a very general regular expression, relying on the presence of the : characters
+ if ("${_line}" MATCHES ":( +)([^:]+:[^:]+)$")
+ # Visual Studio compiler output
+ string (LENGTH "${CMAKE_MATCH_1}" ${_headerDepthVar})
+ get_filename_component(${_headerFileVar} "${CMAKE_MATCH_2}" ABSOLUTE)
+ else()
+ set (${_headerFileVar} "")
+ set (${_headerDepthVar} 0)
+ endif()
+ else()
+ if ("${_line}" MATCHES "^(\\.+) (.*)$")
+ # GCC like output
+ string (LENGTH "${CMAKE_MATCH_1}" ${_headerDepthVar})
+ if (IS_ABSOLUTE "${CMAKE_MATCH_2}")
+ set (${_headerFileVar} "${CMAKE_MATCH_2}")
+ else()
+ get_filename_component(${_headerFileVar} "${CMAKE_MATCH_2}" REALPATH)
+ endif()
+ else()
+ set (${_headerFileVar} "")
+ set (${_headerDepthVar} 0)
+ endif()
+ endif()
+endmacro()
+
+function (cotire_parse_includes _language _scanOutput _ignoredIncudeDirs _honoredIncudeDirs _ignoredExtensions _selectedIncludesVar _unparsedLinesVar)
+ if (WIN32)
+ # prevent CMake macro invocation errors due to backslash characters in Windows paths
+ string (REPLACE "\\" "/" _scanOutput "${_scanOutput}")
+ endif()
+ # canonize slashes
+ string (REPLACE "//" "/" _scanOutput "${_scanOutput}")
+ # prevent semicolon from being interpreted as a line separator
+ string (REPLACE ";" "\\;" _scanOutput "${_scanOutput}")
+ # then separate lines
+ string (REGEX REPLACE "\n" ";" _scanOutput "${_scanOutput}")
+ list (LENGTH _scanOutput _len)
+ # remove duplicate lines to speed up parsing
+ list (REMOVE_DUPLICATES _scanOutput)
+ list (LENGTH _scanOutput _uniqueLen)
+ if (COTIRE_VERBOSE)
+ message (STATUS "Scanning ${_uniqueLen} unique lines of ${_len} for includes")
+ if (_ignoredExtensions)
+ message (STATUS "Ignored extensions: ${_ignoredExtensions}")
+ endif()
+ if (_ignoredIncudeDirs)
+ message (STATUS "Ignored paths: ${_ignoredIncudeDirs}")
+ endif()
+ if (_honoredIncudeDirs)
+ message (STATUS "Included paths: ${_honoredIncudeDirs}")
+ endif()
+ endif()
+ set (_sourceFiles ${ARGN})
+ set (_selectedIncludes "")
+ set (_unparsedLines "")
+ # stack keeps track of inside/outside project status of processed header files
+ set (_headerIsInsideStack "")
+ foreach (_line IN LISTS _scanOutput)
+ if (_line)
+ cotire_parse_line("${_line}" _headerFile _headerDepth)
+ if (_headerFile)
+ cotire_check_header_file_location("${_headerFile}" "${_ignoredIncudeDirs}" "${_honoredIncudeDirs}" _headerIsInside)
+ if (COTIRE_DEBUG)
+ message (STATUS "${_headerDepth}: ${_headerFile} ${_headerIsInside}")
+ endif()
+ # update stack
+ list (LENGTH _headerIsInsideStack _stackLen)
+ if (_headerDepth GREATER _stackLen)
+ math (EXPR _stackLen "${_stackLen} + 1")
+ foreach (_index RANGE ${_stackLen} ${_headerDepth})
+ list (APPEND _headerIsInsideStack ${_headerIsInside})
+ endforeach()
+ else()
+ foreach (_index RANGE ${_headerDepth} ${_stackLen})
+ list (REMOVE_AT _headerIsInsideStack -1)
+ endforeach()
+ list (APPEND _headerIsInsideStack ${_headerIsInside})
+ endif()
+ if (COTIRE_DEBUG)
+ message (STATUS "${_headerIsInsideStack}")
+ endif()
+ # header is a candidate if it is outside project
+ if (NOT _headerIsInside)
+ # get parent header file's inside/outside status
+ if (_headerDepth GREATER 1)
+ math (EXPR _index "${_headerDepth} - 2")
+ list (GET _headerIsInsideStack ${_index} _parentHeaderIsInside)
+ else()
+ set (_parentHeaderIsInside TRUE)
+ endif()
+ # select header file if parent header file is inside project
+ # (e.g., a project header file that includes a standard header file)
+ if (_parentHeaderIsInside)
+ cotire_check_ignore_header_file_path("${_headerFile}" _headerIsIgnored)
+ if (NOT _headerIsIgnored)
+ cotire_check_ignore_header_file_ext("${_headerFile}" _ignoredExtensions _headerIsIgnored)
+ if (NOT _headerIsIgnored)
+ list (APPEND _selectedIncludes "${_headerFile}")
+ else()
+ # fix header's inside status on stack, it is ignored by extension now
+ list (REMOVE_AT _headerIsInsideStack -1)
+ list (APPEND _headerIsInsideStack TRUE)
+ endif()
+ endif()
+ if (COTIRE_DEBUG)
+ message (STATUS "${_headerFile} ${_ignoredExtensions} ${_headerIsIgnored}")
+ endif()
+ endif()
+ endif()
+ else()
+ if (MSVC)
+ # for cl.exe do not keep unparsed lines which solely consist of a source file name
+ string (FIND "${_sourceFiles}" "${_line}" _index)
+ if (_index LESS 0)
+ list (APPEND _unparsedLines "${_line}")
+ endif()
+ else()
+ list (APPEND _unparsedLines "${_line}")
+ endif()
+ endif()
+ endif()
+ endforeach()
+ list (REMOVE_DUPLICATES _selectedIncludes)
+ set (${_selectedIncludesVar} ${_selectedIncludes} PARENT_SCOPE)
+ set (${_unparsedLinesVar} ${_unparsedLines} PARENT_SCOPE)
+endfunction()
+
+function (cotire_scan_includes _includesVar)
+ set(_options "")
+ set(_oneValueArgs COMPILER_ID COMPILER_EXECUTABLE COMPILER_VERSION LANGUAGE UNPARSED_LINES)
+ set(_multiValueArgs COMPILE_DEFINITIONS COMPILE_FLAGS INCLUDE_DIRECTORIES IGNORE_PATH INCLUDE_PATH IGNORE_EXTENSIONS)
+ cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN})
+ set (_sourceFiles ${_option_UNPARSED_ARGUMENTS})
+ if (NOT _option_LANGUAGE)
+ set (_option_LANGUAGE "CXX")
+ endif()
+ if (NOT _option_COMPILER_ID)
+ set (_option_COMPILER_ID "${CMAKE_${_option_LANGUAGE}_ID}")
+ endif()
+ set (_cmd "${_option_COMPILER_EXECUTABLE}" ${_option_COMPILER_ARG1})
+ cotire_init_compile_cmd(_cmd "${_option_LANGUAGE}" "${_option_COMPILER_EXECUTABLE}" "${_option_COMPILER_ARG1}")
+ cotire_add_definitions_to_cmd(_cmd "${_option_LANGUAGE}" ${_option_COMPILE_DEFINITIONS})
+ cotire_add_compile_flags_to_cmd(_cmd ${_option_COMPILE_FLAGS})
+ cotire_add_includes_to_cmd(_cmd "${_option_LANGUAGE}" ${_option_INCLUDE_DIRECTORIES})
+ cotire_add_makedep_flags("${_option_LANGUAGE}" "${_option_COMPILER_ID}" "${_option_COMPILER_VERSION}" _cmd)
+ # only consider existing source files for scanning
+ set (_existingSourceFiles "")
+ foreach (_sourceFile ${_sourceFiles})
+ if (EXISTS "${_sourceFile}")
+ list (APPEND _existingSourceFiles "${_sourceFile}")
+ endif()
+ endforeach()
+ if (NOT _existingSourceFiles)
+ set (${_includesVar} "" PARENT_SCOPE)
+ return()
+ endif()
+ list (APPEND _cmd ${_existingSourceFiles})
+ if (COTIRE_VERBOSE)
+ message (STATUS "execute_process: ${_cmd}")
+ endif()
+ if (_option_COMPILER_ID MATCHES "MSVC")
+ if (COTIRE_DEBUG)
+ message (STATUS "clearing VS_UNICODE_OUTPUT")
+ endif()
+ # cl.exe messes with the output streams unless the environment variable VS_UNICODE_OUTPUT is cleared
+ unset (ENV{VS_UNICODE_OUTPUT})
+ endif()
+ execute_process(COMMAND ${_cmd} WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
+ RESULT_VARIABLE _result OUTPUT_QUIET ERROR_VARIABLE _output)
+ if (_result)
+ message (STATUS "Result ${_result} scanning includes of ${_existingSourceFiles}.")
+ endif()
+ cotire_parse_includes(
+ "${_option_LANGUAGE}" "${_output}"
+ "${_option_IGNORE_PATH}" "${_option_INCLUDE_PATH}"
+ "${_option_IGNORE_EXTENSIONS}"
+ _includes _unparsedLines
+ ${_sourceFiles})
+ set (${_includesVar} ${_includes} PARENT_SCOPE)
+ if (_option_UNPARSED_LINES)
+ set (${_option_UNPARSED_LINES} ${_unparsedLines} PARENT_SCOPE)
+ endif()
+endfunction()
+
+macro (cotire_append_undefs _contentsVar)
+ set (_undefs ${ARGN})
+ if (_undefs)
+ list (REMOVE_DUPLICATES _undefs)
+ foreach (_definition ${_undefs})
+ list (APPEND ${_contentsVar} "#undef ${_definition}")
+ endforeach()
+ endif()
+endmacro()
+
+macro (cotire_comment_str _language _commentText _commentVar)
+ if ("${_language}" STREQUAL "CMAKE")
+ set (${_commentVar} "# ${_commentText}")
+ else()
+ set (${_commentVar} "/* ${_commentText} */")
+ endif()
+endmacro()
+
+function (cotire_write_file _language _file _contents _force)
+ get_filename_component(_moduleName "${COTIRE_CMAKE_MODULE_FILE}" NAME)
+ cotire_comment_str("${_language}" "${_moduleName} ${COTIRE_CMAKE_MODULE_VERSION} generated file" _header1)
+ cotire_comment_str("${_language}" "${_file}" _header2)
+ set (_contents "${_header1}\n${_header2}\n${_contents}")
+ if (COTIRE_DEBUG)
+ message (STATUS "${_contents}")
+ endif()
+ if (_force OR NOT EXISTS "${_file}")
+ file (WRITE "${_file}" "${_contents}")
+ else()
+ file (READ "${_file}" _oldContents)
+ if (NOT "${_oldContents}" STREQUAL "${_contents}")
+ file (WRITE "${_file}" "${_contents}")
+ else()
+ if (COTIRE_DEBUG)
+ message (STATUS "${_file} unchanged")
+ endif()
+ endif()
+ endif()
+endfunction()
+
+function (cotire_generate_unity_source _unityFile)
+ set(_options "")
+ set(_oneValueArgs LANGUAGE)
+ set(_multiValueArgs
+ DEPENDS SOURCES_COMPILE_DEFINITIONS
+ PRE_UNDEFS SOURCES_PRE_UNDEFS POST_UNDEFS SOURCES_POST_UNDEFS PROLOGUE EPILOGUE)
+ cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN})
+ if (_option_DEPENDS)
+ cotire_check_file_up_to_date(_unityFileIsUpToDate "${_unityFile}" ${_option_DEPENDS})
+ if (_unityFileIsUpToDate)
+ return()
+ endif()
+ endif()
+ set (_sourceFiles ${_option_UNPARSED_ARGUMENTS})
+ if (NOT _option_PRE_UNDEFS)
+ set (_option_PRE_UNDEFS "")
+ endif()
+ if (NOT _option_SOURCES_PRE_UNDEFS)
+ set (_option_SOURCES_PRE_UNDEFS "")
+ endif()
+ if (NOT _option_POST_UNDEFS)
+ set (_option_POST_UNDEFS "")
+ endif()
+ if (NOT _option_SOURCES_POST_UNDEFS)
+ set (_option_SOURCES_POST_UNDEFS "")
+ endif()
+ set (_contents "")
+ if (_option_PROLOGUE)
+ list (APPEND _contents ${_option_PROLOGUE})
+ endif()
+ if (_option_LANGUAGE AND _sourceFiles)
+ if ("${_option_LANGUAGE}" STREQUAL "CXX")
+ list (APPEND _contents "#ifdef __cplusplus")
+ elseif ("${_option_LANGUAGE}" STREQUAL "C")
+ list (APPEND _contents "#ifndef __cplusplus")
+ endif()
+ endif()
+ set (_compileUndefinitions "")
+ foreach (_sourceFile ${_sourceFiles})
+ cotire_get_source_compile_definitions(
+ "${_option_CONFIGURATION}" "${_option_LANGUAGE}" "${_sourceFile}" _compileDefinitions
+ ${_option_SOURCES_COMPILE_DEFINITIONS})
+ cotire_get_source_undefs("${_sourceFile}" COTIRE_UNITY_SOURCE_PRE_UNDEFS _sourcePreUndefs ${_option_SOURCES_PRE_UNDEFS})
+ cotire_get_source_undefs("${_sourceFile}" COTIRE_UNITY_SOURCE_POST_UNDEFS _sourcePostUndefs ${_option_SOURCES_POST_UNDEFS})
+ if (_option_PRE_UNDEFS)
+ list (APPEND _compileUndefinitions ${_option_PRE_UNDEFS})
+ endif()
+ if (_sourcePreUndefs)
+ list (APPEND _compileUndefinitions ${_sourcePreUndefs})
+ endif()
+ if (_compileUndefinitions)
+ cotire_append_undefs(_contents ${_compileUndefinitions})
+ set (_compileUndefinitions "")
+ endif()
+ if (_sourcePostUndefs)
+ list (APPEND _compileUndefinitions ${_sourcePostUndefs})
+ endif()
+ if (_option_POST_UNDEFS)
+ list (APPEND _compileUndefinitions ${_option_POST_UNDEFS})
+ endif()
+ foreach (_definition ${_compileDefinitions})
+ if ("${_definition}" MATCHES "^([a-zA-Z0-9_]+)=(.+)$")
+ list (APPEND _contents "#define ${CMAKE_MATCH_1} ${CMAKE_MATCH_2}")
+ list (INSERT _compileUndefinitions 0 "${CMAKE_MATCH_1}")
+ else()
+ list (APPEND _contents "#define ${_definition}")
+ list (INSERT _compileUndefinitions 0 "${_definition}")
+ endif()
+ endforeach()
+ get_filename_component(_sourceFile "${_sourceFile}" ABSOLUTE)
+ if (WIN32)
+ file (TO_NATIVE_PATH "${_sourceFile}" _sourceFile)
+ endif()
+ list (APPEND _contents "#include \"${_sourceFile}\"")
+ endforeach()
+ if (_compileUndefinitions)
+ cotire_append_undefs(_contents ${_compileUndefinitions})
+ set (_compileUndefinitions "")
+ endif()
+ if (_option_LANGUAGE AND _sourceFiles)
+ list (APPEND _contents "#endif")
+ endif()
+ if (_option_EPILOGUE)
+ list (APPEND _contents ${_option_EPILOGUE})
+ endif()
+ list (APPEND _contents "")
+ string (REPLACE ";" "\n" _contents "${_contents}")
+ if (COTIRE_VERBOSE)
+ message ("${_contents}")
+ endif()
+ cotire_write_file("${_option_LANGUAGE}" "${_unityFile}" "${_contents}" TRUE)
+endfunction()
+
+function (cotire_generate_prefix_header _prefixFile)
+ set(_options "")
+ set(_oneValueArgs LANGUAGE COMPILER_EXECUTABLE COMPILER_ID COMPILER_VERSION)
+ set(_multiValueArgs DEPENDS COMPILE_DEFINITIONS COMPILE_FLAGS
+ INCLUDE_DIRECTORIES IGNORE_PATH INCLUDE_PATH IGNORE_EXTENSIONS)
+ cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN})
+ if (_option_DEPENDS)
+ cotire_check_file_up_to_date(_prefixFileIsUpToDate "${_prefixFile}" ${_option_DEPENDS})
+ if (_prefixFileIsUpToDate)
+ return()
+ endif()
+ endif()
+ set (_epilogue "")
+ if (_option_COMPILER_ID MATCHES "Intel")
+ # Intel compiler requires hdrstop pragma to stop generating PCH file
+ set (_epilogue "#pragma hdrstop")
+ endif()
+ set (_sourceFiles ${_option_UNPARSED_ARGUMENTS})
+ cotire_scan_includes(_selectedHeaders ${_sourceFiles}
+ LANGUAGE "${_option_LANGUAGE}"
+ COMPILER_EXECUTABLE "${_option_COMPILER_EXECUTABLE}"
+ COMPILER_ID "${_option_COMPILER_ID}"
+ COMPILER_VERSION "${_option_COMPILER_VERSION}"
+ COMPILE_DEFINITIONS ${_option_COMPILE_DEFINITIONS}
+ COMPILE_FLAGS ${_option_COMPILE_FLAGS}
+ INCLUDE_DIRECTORIES ${_option_INCLUDE_DIRECTORIES}
+ IGNORE_PATH ${_option_IGNORE_PATH}
+ INCLUDE_PATH ${_option_INCLUDE_PATH}
+ IGNORE_EXTENSIONS ${_option_IGNORE_EXTENSIONS}
+ UNPARSED_LINES _unparsedLines)
+ cotire_generate_unity_source("${_prefixFile}" EPILOGUE ${_epilogue} LANGUAGE "${_option_LANGUAGE}" ${_selectedHeaders})
+ set (_unparsedLinesFile "${_prefixFile}.log")
+ if (_unparsedLines)
+ if (COTIRE_VERBOSE OR NOT _selectedHeaders)
+ list (LENGTH _unparsedLines _skippedLineCount)
+ file (RELATIVE_PATH _unparsedLinesFileRelPath "${CMAKE_BINARY_DIR}" "${_unparsedLinesFile}")
+ message (STATUS "${_skippedLineCount} line(s) skipped, see ${_unparsedLinesFileRelPath}")
+ endif()
+ string (REPLACE ";" "\n" _unparsedLines "${_unparsedLines}")
+ endif()
+ file (WRITE "${_unparsedLinesFile}" "${_unparsedLines}\n")
+endfunction()
+
+function (cotire_add_makedep_flags _language _compilerID _compilerVersion _flagsVar)
+ set (_flags ${${_flagsVar}})
+ if (_compilerID MATCHES "MSVC")
+ # cl.exe options used
+ # /nologo suppresses display of sign-on banner
+ # /TC treat all files named on the command line as C source files
+ # /TP treat all files named on the command line as C++ source files
+ # /EP preprocess to stdout without #line directives
+ # /showIncludes list include files
+ set (_sourceFileTypeC "/TC")
+ set (_sourceFileTypeCXX "/TP")
+ if (_flags)
+ # append to list
+ list (APPEND _flags /nologo "${_sourceFileType${_language}}" /EP /showIncludes)
+ else()
+ # return as a flag string
+ set (_flags "${_sourceFileType${_language}} /EP /showIncludes")
+ endif()
+ elseif (_compilerID MATCHES "GNU")
+ # GCC options used
+ # -H print the name of each header file used
+ # -E invoke preprocessor
+ # -fdirectives-only do not expand macros, requires GCC >= 4.3
+ if (_flags)
+ # append to list
+ list (APPEND _flags -H -E)
+ if (NOT "${_compilerVersion}" VERSION_LESS "4.3.0")
+ list (APPEND _flags "-fdirectives-only")
+ endif()
+ else()
+ # return as a flag string
+ set (_flags "-H -E")
+ if (NOT "${_compilerVersion}" VERSION_LESS "4.3.0")
+ set (_flags "${_flags} -fdirectives-only")
+ endif()
+ endif()
+ elseif (_compilerID MATCHES "Clang")
+ # Clang options used
+ # -H print the name of each header file used
+ # -E invoke preprocessor
+ if (_flags)
+ # append to list
+ list (APPEND _flags -H -E)
+ else()
+ # return as a flag string
+ set (_flags "-H -E")
+ endif()
+ elseif (_compilerID MATCHES "Intel")
+ if (WIN32)
+ # Windows Intel options used
+ # /nologo do not display compiler version information
+ # /QH display the include file order
+ # /EP preprocess to stdout, omitting #line directives
+ # /TC process all source or unrecognized file types as C source files
+ # /TP process all source or unrecognized file types as C++ source files
+ set (_sourceFileTypeC "/TC")
+ set (_sourceFileTypeCXX "/TP")
+ if (_flags)
+ # append to list
+ list (APPEND _flags /nologo "${_sourceFileType${_language}}" /EP /QH)
+ else()
+ # return as a flag string
+ set (_flags "${_sourceFileType${_language}} /EP /QH")
+ endif()
+ else()
+ # Linux / Mac OS X Intel options used
+ # -H print the name of each header file used
+ # -EP preprocess to stdout, omitting #line directives
+ # -Kc++ process all source or unrecognized file types as C++ source files
+ if (_flags)
+ # append to list
+ if ("${_language}" STREQUAL "CXX")
+ list (APPEND _flags -Kc++)
+ endif()
+ list (APPEND _flags -H -EP)
+ else()
+ # return as a flag string
+ if ("${_language}" STREQUAL "CXX")
+ set (_flags "-Kc++ ")
+ endif()
+ set (_flags "${_flags}-H -EP")
+ endif()
+ endif()
+ else()
+ message (FATAL_ERROR "Unsupported ${_language} compiler ${_compilerID} version ${_compilerVersion}.")
+ endif()
+ set (${_flagsVar} ${_flags} PARENT_SCOPE)
+endfunction()
+
+function (cotire_add_pch_compilation_flags _language _compilerID _compilerVersion _prefixFile _pchFile _hostFile _flagsVar)
+ set (_flags ${${_flagsVar}})
+ if (_compilerID MATCHES "MSVC")
+ file (TO_NATIVE_PATH "${_prefixFile}" _prefixFileNative)
+ file (TO_NATIVE_PATH "${_pchFile}" _pchFileNative)
+ file (TO_NATIVE_PATH "${_hostFile}" _hostFileNative)
+ # cl.exe options used
+ # /Yc creates a precompiled header file
+ # /Fp specifies precompiled header binary file name
+ # /FI forces inclusion of file
+ # /TC treat all files named on the command line as C source files
+ # /TP treat all files named on the command line as C++ source files
+ # /Zs syntax check only
+ set (_sourceFileTypeC "/TC")
+ set (_sourceFileTypeCXX "/TP")
+ if (_flags)
+ # append to list
+ list (APPEND _flags /nologo "${_sourceFileType${_language}}"
+ "/Yc${_prefixFileNative}" "/Fp${_pchFileNative}" "/FI${_prefixFileNative}" /Zs "${_hostFileNative}")
+ else()
+ # return as a flag string
+ set (_flags "/Yc\"${_prefixFileNative}\" /Fp\"${_pchFileNative}\" /FI\"${_prefixFileNative}\"")
+ endif()
+ elseif (_compilerID MATCHES "GNU|Clang")
+ # GCC / Clang options used
+ # -x specify the source language
+ # -c compile but do not link
+ # -o place output in file
+ set (_xLanguage_C "c-header")
+ set (_xLanguage_CXX "c++-header")
+ if (_flags)
+ # append to list
+ list (APPEND _flags "-x" "${_xLanguage_${_language}}" "-c" "${_prefixFile}" -o "${_pchFile}")
+ else()
+ # return as a flag string
+ set (_flags "-x ${_xLanguage_${_language}} -c \"${_prefixFile}\" -o \"${_pchFile}\"")
+ endif()
+ elseif (_compilerID MATCHES "Intel")
+ if (WIN32)
+ file (TO_NATIVE_PATH "${_prefixFile}" _prefixFileNative)
+ file (TO_NATIVE_PATH "${_pchFile}" _pchFileNative)
+ file (TO_NATIVE_PATH "${_hostFile}" _hostFileNative)
+ # Windows Intel options used
+ # /nologo do not display compiler version information
+ # /Yc create a precompiled header (PCH) file
+ # /Fp specify a path or file name for precompiled header files
+ # /FI tells the preprocessor to include a specified file name as the header file
+ # /TC process all source or unrecognized file types as C source files
+ # /TP process all source or unrecognized file types as C++ source files
+ # /Zs syntax check only
+ # /Wpch-messages enable diagnostics related to pre-compiled headers (requires Intel XE 2013 Update 2)
+ set (_sourceFileTypeC "/TC")
+ set (_sourceFileTypeCXX "/TP")
+ if (_flags)
+ # append to list
+ list (APPEND _flags /nologo "${_sourceFileType${_language}}"
+ "/Yc" "/Fp${_pchFileNative}" "/FI${_prefixFileNative}" /Zs "${_hostFileNative}")
+ if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0")
+ list (APPEND _flags "/Wpch-messages")
+ endif()
+ else()
+ # return as a flag string
+ set (_flags "/Yc /Fp\"${_pchFileNative}\" /FI\"${_prefixFileNative}\"")
+ if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0")
+ set (_flags "${_flags} /Wpch-messages")
+ endif()
+ endif()
+ else()
+ # Linux / Mac OS X Intel options used
+ # -pch-dir location for precompiled header files
+ # -pch-create name of the precompiled header (PCH) to create
+ # -Kc++ process all source or unrecognized file types as C++ source files
+ # -fsyntax-only check only for correct syntax
+ # -Wpch-messages enable diagnostics related to pre-compiled headers (requires Intel XE 2013 Update 2)
+ get_filename_component(_pchDir "${_pchFile}" PATH)
+ get_filename_component(_pchName "${_pchFile}" NAME)
+ set (_xLanguage_C "c-header")
+ set (_xLanguage_CXX "c++-header")
+ if (_flags)
+ # append to list
+ if ("${_language}" STREQUAL "CXX")
+ list (APPEND _flags -Kc++)
+ endif()
+ list (APPEND _flags "-include" "${_prefixFile}" "-pch-dir" "${_pchDir}" "-pch-create" "${_pchName}" "-fsyntax-only" "${_hostFile}")
+ if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0")
+ list (APPEND _flags "-Wpch-messages")
+ endif()
+ else()
+ # return as a flag string
+ set (_flags "-include \"${_prefixFile}\" -pch-dir \"${_pchDir}\" -pch-create \"${_pchName}\"")
+ if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0")
+ set (_flags "${_flags} -Wpch-messages")
+ endif()
+ endif()
+ endif()
+ else()
+ message (FATAL_ERROR "Unsupported ${_language} compiler ${_compilerID} version ${_compilerVersion}.")
+ endif()
+ set (${_flagsVar} ${_flags} PARENT_SCOPE)
+endfunction()
+
+function (cotire_add_pch_inclusion_flags _language _compilerID _compilerVersion _prefixFile _pchFile _flagsVar)
+ set (_flags ${${_flagsVar}})
+ if (_compilerID MATCHES "MSVC")
+ file (TO_NATIVE_PATH "${_prefixFile}" _prefixFileNative)
+ file (TO_NATIVE_PATH "${_pchFile}" _pchFileNative)
+ # cl.exe options used
+ # /Yu uses a precompiled header file during build
+ # /Fp specifies precompiled header binary file name
+ # /FI forces inclusion of file
+ if (_flags)
+ # append to list
+ list (APPEND _flags "/Yu${_prefixFileNative}" "/Fp${_pchFileNative}" "/FI${_prefixFileNative}")
+ else()
+ # return as a flag string
+ set (_flags "/Yu\"${_prefixFileNative}\" /Fp\"${_pchFileNative}\" /FI\"${_prefixFileNative}\"")
+ endif()
+ elseif (_compilerID MATCHES "GNU")
+ # GCC options used
+ # -include process include file as the first line of the primary source file
+ # -Winvalid-pch warns if precompiled header is found but cannot be used
+ if (_flags)
+ # append to list
+ list (APPEND _flags "-include" "${_prefixFile}" "-Winvalid-pch")
+ else()
+ # return as a flag string
+ set (_flags "-include \"${_prefixFile}\" -Winvalid-pch")
+ endif()
+ elseif (_compilerID MATCHES "Clang")
+ # Clang options used
+ # -include process include file as the first line of the primary source file
+ # -Qunused-arguments don't emit warning for unused driver arguments
+ if (_flags)
+ # append to list
+ list (APPEND _flags "-include" "${_prefixFile}" "-Qunused-arguments")
+ else()
+ # return as a flag string
+ set (_flags "-include \"${_prefixFile}\" -Qunused-arguments")
+ endif()
+ elseif (_compilerID MATCHES "Intel")
+ if (WIN32)
+ file (TO_NATIVE_PATH "${_prefixFile}" _prefixFileNative)
+ file (TO_NATIVE_PATH "${_pchFile}" _pchFileNative)
+ # Windows Intel options used
+ # /Yu use a precompiled header (PCH) file
+ # /Fp specify a path or file name for precompiled header files
+ # /FI tells the preprocessor to include a specified file name as the header file
+ # /Wpch-messages enable diagnostics related to pre-compiled headers (requires Intel XE 2013 Update 2)
+ if (_flags)
+ # append to list
+ list (APPEND _flags "/Yu" "/Fp${_pchFileNative}" "/FI${_prefixFileNative}")
+ if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0")
+ list (APPEND _flags "/Wpch-messages")
+ endif()
+ else()
+ # return as a flag string
+ set (_flags "/Yu /Fp\"${_pchFileNative}\" /FI\"${_prefixFileNative}\"")
+ if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0")
+ set (_flags "${_flags} /Wpch-messages")
+ endif()
+ endif()
+ else()
+ # Linux / Mac OS X Intel options used
+ # -pch-dir location for precompiled header files
+ # -pch-use name of the precompiled header (PCH) to use
+ # -include process include file as the first line of the primary source file
+ # -Wpch-messages enable diagnostics related to pre-compiled headers (requires Intel XE 2013 Update 2)
+ get_filename_component(_pchDir "${_pchFile}" PATH)
+ get_filename_component(_pchName "${_pchFile}" NAME)
+ if (_flags)
+ # append to list
+ list (APPEND _flags "-include" "${_prefixFile}" "-pch-dir" "${_pchDir}" "-pch-use" "${_pchName}")
+ if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0")
+ list (APPEND _flags "-Wpch-messages")
+ endif()
+ else()
+ # return as a flag string
+ set (_flags "-include \"${_prefixFile}\" -pch-dir \"${_pchDir}\" -pch-use \"${_pchName}\"")
+ if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0")
+ set (_flags "${_flags} -Wpch-messages")
+ endif()
+ endif()
+ endif()
+ else()
+ message (FATAL_ERROR "Unsupported ${_language} compiler ${_compilerID} version ${_compilerVersion}.")
+ endif()
+ set (${_flagsVar} ${_flags} PARENT_SCOPE)
+endfunction()
+
+function (cotire_precompile_prefix_header _prefixFile _pchFile _hostFile)
+ set(_options "")
+ set(_oneValueArgs COMPILER_EXECUTABLE COMPILER_ID COMPILER_VERSION LANGUAGE)
+ set(_multiValueArgs COMPILE_DEFINITIONS COMPILE_FLAGS INCLUDE_DIRECTORIES)
+ cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN})
+ if (NOT _option_LANGUAGE)
+ set (_option_LANGUAGE "CXX")
+ endif()
+ if (NOT _option_COMPILER_ID)
+ set (_option_COMPILER_ID "${CMAKE_${_option_LANGUAGE}_ID}")
+ endif()
+ cotire_init_compile_cmd(_cmd "${_option_LANGUAGE}" "${_option_COMPILER_EXECUTABLE}" "${_option_COMPILER_ARG1}")
+ cotire_add_definitions_to_cmd(_cmd "${_option_LANGUAGE}" ${_option_COMPILE_DEFINITIONS})
+ cotire_add_compile_flags_to_cmd(_cmd ${_option_COMPILE_FLAGS})
+ cotire_add_includes_to_cmd(_cmd "${_option_LANGUAGE}" ${_option_INCLUDE_DIRECTORIES})
+ cotire_add_pch_compilation_flags(
+ "${_option_LANGUAGE}" "${_option_COMPILER_ID}" "${_option_COMPILER_VERSION}"
+ "${_prefixFile}" "${_pchFile}" "${_hostFile}" _cmd)
+ if (COTIRE_VERBOSE)
+ message (STATUS "execute_process: ${_cmd}")
+ endif()
+ if (_option_COMPILER_ID MATCHES "MSVC")
+ if (COTIRE_DEBUG)
+ message (STATUS "clearing VS_UNICODE_OUTPUT")
+ endif()
+ # cl.exe messes with the output streams unless the environment variable VS_UNICODE_OUTPUT is cleared
+ unset (ENV{VS_UNICODE_OUTPUT})
+ endif()
+ execute_process(COMMAND ${_cmd} WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" RESULT_VARIABLE _result)
+ if (_result)
+ message (FATAL_ERROR "Error ${_result} precompiling ${_prefixFile}.")
+ endif()
+endfunction()
+
+function (cotire_check_precompiled_header_support _language _targetSourceDir _target _msgVar)
+ set (_unsupportedCompiler
+ "Precompiled headers not supported for ${_language} compiler ${CMAKE_${_language}_COMPILER_ID}")
+ if (CMAKE_${_language}_COMPILER_ID MATCHES "MSVC")
+ # supported since Visual Studio C++ 6.0
+ # and CMake does not support an earlier version
+ set (${_msgVar} "" PARENT_SCOPE)
+ elseif (CMAKE_${_language}_COMPILER_ID MATCHES "GNU")
+ # GCC PCH support requires version >= 3.4
+ cotire_determine_compiler_version("${_language}" COTIRE_${_language}_COMPILER)
+ if ("${COTIRE_${_language}_COMPILER_VERSION}" MATCHES ".+" AND
+ "${COTIRE_${_language}_COMPILER_VERSION}" VERSION_LESS "3.4.0")
+ set (${_msgVar} "${_unsupportedCompiler} version ${COTIRE_${_language}_COMPILER_VERSION}." PARENT_SCOPE)
+ else()
+ set (${_msgVar} "" PARENT_SCOPE)
+ endif()
+ elseif (CMAKE_${_language}_COMPILER_ID MATCHES "Clang")
+ # all Clang versions have PCH support
+ set (${_msgVar} "" PARENT_SCOPE)
+ elseif (CMAKE_${_language}_COMPILER_ID MATCHES "Intel")
+ # Intel PCH support requires version >= 8.0.0
+ cotire_determine_compiler_version("${_language}" COTIRE_${_language}_COMPILER)
+ if ("${COTIRE_${_language}_COMPILER_VERSION}" MATCHES ".+" AND
+ "${COTIRE_${_language}_COMPILER_VERSION}" VERSION_LESS "8.0.0")
+ set (${_msgVar} "${_unsupportedCompiler} version ${COTIRE_${_language}_COMPILER_VERSION}." PARENT_SCOPE)
+ else()
+ set (${_msgVar} "" PARENT_SCOPE)
+ endif()
+ else()
+ set (${_msgVar} "${_unsupportedCompiler}." PARENT_SCOPE)
+ endif()
+ if (APPLE)
+ # PCH compilation not supported by GCC / Clang for multi-architecture builds (e.g., i386, x86_64)
+ if (CMAKE_CONFIGURATION_TYPES)
+ set (_configs ${CMAKE_CONFIGURATION_TYPES})
+ elseif (CMAKE_BUILD_TYPE)
+ set (_configs ${CMAKE_BUILD_TYPE})
+ else()
+ set (_configs "None")
+ endif()
+ foreach (_config ${_configs})
+ set (_targetFlags "")
+ cotire_get_target_compile_flags("${_config}" "${_language}" "${_targetSourceDir}" "${_target}" _targetFlags)
+ cotire_filter_compile_flags("${_language}" "arch" _architectures _ignore ${_targetFlags})
+ list (LENGTH _architectures _numberOfArchitectures)
+ if (_numberOfArchitectures GREATER 1)
+ string (REPLACE ";" ", " _architectureStr "${_architectures}")
+ set (${_msgVar}
+ "Precompiled headers not supported on Darwin for multi-architecture builds (${_architectureStr})."
+ PARENT_SCOPE)
+ break()
+ endif()
+ endforeach()
+ endif()
+endfunction()
+
+macro (cotire_get_intermediate_dir _cotireDir)
+ get_filename_component(${_cotireDir} "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${COTIRE_INTDIR}" ABSOLUTE)
+endmacro()
+
+macro (cotire_setup_file_extension_variables)
+ set (_unityFileExt_C ".c")
+ set (_unityFileExt_CXX ".cxx")
+ set (_prefixFileExt_C ".h")
+ set (_prefixFileExt_CXX ".hxx")
+endmacro()
+
+function (cotire_make_single_unity_source_file_path _language _target _unityFileVar)
+ cotire_setup_file_extension_variables()
+ if (NOT DEFINED _unityFileExt_${_language})
+ set (${_unityFileVar} "" PARENT_SCOPE)
+ return()
+ endif()
+ set (_unityFileBaseName "${_target}_${_language}${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}")
+ set (_unityFileName "${_unityFileBaseName}${_unityFileExt_${_language}}")
+ cotire_get_intermediate_dir(_baseDir)
+ set (_unityFile "${_baseDir}/${_unityFileName}")
+ set (${_unityFileVar} "${_unityFile}" PARENT_SCOPE)
+ if (COTIRE_DEBUG)
+ message(STATUS "${_unityFile}")
+ endif()
+endfunction()
+
+function (cotire_make_unity_source_file_paths _language _target _maxIncludes _unityFilesVar)
+ cotire_setup_file_extension_variables()
+ if (NOT DEFINED _unityFileExt_${_language})
+ set (${_unityFileVar} "" PARENT_SCOPE)
+ return()
+ endif()
+ set (_unityFileBaseName "${_target}_${_language}${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}")
+ cotire_get_intermediate_dir(_baseDir)
+ set (_startIndex 0)
+ set (_index 0)
+ set (_unityFiles "")
+ set (_sourceFiles ${ARGN})
+ foreach (_sourceFile ${_sourceFiles})
+ get_source_file_property(_startNew "${_sourceFile}" COTIRE_START_NEW_UNITY_SOURCE)
+ math (EXPR _unityFileCount "${_index} - ${_startIndex}")
+ if (_startNew OR (_maxIncludes GREATER 0 AND NOT _unityFileCount LESS _maxIncludes))
+ if (_index GREATER 0)
+ # start new unity file segment
+ math (EXPR _endIndex "${_index} - 1")
+ set (_unityFileName "${_unityFileBaseName}_${_startIndex}_${_endIndex}${_unityFileExt_${_language}}")
+ list (APPEND _unityFiles "${_baseDir}/${_unityFileName}")
+ endif()
+ set (_startIndex ${_index})
+ endif()
+ math (EXPR _index "${_index} + 1")
+ endforeach()
+ list (LENGTH _sourceFiles _numberOfSources)
+ if (_startIndex EQUAL 0)
+ # there is only a single unity file
+ cotire_make_single_unity_source_file_path(${_language} ${_target} _unityFiles)
+ elseif (_startIndex LESS _numberOfSources)
+ # end with final unity file segment
+ math (EXPR _endIndex "${_index} - 1")
+ set (_unityFileName "${_unityFileBaseName}_${_startIndex}_${_endIndex}${_unityFileExt_${_language}}")
+ list (APPEND _unityFiles "${_baseDir}/${_unityFileName}")
+ endif()
+ set (${_unityFilesVar} ${_unityFiles} PARENT_SCOPE)
+ if (COTIRE_DEBUG)
+ message(STATUS "${_unityFiles}")
+ endif()
+endfunction()
+
+function (cotire_unity_to_prefix_file_path _language _target _unityFile _prefixFileVar)
+ cotire_setup_file_extension_variables()
+ if (NOT DEFINED _unityFileExt_${_language})
+ set (${_prefixFileVar} "" PARENT_SCOPE)
+ return()
+ endif()
+ set (_unityFileBaseName "${_target}_${_language}${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}")
+ set (_prefixFileBaseName "${_target}_${_language}${COTIRE_PREFIX_HEADER_FILENAME_SUFFIX}")
+ string (REPLACE "${_unityFileBaseName}" "${_prefixFileBaseName}" _prefixFile "${_unityFile}")
+ string (REGEX REPLACE "${_unityFileExt_${_language}}$" "${_prefixFileExt_${_language}}" _prefixFile "${_prefixFile}")
+ set (${_prefixFileVar} "${_prefixFile}" PARENT_SCOPE)
+endfunction()
+
+function (cotire_make_prefix_file_name _language _target _prefixFileBaseNameVar _prefixFileNameVar)
+ cotire_setup_file_extension_variables()
+ if (NOT _language)
+ set (_prefixFileBaseName "${_target}${COTIRE_PREFIX_HEADER_FILENAME_SUFFIX}")
+ set (_prefixFileName "${_prefixFileBaseName}${_prefixFileExt_C}")
+ elseif (DEFINED _prefixFileExt_${_language})
+ set (_prefixFileBaseName "${_target}_${_language}${COTIRE_PREFIX_HEADER_FILENAME_SUFFIX}")
+ set (_prefixFileName "${_prefixFileBaseName}${_prefixFileExt_${_language}}")
+ else()
+ set (_prefixFileBaseName "")
+ set (_prefixFileName "")
+ endif()
+ set (${_prefixFileBaseNameVar} "${_prefixFileBaseName}" PARENT_SCOPE)
+ set (${_prefixFileNameVar} "${_prefixFileName}" PARENT_SCOPE)
+endfunction()
+
+function (cotire_make_prefix_file_path _language _target _prefixFileVar)
+ cotire_make_prefix_file_name("${_language}" "${_target}" _prefixFileBaseName _prefixFileName)
+ set (${_prefixFileVar} "" PARENT_SCOPE)
+ if (_prefixFileName)
+ if (NOT _language)
+ set (_language "C")
+ endif()
+ if (MSVC OR CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang|Intel")
+ cotire_get_intermediate_dir(_baseDir)
+ set (${_prefixFileVar} "${_baseDir}/${_prefixFileName}" PARENT_SCOPE)
+ endif()
+ endif()
+endfunction()
+
+function (cotire_make_pch_file_path _language _targetSourceDir _target _pchFileVar)
+ cotire_make_prefix_file_name("${_language}" "${_target}" _prefixFileBaseName _prefixFileName)
+ set (${_pchFileVar} "" PARENT_SCOPE)
+ if (_prefixFileBaseName AND _prefixFileName)
+ cotire_check_precompiled_header_support("${_language}" "${_targetSourceDir}" "${_target}" _msg)
+ if (NOT _msg)
+ if (XCODE)
+ # For Xcode, we completely hand off the compilation of the prefix header to the IDE
+ return()
+ endif()
+ cotire_get_intermediate_dir(_baseDir)
+ if (CMAKE_${_language}_COMPILER_ID MATCHES "MSVC")
+ # MSVC uses the extension .pch added to the prefix header base name
+ set (${_pchFileVar} "${_baseDir}/${_prefixFileBaseName}.pch" PARENT_SCOPE)
+ elseif (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang")
+ # GCC / Clang look for a precompiled header corresponding to the prefix header with the extension .gch appended
+ set (${_pchFileVar} "${_baseDir}/${_prefixFileName}.gch" PARENT_SCOPE)
+ elseif (CMAKE_${_language}_COMPILER_ID MATCHES "Intel")
+ # Intel uses the extension .pchi added to the prefix header base name
+ set (${_pchFileVar} "${_baseDir}/${_prefixFileBaseName}.pchi" PARENT_SCOPE)
+ endif()
+ endif()
+ endif()
+endfunction()
+
+function (cotire_select_unity_source_files _unityFile _sourcesVar)
+ set (_sourceFiles ${ARGN})
+ if (_sourceFiles AND "${_unityFile}" MATCHES "${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}_([0-9]+)_([0-9]+)")
+ set (_startIndex ${CMAKE_MATCH_1})
+ set (_endIndex ${CMAKE_MATCH_2})
+ list (LENGTH _sourceFiles _numberOfSources)
+ if (NOT _startIndex LESS _numberOfSources)
+ math (EXPR _startIndex "${_numberOfSources} - 1")
+ endif()
+ if (NOT _endIndex LESS _numberOfSources)
+ math (EXPR _endIndex "${_numberOfSources} - 1")
+ endif()
+ set (_files "")
+ foreach (_index RANGE ${_startIndex} ${_endIndex})
+ list (GET _sourceFiles ${_index} _file)
+ list (APPEND _files "${_file}")
+ endforeach()
+ else()
+ set (_files ${_sourceFiles})
+ endif()
+ set (${_sourcesVar} ${_files} PARENT_SCOPE)
+endfunction()
+
+function (cotire_get_unity_source_dependencies _language _target _dependencySourcesVar)
+ set (_dependencySources "")
+ # depend on target's generated source files
+ cotire_get_objects_with_property_on(_generatedSources GENERATED SOURCE ${ARGN})
+ if (_generatedSources)
+ # but omit all generated source files that have the COTIRE_EXCLUDED property set to true
+ cotire_get_objects_with_property_on(_excludedGeneratedSources COTIRE_EXCLUDED SOURCE ${_generatedSources})
+ if (_excludedGeneratedSources)
+ list (REMOVE_ITEM _generatedSources ${_excludedGeneratedSources})
+ endif()
+ # and omit all generated source files that have the COTIRE_DEPENDENCY property set to false explicitly
+ cotire_get_objects_with_property_off(_excludedNonDependencySources COTIRE_DEPENDENCY SOURCE ${_generatedSources})
+ if (_excludedNonDependencySources)
+ list (REMOVE_ITEM _generatedSources ${_excludedNonDependencySources})
+ endif()
+ if (_generatedSources)
+ list (APPEND _dependencySources ${_generatedSources})
+ endif()
+ endif()
+ if (COTIRE_DEBUG AND _dependencySources)
+ message (STATUS "${_language} ${_target} unity source depends on ${_dependencySources}")
+ endif()
+ set (${_dependencySourcesVar} ${_dependencySources} PARENT_SCOPE)
+endfunction()
+
+function (cotire_get_prefix_header_dependencies _language _target _dependencySourcesVar)
+ # depend on target source files marked with custom COTIRE_DEPENDENCY property
+ set (_dependencySources "")
+ cotire_get_objects_with_property_on(_dependencySources COTIRE_DEPENDENCY SOURCE ${ARGN})
+ if (COTIRE_DEBUG AND _dependencySources)
+ message (STATUS "${_language} ${_target} prefix header DEPENDS ${_dependencySources}")
+ endif()
+ set (${_dependencySourcesVar} ${_dependencySources} PARENT_SCOPE)
+endfunction()
+
+function (cotire_generate_target_script _language _configurations _targetSourceDir _targetBinaryDir _target _targetScriptVar)
+ set (COTIRE_TARGET_SOURCES ${ARGN})
+ get_filename_component(_moduleName "${COTIRE_CMAKE_MODULE_FILE}" NAME)
+ set (_targetCotireScript "${CMAKE_CURRENT_BINARY_DIR}/${_target}_${_language}_${_moduleName}")
+ cotire_get_prefix_header_dependencies(${_language} ${_target} COTIRE_TARGET_PREFIX_DEPENDS ${COTIRE_TARGET_SOURCES})
+ cotire_get_unity_source_dependencies(${_language} ${_target} COTIRE_TARGET_UNITY_DEPENDS ${COTIRE_TARGET_SOURCES})
+ # set up variables to be configured
+ set (COTIRE_TARGET_LANGUAGE "${_language}")
+ cotire_determine_compiler_version("${COTIRE_TARGET_LANGUAGE}" COTIRE_${_language}_COMPILER)
+ get_target_property(COTIRE_TARGET_IGNORE_PATH ${_target} COTIRE_PREFIX_HEADER_IGNORE_PATH)
+ cotire_add_sys_root_paths(COTIRE_TARGET_IGNORE_PATH)
+ get_target_property(COTIRE_TARGET_INCLUDE_PATH ${_target} COTIRE_PREFIX_HEADER_INCLUDE_PATH)
+ cotire_add_sys_root_paths(COTIRE_TARGET_INCLUDE_PATH)
+ get_target_property(COTIRE_TARGET_PRE_UNDEFS ${_target} COTIRE_UNITY_SOURCE_PRE_UNDEFS)
+ get_target_property(COTIRE_TARGET_POST_UNDEFS ${_target} COTIRE_UNITY_SOURCE_POST_UNDEFS)
+ get_target_property(COTIRE_TARGET_MAXIMUM_NUMBER_OF_INCLUDES ${_target} COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES)
+ cotire_get_source_files_undefs(COTIRE_UNITY_SOURCE_PRE_UNDEFS COTIRE_TARGET_SOURCES_PRE_UNDEFS ${COTIRE_TARGET_SOURCES})
+ cotire_get_source_files_undefs(COTIRE_UNITY_SOURCE_POST_UNDEFS COTIRE_TARGET_SOURCES_POST_UNDEFS ${COTIRE_TARGET_SOURCES})
+ set (COTIRE_TARGET_CONFIGURATION_TYPES "${_configurations}")
+ foreach (_config ${_configurations})
+ string (TOUPPER "${_config}" _upperConfig)
+ cotire_get_target_include_directories(
+ "${_config}" "${_language}" "${_targetSourceDir}" "${_targetBinaryDir}" "${_target}" COTIRE_TARGET_INCLUDE_DIRECTORIES_${_upperConfig})
+ cotire_get_target_compile_definitions(
+ "${_config}" "${_language}" "${_targetSourceDir}" "${_target}" COTIRE_TARGET_COMPILE_DEFINITIONS_${_upperConfig})
+ cotire_get_target_compiler_flags(
+ "${_config}" "${_language}" "${_targetSourceDir}" "${_target}" COTIRE_TARGET_COMPILE_FLAGS_${_upperConfig})
+ cotire_get_source_files_compile_definitions(
+ "${_config}" "${_language}" COTIRE_TARGET_SOURCES_COMPILE_DEFINITIONS_${_upperConfig} ${COTIRE_TARGET_SOURCES})
+ endforeach()
+ get_cmake_property(_vars VARIABLES)
+ string (REGEX MATCHALL "COTIRE_[A-Za-z0-9_]+" _matchVars "${_vars}")
+ # remove COTIRE_VERBOSE which is passed as a CMake define on command line
+ list (REMOVE_ITEM _matchVars COTIRE_VERBOSE)
+ set (_contents "")
+ foreach (_var IN LISTS _matchVars ITEMS
+ MSVC CMAKE_GENERATOR CMAKE_BUILD_TYPE CMAKE_CONFIGURATION_TYPES
+ CMAKE_${_language}_COMPILER_ID CMAKE_${_language}_COMPILER CMAKE_${_language}_COMPILER_ARG1
+ CMAKE_${_language}_SOURCE_FILE_EXTENSIONS)
+ if (DEFINED ${_var})
+ string (REPLACE "\"" "\\\"" _value "${${_var}}")
+ set (_contents "${_contents}set (${_var} \"${_value}\")\n")
+ endif()
+ endforeach()
+ cotire_write_file("CMAKE" "${_targetCotireScript}" "${_contents}" FALSE)
+ set (${_targetScriptVar} "${_targetCotireScript}" PARENT_SCOPE)
+endfunction()
+
+function (cotire_setup_pch_file_compilation _language _targetBinaryDir _targetScript _prefixFile _pchFile)
+ set (_sourceFiles ${ARGN})
+ if (CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel")
+ # for Visual Studio and Intel, we attach the precompiled header compilation to the first source file
+ # the remaining files include the precompiled header, see cotire_setup_prefix_file_inclusion
+ if (_sourceFiles)
+ file (TO_NATIVE_PATH "${_prefixFile}" _prefixFileNative)
+ file (TO_NATIVE_PATH "${_pchFile}" _pchFileNative)
+ list (GET _sourceFiles 0 _hostFile)
+ set (_flags "")
+ cotire_determine_compiler_version("${_language}" COTIRE_${_language}_COMPILER)
+ cotire_add_pch_compilation_flags(
+ "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${COTIRE_${_language}_COMPILER_VERSION}"
+ "${_prefixFile}" "${_pchFile}" "${_hostFile}" _flags)
+ set_property (SOURCE ${_hostFile} APPEND_STRING PROPERTY COMPILE_FLAGS " ${_flags} ")
+ set_property (SOURCE ${_hostFile} APPEND PROPERTY OBJECT_OUTPUTS "${_pchFile}")
+ # make first source file depend on prefix header
+ set_property (SOURCE ${_hostFile} APPEND PROPERTY OBJECT_DEPENDS "${_prefixFile}")
+ endif()
+ elseif ("${CMAKE_GENERATOR}" MATCHES "Makefiles|Ninja")
+ # for makefile based generator, we add a custom command to precompile the prefix header
+ if (_targetScript)
+ cotire_set_cmd_to_prologue(_cmds)
+ list (GET _sourceFiles 0 _hostFile)
+ list (APPEND _cmds -P "${COTIRE_CMAKE_MODULE_FILE}" "precompile" "${_targetScript}" "${_prefixFile}" "${_pchFile}" "${_hostFile}")
+ file (RELATIVE_PATH _pchFileRelPath "${CMAKE_BINARY_DIR}" "${_pchFile}")
+ if (COTIRE_DEBUG)
+ message (STATUS "add_custom_command: OUTPUT ${_pchFile} ${_cmds} DEPENDS ${_prefixFile} IMPLICIT_DEPENDS ${_language} ${_prefixFile}")
+ endif()
+ set_property (SOURCE "${_pchFile}" PROPERTY GENERATED TRUE)
+ add_custom_command(OUTPUT "${_pchFile}"
+ COMMAND ${_cmds}
+ DEPENDS "${_prefixFile}"
+ IMPLICIT_DEPENDS ${_language} "${_prefixFile}"
+ WORKING_DIRECTORY "${_targetSourceDir}"
+ COMMENT "Building ${_language} precompiled header ${_pchFileRelPath}" VERBATIM)
+ endif()
+ endif()
+endfunction()
+
+function (cotire_setup_prefix_file_inclusion _language _target _wholeTarget _prefixFile _pchFile)
+ set (_sourceFiles ${ARGN})
+ if (CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel")
+ # for Visual Studio and Intel, we include the precompiled header in all but the first source file
+ # the first source file does the precompiled header compilation, see cotire_setup_pch_file_compilation
+ list (LENGTH _sourceFiles _numberOfSourceFiles)
+ if (_numberOfSourceFiles GREATER 1)
+ # mark sources as cotired to prevent them from being used in another cotired target
+ set_source_files_properties(${_sourceFiles} PROPERTIES COTIRE_TARGET "${_target}")
+ list (REMOVE_AT _sourceFiles 0)
+ set (_flags "")
+ cotire_determine_compiler_version("${_language}" COTIRE_${_language}_COMPILER)
+ cotire_add_pch_inclusion_flags(
+ "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${COTIRE_${_language}_COMPILER_VERSION}"
+ "${_prefixFile}" "${_pchFile}" _flags)
+ set_property (SOURCE ${_sourceFiles} APPEND_STRING PROPERTY COMPILE_FLAGS " ${_flags} ")
+ # make source files depend on precompiled header
+ set_property (SOURCE ${_sourceFiles} APPEND PROPERTY OBJECT_DEPENDS "${_pchFile}")
+ endif()
+ elseif ("${CMAKE_GENERATOR}" MATCHES "Makefiles|Ninja")
+ if (NOT _wholeTarget)
+ # for makefile based generator, we force the inclusion of the prefix header for a subset
+ # of the source files, if this is a multi-language target or has excluded files
+ set (_flags "")
+ cotire_determine_compiler_version("${_language}" COTIRE_${_language}_COMPILER)
+ cotire_add_pch_inclusion_flags(
+ "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${COTIRE_${_language}_COMPILER_VERSION}"
+ "${_prefixFile}" "${_pchFile}" _flags)
+ set_property (SOURCE ${_sourceFiles} APPEND_STRING PROPERTY COMPILE_FLAGS " ${_flags} ")
+ # mark sources as cotired to prevent them from being used in another cotired target
+ set_source_files_properties(${_sourceFiles} PROPERTIES COTIRE_TARGET "${_target}")
+ endif()
+ # make source files depend on precompiled header
+ set_property (SOURCE ${_sourceFiles} APPEND PROPERTY OBJECT_DEPENDS "${_pchFile}")
+ endif()
+endfunction()
+
+function (cotire_get_first_set_property_value _propertyValueVar _type _object)
+ set (_properties ${ARGN})
+ foreach (_property ${_properties})
+ get_property(_propertyValue ${_type} "${_object}" PROPERTY ${_property})
+ if (_propertyValue)
+ set (${_propertyValueVar} ${_propertyValue} PARENT_SCOPE)
+ return()
+ endif()
+ endforeach()
+ set (${_propertyValueVar} "" PARENT_SCOPE)
+endfunction()
+
+function (cotire_setup_combine_command _language _sourceDir _targetScript _joinedFile _cmdsVar)
+ set (_files ${ARGN})
+ set (_filesPaths "")
+ foreach (_file ${_files})
+ if (IS_ABSOLUTE "${_file}")
+ set (_filePath "${_file}")
+ else()
+ get_filename_component(_filePath "${_sourceDir}/${_file}" ABSOLUTE)
+ endif()
+ file (RELATIVE_PATH _fileRelPath "${_sourceDir}" "${_filePath}")
+ if (NOT IS_ABSOLUTE "${_fileRelPath}" AND NOT "${_fileRelPath}" MATCHES "^\\.\\.")
+ list (APPEND _filesPaths "${_fileRelPath}")
+ else()
+ list (APPEND _filesPaths "${_filePath}")
+ endif()
+ endforeach()
+ cotire_set_cmd_to_prologue(_prefixCmd)
+ list (APPEND _prefixCmd -P "${COTIRE_CMAKE_MODULE_FILE}" "combine")
+ if (_targetScript)
+ list (APPEND _prefixCmd "${_targetScript}")
+ endif()
+ list (APPEND _prefixCmd "${_joinedFile}" ${_filesPaths})
+ if (COTIRE_DEBUG)
+ message (STATUS "add_custom_command: OUTPUT ${_joinedFile} COMMAND ${_prefixCmd} DEPENDS ${_files}")
+ endif()
+ set_property (SOURCE "${_joinedFile}" PROPERTY GENERATED TRUE)
+ file (RELATIVE_PATH _joinedFileRelPath "${CMAKE_BINARY_DIR}" "${_joinedFile}")
+ get_filename_component(_joinedFileName "${_joinedFileRelPath}" NAME_WE)
+ if (_language AND _joinedFileName MATCHES "${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}$")
+ set (_comment "Generating ${_language} unity source ${_joinedFileRelPath}")
+ elseif (_language AND _joinedFileName MATCHES "${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}$")
+ set (_comment "Generating ${_language} prefix header ${_joinedFileRelPath}")
+ else()
+ set (_comment "Generating ${_joinedFileRelPath}")
+ endif()
+ add_custom_command(
+ OUTPUT "${_joinedFile}"
+ COMMAND ${_prefixCmd}
+ DEPENDS ${_files}
+ COMMENT "${_comment}"
+ WORKING_DIRECTORY "${_sourceDir}" VERBATIM)
+ list (APPEND ${_cmdsVar} COMMAND ${_prefixCmd})
+ set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE)
+endfunction()
+
+function (cotire_setup_target_pch_usage _languages _targetSourceDir _target _wholeTarget)
+ if (XCODE)
+ # for Xcode, we attach a pre-build action to generate the unity sources and prefix headers
+ # if necessary, we also generate a single prefix header which includes all language specific prefix headers
+ set (_prefixFiles "")
+ foreach (_language ${_languages})
+ get_property(_prefixFile TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER)
+ if (_prefixFile)
+ list (APPEND _prefixFiles "${_prefixFile}")
+ endif()
+ endforeach()
+ set (_cmds ${ARGN})
+ list (LENGTH _prefixFiles _numberOfPrefixFiles)
+ if (_numberOfPrefixFiles GREATER 1)
+ cotire_make_prefix_file_path("" ${_target} _prefixHeader)
+ cotire_setup_combine_command("" "${_targetSourceDir}" "" "${_prefixHeader}" _cmds ${_prefixFiles})
+ else()
+ set (_prefixHeader "${_prefixFiles}")
+ endif()
+ if (COTIRE_DEBUG)
+ message (STATUS "add_custom_command: TARGET ${_target} PRE_BUILD ${_cmds}")
+ endif()
+ add_custom_command(TARGET "${_target}"
+ PRE_BUILD ${_cmds}
+ WORKING_DIRECTORY "${_targetSourceDir}"
+ COMMENT "Updating target ${_target} prefix headers" VERBATIM)
+ # make Xcode precompile the generated prefix header with ProcessPCH and ProcessPCH++
+ set_target_properties(${_target} PROPERTIES XCODE_ATTRIBUTE_GCC_PRECOMPILE_PREFIX_HEADER "YES")
+ set_target_properties(${_target} PROPERTIES XCODE_ATTRIBUTE_GCC_PREFIX_HEADER "${_prefixHeader}")
+ elseif ("${CMAKE_GENERATOR}" MATCHES "Makefiles|Ninja")
+ # for makefile based generator, we force inclusion of the prefix header for all target source files
+ # if this is a single-language target without any excluded files
+ if (_wholeTarget)
+ set (_language "${_languages}")
+ # for Visual Studio and Intel, precompiled header inclusion is always done on the source file level
+ # see cotire_setup_prefix_file_inclusion
+ if (NOT CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel")
+ get_property(_prefixFile TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER)
+ get_property(_pchFile TARGET ${_target} PROPERTY COTIRE_${_language}_PRECOMPILED_HEADER)
+ set (_flags "")
+ cotire_determine_compiler_version("${_language}" COTIRE_${_language}_COMPILER)
+ cotire_add_pch_inclusion_flags(
+ "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${COTIRE_${_language}_COMPILER_VERSION}"
+ "${_prefixFile}" "${_pchFile}" _flags)
+ set_property (TARGET ${_target} APPEND_STRING PROPERTY COMPILE_FLAGS " ${_flags} ")
+ endif()
+ endif()
+ endif()
+endfunction()
+
+function (cotire_setup_unity_generation_commands _language _targetSourceDir _target _targetScript _unityFiles _cmdsVar)
+ set (_dependencySources "")
+ cotire_get_unity_source_dependencies(${_language} ${_target} _dependencySources ${ARGN})
+ foreach (_unityFile ${_unityFiles})
+ file (RELATIVE_PATH _unityFileRelPath "${CMAKE_BINARY_DIR}" "${_unityFile}")
+ set_property (SOURCE "${_unityFile}" PROPERTY GENERATED TRUE)
+ # set up compiled unity source dependencies
+ # this ensures that missing source files are generated before the unity file is compiled
+ if (COTIRE_DEBUG AND _dependencySources)
+ message (STATUS "${_unityFile} OBJECT_DEPENDS ${_dependencySources}")
+ endif()
+ if (_dependencySources)
+ set_property (SOURCE "${_unityFile}" PROPERTY OBJECT_DEPENDS ${_dependencySources})
+ endif()
+ if (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel")
+ # unity file compilation results in potentially huge object file, thus use /bigobj by default unter MSVC and Windows Intel
+ set_property (SOURCE "${_unityFile}" APPEND_STRING PROPERTY COMPILE_FLAGS "/bigobj")
+ endif()
+ cotire_set_cmd_to_prologue(_unityCmd)
+ list (APPEND _unityCmd -P "${COTIRE_CMAKE_MODULE_FILE}" "unity" "${_targetScript}" "${_unityFile}")
+ if (COTIRE_DEBUG)
+ message (STATUS "add_custom_command: OUTPUT ${_unityFile} COMMAND ${_unityCmd} DEPENDS ${_targetScript}")
+ endif()
+ add_custom_command(
+ OUTPUT "${_unityFile}"
+ COMMAND ${_unityCmd}
+ DEPENDS "${_targetScript}"
+ COMMENT "Generating ${_language} unity source ${_unityFileRelPath}"
+ WORKING_DIRECTORY "${_targetSourceDir}" VERBATIM)
+ list (APPEND ${_cmdsVar} COMMAND ${_unityCmd})
+ endforeach()
+ list (LENGTH _unityFiles _numberOfUnityFiles)
+ if (_numberOfUnityFiles GREATER 1)
+ # create a joint unity file from all unity file segments
+ cotire_make_single_unity_source_file_path(${_language} ${_target} _unityFile)
+ cotire_setup_combine_command(${_language} "${_targetSourceDir}" "${_targetScript}" "${_unityFile}" ${_cmdsVar} ${_unityFiles})
+ endif()
+ set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE)
+endfunction()
+
+function (cotire_setup_single_prefix_generation_command _language _target _targetSourceDir _targetScript _prefixFile _unityFile _cmdsVar)
+ set (_sourceFiles ${ARGN})
+ set (_dependencySources "")
+ cotire_get_prefix_header_dependencies(${_language} ${_target} _dependencySources ${_sourceFiles})
+ cotire_set_cmd_to_prologue(_prefixCmd)
+ list (APPEND _prefixCmd -P "${COTIRE_CMAKE_MODULE_FILE}" "prefix" "${_targetScript}" "${_prefixFile}" "${_unityFile}")
+ set_property (SOURCE "${_prefixFile}" PROPERTY GENERATED TRUE)
+ if (COTIRE_DEBUG)
+ message (STATUS "add_custom_command: OUTPUT ${_prefixFile} COMMAND ${_prefixCmd} DEPENDS ${_targetScript} ${_unityFile} ${_dependencySources}")
+ endif()
+ file (RELATIVE_PATH _prefixFileRelPath "${CMAKE_BINARY_DIR}" "${_prefixFile}")
+ add_custom_command(
+ OUTPUT "${_prefixFile}" "${_prefixFile}.log"
+ COMMAND ${_prefixCmd}
+ DEPENDS "${_targetScript}" "${_unityFile}" ${_dependencySources}
+ COMMENT "Generating ${_language} prefix header ${_prefixFileRelPath}"
+ WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" VERBATIM)
+ list (APPEND ${_cmdsVar} COMMAND ${_prefixCmd})
+ set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE)
+endfunction()
+
+function (cotire_setup_multi_prefix_generation_command _language _target _targetSourceDir _targetScript _prefixFile _unityFiles _cmdsVar)
+ set (_sourceFiles ${ARGN})
+ list (LENGTH _unityFiles _numberOfUnityFiles)
+ if (_numberOfUnityFiles GREATER 1)
+ cotire_make_single_unity_source_file_path(${_language} ${_target} _unityFile)
+ cotire_setup_single_prefix_generation_command(
+ ${_language} ${_target} "${_targetSourceDir}" "${_targetScript}"
+ "${_prefixFile}" "${_unityFile}" ${_cmdsVar} ${_sourceFiles})
+ else()
+ cotire_setup_single_prefix_generation_command(
+ ${_language} ${_target} "${_targetSourceDir}" "${_targetScript}"
+ "${_prefixFile}" "${_unityFiles}" ${_cmdsVar} ${_sourceFiles})
+ endif()
+ set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE)
+endfunction()
+
+function (cotire_init_cotire_target_properties _target)
+ get_property(_isSet TARGET ${_target} PROPERTY COTIRE_ENABLE_PRECOMPILED_HEADER SET)
+ if (NOT _isSet)
+ set_property(TARGET ${_target} PROPERTY COTIRE_ENABLE_PRECOMPILED_HEADER TRUE)
+ endif()
+ get_property(_isSet TARGET ${_target} PROPERTY COTIRE_ADD_UNITY_BUILD SET)
+ if (NOT _isSet)
+ set_property(TARGET ${_target} PROPERTY COTIRE_ADD_UNITY_BUILD TRUE)
+ endif()
+ get_property(_isSet TARGET ${_target} PROPERTY COTIRE_ADD_CLEAN SET)
+ if (NOT _isSet)
+ set_property(TARGET ${_target} PROPERTY COTIRE_ADD_CLEAN FALSE)
+ endif()
+ get_property(_isSet TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_IGNORE_PATH SET)
+ if (NOT _isSet)
+ set_property(TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_IGNORE_PATH "${CMAKE_SOURCE_DIR}")
+ cotire_check_is_path_relative_to("${CMAKE_BINARY_DIR}" _isRelative "${CMAKE_SOURCE_DIR}")
+ if (NOT _isRelative)
+ set_property(TARGET ${_target} APPEND PROPERTY COTIRE_PREFIX_HEADER_IGNORE_PATH "${CMAKE_BINARY_DIR}")
+ endif()
+ endif()
+ get_property(_isSet TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_INCLUDE_PATH SET)
+ if (NOT _isSet)
+ set_property(TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_INCLUDE_PATH "")
+ endif()
+ get_property(_isSet TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_PRE_UNDEFS SET)
+ if (NOT _isSet)
+ set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_PRE_UNDEFS "")
+ endif()
+ get_property(_isSet TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_POST_UNDEFS SET)
+ if (NOT _isSet)
+ set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_POST_UNDEFS "")
+ endif()
+ get_property(_isSet TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES SET)
+ if (NOT _isSet)
+ if (COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES)
+ set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES "${COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES}")
+ else()
+ set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES "")
+ endif()
+ endif()
+endfunction()
+
+function (cotire_make_target_message _target _languages _disableMsg _targetMsgVar)
+ get_target_property(_targetUsePCH ${_target} COTIRE_ENABLE_PRECOMPILED_HEADER)
+ get_target_property(_targetAddSCU ${_target} COTIRE_ADD_UNITY_BUILD)
+ string (REPLACE ";" " " _languagesStr "${_languages}")
+ string (REPLACE ";" ", " _excludedStr "${ARGN}")
+ set (_targetMsg "")
+ if (NOT _languages)
+ set (_targetMsg "Target ${_target} cannot be cotired.")
+ if (_disableMsg)
+ set (_targetMsg "${_targetMsg} ${_disableMsg}")
+ endif()
+ elseif (NOT _targetUsePCH AND NOT _targetAddSCU)
+ set (_targetMsg "${_languagesStr} target ${_target} cotired without unity build and precompiled header.")
+ if (_disableMsg)
+ set (_targetMsg "${_targetMsg} ${_disableMsg}")
+ endif()
+ elseif (NOT _targetUsePCH)
+ if (_allExcludedSourceFiles)
+ set (_targetMsg "${_languagesStr} target ${_target} cotired excluding files ${_excludedStr} without precompiled header.")
+ else()
+ set (_targetMsg "${_languagesStr} target ${_target} cotired without precompiled header.")
+ endif()
+ if (_disableMsg)
+ set (_targetMsg "${_targetMsg} ${_disableMsg}")
+ endif()
+ elseif (NOT _targetAddSCU)
+ if (_allExcludedSourceFiles)
+ set (_targetMsg "${_languagesStr} target ${_target} cotired excluding files ${_excludedStr} without unity build.")
+ else()
+ set (_targetMsg "${_languagesStr} target ${_target} cotired without unity build.")
+ endif()
+ else()
+ if (_allExcludedSourceFiles)
+ set (_targetMsg "${_languagesStr} target ${_target} cotired excluding files ${_excludedStr}.")
+ else()
+ set (_targetMsg "${_languagesStr} target ${_target} cotired.")
+ endif()
+ endif()
+ set (${_targetMsgVar} "${_targetMsg}" PARENT_SCOPE)
+endfunction()
+
+function (cotire_choose_target_languages _targetSourceDir _target _targetLanguagesVar)
+ set (_languages ${ARGN})
+ set (_allSourceFiles "")
+ set (_allExcludedSourceFiles "")
+ set (_allCotiredSourceFiles "")
+ set (_targetLanguages "")
+ get_target_property(_targetType ${_target} TYPE)
+ get_target_property(_targetSourceFiles ${_target} SOURCES)
+ get_target_property(_targetUsePCH ${_target} COTIRE_ENABLE_PRECOMPILED_HEADER)
+ get_target_property(_targetAddSCU ${_target} COTIRE_ADD_UNITY_BUILD)
+ set (_disableMsg "")
+ foreach (_language ${_languages})
+ get_target_property(_prefixHeader ${_target} COTIRE_${_language}_PREFIX_HEADER)
+ get_target_property(_unityBuildFile ${_target} COTIRE_${_language}_UNITY_SOURCE)
+ if (_prefixHeader OR _unityBuildFile)
+ message (WARNING "Target ${_target} has already been cotired.")
+ set (${_targetLanguagesVar} "" PARENT_SCOPE)
+ return()
+ endif()
+ if (_targetUsePCH AND "${_language}" STREQUAL "C" OR "${_language}" STREQUAL "CXX")
+ cotire_check_precompiled_header_support("${_language}" "${_targetSourceDir}" "${_target}" _disableMsg)
+ if (_disableMsg)
+ set (_targetUsePCH FALSE)
+ endif()
+ endif()
+ set (_sourceFiles "")
+ set (_excludedSources "")
+ set (_cotiredSources "")
+ cotire_filter_language_source_files(${_language} _sourceFiles _excludedSources _cotiredSources ${_targetSourceFiles})
+ if (_sourceFiles OR _excludedSources OR _cotiredSources)
+ list (APPEND _targetLanguages ${_language})
+ endif()
+ if (_sourceFiles)
+ list (APPEND _allSourceFiles ${_sourceFiles})
+ endif()
+ if (_excludedSources)
+ list (APPEND _allExcludedSourceFiles ${_excludedSources})
+ endif()
+ if (_cotiredSources)
+ list (APPEND _allCotiredSourceFiles ${_cotiredSources})
+ endif()
+ endforeach()
+ set (_targetMsgLevel STATUS)
+ if (NOT _targetLanguages)
+ string (REPLACE ";" " or " _languagesStr "${_languages}")
+ set (_disableMsg "No ${_languagesStr} source files.")
+ set (_targetUsePCH FALSE)
+ set (_targetAddSCU FALSE)
+ endif()
+ if (_targetUsePCH)
+ list (LENGTH _allSourceFiles _numberOfSources)
+ if (_numberOfSources LESS ${COTIRE_MINIMUM_NUMBER_OF_TARGET_SOURCES})
+ set (_disableMsg "Too few applicable sources.")
+ set (_targetUsePCH FALSE)
+ elseif (_allCotiredSourceFiles)
+ cotire_get_source_file_property_values(_cotireTargets COTIRE_TARGET ${_allCotiredSourceFiles})
+ list (REMOVE_DUPLICATES _cotireTargets)
+ string (REPLACE ";" ", " _cotireTargetsStr "${_cotireTargets}")
+ set (_disableMsg "Target sources already include a precompiled header for target(s) ${_cotireTargets}.")
+ set (_disableMsg "${_disableMsg} Set target property COTIRE_ENABLE_PRECOMPILED_HEADER to FALSE for targets ${_target},")
+ set (_disableMsg "${_disableMsg} ${_cotireTargetsStr} to get a workable build system.")
+ set (_targetMsgLevel SEND_ERROR)
+ set (_targetUsePCH FALSE)
+ elseif (XCODE AND _allExcludedSourceFiles)
+ # for Xcode, we cannot apply the precompiled header to individual sources, only to the whole target
+ set (_disableMsg "Exclusion of source files not supported for generator Xcode.")
+ set (_targetUsePCH FALSE)
+ elseif (XCODE AND "${_targetType}" STREQUAL "OBJECT_LIBRARY")
+ # for Xcode, we cannot apply the required PRE_BUILD action to generate the prefix header to an OBJECT_LIBRARY target
+ set (_disableMsg "Required PRE_BUILD action not supported for OBJECT_LIBRARY targets for generator Xcode.")
+ set (_targetUsePCH FALSE)
+ endif()
+ endif()
+ set_property(TARGET ${_target} PROPERTY COTIRE_ENABLE_PRECOMPILED_HEADER ${_targetUsePCH})
+ set_property(TARGET ${_target} PROPERTY COTIRE_ADD_UNITY_BUILD ${_targetAddSCU})
+ cotire_make_target_message(${_target} "${_targetLanguages}" "${_disableMsg}" _targetMsg ${_allExcludedSourceFiles})
+ if (_targetMsg)
+ if (NOT DEFINED COTIREMSG_${_target})
+ set (COTIREMSG_${_target} "")
+ endif()
+ if (COTIRE_VERBOSE OR NOT "${_targetMsgLevel}" STREQUAL "STATUS" OR
+ NOT "${COTIREMSG_${_target}}" STREQUAL "${_targetMsg}")
+ # cache message to avoid redundant messages on re-configure
+ set (COTIREMSG_${_target} "${_targetMsg}" CACHE INTERNAL "${_target} cotire message.")
+ message (${_targetMsgLevel} "${_targetMsg}")
+ endif()
+ endif()
+ set (${_targetLanguagesVar} ${_targetLanguages} PARENT_SCOPE)
+endfunction()
+
+function (cotire_compute_unity_max_number_of_includes _target _maxIncludesVar)
+ set (_sourceFiles ${ARGN})
+ get_target_property(_maxIncludes ${_target} COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES)
+ if (_maxIncludes MATCHES "(-j|--parallel|--jobs) ?([0-9]*)")
+ set (_numberOfThreads "${CMAKE_MATCH_2}")
+ if (NOT _numberOfThreads)
+ # use all available cores
+ ProcessorCount(_numberOfThreads)
+ endif()
+ list (LENGTH _sourceFiles _numberOfSources)
+ math (EXPR _maxIncludes "(${_numberOfSources} + ${_numberOfThreads} - 1) / ${_numberOfThreads}")
+ # a unity source segment must not contain less than COTIRE_MINIMUM_NUMBER_OF_TARGET_SOURCES files
+ if (_maxIncludes LESS ${COTIRE_MINIMUM_NUMBER_OF_TARGET_SOURCES})
+ set (_maxIncludes ${COTIRE_MINIMUM_NUMBER_OF_TARGET_SOURCES})
+ endif()
+ elseif (NOT _maxIncludes MATCHES "[0-9]+")
+ set (_maxIncludes 0)
+ endif()
+ if (COTIRE_DEBUG)
+ message (STATUS "${_target} unity source max includes = ${_maxIncludes}")
+ endif()
+ set (${_maxIncludesVar} ${_maxIncludes} PARENT_SCOPE)
+endfunction()
+
+function (cotire_process_target_language _language _configurations _targetSourceDir _targetBinaryDir _target _wholeTargetVar _cmdsVar)
+ set (${_cmdsVar} "" PARENT_SCOPE)
+ get_target_property(_targetSourceFiles ${_target} SOURCES)
+ set (_sourceFiles "")
+ set (_excludedSources "")
+ set (_cotiredSources "")
+ cotire_filter_language_source_files(${_language} _sourceFiles _excludedSources _cotiredSources ${_targetSourceFiles})
+ if (NOT _sourceFiles AND NOT _cotiredSources)
+ return()
+ endif()
+ set (_wholeTarget ${${_wholeTargetVar}})
+ set (_cmds "")
+ # check for user provided unity source file list
+ get_property(_unitySourceFiles TARGET ${_target} PROPERTY COTIRE_${_language}_UNITY_SOURCE_INIT)
+ if (NOT _unitySourceFiles)
+ set (_unitySourceFiles ${_sourceFiles} ${_cotiredSources})
+ endif()
+ cotire_generate_target_script(
+ ${_language} "${_configurations}" "${_targetSourceDir}" "${_targetBinaryDir}" ${_target} _targetScript ${_unitySourceFiles})
+ cotire_compute_unity_max_number_of_includes(${_target} _maxIncludes ${_unitySourceFiles})
+ cotire_make_unity_source_file_paths(${_language} ${_target} ${_maxIncludes} _unityFiles ${_unitySourceFiles})
+ if (NOT _unityFiles)
+ return()
+ endif()
+ cotire_setup_unity_generation_commands(
+ ${_language} "${_targetSourceDir}" ${_target} "${_targetScript}" "${_unityFiles}" _cmds ${_unitySourceFiles})
+ cotire_make_prefix_file_path(${_language} ${_target} _prefixFile)
+ if (_prefixFile)
+ # check for user provided prefix header files
+ get_property(_prefixHeaderFiles TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER_INIT)
+ if (_prefixHeaderFiles)
+ cotire_setup_combine_command(${_language} "${_targetSourceDir}" "${_targetScript}" "${_prefixFile}" _cmds ${_prefixHeaderFiles})
+ else()
+ cotire_setup_multi_prefix_generation_command(
+ ${_language} ${_target} "${_targetSourceDir}" "${_targetScript}" "${_prefixFile}" "${_unityFiles}" _cmds ${_unitySourceFiles})
+ endif()
+ get_target_property(_targetUsePCH ${_target} COTIRE_ENABLE_PRECOMPILED_HEADER)
+ if (_targetUsePCH)
+ cotire_make_pch_file_path(${_language} "${_targetSourceDir}" ${_target} _pchFile)
+ if (_pchFile)
+ cotire_setup_pch_file_compilation(
+ ${_language} "${_targetBinaryDir}" "${_targetScript}" "${_prefixFile}" "${_pchFile}" ${_sourceFiles})
+ if (_excludedSources)
+ set (_wholeTarget FALSE)
+ endif()
+ cotire_setup_prefix_file_inclusion(
+ ${_language} ${_target} ${_wholeTarget} "${_prefixFile}" "${_pchFile}" ${_sourceFiles})
+ endif()
+ endif()
+ endif()
+ # mark target as cotired for language
+ set_property(TARGET ${_target} PROPERTY COTIRE_${_language}_UNITY_SOURCE "${_unityFiles}")
+ if (_prefixFile)
+ set_property(TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER "${_prefixFile}")
+ if (_targetUsePCH AND _pchFile)
+ set_property(TARGET ${_target} PROPERTY COTIRE_${_language}_PRECOMPILED_HEADER "${_pchFile}")
+ endif()
+ endif()
+ set (${_wholeTargetVar} ${_wholeTarget} PARENT_SCOPE)
+ set (${_cmdsVar} ${_cmds} PARENT_SCOPE)
+endfunction()
+
+function (cotire_setup_clean_target _target)
+ set (_cleanTargetName "${_target}${COTIRE_CLEAN_TARGET_SUFFIX}")
+ if (NOT TARGET "${_cleanTargetName}")
+ cotire_set_cmd_to_prologue(_cmds)
+ get_filename_component(_outputDir "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}" ABSOLUTE)
+ list (APPEND _cmds -P "${COTIRE_CMAKE_MODULE_FILE}" "cleanup" "${_outputDir}" "${COTIRE_INTDIR}" "${_target}")
+ add_custom_target(${_cleanTargetName} COMMAND ${_cmds} WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
+ COMMENT "Cleaning up target ${_target} cotire generated files" VERBATIM)
+ cotire_init_target("${_cleanTargetName}")
+ endif()
+endfunction()
+
+function (cotire_setup_pch_target _languages _configurations _target)
+ if ("${CMAKE_GENERATOR}" MATCHES "Makefiles|Ninja")
+ # for makefile based generators, we add a custom target to trigger the generation of the cotire related files
+ set (_dependsFiles "")
+ foreach (_language ${_languages})
+ set (_props COTIRE_${_language}_PREFIX_HEADER COTIRE_${_language}_UNITY_SOURCE)
+ if (NOT CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel")
+ # Visual Studio and Intel only create precompiled header as a side effect
+ list (INSERT _props 0 COTIRE_${_language}_PRECOMPILED_HEADER)
+ endif()
+ cotire_get_first_set_property_value(_dependsFile TARGET ${_target} ${_props})
+ if (_dependsFile)
+ list (APPEND _dependsFiles "${_dependsFile}")
+ endif()
+ endforeach()
+ if (_dependsFiles)
+ set (_pchTargetName "${_target}${COTIRE_PCH_TARGET_SUFFIX}")
+ add_custom_target("${_pchTargetName}" DEPENDS ${_dependsFiles})
+ cotire_init_target("${_pchTargetName}")
+ cotire_add_to_pch_all_target(${_pchTargetName})
+ endif()
+ else()
+ # for other generators, we add the "clean all" target to clean up the precompiled header
+ cotire_setup_clean_all_target()
+ endif()
+endfunction()
+
+function (cotire_setup_unity_build_target _languages _configurations _targetSourceDir _target)
+ get_target_property(_unityTargetName ${_target} COTIRE_UNITY_TARGET_NAME)
+ if (NOT _unityTargetName)
+ set (_unityTargetName "${_target}${COTIRE_UNITY_BUILD_TARGET_SUFFIX}")
+ endif()
+ # determine unity target sub type
+ get_target_property(_targetType ${_target} TYPE)
+ if ("${_targetType}" STREQUAL "EXECUTABLE")
+ get_target_property(_isWin32 ${_target} WIN32_EXECUTABLE)
+ get_target_property(_isMacOSX_Bundle ${_target} MACOSX_BUNDLE)
+ if (_isWin32)
+ set (_unityTargetSubType WIN32)
+ elseif (_isMacOSX_Bundle)
+ set (_unityTargetSubType MACOSX_BUNDLE)
+ else()
+ set (_unityTargetSubType "")
+ endif()
+ elseif (_targetType MATCHES "(STATIC|SHARED|MODULE|OBJECT)_LIBRARY")
+ set (_unityTargetSubType "${CMAKE_MATCH_1}")
+ else()
+ message (WARNING "Unknown target type ${_targetType}.")
+ return()
+ endif()
+ # determine unity target sources
+ get_target_property(_targetSourceFiles ${_target} SOURCES)
+ set (_unityTargetSources ${_targetSourceFiles})
+ foreach (_language ${_languages})
+ get_property(_unityFiles TARGET ${_target} PROPERTY COTIRE_${_language}_UNITY_SOURCE)
+ if (_unityFiles)
+ # remove source files that are included in the unity source
+ set (_sourceFiles "")
+ set (_excludedSources "")
+ set (_cotiredSources "")
+ cotire_filter_language_source_files(${_language} _sourceFiles _excludedSources _cotiredSources ${_targetSourceFiles})
+ if (_sourceFiles OR _cotiredSources)
+ list (REMOVE_ITEM _unityTargetSources ${_sourceFiles} ${_cotiredSources})
+ endif()
+ # if cotire is applied to a target which has not been added in the current source dir,
+ # non-existing files cannot be referenced from the unity build target (this is a CMake restriction)
+ if (NOT "${_targetSourceDir}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}")
+ set (_nonExistingFiles "")
+ foreach (_file ${_unityTargetSources})
+ if (NOT EXISTS "${_file}")
+ list (APPEND _nonExistingFiles "${_file}")
+ endif()
+ endforeach()
+ if (_nonExistingFiles)
+ if (COTIRE_VERBOSE)
+ message (STATUS "removing non-existing ${_nonExistingFiles} from ${_unityTargetName}")
+ endif()
+ list (REMOVE_ITEM _unityTargetSources ${_nonExistingFiles})
+ endif()
+ endif()
+ # add unity source files instead
+ list (APPEND _unityTargetSources ${_unityFiles})
+ endif()
+ endforeach()
+ if (COTIRE_DEBUG)
+ message (STATUS "add ${_targetType} ${_unityTargetName} ${_unityTargetSubType} EXCLUDE_FROM_ALL ${_unityTargetSources}")
+ endif()
+ # generate unity target
+ if ("${_targetType}" STREQUAL "EXECUTABLE")
+ add_executable(${_unityTargetName} ${_unityTargetSubType} EXCLUDE_FROM_ALL ${_unityTargetSources})
+ else()
+ add_library(${_unityTargetName} ${_unityTargetSubType} EXCLUDE_FROM_ALL ${_unityTargetSources})
+ endif()
+ set (_outputDirProperties
+ ARCHIVE_OUTPUT_DIRECTORY ARCHIVE_OUTPUT_DIRECTORY_<CONFIG>
+ LIBRARY_OUTPUT_DIRECTORY LIBRARY_OUTPUT_DIRECTORY_<CONFIG>
+ RUNTIME_OUTPUT_DIRECTORY RUNTIME_OUTPUT_DIRECTORY_<CONFIG>)
+ # copy output location properties
+ if (COTIRE_UNITY_OUTPUT_DIRECTORY)
+ set (_setDefaultOutputDir TRUE)
+ if (IS_ABSOLUTE "${COTIRE_UNITY_OUTPUT_DIRECTORY}")
+ set (_outputDir "${COTIRE_UNITY_OUTPUT_DIRECTORY}")
+ else()
+ cotrie_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} ${_outputDirProperties})
+ cotrie_resolve_config_properites("${_configurations}" _properties ${_outputDirProperties})
+ foreach (_property ${_properties})
+ get_property(_outputDir TARGET ${_target} PROPERTY ${_property})
+ if (_outputDir)
+ get_filename_component(_outputDir "${_outputDir}/${COTIRE_UNITY_OUTPUT_DIRECTORY}" ABSOLUTE)
+ set_property(TARGET ${_unityTargetName} PROPERTY ${_property} "${_outputDir}")
+ set (_setDefaultOutputDir FALSE)
+ endif()
+ endforeach()
+ if (_setDefaultOutputDir)
+ get_filename_component(_outputDir "${CMAKE_CURRENT_BINARY_DIR}/${COTIRE_UNITY_OUTPUT_DIRECTORY}" ABSOLUTE)
+ endif()
+ endif()
+ if (_setDefaultOutputDir)
+ set_target_properties(${_unityTargetName} PROPERTIES
+ ARCHIVE_OUTPUT_DIRECTORY "${_outputDir}"
+ LIBRARY_OUTPUT_DIRECTORY "${_outputDir}"
+ RUNTIME_OUTPUT_DIRECTORY "${_outputDir}")
+ endif()
+ else()
+ cotrie_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} ${_outputDirProperties})
+ endif()
+ # copy output name
+ cotrie_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName}
+ ARCHIVE_OUTPUT_NAME ARCHIVE_OUTPUT_NAME_<CONFIG>
+ LIBRARY_OUTPUT_NAME LIBRARY_OUTPUT_NAME_<CONFIG>
+ OUTPUT_NAME OUTPUT_NAME_<CONFIG>
+ RUNTIME_OUTPUT_NAME RUNTIME_OUTPUT_NAME_<CONFIG>
+ PREFIX <CONFIG>_POSTFIX SUFFIX)
+ # copy compile stuff
+ cotrie_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName}
+ COMPILE_DEFINITIONS COMPILE_DEFINITIONS_<CONFIG>
+ COMPILE_FLAGS Fortran_FORMAT
+ INCLUDE_DIRECTORIES
+ INTERPROCEDURAL_OPTIMIZATION INTERPROCEDURAL_OPTIMIZATION_<CONFIG>
+ POSITION_INDEPENDENT_CODE)
+ # copy link stuff
+ cotrie_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName}
+ BUILD_WITH_INSTALL_RPATH INSTALL_RPATH INSTALL_RPATH_USE_LINK_PATH SKIP_BUILD_RPATH
+ LINKER_LANGUAGE LINK_DEPENDS
+ LINK_FLAGS LINK_FLAGS_<CONFIG>
+ LINK_INTERFACE_LIBRARIES LINK_INTERFACE_LIBRARIES_<CONFIG>
+ LINK_INTERFACE_MULTIPLICITY LINK_INTERFACE_MULTIPLICITY_<CONFIG>
+ LINK_SEARCH_START_STATIC LINK_SEARCH_END_STATIC
+ STATIC_LIBRARY_FLAGS STATIC_LIBRARY_FLAGS_<CONFIG>
+ NO_SONAME SOVERSION VERSION)
+ # copy Qt stuff
+ cotrie_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName}
+ AUTOMOC AUTOMOC_MOC_OPTIONS)
+ # copy cmake stuff
+ cotrie_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName}
+ IMPLICIT_DEPENDS_INCLUDE_TRANSFORM RULE_LAUNCH_COMPILE RULE_LAUNCH_CUSTOM RULE_LAUNCH_LINK)
+ # copy platform stuff
+ if (APPLE)
+ cotrie_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName}
+ BUNDLE BUNDLE_EXTENSION FRAMEWORK INSTALL_NAME_DIR MACOSX_BUNDLE_INFO_PLIST MACOSX_FRAMEWORK_INFO_PLIST
+ OSX_ARCHITECTURES OSX_ARCHITECTURES_<CONFIG> PRIVATE_HEADER PUBLIC_HEADER RESOURCE)
+ elseif (WIN32)
+ cotrie_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName}
+ GNUtoMS
+ PDB_NAME PDB_NAME_<CONFIG> PDB_OUTPUT_DIRECTORY PDB_OUTPUT_DIRECTORY_<CONFIG>
+ VS_DOTNET_REFERENCES VS_GLOBAL_KEYWORD VS_GLOBAL_PROJECT_TYPES VS_KEYWORD
+ VS_SCC_AUXPATH VS_SCC_LOCALPATH VS_SCC_PROJECTNAME VS_SCC_PROVIDER
+ VS_WINRT_EXTENSIONS VS_WINRT_REFERENCES)
+ endif()
+ # use output name from original target
+ get_target_property(_targetOutputName ${_unityTargetName} OUTPUT_NAME)
+ if (NOT _targetOutputName)
+ set_property(TARGET ${_unityTargetName} PROPERTY OUTPUT_NAME "${_target}")
+ endif()
+ # use export symbol from original target
+ cotire_get_target_export_symbol("${_target}" _defineSymbol)
+ if (_defineSymbol)
+ set_property(TARGET ${_unityTargetName} PROPERTY DEFINE_SYMBOL "${_defineSymbol}")
+ if ("${_targetType}" STREQUAL "EXECUTABLE")
+ set_property(TARGET ${_unityTargetName} PROPERTY ENABLE_EXPORTS TRUE)
+ endif()
+ endif()
+ cotire_init_target(${_unityTargetName})
+ cotire_add_to_unity_all_target(${_unityTargetName})
+ set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_TARGET_NAME "${_unityTargetName}")
+endfunction(cotire_setup_unity_build_target)
+
+function (cotire_target _target)
+ set(_options "")
+ set(_oneValueArgs SOURCE_DIR BINARY_DIR)
+ set(_multiValueArgs LANGUAGES CONFIGURATIONS)
+ cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN})
+ if (NOT _option_SOURCE_DIR)
+ set (_option_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
+ endif()
+ if (NOT _option_BINARY_DIR)
+ set (_option_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}")
+ endif()
+ if (NOT _option_LANGUAGES)
+ get_property (_option_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
+ endif()
+ if (NOT _option_CONFIGURATIONS)
+ if (CMAKE_CONFIGURATION_TYPES)
+ set (_option_CONFIGURATIONS ${CMAKE_CONFIGURATION_TYPES})
+ elseif (CMAKE_BUILD_TYPE)
+ set (_option_CONFIGURATIONS "${CMAKE_BUILD_TYPE}")
+ else()
+ set (_option_CONFIGURATIONS "None")
+ endif()
+ endif()
+ # trivial checks
+ get_target_property(_imported ${_target} IMPORTED)
+ if (_imported)
+ message (WARNING "Imported target ${_target} cannot be cotired.")
+ return()
+ endif()
+ # check if target needs to be cotired for build type
+ # when using configuration types, the test is performed at build time
+ cotire_init_cotire_target_properties(${_target})
+ if (NOT CMAKE_CONFIGURATION_TYPES)
+ if (CMAKE_BUILD_TYPE)
+ list (FIND _option_CONFIGURATIONS "${CMAKE_BUILD_TYPE}" _index)
+ else()
+ list (FIND _option_CONFIGURATIONS "None" _index)
+ endif()
+ if (_index EQUAL -1)
+ if (COTIRE_DEBUG)
+ message (STATUS "CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} not cotired (${_option_CONFIGURATIONS})")
+ endif()
+ return()
+ endif()
+ endif()
+ # choose languages that apply to the target
+ cotire_choose_target_languages("${_option_SOURCE_DIR}" "${_target}" _targetLanguages ${_option_LANGUAGES})
+ if (NOT _targetLanguages)
+ return()
+ endif()
+ list (LENGTH _targetLanguages _numberOfLanguages)
+ if (_numberOfLanguages GREATER 1)
+ set (_wholeTarget FALSE)
+ else()
+ set (_wholeTarget TRUE)
+ endif()
+ set (_cmds "")
+ foreach (_language ${_targetLanguages})
+ cotire_process_target_language("${_language}" "${_option_CONFIGURATIONS}"
+ "${_option_SOURCE_DIR}" "${_option_BINARY_DIR}" ${_target} _wholeTarget _cmd)
+ if (_cmd)
+ list (APPEND _cmds ${_cmd})
+ endif()
+ endforeach()
+ get_target_property(_targetAddSCU ${_target} COTIRE_ADD_UNITY_BUILD)
+ if (_targetAddSCU)
+ cotire_setup_unity_build_target("${_targetLanguages}" "${_option_CONFIGURATIONS}" "${_option_SOURCE_DIR}" ${_target})
+ endif()
+ get_target_property(_targetUsePCH ${_target} COTIRE_ENABLE_PRECOMPILED_HEADER)
+ if (_targetUsePCH)
+ cotire_setup_target_pch_usage("${_targetLanguages}" "${_option_SOURCE_DIR}" ${_target} ${_wholeTarget} ${_cmds})
+ cotire_setup_pch_target("${_targetLanguages}" "${_option_CONFIGURATIONS}" ${_target})
+ endif()
+ get_target_property(_targetAddCleanTarget ${_target} COTIRE_ADD_CLEAN)
+ if (_targetAddCleanTarget)
+ cotire_setup_clean_target(${_target})
+ endif()
+endfunction()
+
+function (cotire_cleanup _binaryDir _cotireIntermediateDirName _targetName)
+ if (_targetName)
+ file (GLOB_RECURSE _cotireFiles "${_binaryDir}/${_targetName}*.*")
+ else()
+ file (GLOB_RECURSE _cotireFiles "${_binaryDir}/*.*")
+ endif()
+ # filter files in intermediate directory
+ set (_filesToRemove "")
+ foreach (_file ${_cotireFiles})
+ get_filename_component(_dir "${_file}" PATH)
+ get_filename_component(_dirName "${_dir}" NAME)
+ if ("${_dirName}" STREQUAL "${_cotireIntermediateDirName}")
+ list (APPEND _filesToRemove "${_file}")
+ endif()
+ endforeach()
+ if (_filesToRemove)
+ if (COTIRE_VERBOSE)
+ message (STATUS "removing ${_filesToRemove}")
+ endif()
+ file (REMOVE ${_filesToRemove})
+ endif()
+endfunction()
+
+function (cotire_init_target _targetName)
+ if (COTIRE_TARGETS_FOLDER)
+ set_target_properties(${_targetName} PROPERTIES FOLDER "${COTIRE_TARGETS_FOLDER}")
+ endif()
+ if (MSVC_IDE)
+ set_target_properties(${_targetName} PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD TRUE)
+ endif()
+endfunction()
+
+function (cotire_add_to_pch_all_target _pchTargetName)
+ set (_targetName "${COTIRE_PCH_ALL_TARGET_NAME}")
+ if (NOT TARGET "${_targetName}")
+ add_custom_target("${_targetName}" WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" VERBATIM)
+ cotire_init_target("${_targetName}")
+ endif()
+ cotire_setup_clean_all_target()
+ add_dependencies(${_targetName} ${_pchTargetName})
+endfunction()
+
+function (cotire_add_to_unity_all_target _unityTargetName)
+ set (_targetName "${COTIRE_UNITY_BUILD_ALL_TARGET_NAME}")
+ if (NOT TARGET "${_targetName}")
+ add_custom_target("${_targetName}" WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" VERBATIM)
+ cotire_init_target("${_targetName}")
+ endif()
+ cotire_setup_clean_all_target()
+ add_dependencies(${_targetName} ${_unityTargetName})
+endfunction()
+
+function (cotire_setup_clean_all_target)
+ set (_targetName "${COTIRE_CLEAN_ALL_TARGET_NAME}")
+ if (NOT TARGET "${_targetName}")
+ cotire_set_cmd_to_prologue(_cmds)
+ list (APPEND _cmds -P "${COTIRE_CMAKE_MODULE_FILE}" "cleanup" "${CMAKE_BINARY_DIR}" "${COTIRE_INTDIR}")
+ add_custom_target(${_targetName} COMMAND ${_cmds}
+ WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" COMMENT "Cleaning up all cotire generated files" VERBATIM)
+ cotire_init_target("${_targetName}")
+ endif()
+endfunction()
+
+function (cotire)
+ set(_options "")
+ set(_oneValueArgs SOURCE_DIR BINARY_DIR)
+ set(_multiValueArgs LANGUAGES CONFIGURATIONS)
+ cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN})
+ set (_targets ${_option_UNPARSED_ARGUMENTS})
+ if (NOT _option_SOURCE_DIR)
+ set (_option_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
+ endif()
+ if (NOT _option_BINARY_DIR)
+ set (_option_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}")
+ endif()
+ foreach (_target ${_targets})
+ if (TARGET ${_target})
+ cotire_target(${_target} LANGUAGES ${_option_LANGUAGES} CONFIGURATIONS ${_option_CONFIGURATIONS}
+ SOURCE_DIR "${_option_SOURCE_DIR}" BINARY_DIR "${_option_BINARY_DIR}")
+ else()
+ message (WARNING "${_target} is not a target")
+ endif()
+ endforeach()
+endfunction()
+
+if (CMAKE_SCRIPT_MODE_FILE)
+
+ # cotire is being run in script mode
+ # locate -P on command args
+ set (COTIRE_ARGC -1)
+ foreach (_index RANGE ${CMAKE_ARGC})
+ if (COTIRE_ARGC GREATER -1)
+ set (COTIRE_ARGV${COTIRE_ARGC} "${CMAKE_ARGV${_index}}")
+ math (EXPR COTIRE_ARGC "${COTIRE_ARGC} + 1")
+ elseif ("${CMAKE_ARGV${_index}}" STREQUAL "-P")
+ set (COTIRE_ARGC 0)
+ endif()
+ endforeach()
+
+ # include target script if available
+ if ("${COTIRE_ARGV2}" MATCHES "\\.cmake$")
+ # the included target scripts sets up additional variables relating to the target (e.g., COTIRE_TARGET_SOURCES)
+ include("${COTIRE_ARGV2}")
+ endif()
+
+ if (COTIRE_DEBUG)
+ message (STATUS "${COTIRE_ARGV0} ${COTIRE_ARGV1} ${COTIRE_ARGV2} ${COTIRE_ARGV3} ${COTIRE_ARGV4} ${COTIRE_ARGV5}")
+ endif()
+
+ if (WIN32)
+ # for MSVC, compiler IDs may not always be set correctly
+ if (MSVC)
+ set (CMAKE_C_COMPILER_ID "MSVC")
+ set (CMAKE_CXX_COMPILER_ID "MSVC")
+ endif()
+ endif()
+
+ if (NOT COTIRE_BUILD_TYPE)
+ set (COTIRE_BUILD_TYPE "None")
+ endif()
+ string (TOUPPER "${COTIRE_BUILD_TYPE}" _upperConfig)
+ set (_includeDirs ${COTIRE_TARGET_INCLUDE_DIRECTORIES_${_upperConfig}})
+ set (_compileDefinitions ${COTIRE_TARGET_COMPILE_DEFINITIONS_${_upperConfig}})
+ set (_compileFlags ${COTIRE_TARGET_COMPILE_FLAGS_${_upperConfig}})
+ # check if target has been cotired for actual build type COTIRE_BUILD_TYPE
+ list (FIND COTIRE_TARGET_CONFIGURATION_TYPES "${COTIRE_BUILD_TYPE}" _index)
+ if (_index GREATER -1)
+ set (_sources ${COTIRE_TARGET_SOURCES})
+ set (_sourcesDefinitions ${COTIRE_TARGET_SOURCES_COMPILE_DEFINITIONS_${_upperConfig}})
+ else()
+ if (COTIRE_DEBUG)
+ message (STATUS "COTIRE_BUILD_TYPE=${COTIRE_BUILD_TYPE} not cotired (${COTIRE_TARGET_CONFIGURATION_TYPES})")
+ endif()
+ set (_sources "")
+ set (_sourcesDefinitions "")
+ endif()
+ set (_targetPreUndefs ${COTIRE_TARGET_PRE_UNDEFS})
+ set (_targetPostUndefs ${COTIRE_TARGET_POST_UNDEFS})
+ set (_sourcesPreUndefs ${COTIRE_TARGET_SOURCES_PRE_UNDEFS})
+ set (_sourcesPostUndefs ${COTIRE_TARGET_SOURCES_POST_UNDEFS})
+
+ if ("${COTIRE_ARGV1}" STREQUAL "unity")
+
+ cotire_select_unity_source_files("${COTIRE_ARGV3}" _sources ${_sources})
+ cotire_generate_unity_source(
+ "${COTIRE_ARGV3}" ${_sources}
+ LANGUAGE "${COTIRE_TARGET_LANGUAGE}"
+ DEPENDS "${COTIRE_ARGV0}" "${COTIRE_ARGV2}"
+ SOURCES_COMPILE_DEFINITIONS ${_sourcesDefinitions}
+ PRE_UNDEFS ${_targetPreUndefs}
+ POST_UNDEFS ${_targetPostUndefs}
+ SOURCES_PRE_UNDEFS ${_sourcesPreUndefs}
+ SOURCES_POST_UNDEFS ${_sourcesPostUndefs})
+
+ elseif ("${COTIRE_ARGV1}" STREQUAL "prefix")
+
+ set (_files "")
+ foreach (_index RANGE 4 ${COTIRE_ARGC})
+ if (COTIRE_ARGV${_index})
+ list (APPEND _files "${COTIRE_ARGV${_index}}")
+ endif()
+ endforeach()
+
+ cotire_generate_prefix_header(
+ "${COTIRE_ARGV3}" ${_files}
+ COMPILER_EXECUTABLE "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER}"
+ COMPILER_ARG1 ${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_ARG1}
+ COMPILER_ID "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_ID}"
+ COMPILER_VERSION "${COTIRE_${COTIRE_TARGET_LANGUAGE}_COMPILER_VERSION}"
+ LANGUAGE "${COTIRE_TARGET_LANGUAGE}"
+ DEPENDS "${COTIRE_ARGV0}" "${COTIRE_ARGV4}" ${COTIRE_TARGET_PREFIX_DEPENDS}
+ IGNORE_PATH "${COTIRE_TARGET_IGNORE_PATH};${COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_PATH}"
+ INCLUDE_PATH ${COTIRE_TARGET_INCLUDE_PATH}
+ IGNORE_EXTENSIONS "${CMAKE_${COTIRE_TARGET_LANGUAGE}_SOURCE_FILE_EXTENSIONS};${COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_EXTENSIONS}"
+ INCLUDE_DIRECTORIES ${_includeDirs}
+ COMPILE_DEFINITIONS ${_compileDefinitions}
+ COMPILE_FLAGS ${_compileFlags})
+
+ elseif ("${COTIRE_ARGV1}" STREQUAL "precompile")
+
+ set (_files "")
+ foreach (_index RANGE 5 ${COTIRE_ARGC})
+ if (COTIRE_ARGV${_index})
+ list (APPEND _files "${COTIRE_ARGV${_index}}")
+ endif()
+ endforeach()
+
+ cotire_precompile_prefix_header(
+ "${COTIRE_ARGV3}" "${COTIRE_ARGV4}" "${COTIRE_ARGV5}"
+ COMPILER_EXECUTABLE "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER}"
+ COMPILER_ARG1 ${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_ARG1}
+ COMPILER_ID "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_ID}"
+ COMPILER_VERSION "${COTIRE_${COTIRE_TARGET_LANGUAGE}_COMPILER_VERSION}"
+ LANGUAGE "${COTIRE_TARGET_LANGUAGE}"
+ INCLUDE_DIRECTORIES ${_includeDirs}
+ COMPILE_DEFINITIONS ${_compileDefinitions}
+ COMPILE_FLAGS ${_compileFlags})
+
+ elseif ("${COTIRE_ARGV1}" STREQUAL "combine")
+
+ if (COTIRE_TARGET_LANGUAGE)
+ set (_startIndex 3)
+ else()
+ set (_startIndex 2)
+ endif()
+ set (_files "")
+ foreach (_index RANGE ${_startIndex} ${COTIRE_ARGC})
+ if (COTIRE_ARGV${_index})
+ list (APPEND _files "${COTIRE_ARGV${_index}}")
+ endif()
+ endforeach()
+ if (COTIRE_TARGET_LANGUAGE)
+ cotire_generate_unity_source(${_files} LANGUAGE "${COTIRE_TARGET_LANGUAGE}")
+ else()
+ cotire_generate_unity_source(${_files})
+ endif()
+
+ elseif ("${COTIRE_ARGV1}" STREQUAL "cleanup")
+
+ cotire_cleanup("${COTIRE_ARGV2}" "${COTIRE_ARGV3}" "${COTIRE_ARGV4}")
+
+ else()
+ message (FATAL_ERROR "Unknown cotire command \"${COTIRE_ARGV1}\".")
+ endif()
+
+else()
+
+ # cotire is being run in include mode
+ # set up all variable and property definitions
+
+ unset (COTIRE_C_COMPILER_VERSION CACHE)
+ unset (COTIRE_CXX_COMPILER_VERSION CACHE)
+
+ if (NOT DEFINED COTIRE_DEBUG_INIT)
+ if (DEFINED COTIRE_DEBUG)
+ set (COTIRE_DEBUG_INIT ${COTIRE_DEBUG})
+ else()
+ set (COTIRE_DEBUG_INIT FALSE)
+ endif()
+ endif()
+ option (COTIRE_DEBUG "Enable cotire debugging output?" ${COTIRE_DEBUG_INIT})
+
+ if (NOT DEFINED COTIRE_VERBOSE_INIT)
+ if (DEFINED COTIRE_VERBOSE)
+ set (COTIRE_VERBOSE_INIT ${COTIRE_VERBOSE})
+ else()
+ set (COTIRE_VERBOSE_INIT FALSE)
+ endif()
+ endif()
+ option (COTIRE_VERBOSE "Enable cotire verbose output?" ${COTIRE_VERBOSE_INIT})
+
+ set (COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_EXTENSIONS "inc;inl;ipp" CACHE STRING
+ "Ignore headers with the listed file extensions from the generated prefix header.")
+
+ set (COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_PATH "" CACHE STRING
+ "Ignore headers from these directories when generating the prefix header.")
+
+ set (COTIRE_UNITY_SOURCE_EXCLUDE_EXTENSIONS "m;mm" CACHE STRING
+ "Ignore sources with the listed file extensions from the generated unity source.")
+
+ set (COTIRE_MINIMUM_NUMBER_OF_TARGET_SOURCES "3" CACHE STRING
+ "Minimum number of sources in target required to enable use of precompiled header.")
+
+ if (NOT DEFINED COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES_INIT)
+ if (DEFINED COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES)
+ set (COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES_INIT ${COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES})
+ elseif ("${CMAKE_GENERATOR}" MATCHES "JOM|Ninja|Visual Studio")
+ # enable parallelization for generators that run multiple jobs by default
+ set (COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES_INIT "-j")
+ else()
+ set (COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES_INIT "0")
+ endif()
+ endif()
+ set (COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES "${COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES_INIT}" CACHE STRING
+ "Maximum number of source files to include in a single unity source file.")
+
+ if (NOT COTIRE_PREFIX_HEADER_FILENAME_SUFFIX)
+ set (COTIRE_PREFIX_HEADER_FILENAME_SUFFIX "_prefix")
+ endif()
+ if (NOT COTIRE_UNITY_SOURCE_FILENAME_SUFFIX)
+ set (COTIRE_UNITY_SOURCE_FILENAME_SUFFIX "_unity")
+ endif()
+ if (NOT COTIRE_INTDIR)
+ set (COTIRE_INTDIR "cotire")
+ endif()
+ if (NOT COTIRE_PCH_ALL_TARGET_NAME)
+ set (COTIRE_PCH_ALL_TARGET_NAME "all_pch")
+ endif()
+ if (NOT COTIRE_UNITY_BUILD_ALL_TARGET_NAME)
+ set (COTIRE_UNITY_BUILD_ALL_TARGET_NAME "all_unity")
+ endif()
+ if (NOT COTIRE_CLEAN_ALL_TARGET_NAME)
+ set (COTIRE_CLEAN_ALL_TARGET_NAME "clean_cotire")
+ endif()
+ if (NOT COTIRE_CLEAN_TARGET_SUFFIX)
+ set (COTIRE_CLEAN_TARGET_SUFFIX "_clean_cotire")
+ endif()
+ if (NOT COTIRE_PCH_TARGET_SUFFIX)
+ set (COTIRE_PCH_TARGET_SUFFIX "_pch")
+ endif()
+ if (NOT COTIRE_UNITY_BUILD_TARGET_SUFFIX)
+ set (COTIRE_UNITY_BUILD_TARGET_SUFFIX "_unity")
+ endif()
+ if (NOT DEFINED COTIRE_TARGETS_FOLDER)
+ set (COTIRE_TARGETS_FOLDER "cotire")
+ endif()
+ if (NOT DEFINED COTIRE_UNITY_OUTPUT_DIRECTORY)
+ if ("${CMAKE_GENERATOR}" MATCHES "Ninja")
+ # generated Ninja build files do not work if the unity target produces the same output file as the cotired target
+ set (COTIRE_UNITY_OUTPUT_DIRECTORY "unity")
+ else()
+ set (COTIRE_UNITY_OUTPUT_DIRECTORY "")
+ endif()
+ endif()
+
+ # define cotire cache variables
+
+ define_property(
+ CACHED_VARIABLE PROPERTY "COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_PATH"
+ BRIEF_DOCS "Ignore headers from these directories when generating the prefix header."
+ FULL_DOCS
+ "The variable can be set to a semicolon separated list of include directories."
+ "If a header file is found in one of these directories or sub-directories, it will be excluded from the generated prefix header."
+ "If not defined, defaults to empty list."
+ )
+
+ define_property(
+ CACHED_VARIABLE PROPERTY "COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_EXTENSIONS"
+ BRIEF_DOCS "Ignore includes with the listed file extensions from the generated prefix header."
+ FULL_DOCS
+ "The variable can be set to a semicolon separated list of file extensions."
+ "If a header file extension matches one in the list, it will be excluded from the generated prefix header."
+ "Includes with an extension in CMAKE_<LANG>_SOURCE_FILE_EXTENSIONS are always ignored."
+ "If not defined, defaults to inc;inl;ipp."
+ )
+
+ define_property(
+ CACHED_VARIABLE PROPERTY "COTIRE_UNITY_SOURCE_EXCLUDE_EXTENSIONS"
+ BRIEF_DOCS "Exclude sources with the listed file extensions from the generated unity source."
+ FULL_DOCS
+ "The variable can be set to a semicolon separated list of file extensions."
+ "If a source file extension matches one in the list, it will be excluded from the generated unity source file."
+ "Source files with an extension in CMAKE_<LANG>_IGNORE_EXTENSIONS are always excluded."
+ "If not defined, defaults to m;mm."
+ )
+
+ define_property(
+ CACHED_VARIABLE PROPERTY "COTIRE_MINIMUM_NUMBER_OF_TARGET_SOURCES"
+ BRIEF_DOCS "Minimum number of sources in target required to enable use of precompiled header."
+ FULL_DOCS
+ "The variable can be set to an integer > 0."
+ "If a target contains less than that number of source files, cotire will not enable the use of the precompiled header for the target."
+ "If not defined, defaults to 3."
+ )
+
+ define_property(
+ CACHED_VARIABLE PROPERTY "COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES"
+ BRIEF_DOCS "Maximum number of source files to include in a single unity source file."
+ FULL_DOCS
+ "This may be set to an integer >= 0."
+ "If 0, cotire will only create a single unity source file."
+ "If a target contains more than that number of source files, cotire will create multiple unity source files for it."
+ "Can be set to \"-j\" to optimize the count of unity source files for the number of available processor cores."
+ "Can be set to \"-j jobs\" to optimize the number of unity source files for the given number of simultaneous jobs."
+ "Is used to initialize the target property COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES."
+ "Defaults to \"-j\" for the generators Visual Studio, JOM or Ninja. Defaults to 0 otherwise."
+ )
+
+ # define cotire directory properties
+
+ define_property(
+ DIRECTORY PROPERTY "COTIRE_ENABLE_PRECOMPILED_HEADER"
+ BRIEF_DOCS "Modify build command of cotired targets added in this directory to make use of the generated precompiled header."
+ FULL_DOCS
+ "See target property COTIRE_ENABLE_PRECOMPILED_HEADER."
+ )
+
+ define_property(
+ DIRECTORY PROPERTY "COTIRE_ADD_UNITY_BUILD"
+ BRIEF_DOCS "Add a new target that performs a unity build for cotired targets added in this directory."
+ FULL_DOCS
+ "See target property COTIRE_ADD_UNITY_BUILD."
+ )
+
+ define_property(
+ DIRECTORY PROPERTY "COTIRE_ADD_CLEAN"
+ BRIEF_DOCS "Add a new target that cleans all cotire generated files for cotired targets added in this directory."
+ FULL_DOCS
+ "See target property COTIRE_ADD_CLEAN."
+ )
+
+ define_property(
+ DIRECTORY PROPERTY "COTIRE_PREFIX_HEADER_IGNORE_PATH"
+ BRIEF_DOCS "Ignore headers from these directories when generating the prefix header."
+ FULL_DOCS
+ "See target property COTIRE_PREFIX_HEADER_IGNORE_PATH."
+ )
+
+ define_property(
+ DIRECTORY PROPERTY "COTIRE_PREFIX_HEADER_INCLUDE_PATH"
+ BRIEF_DOCS "Honor headers from these directories when generating the prefix header."
+ FULL_DOCS
+ "See target property COTIRE_PREFIX_HEADER_INCLUDE_PATH."
+ )
+
+ define_property(
+ DIRECTORY PROPERTY "COTIRE_UNITY_SOURCE_PRE_UNDEFS"
+ BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file before the inclusion of each source file."
+ FULL_DOCS
+ "See target property COTIRE_UNITY_SOURCE_PRE_UNDEFS."
+ )
+
+ define_property(
+ DIRECTORY PROPERTY "COTIRE_UNITY_SOURCE_POST_UNDEFS"
+ BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file after the inclusion of each source file."
+ FULL_DOCS
+ "See target property COTIRE_UNITY_SOURCE_POST_UNDEFS."
+ )
+
+ define_property(
+ DIRECTORY PROPERTY "COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES"
+ BRIEF_DOCS "Maximum number of source files to include in a single unity source file."
+ FULL_DOCS
+ "See target property COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES."
+ )
+
+ # define cotire target properties
+
+ define_property(
+ TARGET PROPERTY "COTIRE_ENABLE_PRECOMPILED_HEADER" INHERITED
+ BRIEF_DOCS "Modify this target's build command to make use of the generated precompiled header."
+ FULL_DOCS
+ "If this property is set to TRUE, cotire will modify the build command to make use of the generated precompiled header."
+ "Irrespective of the value of this property, cotire will setup custom commands to generate the unity source and prefix header for the target."
+ "For makefile based generators cotire will also set up a custom target to manually invoke the generation of the precompiled header."
+ "The target name will be set to this target's name with the suffix _pch appended."
+ "Inherited from directory."
+ "Defaults to TRUE."
+ )
+
+ define_property(
+ TARGET PROPERTY "COTIRE_ADD_UNITY_BUILD" INHERITED
+ BRIEF_DOCS "Add a new target that performs a unity build for this target."
+ FULL_DOCS
+ "If this property is set to TRUE, cotire creates a new target of the same type that uses the generated unity source file instead of the target sources."
+ "Most of the relevant target properties will be copied from this target to the new unity build target."
+ "Target dependencies and linked libraries have to be manually set up for the new unity build target."
+ "The unity target name will be set to this target's name with the suffix _unity appended."
+ "Inherited from directory."
+ "Defaults to TRUE."
+ )
+
+ define_property(
+ TARGET PROPERTY "COTIRE_ADD_CLEAN" INHERITED
+ BRIEF_DOCS "Add a new target that cleans all cotire generated files for this target."
+ FULL_DOCS
+ "If this property is set to TRUE, cotire creates a new target that clean all files (unity source, prefix header, precompiled header)."
+ "The clean target name will be set to this target's name with the suffix _clean_cotire appended."
+ "Inherited from directory."
+ "Defaults to FALSE."
+ )
+
+ define_property(
+ TARGET PROPERTY "COTIRE_PREFIX_HEADER_IGNORE_PATH" INHERITED
+ BRIEF_DOCS "Ignore headers from these directories when generating the prefix header."
+ FULL_DOCS
+ "The property can be set to a list of directories."
+ "If a header file is found in one of these directories or sub-directories, it will be excluded from the generated prefix header."
+ "Inherited from directory."
+ "If not set, this property is initialized to \${CMAKE_SOURCE_DIR};\${CMAKE_BINARY_DIR}."
+ )
+
+ define_property(
+ TARGET PROPERTY "COTIRE_PREFIX_HEADER_INCLUDE_PATH" INHERITED
+ BRIEF_DOCS "Honor headers from these directories when generating the prefix header."
+ FULL_DOCS
+ "The property can be set to a list of directories."
+ "If a header file is found in one of these directories or sub-directories, it will be included in the generated prefix header."
+ "If a header file is both selected by COTIRE_PREFIX_HEADER_IGNORE_PATH and COTIRE_PREFIX_HEADER_INCLUDE_PATH,"
+ "the option which yields the closer relative path match wins."
+ "Inherited from directory."
+ "If not set, this property is initialized to the empty list."
+ )
+
+ define_property(
+ TARGET PROPERTY "COTIRE_UNITY_SOURCE_PRE_UNDEFS" INHERITED
+ BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file before the inclusion of each target source file."
+ FULL_DOCS
+ "This may be set to a semicolon-separated list of preprocessor symbols."
+ "cotire will add corresponding #undef directives to the generated unit source file before each target source file."
+ "Inherited from directory."
+ "Defaults to empty string."
+ )
+
+ define_property(
+ TARGET PROPERTY "COTIRE_UNITY_SOURCE_POST_UNDEFS" INHERITED
+ BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file after the inclusion of each target source file."
+ FULL_DOCS
+ "This may be set to a semicolon-separated list of preprocessor symbols."
+ "cotire will add corresponding #undef directives to the generated unit source file after each target source file."
+ "Inherited from directory."
+ "Defaults to empty string."
+ )
+
+ define_property(
+ TARGET PROPERTY "COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES" INHERITED
+ BRIEF_DOCS "Maximum number of source files to include in a single unity source file."
+ FULL_DOCS
+ "This may be set to an integer > 0."
+ "If a target contains more than that number of source files, cotire will create multiple unity build files for it."
+ "If not set, cotire will only create a single unity source file."
+ "Inherited from directory."
+ "Defaults to empty."
+ )
+
+ define_property(
+ TARGET PROPERTY "COTIRE_<LANG>_UNITY_SOURCE_INIT"
+ BRIEF_DOCS "User provided unity source file to be used instead of the automatically generated one."
+ FULL_DOCS
+ "If set, cotire will only add the given file(s) to the generated unity source file."
+ "If not set, cotire will add all the target source files to the generated unity source file."
+ "The property can be set to a user provided unity source file."
+ "Defaults to empty."
+ )
+
+ define_property(
+ TARGET PROPERTY "COTIRE_<LANG>_PREFIX_HEADER_INIT"
+ BRIEF_DOCS "User provided prefix header file to be used instead of the automatically generated one."
+ FULL_DOCS
+ "If set, cotire will add the given header file(s) to the generated prefix header file."
+ "If not set, cotire will generate a prefix header by tracking the header files included by the unity source file."
+ "The property can be set to a user provided prefix header file (e.g., stdafx.h)."
+ "Defaults to empty."
+ )
+
+ define_property(
+ TARGET PROPERTY "COTIRE_<LANG>_UNITY_SOURCE"
+ BRIEF_DOCS "Read-only property. The generated <LANG> unity source file(s)."
+ FULL_DOCS
+ "cotire sets this property to the path of the generated <LANG> single computation unit source file for the target."
+ "Defaults to empty string."
+ )
+
+ define_property(
+ TARGET PROPERTY "COTIRE_<LANG>_PREFIX_HEADER"
+ BRIEF_DOCS "Read-only property. The generated <LANG> prefix header file."
+ FULL_DOCS
+ "cotire sets this property to the full path of the generated <LANG> language prefix header for the target."
+ "Defaults to empty string."
+ )
+
+ define_property(
+ TARGET PROPERTY "COTIRE_<LANG>_PRECOMPILED_HEADER"
+ BRIEF_DOCS "Read-only property. The generated <LANG> precompiled header file."
+ FULL_DOCS
+ "cotire sets this property to the full path of the generated <LANG> language precompiled header binary for the target."
+ "Defaults to empty string."
+ )
+
+ define_property(
+ TARGET PROPERTY "COTIRE_UNITY_TARGET_NAME"
+ BRIEF_DOCS "The name of the generated unity build target corresponding to this target."
+ FULL_DOCS
+ "This property can be set to the desired name of the unity target that will be created by cotire."
+ "If not set, the unity target name will be set to this target's name with the suffix _unity appended."
+ "After this target has been processed by cotire, the property is set to the actual name of the generated unity target."
+ "Defaults to empty string."
+ )
+
+ # define cotire source properties
+
+ define_property(
+ SOURCE PROPERTY "COTIRE_EXCLUDED"
+ BRIEF_DOCS "Do not modify source file's build command."
+ FULL_DOCS
+ "If this property is set to TRUE, the source file's build command will not be modified to make use of the precompiled header."
+ "The source file will also be excluded from the generated unity source file."
+ "Source files that have their COMPILE_FLAGS property set will be excluded by default."
+ "Defaults to FALSE."
+ )
+
+ define_property(
+ SOURCE PROPERTY "COTIRE_DEPENDENCY"
+ BRIEF_DOCS "Add this source file to dependencies of the automatically generated prefix header file."
+ FULL_DOCS
+ "If this property is set to TRUE, the source file is added to dependencies of the generated prefix header file."
+ "If the file is modified, cotire will re-generate the prefix header source upon build."
+ "Defaults to FALSE."
+ )
+
+ define_property(
+ SOURCE PROPERTY "COTIRE_UNITY_SOURCE_PRE_UNDEFS"
+ BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file before the inclusion of this source file."
+ FULL_DOCS
+ "This may be set to a semicolon-separated list of preprocessor symbols."
+ "cotire will add corresponding #undef directives to the generated unit source file before this file is included."
+ "Defaults to empty string."
+ )
+
+ define_property(
+ SOURCE PROPERTY "COTIRE_UNITY_SOURCE_POST_UNDEFS"
+ BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file after the inclusion of this source file."
+ FULL_DOCS
+ "This may be set to a semicolon-separated list of preprocessor symbols."
+ "cotire will add corresponding #undef directives to the generated unit source file after this file is included."
+ "Defaults to empty string."
+ )
+
+ define_property(
+ SOURCE PROPERTY "COTIRE_START_NEW_UNITY_SOURCE"
+ BRIEF_DOCS "Start a new unity source file which includes this source file as the first one."
+ FULL_DOCS
+ "If this property is set to TRUE, cotire will complete the current unity file and start a new one."
+ "The new unity source file will include this source file as the first one."
+ "This property essentially works as a separator for unity source files."
+ "Defaults to FALSE."
+ )
+
+ define_property(
+ SOURCE PROPERTY "COTIRE_TARGET"
+ BRIEF_DOCS "Read-only property. Mark this source file as cotired for the given target."
+ FULL_DOCS
+ "cotire sets this property to the name of target, that the source file's build command has been altered for."
+ "Defaults to empty string."
+ )
+
+ message (STATUS "cotire ${COTIRE_CMAKE_MODULE_VERSION} loaded.")
+
+endif()
diff --git a/CMake/memaccess.cmake b/CMake/memaccess.cmake
new file mode 100644
index 0000000..05aaf1a
--- /dev/null
+++ b/CMake/memaccess.cmake
@@ -0,0 +1,41 @@
+##
+## This file is part of the PulseView project.
+##
+## Copyright (C) 2014 Marcus Comstedt <marcus at mc.pp.se>
+##
+## This program is free software: you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+##
+
+include(CheckCSourceRuns)
+
+function(memaccess_check_unaligned_le _var)
+if(NOT CMAKE_CROSSCOMPILING)
+CHECK_C_SOURCE_RUNS("
+#include <stdint.h>
+int main() {
+ int i;
+ union { uint64_t u64; uint8_t u8[16]; } d;
+ uint64_t v;
+ for (i=0; i<16; i++)
+ d.u8[i] = i;
+ v = *(uint64_t *)(d.u8+1);
+ if (v != 0x0807060504030201ULL)
+ return 1;
+ return 0;
+}" ${_var})
+endif()
+if(CMAKE_CROSSCOMPILING)
+ message(STATUS "Cross compiling - using portable code for memory access")
+endif()
+endfunction()
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..50dde49
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,373 @@
+##
+## This file is part of the PulseView project.
+##
+## Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+## Copyright (C) 2012-2013 Alexandru Gagniuc <mr.nuke.me at gmail.com>
+##
+## This program is free software: you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+##
+
+cmake_minimum_required(VERSION 2.8.6)
+
+include(FindPkgConfig)
+include(GNUInstallDirs)
+
+set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMake")
+
+project(pulseview)
+
+#===============================================================================
+#= User Options
+#-------------------------------------------------------------------------------
+
+option(DISABLE_WERROR "Build without -Werror" FALSE)
+option(ENABLE_SIGNALS "Build with UNIX signals" TRUE)
+option(ENABLE_DECODE "Build with libsigrokdecode" TRUE)
+option(ENABLE_COTIRE "Enable cotire" FALSE)
+option(ENABLE_TESTS "Enable unit tests" FALSE)
+option(STATIC_PKGDEPS_LIBS "Statically link to (pkg-config) libraries" FALSE)
+
+if(WIN32)
+ # On Windows/MinGW we need to statically link to libraries.
+ # This option is user configurable, but enable it by default on win32.
+ set(STATIC_PKGDEPS_LIBS TRUE)
+
+ # For boost-thread we need two additional settings on win32:
+ set(Boost_USE_STATIC_LIBS ON)
+ add_definitions(-DBOOST_THREAD_USE_LIB)
+
+ # Windows does not support UNIX signals.
+ set(ENABLE_SIGNALS FALSE)
+endif()
+
+if(NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING
+ "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel."
+ FORCE)
+endif()
+
+#===============================================================================
+#= Dependencies
+#-------------------------------------------------------------------------------
+
+list(APPEND PKGDEPS libsigrok>=0.3.0)
+
+if(ENABLE_DECODE)
+ list(APPEND PKGDEPS libsigrokdecode>=0.3.0)
+endif()
+
+find_package(PkgConfig)
+pkg_check_modules(PKGDEPS REQUIRED ${PKGDEPS})
+
+find_program(QT_QMAKE_EXECUTABLE NAMES qmake4 qmake-qt4 qmake-mac)
+find_package(Qt4 REQUIRED)
+
+# Find the platform's thread library (needed for boost-thread).
+# This will set ${CMAKE_THREAD_LIBS_INIT} to the correct, OS-specific value.
+find_package(Threads)
+
+if(WIN32)
+ # On Windows/MinGW we need to use 'thread_win32' instead of 'thread'.
+ # The library is named libboost_thread_win32* (not libboost_thread*).
+ find_package(Boost 1.42 COMPONENTS filesystem system thread_win32 REQUIRED)
+else()
+ find_package(Boost 1.42 COMPONENTS filesystem system thread REQUIRED)
+endif()
+
+#===============================================================================
+#= System Introspection
+#-------------------------------------------------------------------------------
+
+include(memaccess)
+memaccess_check_unaligned_le(HAVE_UNALIGNED_LITTLE_ENDIAN_ACCESS)
+
+#===============================================================================
+#= Config Header
+#-------------------------------------------------------------------------------
+
+set(PV_TITLE PulseView)
+set(PV_DESCRIPTION "A GUI for sigrok")
+
+set(PV_VERSION_MAJOR 0)
+set(PV_VERSION_MINOR 2)
+set(PV_VERSION_MICRO 0)
+set(PV_VERSION_STRING
+ ${PV_VERSION_MAJOR}.${PV_VERSION_MINOR}.${PV_VERSION_MICRO}
+)
+
+configure_file (
+ ${PROJECT_SOURCE_DIR}/config.h.in
+ ${PROJECT_BINARY_DIR}/config.h
+)
+
+#===============================================================================
+#= Sources
+#-------------------------------------------------------------------------------
+
+set(pulseview_SOURCES
+ main.cpp
+ pv/devicemanager.cpp
+ pv/mainwindow.cpp
+ pv/sigsession.cpp
+ pv/storesession.cpp
+ pv/data/analog.cpp
+ pv/data/analogsnapshot.cpp
+ pv/data/logic.cpp
+ pv/data/logicsnapshot.cpp
+ pv/data/signaldata.cpp
+ pv/data/snapshot.cpp
+ pv/device/device.cpp
+ pv/device/file.cpp
+ pv/device/devinst.cpp
+ pv/device/inputfile.cpp
+ pv/device/sessionfile.cpp
+ pv/dialogs/about.cpp
+ pv/dialogs/connect.cpp
+ pv/dialogs/storeprogress.cpp
+ pv/popups/deviceoptions.cpp
+ pv/popups/probes.cpp
+ pv/prop/bool.cpp
+ pv/prop/double.cpp
+ pv/prop/enum.cpp
+ pv/prop/int.cpp
+ pv/prop/property.cpp
+ pv/prop/string.cpp
+ pv/prop/binding/binding.cpp
+ pv/prop/binding/deviceoptions.cpp
+ pv/toolbars/samplingbar.cpp
+ pv/view/analogsignal.cpp
+ pv/view/cursor.cpp
+ pv/view/cursorpair.cpp
+ pv/view/header.cpp
+ pv/view/marginwidget.cpp
+ pv/view/logicsignal.cpp
+ pv/view/ruler.cpp
+ pv/view/selectableitem.cpp
+ pv/view/signal.cpp
+ pv/view/timemarker.cpp
+ pv/view/trace.cpp
+ pv/view/tracepalette.cpp
+ pv/view/view.cpp
+ pv/view/viewport.cpp
+ pv/widgets/colourbutton.cpp
+ pv/widgets/colourpopup.cpp
+ pv/widgets/popup.cpp
+ pv/widgets/popuptoolbutton.cpp
+ pv/widgets/sweeptimingwidget.cpp
+ pv/widgets/wellarray.cpp
+)
+
+# This list includes only QObject derived class headers.
+set(pulseview_HEADERS
+ pv/mainwindow.h
+ pv/sigsession.h
+ pv/storesession.h
+ pv/device/devinst.h
+ pv/dialogs/about.h
+ pv/dialogs/connect.h
+ pv/dialogs/storeprogress.h
+ pv/popups/probes.h
+ pv/popups/deviceoptions.h
+ pv/prop/bool.h
+ pv/prop/double.h
+ pv/prop/enum.h
+ pv/prop/int.h
+ pv/prop/property.h
+ pv/prop/string.h
+ pv/toolbars/samplingbar.h
+ pv/view/cursor.h
+ pv/view/header.h
+ pv/view/logicsignal.h
+ pv/view/marginwidget.h
+ pv/view/ruler.h
+ pv/view/selectableitem.h
+ pv/view/signal.h
+ pv/view/timemarker.h
+ pv/view/trace.h
+ pv/view/view.h
+ pv/view/viewport.h
+ pv/widgets/colourbutton.h
+ pv/widgets/colourpopup.h
+ pv/widgets/popup.h
+ pv/widgets/popuptoolbutton.h
+ pv/widgets/sweeptimingwidget.h
+ pv/widgets/wellarray.h
+)
+
+set(pulseview_FORMS
+ pv/dialogs/about.ui
+)
+
+set(pulseview_RESOURCES
+ pulseview.qrc
+)
+
+if(ENABLE_SIGNALS)
+ list(APPEND pulseview_SOURCES signalhandler.cpp)
+ list(APPEND pulseview_HEADERS signalhandler.h)
+endif()
+
+if(ENABLE_DECODE)
+ list(APPEND pulseview_SOURCES
+ pv/data/decoderstack.cpp
+ pv/data/decode/annotation.cpp
+ pv/data/decode/decoder.cpp
+ pv/data/decode/row.cpp
+ pv/data/decode/rowdata.cpp
+ pv/prop/binding/decoderoptions.cpp
+ pv/view/decodetrace.cpp
+ pv/widgets/decodergroupbox.cpp
+ pv/widgets/decodermenu.cpp
+ )
+
+ list(APPEND pulseview_HEADERS
+ pv/data/decoderstack.h
+ pv/view/decodetrace.h
+ pv/widgets/decodergroupbox.h
+ pv/widgets/decodermenu.h
+ )
+endif()
+
+if(WIN32)
+ # Use the sigrok icon for the pulseview.exe executable.
+ set(CMAKE_RC_COMPILE_OBJECT "${CMAKE_RC_COMPILER} -O coff -I${CMAKE_CURRENT_SOURCE_DIR} <SOURCE> <OBJECT>")
+ enable_language(RC)
+ list(APPEND pulseview_SOURCES pulseviewico.rc)
+endif()
+
+qt4_wrap_cpp(pulseview_HEADERS_MOC ${pulseview_HEADERS})
+qt4_wrap_ui(pulseview_FORMS_HEADERS ${pulseview_FORMS})
+qt4_add_resources(pulseview_RESOURCES_RCC ${pulseview_RESOURCES})
+
+include(${QT_USE_FILE})
+
+#===============================================================================
+#= Global Definitions
+#-------------------------------------------------------------------------------
+
+add_definitions(${QT_DEFINITIONS})
+add_definitions(-D__STDC_LIMIT_MACROS)
+add_definitions(-Wall -Wextra)
+
+if(ENABLE_DECODE)
+ add_definitions(-DENABLE_DECODE)
+endif()
+
+if(NOT DISABLE_WERROR)
+ add_definitions(-Werror)
+endif()
+
+#===============================================================================
+#= Global Include Directories
+#-------------------------------------------------------------------------------
+
+include_directories(
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${Boost_INCLUDE_DIRS}
+)
+
+if(STATIC_PKGDEPS_LIBS)
+ include_directories(${PKGDEPS_STATIC_INCLUDE_DIRS})
+else()
+ include_directories(${PKGDEPS_INCLUDE_DIRS})
+endif()
+
+#===============================================================================
+#= Linker Configuration
+#-------------------------------------------------------------------------------
+
+link_directories(${Boost_LIBRARY_DIRS})
+
+set(PULSEVIEW_LINK_LIBS
+ ${Boost_LIBRARIES}
+ ${CMAKE_THREAD_LIBS_INIT}
+ ${QT_LIBRARIES}
+)
+
+if(STATIC_PKGDEPS_LIBS)
+ link_directories(${PKGDEPS_STATIC_LIBRARY_DIRS})
+ list(APPEND PULSEVIEW_LINK_LIBS ${PKGDEPS_STATIC_LIBRARIES})
+if(WIN32)
+ # Workaround for a MinGW linking issue.
+ list(APPEND PULSEVIEW_LINK_LIBS "-llzma -llcms2")
+endif()
+else()
+ link_directories(${PKGDEPS_LIBRARY_DIRS})
+ list(APPEND PULSEVIEW_LINK_LIBS ${PKGDEPS_LIBRARIES})
+endif()
+
+if(WIN32)
+ # On Windows we need to statically link the libqsvg imageformat
+ # plugin (and the QtSvg component) for SVG graphics/icons to work.
+ add_definitions(-DQT_STATICPLUGIN)
+ link_directories("${QT_PLUGINS_DIR}/imageformats")
+ list(APPEND PULSEVIEW_LINK_LIBS ${QT_QTSVG_LIBRARY})
+ list(APPEND PULSEVIEW_LINK_LIBS "-lqsvg")
+endif()
+
+add_executable(${PROJECT_NAME}
+ ${pulseview_SOURCES}
+ ${pulseview_HEADERS_MOC}
+ ${pulseview_FORMS_HEADERS}
+ ${pulseview_RESOURCES_RCC}
+)
+
+target_link_libraries(${PROJECT_NAME} ${PULSEVIEW_LINK_LIBS})
+
+if(WIN32)
+ # Pass -mwindows so that no "DOS box" opens when PulseView is started.
+ set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-mwindows")
+endif()
+
+if(ENABLE_COTIRE)
+ include(cotire)
+ cotire(${PROJECT_NAME})
+endif()
+
+#===============================================================================
+#= Installation
+#-------------------------------------------------------------------------------
+
+# Install the executable.
+install(TARGETS ${PROJECT_NAME} DESTINATION bin/)
+
+# Install the manpage.
+install(FILES doc/pulseview.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 COMPONENT doc)
+
+#===============================================================================
+#= Packaging (handled by CPack)
+#-------------------------------------------------------------------------------
+
+set(CPACK_PACKAGE_VERSION_MAJOR ${PV_VERSION_MAJOR})
+set(CPACK_PACKAGE_VERSION_MINOR ${PV_VERSION_MINOR})
+set(CPACK_PACKAGE_VERSION_PATCH ${PV_VERSION_MICRO})
+set(CPACK_PACKAGE_DESCRIPTION_FILE ${CMAKE_CURRENT_SOURCE_DIR}/README)
+set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/COPYING)
+set(CPACK_SOURCE_IGNORE_FILES ${CMAKE_CURRENT_BINARY_DIR} ".gitignore" ".git")
+set(CPACK_SOURCE_PACKAGE_FILE_NAME
+ "${CMAKE_PROJECT_NAME}-${PV_VERSION_MAJOR}.${PV_VERSION_MINOR}.${PV_VERSION_MICRO}")
+set(CPACK_SOURCE_GENERATOR "TGZ")
+
+include(CPack)
+
+#===============================================================================
+#= Tests
+#-------------------------------------------------------------------------------
+
+if(ENABLE_TESTS)
+ add_subdirectory(test)
+ enable_testing()
+ add_test(test ${CMAKE_CURRENT_BINARY_DIR}/test/pulseview-test)
+endif()
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ 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.
+
+ This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/HACKING b/HACKING
new file mode 100644
index 0000000..d1edc87
--- /dev/null
+++ b/HACKING
@@ -0,0 +1,79 @@
+-------------------------------------------------------------------------------
+HACKING
+-------------------------------------------------------------------------------
+
+Coding style
+------------
+
+This project is programmed using the Linux kernel coding style, see
+http://lxr.linux.no/linux/Documentation/CodingStyle for details.
+
+Please use the same style for any code contributions, thanks!
+
+In some exceptional cases deviations from the above coding guidelines are
+OK (in order to meet Qt/C++ related guidelines, for example).
+
+
+Contributions
+-------------
+
+ - Patches should be sent to the development mailinglist at
+ sigrok-devel at lists.sourceforge.net (please subscribe to the list first).
+
+ https://lists.sourceforge.net/lists/listinfo/sigrok-devel
+
+ - Alternatively, you can also clone the git repository and let us know
+ from where to pull/review your changes. You can use gitorious.org,
+ github.com, or any other public git hosting site.
+
+
+Random notes
+------------
+
+ - Consistently use g_try_malloc() / g_try_malloc0(). Do not use standard
+ malloc()/calloc() if it can be avoided (sometimes other libs such
+ as libftdi can return malloc()'d memory, for example).
+
+ - Always properly match allocations with the proper *free() functions. If
+ glib's g_try_malloc()/g_try_malloc0() was used, use g_free() to free the
+ memory. Otherwise use standard free(). Never use the wrong function!
+
+ - Never use g_malloc() or g_malloc0(). These functions do not return NULL
+ if not enough memory is available but rather lead to an exit() or segfault
+ instead. This behaviour is not acceptable.
+ Use g_try_malloc()/g_try_malloc0() instead and check the return value.
+
+ - Use glib's gboolean / TRUE / FALSE for boolean types consistently.
+ Do not use <stdbool.h> and its true / false, and do not invent private
+ definitions for this either.
+
+ - Consistently use the same naming convention for #include guards in headers:
+ <PROJECTNAME>_<PATH_TO_FILE>_<FILE>
+ This ensures that all #include guards are always unique and consistent.
+ Example: PULSEVIEW_PV_VIEW_RULER_H
+
+ - Consistently use the same naming convention for functions, if appropriate:
+
+ Getter/setter function names should usually end with "_get" or "_set".
+ Functions creating new "objects" should end with "_new".
+ Functions destroying "objects" should end with "_destroy".
+ Functions adding or removing items (e.g. from lists) should end with
+ either "_add" or "_remove".
+ Functions operating on all items from a list (not on only one of them),
+ should end with "_all", e.g. "_remove_all", "_get_all", and so on.
+ Use "_remove_all" in favor of "_clear" for consistency.
+
+ - In Doxygen comments, put an empty line between the block of @param lines
+ and the final @return line. The @param lines themselves (if there is more
+ than one) are not separated by empty lines.
+
+
+Release engineering
+-------------------
+
+See
+
+ http://sigrok.org/wiki/Developers/Release_process
+
+for a list of items that need to be done when releasing a new tarball.
+
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..4a93e08
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,53 @@
+-------------------------------------------------------------------------------
+INSTALL
+-------------------------------------------------------------------------------
+
+Requirements
+------------
+
+ - git
+ - g++
+ - make
+ - libtool
+ - pkg-config >= 0.22
+ - cmake >= 2.8.6
+ - libglib >= 2.28.0
+ - Qt >= 4.5
+ - libboost >= 1.42 (including the following libs):
+ - libboost-system
+ - libboost-thread
+ - libboost-filesystem
+ - libboost-test (optional, only needed to run the unit tests)
+ - libsigrok >= 0.3.0
+ - libsigrokdecode >= 0.3.0
+
+
+Building and installing
+-----------------------
+
+In order to get the PulseView source code and build it, run:
+
+ $ git clone git://sigrok.org/pulseview
+ $ cd pulseview
+ $ cmake .
+ $ make
+
+For installing PulseView:
+
+ $ make install
+
+See the following wiki page for more (OS-specific) instructions:
+
+ http://sigrok.org/wiki/Building
+
+
+Creating a source distribution package
+--------------------------------------
+
+In order to build a source package begin with an unconfigured source tree.
+
+ $ mkdir dist
+ $ cd dist
+ $ cmake ..
+ $ make package_source
+
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..696f13a
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,101 @@
+0.2.0 (2014-05-06)
+------------------
+
+ * Add support for protocol decoding.
+ - A menu item Decoders->Add can add (only) low-level protocol decoders.
+ - Clicking on a decoder's arrow on the left-hand side allows stacking
+ (only) further, higher-level decoders (bug #333).
+ - Add support for multiple decoder rows per PD (bugs #161, #303).
+ * Add support for loading data from input files.
+ * Add support for saving logic analyzer data in *.sr files (bug #245).
+ * Show the filename of loaded *.sr files as "device name" in a drop-down.
+ * Add support for the new "channel groups" feature in libsigrok.
+ * Add a "Zoom to fit" and a "Zoom 1:1" icon/button.
+ * Add a "Channels" icon/button for enabling and configuring device channels.
+ * Add a "Device options" icon/button for configuring device specific settings
+ such as voltage thresholds.
+ * Double-clicking will zoom into the location the cursor points to.
+ * Add shortcut keys for Zoom-in (CTRL++) and Zoom-out (Ctrl+-) (bug #235).
+ * Add initial, basic support for analog data sources, such as oscilloscopes.
+ This has been tested on e.g. the Rigol DS1052E oscilloscope (bug #127).
+ * Almost all operations in PulseView work in an "auto-apply" style, i.e.
+ any changes in the GUI popups (decoder channel assignment, decoder option
+ changes, device channel changes, device option changes, and so on) have an
+ immediate effect (you don't have to click on any "OK" buttons).
+ * Open file dialog: Add 'All files' selection possibility.
+ * Added GUI support for the following libsigrok config options:
+ - SR_CONF_VOLTAGE_THRESHOLD
+ - SR_CONF_TRIGGER_SLOPE
+ - SR_CONF_EXTERNAL_CLOCK
+ - SR_CONF_CLOCK_EDGE
+ * Updated build requirements:
+ - cmake >= 2.8.6 (required)
+ - libsigrok >= 0.3.0 (required)
+ - libsigrokdecode >= 0.3.0 (required)
+ Note: libsigrokdecode is now always required (was optional before).
+ - libboost-filesystem >= 1.42 (required)
+ - libboost-test >= 1.42 (optional, only needed for running unit tests)
+ * Fix a build issue on NetBSD due to an incorrect #include.
+ * Fix a channel handling issue for devices with >= 16 channels.
+ * Fixed a few display issues related to UTF-8 strings in decoder annotations,
+ decoder options, and so on (bug #307, and other fixes without bug numbers).
+ * Don't decode if required PD channels were not supplied (bug #204).
+ * Fix some issues with partially disappearing PD annotations.
+ * Update the samplerate selector when the device config changes (since e.g.
+ en-/disabling logic analyzer channels can change available samplerates).
+ * Fix a samplerate selector and device config update issue (bug #296).
+ * Minor performance improvements via memory pre-allocation in some areas.
+ * Remove old PD annotations upon new captures (bugs #172, #302).
+ * Portability improvements by using portable sample pack/unpack code.
+ * Fix a segfault related to thread joining (bug #323).
+ * Fix a minor issue which lead to trigger config being unusable (bug #318).
+ * Fix an issue which lead to the user-selected samplerate in the drop-down
+ box not being selected and shown again after clicking "Run" (bug #324).
+ * Start a new decode session whenever a new frame begins.
+ * Fix an issue which arose when a session file load failed (bug #331).
+ * Fix a segfault if only exactly one libsigrok driver is available (bug #334).
+ * Fix an issue with channel names not being updated while typing (bug #338).
+ * Fix an issue by stopping the capture when a device is changed (bug #223).
+ * Fix an issue with the displayed cursor popup time (bug #229).
+ * Fix handling of different integer types in some properties (bug #203).
+ * Hide the 'Configure device' button when the popup would be empty (bug #232).
+ * Fix a decoding chunk buffer / unitsize issue (bugs #171, #225).
+ * Fix a zoom issue due to invalid samplerate, assume 1Hz if needed (bug #278).
+ * Fix a QWellArray related issue by dropping QT_NO_MENU code (bug #265).
+ * Fix an analog data channel interleaving issue (bug #279).
+ * Only show the sample count widget if needed (i.e., hide it for scopes).
+ * SweepTimingWidget: Show a 1-2-5 based list for samplerate and samplecount.
+ * Fixed a unit test issue with AnalogSnapshotTest (bug #286).
+ * Add an extra sample to edges to make the end sample visible (bug #280).
+ * Fix an issue with decoder errors being shown even after the error was fixed.
+ * Add support for SR_CONF_LIMIT_SAMPLES (device-specific limits) (bug #74).
+ * Windows:
+ - Use the sigrok logo as icon for pulseview.exe (bugs #110, #238).
+ - Fix SVG icons not being displayed on Windows (bug #239).
+ - Ship libusb0.dll in the NSIS based installer (bug #241).
+ - Additionally ship decoders, firmware files, example *.sr files, and
+ zadig.exe and zadig_xp.exe in the NSIS based installer.
+ - Fix a "working path" issue resulting in PDs not being usable.
+ * Device scan: Disable "OK" button if no devices were found (bug #237).
+ * Fix a segfault related to incorrect decoder option handling (bug #160).
+ * Fix an off-by-one issue resulting in one PD not being usable (bug #164).
+ * Fix a PD channel auto-select logic issue with optional channels (bug #310).
+ * Fix an issue when cancelling "Connect to Device" (bug #242).
+ * Avoid confusing annotation color changes (bug #311).
+ * Fix an issue that caused some decoded data to not be shown (bug #308).
+ * Don't allow disabled channels (channel arrows) to be selected (bug #313).
+ * The channel name field is now resized to always fit the contents (bug #167).
+ * Build fixes for systems with (among other versions) Qt5 installed.
+ * Fix a build issue related to missing libboost-filesystem (bug #133).
+ * Add Cotire (optional, disabled by default) support for build performance.
+ * Fix a build issue related to Cotire (bug #217).
+ * Fix an issue with missing channel names from loaded files (bug #126).
+ * Fix missing samplerate loading from session files (bug #123).
+ * Fix some build issues related to C++ namespaces (bug #196).
+ * Fix a file loading issue which triggered an assert (bug #320).
+
+0.1.0 (2013-05-04)
+------------------
+
+ * Initial release.
+
diff --git a/README b/README
new file mode 100644
index 0000000..e4aa3c1
--- /dev/null
+++ b/README
@@ -0,0 +1,69 @@
+-------------------------------------------------------------------------------
+README
+-------------------------------------------------------------------------------
+
+The sigrok project aims at creating a portable, cross-platform,
+Free/Libre/Open-Source signal analysis software suite that supports various
+device types (such as logic analyzers, oscilloscopes, multimeters, and more).
+
+PulseView is a Qt-based LA/scope/MSO GUI for sigrok.
+
+
+Status
+------
+
+PulseView is in a usable state and has had official tarball releases.
+
+However, it is still work in progress. Some basic functionality
+is available and working, but other things are still on the TODO list.
+
+
+Copyright and license
+---------------------
+
+PulseView is licensed under the terms of the GNU General Public License
+(GPL), version 3 or later.
+
+While some individual source code files are licensed under the GPLv2+, and
+some files are licensed under the GPLv3+, this doesn't change the fact that
+the program as a whole is licensed under the terms of the GPLv3+ (e.g. also
+due to the fact that it links against GPLv3+ libraries).
+
+Please see the individual source files for the full list of copyright holders.
+
+
+Copyright notices
+-----------------
+
+A copyright notice indicating a range of years, must be interpreted as having
+had copyrightable material added in each of those years.
+
+Example:
+
+ Copyright (C) 2010-2013 Contributor Name
+
+is to be interpreted as
+
+ Copyright (C) 2010,2011,2012,2013 Contributor Name
+
+
+Mailing lists
+-------------
+
+There are two mailing lists for sigrok/PulseView:
+
+ https://lists.sourceforge.net/lists/listinfo/sigrok-devel
+ https://lists.sourceforge.net/lists/listinfo/sigrok-commits
+
+
+IRC
+---
+
+You can find the sigrok developers in the #sigrok IRC channel on Freenode.
+
+
+Website
+-------
+
+ http://sigrok.org/wiki/PulseView
+
diff --git a/config.h.in b/config.h.in
new file mode 100644
index 0000000..aa6062d
--- /dev/null
+++ b/config.h.in
@@ -0,0 +1,37 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Alexandru Gagniuc <mr.nuke.me at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _PULSEVIEW_CONFIG_H
+#define _PULSEVIEW_CONFIG_H
+
+/* Application details */
+#define PV_TITLE "@PV_TITLE@"
+#define PV_DESCRIPTION "@PV_DESCRIPTION@"
+#define PV_BIN_NAME "@PROJECT_NAME@"
+
+/* Pulseview version information */
+#define PV_VERSION_MAJOR @PV_VERSION_MAJOR@
+#define PV_VERSION_MINOR @PV_VERSION_MINOR@
+#define PV_VERSION_MICRO @PV_VERSION_MICRO@
+#define PV_VERSION_STRING "@PV_VERSION_STRING@"
+
+/* Platform properties */
+#cmakedefine HAVE_UNALIGNED_LITTLE_ENDIAN_ACCESS
+
+#endif
diff --git a/contrib/pulseview.desktop b/contrib/pulseview.desktop
new file mode 100644
index 0000000..cbab083
--- /dev/null
+++ b/contrib/pulseview.desktop
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Name=PulseView
+GenericName=Signal acquisition GUI for sigrok
+Categories=Electronics;
+Comment=Control your Logic Analyzer, Oscilloscope, or MSO
+Exec=pulseview
+Icon=sigrok-logo-notext
+Type=Application
+
diff --git a/contrib/pulseview_cross.nsi b/contrib/pulseview_cross.nsi
new file mode 100644
index 0000000..3c39351
--- /dev/null
+++ b/contrib/pulseview_cross.nsi
@@ -0,0 +1,252 @@
+##
+## This file is part of the PulseView project.
+##
+## Copyright (C) 2013-2014 Uwe Hermann <uwe at hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program 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 this program; if not, write to the Free Software
+## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+##
+
+#
+# This file is used to create the PulseView Windows installer via NSIS.
+# It is meant for use in a cross-compile setup (not for native builds).
+# See the 'sigrok-cross-mingw' script in the sigrok-util repo for details.
+#
+# NSIS documentation:
+# http://nsis.sourceforge.net/Docs/
+# http://nsis.sourceforge.net/Docs/Modern%20UI%202/Readme.html
+#
+
+# Include the "Modern UI" header, which gives us the usual Windows look-n-feel.
+!include "MUI2.nsh"
+
+
+# --- Global stuff ------------------------------------------------------------
+
+# Installer/product name.
+Name "PulseView"
+
+# Filename of the installer executable.
+OutFile "pulseview-0.2.0-installer.exe"
+
+# Where to install the application.
+InstallDir "$PROGRAMFILES\sigrok\PulseView"
+
+# Request admin privileges for Windows Vista and Windows 7.
+# http://nsis.sourceforge.net/Docs/Chapter4.html
+RequestExecutionLevel admin
+
+# Local helper definitions.
+!define REGSTR "Software\Microsoft\Windows\CurrentVersion\Uninstall\PulseView"
+
+
+# --- MUI interface configuration ---------------------------------------------
+
+# Use the following icon for the installer EXE file.
+!define MUI_ICON "../icons/sigrok-logo-notext.ico"
+
+# Show a nice image at the top of each installer page.
+!define MUI_HEADERIMAGE
+
+# Don't automatically go to the Finish page so the user can check the log.
+!define MUI_FINISHPAGE_NOAUTOCLOSE
+
+# Upon "cancel", ask the user if he really wants to abort the installer.
+!define MUI_ABORTWARNING
+
+# Don't force the user to accept the license, just show it.
+# Details: http://trac.videolan.org/vlc/ticket/3124
+!define MUI_LICENSEPAGE_BUTTON $(^NextBtn)
+!define MUI_LICENSEPAGE_TEXT_BOTTOM "Click Next to continue."
+
+# Path where the cross-compiled sigrok tools and libraries are located.
+# Change this to where-ever you installed libsigrok.a and so on.
+!define CROSS "$%HOME%/sr_mingw"
+
+# Path where the cross-compiled MXE tools and libraries are located.
+# Change this to where-ever you installed MXE (and the files it built).
+!define MXE "$%HOME%/mxe-git/usr/i686-pc-mingw32"
+
+
+# --- MUI pages ---------------------------------------------------------------
+
+# Show a nice "Welcome to the ... Setup Wizard" page.
+!insertmacro MUI_PAGE_WELCOME
+
+# Show the license of the project.
+!insertmacro MUI_PAGE_LICENSE "../COPYING"
+
+# Show a screen which allows the user to select which components to install.
+!insertmacro MUI_PAGE_COMPONENTS
+
+# Allow the user to select a different install directory.
+!insertmacro MUI_PAGE_DIRECTORY
+
+# Perform the actual installation, i.e. install the files.
+!insertmacro MUI_PAGE_INSTFILES
+
+# Show a final "We're done, click Finish to close this wizard" message.
+!insertmacro MUI_PAGE_FINISH
+
+# Pages used for the uninstaller.
+!insertmacro MUI_UNPAGE_WELCOME
+!insertmacro MUI_UNPAGE_CONFIRM
+!insertmacro MUI_UNPAGE_INSTFILES
+!insertmacro MUI_UNPAGE_FINISH
+
+
+# --- MUI language files ------------------------------------------------------
+
+# Select an installer language (required!).
+!insertmacro MUI_LANGUAGE "English"
+
+
+# --- Default section ---------------------------------------------------------
+
+Section "PulseView (required)" Section1
+
+ # This section is gray (can't be disabled) in the component list.
+ SectionIn RO
+
+ # Install the file(s) specified below into the specified directory.
+ SetOutPath "$INSTDIR"
+
+ # License file.
+ File "../COPYING"
+
+ # PulseView (statically linked, includes all libs).
+ File "${CROSS}/bin/pulseview.exe"
+
+ # libusb0.dll (needed for libusb-0.1).
+ File "${CROSS}/libusb0.dll"
+
+ # Zadig (used for installing libusb-win32 and WinUSB drivers).
+ File "${CROSS}/zadig.exe"
+ File "${CROSS}/zadig_xp.exe"
+
+ # Python
+ File "${CROSS}/python32.dll"
+ File "${CROSS}/python32.zip"
+
+ # Protocol decoders.
+ SetOutPath "$INSTDIR\decoders"
+ File /r /x "__pycache__" "${CROSS}/share/libsigrokdecode/decoders/*"
+
+ # Firmware files.
+ SetOutPath "$INSTDIR\firmware"
+ File /r "${CROSS}/share/sigrok-firmware/*"
+
+ # Example *.sr files.
+ SetOutPath "$INSTDIR\examples"
+ File /r "${CROSS}/share/sigrok-dumps/*"
+
+ # Generate the uninstaller executable.
+ WriteUninstaller "$INSTDIR\Uninstall.exe"
+
+ # Create a sub-directory in the start menu.
+ CreateDirectory "$SMPROGRAMS\sigrok"
+ CreateDirectory "$SMPROGRAMS\sigrok\PulseView"
+
+ # Create a shortcut for the PulseView application.
+ SetOutPath "$INSTDIR"
+ CreateShortCut "$SMPROGRAMS\sigrok\PulseView\PulseView.lnk" \
+ "$INSTDIR\pulseview.exe" "" "$INSTDIR\pulseview.exe" \
+ 0 SW_SHOWNORMAL \
+ "" "Open-source, portable sigrok GUI"
+
+ # Create a shortcut for the uninstaller.
+ CreateShortCut "$SMPROGRAMS\sigrok\PulseView\Uninstall.lnk" \
+ "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0 \
+ SW_SHOWNORMAL "" "Uninstall PulseView"
+
+ # Create registry keys for "Add/remove programs" in the control panel.
+ WriteRegStr HKLM "${REGSTR}" "DisplayName" "PulseView"
+ WriteRegStr HKLM "${REGSTR}" "UninstallString" \
+ "$\"$INSTDIR\Uninstall.exe$\""
+ WriteRegStr HKLM "${REGSTR}" "InstallLocation" "$\"$INSTDIR$\""
+ WriteRegStr HKLM "${REGSTR}" "DisplayIcon" \
+ "$\"$INSTDIR\sigrok-logo-notext.ico$\""
+ WriteRegStr HKLM "${REGSTR}" "Publisher" "sigrok"
+ WriteRegStr HKLM "${REGSTR}" "HelpLink" \
+ "http://sigrok.org/wiki/PulseView"
+ WriteRegStr HKLM "${REGSTR}" "URLUpdateInfo" \
+ "http://sigrok.org/wiki/Downloads"
+ WriteRegStr HKLM "${REGSTR}" "URLInfoAbout" "http://sigrok.org"
+ WriteRegStr HKLM "${REGSTR}" "DisplayVersion" "0.2.0"
+ WriteRegStr HKLM "${REGSTR}" "Contact" \
+ "sigrok-devel at lists.sourceforge.org"
+ WriteRegStr HKLM "${REGSTR}" "Comments" \
+ "This is a Qt based sigrok GUI."
+
+ # Display "Remove" instead of "Modify/Remove" in the control panel.
+ WriteRegDWORD HKLM "${REGSTR}" "NoModify" 1
+ WriteRegDWORD HKLM "${REGSTR}" "NoRepair" 1
+
+SectionEnd
+
+
+# --- Uninstaller section -----------------------------------------------------
+
+Section "Uninstall"
+
+ # Always delete the uninstaller first (yes, this really works).
+ Delete "$INSTDIR\Uninstall.exe"
+
+ # Delete the application, the application data, and related libs.
+ Delete "$INSTDIR\COPYING"
+ Delete "$INSTDIR\pulseview.exe"
+ Delete "$INSTDIR\libusb0.dll"
+ Delete "$INSTDIR\zadig.exe"
+ Delete "$INSTDIR\zadig_xp.exe"
+ Delete "$INSTDIR\python32.dll"
+ Delete "$INSTDIR\python32.zip"
+
+ # Delete all decoders and everything else in decoders/.
+ # There could be *.pyc files or __pycache__ subdirs and so on.
+ RMDir /r "$INSTDIR\decoders\*"
+
+ # Delete the firmware files.
+ RMDir /r "$INSTDIR\firmware\*"
+
+ # Delete the example *.sr files.
+ RMDir /r "$INSTDIR\examples\*"
+
+ # Delete the install directory and its sub-directories.
+ RMDir "$INSTDIR\decoders"
+ RMDir "$INSTDIR\firmware"
+ RMDir "$INSTDIR\examples"
+ RMDir "$INSTDIR"
+
+ # Delete the links from the start menu.
+ Delete "$SMPROGRAMS\sigrok\PulseView\PulseView.lnk"
+ Delete "$SMPROGRAMS\sigrok\PulseView\Uninstall.lnk"
+
+ # Delete the sub-directory in the start menu.
+ RMDir "$SMPROGRAMS\sigrok\PulseView"
+ RMDir "$SMPROGRAMS\sigrok"
+
+ # Delete the registry key(s).
+ DeleteRegKey HKLM "${REGSTR}"
+
+SectionEnd
+
+
+# --- Component selection section descriptions --------------------------------
+
+LangString DESC_Section1 ${LANG_ENGLISH} "This installs the PulseView sigrok GUI, some firmware files, the protocol decoders, some example files, and all required libraries."
+
+!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
+!insertmacro MUI_DESCRIPTION_TEXT ${Section1} $(DESC_Section1)
+!insertmacro MUI_FUNCTION_DESCRIPTION_END
+
diff --git a/doc/pulseview.1 b/doc/pulseview.1
new file mode 100644
index 0000000..b830f08
--- /dev/null
+++ b/doc/pulseview.1
@@ -0,0 +1,59 @@
+.TH PULSEVIEW 1 "May 4, 2013"
+.SH "NAME"
+PulseView \- Qt-based LA/scope/MSO GUI for sigrok
+.SH "SYNOPSIS"
+.B pulseview \fR[\fB\-lh?V\fR] [\fB\-l\fR|\fB\-\-loglevel\fR] [\fB\-h\fR|\fB\-?\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fBfile.sr\fR]
+.SH "DESCRIPTION"
+.B PulseView
+is a cross-platform Qt-based GUI for the
+.B sigrok
+software suite for test and measurement equipment such as logic analyzers,
+oscilloscopes, MSOs, and more.
+.SH "OPTIONS"
+.B PulseView
+has very few command line options, as most configuration elements are
+available from the GUI itself.
+.sp
+If the optional \fBfile.sr\fR argument is supplied, PulseView tries to open
+the specified file. It has to be in the "libsigrok session" format.
+.TP
+.B "\-l, \-\-loglevel"
+Set the libsigrok and libsigrokdecode loglevel. At the moment PulseView
+doesn't support setting the two loglevels independently. The higher the
+number, the more debug output will be printed. Valid loglevels are:
+.sp
+\fB0\fP None
+.br
+\fB1\fP Error
+.br
+\fB2\fP Warnings
+.br
+\fB3\fP Informational
+.br
+\fB4\fP Debug
+.br
+\fB5\fP Spew
+.TP
+.B "\-h, \-?, \-\-help"
+Show a help text and exit.
+.TP
+.B "\-V, \-\-version"
+Show version information and exit.
+.SH "EXIT STATUS"
+.B PulseView
+exits with 0 on success, 1 on most failures.
+.SH "SEE ALSO"
+\fBsigrok\-cli\fP(1)
+.SH "BUGS"
+Please report any bugs via Bugzilla
+.RB "(" http://sigrok.org/bugzilla ")"
+or on the sigrok\-devel mailing list
+.RB "(" sigrok\-devel at lists.souceforge.net ")."
+.SH "LICENSE"
+.B PulseView
+is covered by the GNU General Public License (GPL), version 3 or later.
+.SH "AUTHORS"
+Please see the individual source code files.
+.PP
+This manual page was written by Uwe Hermann <uwe at hermann\-uwe.de>.
+It is licensed under the terms of the GNU GPL (version 2 or later).
diff --git a/extdef.h b/extdef.h
new file mode 100644
index 0000000..8af615c
--- /dev/null
+++ b/extdef.h
@@ -0,0 +1,29 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_EXTDEF_H
+#define PULSEVIEW_EXTDEF_H
+
+#define countof(x) (sizeof(x)/sizeof(x[0]))
+
+#define begin_element(x) (&x[0])
+#define end_element(x) (&x[countof(x)])
+
+#endif // PULSEVIEW_EXTDEF_H
diff --git a/icons/application-exit.png b/icons/application-exit.png
new file mode 100644
index 0000000..28ac66f
Binary files /dev/null and b/icons/application-exit.png differ
diff --git a/icons/configure.png b/icons/configure.png
new file mode 100644
index 0000000..cc91d65
Binary files /dev/null and b/icons/configure.png differ
diff --git a/icons/decoder-delete.svg b/icons/decoder-delete.svg
new file mode 100644
index 0000000..b9db4ea
--- /dev/null
+++ b/icons/decoder-delete.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg2989" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <metadata id="metadata2994">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ <dc:title/>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g id="layer1" stroke-linejoin="miter" style="stroke-dasharray:none;" transform="translate(0,-1036.3622)" stroke="#000" stroke-linecap="square" stroke-miterlimit="4" stroke-width="2" fill="none">
+ <path id="path3007" style="stroke-dasharray:none;" d="m11,1047.4-6-6"/>
+ <path id="path2986" style="stroke-dasharray:none;" d="m5,1047.4,6-6"/>
+ </g>
+</svg>
diff --git a/icons/decoder-hidden.svg b/icons/decoder-hidden.svg
new file mode 100644
index 0000000..d462200
--- /dev/null
+++ b/icons/decoder-hidden.svg
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="22"
+ height="22"
+ id="svg2989">
+ <defs
+ id="defs2991" />
+ <metadata
+ id="metadata2994">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <path
+ d="m 19.426489,11.070664 a 11,11 0 0 1 -16.8529782,-1e-6"
+ transform="matrix(1.1867339,0,0,1.1867339,-2.0540724,-2.1379314)"
+ id="path3053"
+ style="fill:none;stroke:#000000;stroke-width:1.26397336" />
+</svg>
diff --git a/icons/decoder-shown.svg b/icons/decoder-shown.svg
new file mode 100644
index 0000000..6050bdc
--- /dev/null
+++ b/icons/decoder-shown.svg
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="22"
+ height="22"
+ id="svg2989">
+ <defs
+ id="defs2991" />
+ <metadata
+ id="metadata2994">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <path
+ d="M 13.390812,-9.3707772 A 5.87888,5.87888 0 0 1 14.476134,0.74106762"
+ transform="matrix(0.89442417,0,0,0.89442417,1.1613345,14.644558)"
+ id="path2989"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+ <path
+ d="m 12,-4 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+ transform="matrix(1.3065319,0,0,1.3065319,-3.3718505,16.219638)"
+ id="path2995"
+ style="fill:#000000;stroke:none" />
+ <path
+ d="M 10.875,6.34375 C 7.9493854,6.36765 5.0099958,7.3787791 2.59375,9.40625 2.0116039,9.8947285 1.4884785,10.417854 1,11 1.4884785,11.582146 2.0116039,12.105271 2.59375,12.59375 8.1165975,17.227969 16.365781,16.522848 21,11 18.393252,7.8933983 14.636505,6.3130214 10.875,6.34375 z"
+ id="path3053"
+ style="fill:none;stroke:#000000;stroke-width:1.5" />
+ <path
+ d="M 7.5357022,0.74972325 A 5.87888,5.87888 0 0 1 8.1948438,-9.1664619"
+ transform="matrix(0.89442417,0,0,0.89442417,1.1613345,14.644558)"
+ id="path3057"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+</svg>
diff --git a/icons/document-open.png b/icons/document-open.png
new file mode 100644
index 0000000..254a6b8
Binary files /dev/null and b/icons/document-open.png differ
diff --git a/icons/document-save-as.png b/icons/document-save-as.png
new file mode 100644
index 0000000..0f7b299
Binary files /dev/null and b/icons/document-save-as.png differ
diff --git a/icons/probes.svg b/icons/probes.svg
new file mode 100644
index 0000000..8e7e604
--- /dev/null
+++ b/icons/probes.svg
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.1"
+ width="22"
+ height="22"
+ id="svg2">
+ <metadata
+ id="metadata27">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs4">
+ <radialGradient
+ cx="15"
+ cy="20.5"
+ r="5.8534002"
+ id="radialGradient3934"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0000001,4.9521507e-8,-2.6903182e-6,0.42710347,6.4579919e-5,11.744378)">
+ <stop
+ id="stop3930"
+ style="stop-color:#888a85;stop-opacity:0.5299539"
+ offset="0" />
+ <stop
+ id="stop3932"
+ style="stop-color:#888a85;stop-opacity:0"
+ offset="1" />
+ </radialGradient>
+ <radialGradient
+ cx="15"
+ cy="20.5"
+ r="5.8534002"
+ id="radialGradient3009"
+ xlink:href="#radialGradient3934"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.8792554,3.9617209e-8,-5.0557945e-6,0.34168281,-17.188707,12.995502)" />
+ </defs>
+ <path
+ d="M 21.999281,20 A 11.00022,2.0000344 0 0 1 -0.00115994,20 11.00022,2.0000344 0 1 1 21.999281,20 z"
+ id="path3926"
+ style="fill:url(#radialGradient3009);fill-rule:evenodd" />
+ <path
+ d="m 10,8 1.5,-1.5 3,3 L 13,11 z"
+ id="path3806"
+ style="fill:#cc0000;stroke:#a40000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 1.5,19.5 2,-2"
+ id="path3808"
+ style="fill:none;stroke:#888a85;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="M 9.5,7.5 10,10 3.9999998,16 2,18.5 2.5,19 4.9999998,17 11,11 l 2.5,0.5 z"
+ id="path3804"
+ style="fill:#cc0000;stroke:#a40000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="M 16.5,0.5 15.545182,1.4548178 15.5,2.5 15,3 14.433209,2.5667912 11,6 15,10 18.5,6.5 18,6 l 0.5,-0.5 1,0 1,-1 C 19,4 17,2 16.5,0.5 z"
+ id="path3793"
+ style="fill:#cc0000;stroke:#a40000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" />
+</svg>
diff --git a/icons/sigrok-logo-notext.ico b/icons/sigrok-logo-notext.ico
new file mode 100644
index 0000000..8f1228c
Binary files /dev/null and b/icons/sigrok-logo-notext.ico differ
diff --git a/icons/sigrok-logo-notext.png b/icons/sigrok-logo-notext.png
new file mode 100644
index 0000000..cf01c3f
Binary files /dev/null and b/icons/sigrok-logo-notext.png differ
diff --git a/icons/status-green.svg b/icons/status-green.svg
new file mode 100644
index 0000000..e9ba2e8
--- /dev/null
+++ b/icons/status-green.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg id="svg4376" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="22" width="22" version="1.0" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <defs id="defs3">
+ <radialGradient id="radialGradient3774" gradientUnits="userSpaceOnUse" cy="6" cx="7.5" gradientTransform="matrix(1.0799,0,0,1.0799,-2.4045,-1.3652)" r="7.5">
+ <stop id="stop3292" stop-color="#c1f093" offset="0"/>
+ <stop id="stop3294" stop-color="#8eea34" offset=".64486"/>
+ <stop id="stop3296" stop-color="#73d216" offset="1"/>
+ </radialGradient>
+ </defs>
+ <path id="path2996" stroke-linejoin="round" d="m17 7.5a8.5 8.5 0 1 1 -17 0 8.5 8.5 0 1 1 17 0z" transform="translate(2.5,3.5)" stroke="#3b7404" stroke-linecap="round" fill="url(#radialGradient3774)"/>
+ <path id="path3766" stroke-linejoin="round" d="m16 7.5a7.5 7.5 0 1 1 -15 0 7.5 7.5 0 1 1 15 0z" stroke-opacity=".64338" transform="translate(2.5,3.5)" stroke="#fff" stroke-linecap="round" fill="none"/>
+</svg>
diff --git a/icons/status-grey.svg b/icons/status-grey.svg
new file mode 100644
index 0000000..33f75d8
--- /dev/null
+++ b/icons/status-grey.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg id="svg4376" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="22" width="22" version="1.0" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <defs id="defs3">
+ <radialGradient id="radialGradient3774" gradientUnits="userSpaceOnUse" cy="6" cx="7.5" gradientTransform="matrix(1.0799,0,0,1.0799,-2.4045,-1.3652)" r="7.5">
+ <stop id="stop3778" stop-color="#d3d7cf" offset="0"/>
+ <stop id="stop3780" stop-color="#babdb6" offset=".64486"/>
+ <stop id="stop3782" stop-color="#888a85" offset="1"/>
+ </radialGradient>
+ </defs>
+ <path id="path2996" stroke-linejoin="round" d="m17 7.5a8.5 8.5 0 1 1 -17 0 8.5 8.5 0 1 1 17 0z" transform="translate(2.5,3.5)" stroke="#555753" stroke-linecap="round" fill="url(#radialGradient3774)"/>
+ <path id="path3766" stroke-linejoin="round" d="m16 7.5a7.5 7.5 0 1 1 -15 0 7.5 7.5 0 1 1 15 0z" stroke-opacity=".64338" transform="translate(2.5,3.5)" stroke="#fff" stroke-linecap="round" fill="none"/>
+</svg>
diff --git a/icons/status-red.svg b/icons/status-red.svg
new file mode 100644
index 0000000..593b307
--- /dev/null
+++ b/icons/status-red.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg id="svg4376" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="22" width="22" version="1.0" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <defs id="defs3">
+ <radialGradient id="radialGradient3774" gradientUnits="userSpaceOnUse" cy="6" cx="7.5" gradientTransform="matrix(1.0799,0,0,1.0799,-2.4045,-1.3652)" r="7.5">
+ <stop id="stop3292" stop-color="#f24e4e" offset="0"/>
+ <stop id="stop3294" stop-color="#c00" offset=".64486"/>
+ <stop id="stop3296" stop-color="#a40000" offset="1"/>
+ </radialGradient>
+ </defs>
+ <path id="path2996" stroke-linejoin="round" d="m17 7.5a8.5 8.5 0 1 1 -17 0 8.5 8.5 0 1 1 17 0z" transform="translate(2.5,3.5)" stroke="#780000" stroke-linecap="round" fill="url(#radialGradient3774)"/>
+ <path id="path3766" stroke-linejoin="round" d="m16 7.5a7.5 7.5 0 1 1 -15 0 7.5 7.5 0 1 1 15 0z" stroke-opacity=".64338" transform="translate(2.5,3.5)" stroke="#fff" stroke-linecap="round" fill="none"/>
+</svg>
diff --git a/icons/trigger-change.svg b/icons/trigger-change.svg
new file mode 100644
index 0000000..1786d4b
--- /dev/null
+++ b/icons/trigger-change.svg
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ width="22"
+ height="22"
+ id="svg2989"
+ inkscape:version="0.48.3.1 r9886"
+ sodipodi:docname="trigger-rising.svg">
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1680"
+ inkscape:window-height="995"
+ id="namedview7"
+ showgrid="true"
+ inkscape:zoom="36.791193"
+ inkscape:cx="7.942203"
+ inkscape:cy="10.877688"
+ inkscape:window-x="1278"
+ inkscape:window-y="-3"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg2989">
+ <inkscape:grid
+ type="xygrid"
+ id="grid2984"
+ empspacing="2"
+ visible="true"
+ enabled="true"
+ snapvisiblegridlinesonly="true" />
+ </sodipodi:namedview>
+ <defs
+ id="defs2991" />
+ <metadata
+ id="metadata2994">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-1030.3622)"
+ id="layer1">
+ <path
+ d="m 4,1048.3622 5,0 4,-14 5,0"
+ id="path3007"
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:square;stroke-linejoin:miter;stroke-opacity:1"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccc" />
+ <path
+ sodipodi:nodetypes="cccc"
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:square;stroke-linejoin:miter;stroke-opacity:1"
+ id="path3012"
+ d="m 4,1034.3622 5,0 4,14 5,0" />
+ </g>
+</svg>
diff --git a/icons/trigger-falling.svg b/icons/trigger-falling.svg
new file mode 100644
index 0000000..24bd4f6
--- /dev/null
+++ b/icons/trigger-falling.svg
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="22"
+ height="22"
+ id="svg2989">
+ <defs
+ id="defs2991" />
+ <metadata
+ id="metadata2994">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-1030.3622)"
+ id="layer1">
+ <path
+ d="m 18,1048.3622 -5,0 -4,-14 -5,0"
+ id="path3007"
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:square;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/icons/trigger-high.svg b/icons/trigger-high.svg
new file mode 100644
index 0000000..d2150d8
--- /dev/null
+++ b/icons/trigger-high.svg
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="22"
+ height="22"
+ id="svg2989">
+ <defs
+ id="defs2991" />
+ <metadata
+ id="metadata2994">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-1030.3622)"
+ id="layer1">
+ <path
+ d="m 4,1034.3622 14,0"
+ id="path3007"
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:square;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/icons/trigger-low.svg b/icons/trigger-low.svg
new file mode 100644
index 0000000..aa71a98
--- /dev/null
+++ b/icons/trigger-low.svg
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="22"
+ height="22"
+ id="svg2989">
+ <defs
+ id="defs2991" />
+ <metadata
+ id="metadata2994">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-1030.3622)"
+ id="layer1">
+ <path
+ d="m 4,1048.3622 14,0"
+ id="path3007"
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:square;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/icons/trigger-none.svg b/icons/trigger-none.svg
new file mode 100644
index 0000000..196264d
--- /dev/null
+++ b/icons/trigger-none.svg
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="22"
+ height="22"
+ id="svg2989">
+ <defs
+ id="defs2991" />
+ <metadata
+ id="metadata2994">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <rect
+ width="4"
+ height="4"
+ x="9"
+ y="9"
+ id="rect3033"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none" />
+</svg>
diff --git a/icons/trigger-rising.svg b/icons/trigger-rising.svg
new file mode 100644
index 0000000..ac1550f
--- /dev/null
+++ b/icons/trigger-rising.svg
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="22"
+ height="22"
+ id="svg2989">
+ <defs
+ id="defs2991" />
+ <metadata
+ id="metadata2994">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-1030.3622)"
+ id="layer1">
+ <path
+ d="m 4,1048.3622 5,0 4,-14 5,0"
+ id="path3007"
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:square;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/icons/zoom-fit.png b/icons/zoom-fit.png
new file mode 100644
index 0000000..babe581
Binary files /dev/null and b/icons/zoom-fit.png differ
diff --git a/icons/zoom-in.png b/icons/zoom-in.png
new file mode 100644
index 0000000..55f9ae1
Binary files /dev/null and b/icons/zoom-in.png differ
diff --git a/icons/zoom-original.png b/icons/zoom-original.png
new file mode 100644
index 0000000..8ae3043
Binary files /dev/null and b/icons/zoom-original.png differ
diff --git a/icons/zoom-out.png b/icons/zoom-out.png
new file mode 100644
index 0000000..9f7627e
Binary files /dev/null and b/icons/zoom-out.png differ
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..2c6a59e
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,181 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef ENABLE_DECODE
+#include <libsigrokdecode/libsigrokdecode.h> /* First, so we avoid a _POSIX_C_SOURCE warning. */
+#endif
+
+#include <stdint.h>
+#include <libsigrok/libsigrok.h>
+
+#include <getopt.h>
+
+#include <QtGui/QApplication>
+#include <QDebug>
+
+#ifdef ENABLE_SIGNALS
+#include "signalhandler.h"
+#endif
+
+#include "pv/devicemanager.h"
+#include "pv/mainwindow.h"
+
+#include "config.h"
+
+#ifdef _WIN32
+// The static qsvg lib is required for SVG graphics/icons (on Windows).
+#include <QtPlugin>
+Q_IMPORT_PLUGIN(qsvg)
+#endif
+
+void usage()
+{
+ fprintf(stdout,
+ "Usage:\n"
+ " %s [OPTION…] [FILE] — %s\n"
+ "\n"
+ "Help Options:\n"
+ " -l, --loglevel Set libsigrok/libsigrokdecode loglevel\n"
+ " -V, --version Show release version\n"
+ " -h, -?, --help Show help option\n"
+ "\n", PV_BIN_NAME, PV_DESCRIPTION);
+}
+
+int main(int argc, char *argv[])
+{
+ int ret = 0;
+ struct sr_context *sr_ctx = NULL;
+ const char *open_file = NULL;
+
+ QApplication a(argc, argv);
+
+ // Set some application metadata
+ QApplication::setApplicationVersion(PV_VERSION_STRING);
+ QApplication::setApplicationName("PulseView");
+ QApplication::setOrganizationDomain("http://www.sigrok.org");
+
+ // Parse arguments
+ while (1) {
+ static const struct option long_options[] = {
+ {"loglevel", required_argument, 0, 'l'},
+ {"version", no_argument, 0, 'V'},
+ {"help", no_argument, 0, 'h'},
+ {0, 0, 0, 0}
+ };
+
+ const int c = getopt_long(argc, argv,
+ "l:Vh?", long_options, NULL);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'l':
+ {
+ const int loglevel = atoi(optarg);
+ sr_log_loglevel_set(loglevel);
+
+#ifdef ENABLE_DECODE
+ srd_log_loglevel_set(loglevel);
+#endif
+
+ break;
+ }
+
+ case 'V':
+ // Print version info
+ fprintf(stdout, "%s %s\n", PV_TITLE, PV_VERSION_STRING);
+ return 0;
+
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ }
+ }
+
+ if (argc - optind > 1) {
+ fprintf(stderr, "Only one file can be openened.\n");
+ return 1;
+ } else if (argc - optind == 1)
+ open_file = argv[argc - 1];
+
+ // Initialise libsigrok
+ if (sr_init(&sr_ctx) != SR_OK) {
+ qDebug() << "ERROR: libsigrok init failed.";
+ return 1;
+ }
+
+ do {
+
+#ifdef ENABLE_DECODE
+ // Initialise libsigrokdecode
+ if (srd_init(NULL) != SRD_OK) {
+ qDebug() << "ERROR: libsigrokdecode init failed.";
+ break;
+ }
+
+ // Load the protocol decoders
+ srd_decoder_load_all();
+#endif
+
+ try {
+ // Create the device manager, initialise the drivers
+ pv::DeviceManager device_manager(sr_ctx);
+
+ // Initialise the main window
+ pv::MainWindow w(device_manager, open_file);
+ w.show();
+
+#ifdef ENABLE_SIGNALS
+ if(SignalHandler::prepare_signals()) {
+ SignalHandler *const handler =
+ new SignalHandler(&w);
+ QObject::connect(handler,
+ SIGNAL(int_received()),
+ &w, SLOT(close()));
+ QObject::connect(handler,
+ SIGNAL(term_received()),
+ &w, SLOT(close()));
+ } else {
+ qWarning() <<
+ "Could not prepare signal handler.";
+ }
+#endif
+
+ // Run the application
+ ret = a.exec();
+
+ } catch(std::exception e) {
+ qDebug() << e.what();
+ }
+
+#ifdef ENABLE_DECODE
+ // Destroy libsigrokdecode
+ srd_exit();
+#endif
+
+ } while (0);
+
+ // Destroy libsigrok
+ if (sr_ctx)
+ sr_exit(sr_ctx);
+
+ return ret;
+}
diff --git a/pulseview.qrc b/pulseview.qrc
new file mode 100644
index 0000000..98edc39
--- /dev/null
+++ b/pulseview.qrc
@@ -0,0 +1,26 @@
+<RCC>
+ <qresource prefix="/" >
+ <file>icons/application-exit.png</file>
+ <file>icons/configure.png</file>
+ <file>icons/decoder-delete.svg</file>
+ <file>icons/decoder-hidden.svg</file>
+ <file>icons/decoder-shown.svg</file>
+ <file>icons/document-open.png</file>
+ <file>icons/document-save-as.png</file>
+ <file>icons/probes.svg</file>
+ <file>icons/sigrok-logo-notext.png</file>
+ <file>icons/status-green.svg</file>
+ <file>icons/status-grey.svg</file>
+ <file>icons/status-red.svg</file>
+ <file>icons/trigger-change.svg</file>
+ <file>icons/trigger-falling.svg</file>
+ <file>icons/trigger-high.svg</file>
+ <file>icons/trigger-low.svg</file>
+ <file>icons/trigger-none.svg</file>
+ <file>icons/trigger-rising.svg</file>
+ <file>icons/zoom-fit.png</file>
+ <file>icons/zoom-in.png</file>
+ <file>icons/zoom-original.png</file>
+ <file>icons/zoom-out.png</file>
+ </qresource>
+</RCC>
diff --git a/pulseviewico.rc b/pulseviewico.rc
new file mode 100644
index 0000000..4ba6fd0
--- /dev/null
+++ b/pulseviewico.rc
@@ -0,0 +1 @@
+IDI_ICON1 ICON DISCARDABLE "icons/sigrok-logo-notext.ico"
diff --git a/pv/data/analog.cpp b/pv/data/analog.cpp
new file mode 100644
index 0000000..21ddb5f
--- /dev/null
+++ b/pv/data/analog.cpp
@@ -0,0 +1,64 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <boost/foreach.hpp>
+
+#include "analog.h"
+#include "analogsnapshot.h"
+
+using boost::shared_ptr;
+using std::deque;
+using std::max;
+
+namespace pv {
+namespace data {
+
+Analog::Analog() :
+ SignalData()
+{
+}
+
+void Analog::push_snapshot(shared_ptr<AnalogSnapshot> &snapshot)
+{
+ _snapshots.push_front(snapshot);
+}
+
+deque< shared_ptr<AnalogSnapshot> >& Analog::get_snapshots()
+{
+ return _snapshots;
+}
+
+void Analog::clear()
+{
+ _snapshots.clear();
+}
+
+uint64_t Analog::get_max_sample_count() const
+{
+ uint64_t l = 0;
+ BOOST_FOREACH(const boost::shared_ptr<AnalogSnapshot> s, _snapshots) {
+ assert(s);
+ l = max(l, s->get_sample_count());
+ }
+ return l;
+}
+
+} // namespace data
+} // namespace pv
diff --git a/pv/data/analog.h b/pv/data/analog.h
new file mode 100644
index 0000000..42e3167
--- /dev/null
+++ b/pv/data/analog.h
@@ -0,0 +1,56 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_DATA_ANALOG_H
+#define PULSEVIEW_PV_DATA_ANALOG_H
+
+#include "signaldata.h"
+
+#include <boost/shared_ptr.hpp>
+#include <deque>
+
+namespace pv {
+namespace data {
+
+class AnalogSnapshot;
+
+class Analog : public SignalData
+{
+public:
+ Analog();
+
+ void push_snapshot(
+ boost::shared_ptr<AnalogSnapshot> &snapshot);
+
+ std::deque< boost::shared_ptr<AnalogSnapshot> >&
+ get_snapshots();
+
+ void clear();
+
+ uint64_t get_max_sample_count() const;
+
+private:
+ std::deque< boost::shared_ptr<AnalogSnapshot> > _snapshots;
+};
+
+} // namespace data
+} // namespace pv
+
+#endif // PULSEVIEW_PV_DATA_ANALOG_H
diff --git a/pv/data/analogsnapshot.cpp b/pv/data/analogsnapshot.cpp
new file mode 100644
index 0000000..4d4f5d6
--- /dev/null
+++ b/pv/data/analogsnapshot.cpp
@@ -0,0 +1,215 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <extdef.h>
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include <algorithm>
+
+#include <boost/foreach.hpp>
+
+#include "analogsnapshot.h"
+
+using boost::lock_guard;
+using boost::recursive_mutex;
+using std::max;
+using std::max_element;
+using std::min;
+using std::min_element;
+
+namespace pv {
+namespace data {
+
+const int AnalogSnapshot::EnvelopeScalePower = 4;
+const int AnalogSnapshot::EnvelopeScaleFactor = 1 << EnvelopeScalePower;
+const float AnalogSnapshot::LogEnvelopeScaleFactor =
+ logf(EnvelopeScaleFactor);
+const uint64_t AnalogSnapshot::EnvelopeDataUnit = 64*1024; // bytes
+
+AnalogSnapshot::AnalogSnapshot(const uint64_t expected_num_samples) :
+ Snapshot(sizeof(float))
+{
+ set_capacity(expected_num_samples);
+
+ lock_guard<recursive_mutex> lock(_mutex);
+ memset(_envelope_levels, 0, sizeof(_envelope_levels));
+}
+
+AnalogSnapshot::~AnalogSnapshot()
+{
+ lock_guard<recursive_mutex> lock(_mutex);
+ BOOST_FOREACH(Envelope &e, _envelope_levels)
+ free(e.samples);
+}
+
+void AnalogSnapshot::append_interleaved_samples(const float *data,
+ size_t sample_count, size_t stride)
+{
+ assert(_unit_size == sizeof(float));
+
+ lock_guard<recursive_mutex> lock(_mutex);
+
+ _data = realloc(_data, (_sample_count + sample_count) * sizeof(float));
+
+ float *dst = (float*)_data + _sample_count;
+ const float *dst_end = dst + sample_count;
+ while (dst != dst_end)
+ {
+ *dst++ = *data;
+ data += stride;
+ }
+
+ _sample_count += sample_count;
+
+ // Generate the first mip-map from the data
+ append_payload_to_envelope_levels();
+}
+
+const float* AnalogSnapshot::get_samples(
+ int64_t start_sample, int64_t end_sample) const
+{
+ assert(start_sample >= 0);
+ assert(start_sample < (int64_t)_sample_count);
+ assert(end_sample >= 0);
+ assert(end_sample < (int64_t)_sample_count);
+ assert(start_sample <= end_sample);
+
+ lock_guard<recursive_mutex> lock(_mutex);
+
+ float *const data = new float[end_sample - start_sample];
+ memcpy(data, (float*)_data + start_sample, sizeof(float) *
+ (end_sample - start_sample));
+ return data;
+}
+
+void AnalogSnapshot::get_envelope_section(EnvelopeSection &s,
+ uint64_t start, uint64_t end, float min_length) const
+{
+ assert(end <= get_sample_count());
+ assert(start <= end);
+ assert(min_length > 0);
+
+ lock_guard<recursive_mutex> lock(_mutex);
+
+ const unsigned int min_level = max((int)floorf(logf(min_length) /
+ LogEnvelopeScaleFactor) - 1, 0);
+ const unsigned int scale_power = (min_level + 1) *
+ EnvelopeScalePower;
+ start >>= scale_power;
+ end >>= scale_power;
+
+ s.start = start << scale_power;
+ s.scale = 1 << scale_power;
+ s.length = end - start;
+ s.samples = new EnvelopeSample[s.length];
+ memcpy(s.samples, _envelope_levels[min_level].samples + start,
+ s.length * sizeof(EnvelopeSample));
+}
+
+void AnalogSnapshot::reallocate_envelope(Envelope &e)
+{
+ const uint64_t new_data_length = ((e.length + EnvelopeDataUnit - 1) /
+ EnvelopeDataUnit) * EnvelopeDataUnit;
+ if (new_data_length > e.data_length)
+ {
+ e.data_length = new_data_length;
+ e.samples = (EnvelopeSample*)realloc(e.samples,
+ new_data_length * sizeof(EnvelopeSample));
+ }
+}
+
+void AnalogSnapshot::append_payload_to_envelope_levels()
+{
+ Envelope &e0 = _envelope_levels[0];
+ uint64_t prev_length;
+ EnvelopeSample *dest_ptr;
+
+ // Expand the data buffer to fit the new samples
+ prev_length = e0.length;
+ e0.length = _sample_count / EnvelopeScaleFactor;
+
+ // Break off if there are no new samples to compute
+ if (e0.length == prev_length)
+ return;
+
+ reallocate_envelope(e0);
+
+ dest_ptr = e0.samples + prev_length;
+
+ // Iterate through the samples to populate the first level mipmap
+ const float *const end_src_ptr = (float*)_data +
+ e0.length * EnvelopeScaleFactor;
+ for (const float *src_ptr = (float*)_data +
+ prev_length * EnvelopeScaleFactor;
+ src_ptr < end_src_ptr; src_ptr += EnvelopeScaleFactor)
+ {
+ const EnvelopeSample sub_sample = {
+ *min_element(src_ptr, src_ptr + EnvelopeScaleFactor),
+ *max_element(src_ptr, src_ptr + EnvelopeScaleFactor),
+ };
+
+ *dest_ptr++ = sub_sample;
+ }
+
+ // Compute higher level mipmaps
+ for (unsigned int level = 1; level < ScaleStepCount; level++)
+ {
+ Envelope &e = _envelope_levels[level];
+ const Envelope &el = _envelope_levels[level-1];
+
+ // Expand the data buffer to fit the new samples
+ prev_length = e.length;
+ e.length = el.length / EnvelopeScaleFactor;
+
+ // Break off if there are no more samples to computed
+ if (e.length == prev_length)
+ break;
+
+ reallocate_envelope(e);
+
+ // Subsample the level lower level
+ const EnvelopeSample *src_ptr =
+ el.samples + prev_length * EnvelopeScaleFactor;
+ const EnvelopeSample *const end_dest_ptr = e.samples + e.length;
+ for (dest_ptr = e.samples + prev_length;
+ dest_ptr < end_dest_ptr; dest_ptr++)
+ {
+ const EnvelopeSample *const end_src_ptr =
+ src_ptr + EnvelopeScaleFactor;
+
+ EnvelopeSample sub_sample = *src_ptr++;
+ while (src_ptr < end_src_ptr)
+ {
+ sub_sample.min = min(sub_sample.min, src_ptr->min);
+ sub_sample.max = max(sub_sample.max, src_ptr->max);
+ src_ptr++;
+ }
+
+ *dest_ptr = sub_sample;
+ }
+ }
+}
+
+} // namespace data
+} // namespace pv
diff --git a/pv/data/analogsnapshot.h b/pv/data/analogsnapshot.h
new file mode 100644
index 0000000..b60c2ce
--- /dev/null
+++ b/pv/data/analogsnapshot.h
@@ -0,0 +1,96 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_DATA_ANALOGSNAPSHOT_H
+#define PULSEVIEW_PV_DATA_ANALOGSNAPSHOT_H
+
+#include "snapshot.h"
+
+#include <utility>
+#include <vector>
+
+namespace AnalogSnapshotTest {
+class Basic;
+}
+
+namespace pv {
+namespace data {
+
+class AnalogSnapshot : public Snapshot
+{
+public:
+ struct EnvelopeSample
+ {
+ float min;
+ float max;
+ };
+
+ struct EnvelopeSection
+ {
+ uint64_t start;
+ unsigned int scale;
+ uint64_t length;
+ EnvelopeSample *samples;
+ };
+
+private:
+ struct Envelope
+ {
+ uint64_t length;
+ uint64_t data_length;
+ EnvelopeSample *samples;
+ };
+
+private:
+ static const unsigned int ScaleStepCount = 10;
+ static const int EnvelopeScalePower;
+ static const int EnvelopeScaleFactor;
+ static const float LogEnvelopeScaleFactor;
+ static const uint64_t EnvelopeDataUnit;
+
+public:
+ AnalogSnapshot(uint64_t expected_num_samples = 0);
+
+ virtual ~AnalogSnapshot();
+
+ void append_interleaved_samples(const float *data,
+ size_t sample_count, size_t stride);
+
+ const float* get_samples(int64_t start_sample,
+ int64_t end_sample) const;
+
+ void get_envelope_section(EnvelopeSection &s,
+ uint64_t start, uint64_t end, float min_length) const;
+
+private:
+ void reallocate_envelope(Envelope &l);
+
+ void append_payload_to_envelope_levels();
+
+private:
+ struct Envelope _envelope_levels[ScaleStepCount];
+
+ friend class AnalogSnapshotTest::Basic;
+};
+
+} // namespace data
+} // namespace pv
+
+#endif // PULSEVIEW_PV_DATA_ANALOGSNAPSHOT_H
diff --git a/pv/data/decode/annotation.cpp b/pv/data/decode/annotation.cpp
new file mode 100644
index 0000000..b0517ae
--- /dev/null
+++ b/pv/data/decode/annotation.cpp
@@ -0,0 +1,73 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+extern "C" {
+#include <libsigrokdecode/libsigrokdecode.h>
+}
+
+#include <vector>
+
+#include "annotation.h"
+
+namespace pv {
+namespace data {
+namespace decode {
+
+Annotation::Annotation(const srd_proto_data *const pdata) :
+ _start_sample(pdata->start_sample),
+ _end_sample(pdata->end_sample)
+{
+ assert(pdata);
+ const srd_proto_data_annotation *const pda =
+ (const srd_proto_data_annotation*)pdata->data;
+ assert(pda);
+
+ _format = pda->ann_format;
+
+ const char *const *annotations = (char**)pda->ann_text;
+ while(*annotations) {
+ _annotations.push_back(QString::fromUtf8(*annotations));
+ annotations++;
+ }
+}
+
+uint64_t Annotation::start_sample() const
+{
+ return _start_sample;
+}
+
+uint64_t Annotation::end_sample() const
+{
+ return _end_sample;
+}
+
+int Annotation::format() const
+{
+ return _format;
+}
+
+const std::vector<QString>& Annotation::annotations() const
+{
+ return _annotations;
+}
+
+} // namespace decode
+} // namespace data
+} // namespace pv
diff --git a/pv/data/decode/annotation.h b/pv/data/decode/annotation.h
new file mode 100644
index 0000000..0d7fd5d
--- /dev/null
+++ b/pv/data/decode/annotation.h
@@ -0,0 +1,55 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_VIEW_DECODE_ANNOTATION_H
+#define PULSEVIEW_PV_VIEW_DECODE_ANNOTATION_H
+
+#include <stdint.h>
+
+#include <QString>
+
+struct srd_proto_data;
+
+namespace pv {
+namespace data {
+namespace decode {
+
+class Annotation
+{
+public:
+ Annotation(const srd_proto_data *const pdata);
+
+ uint64_t start_sample() const;
+ uint64_t end_sample() const;
+ int format() const;
+ const std::vector<QString>& annotations() const;
+
+private:
+ uint64_t _start_sample;
+ uint64_t _end_sample;
+ int _format;
+ std::vector<QString> _annotations;
+};
+
+} // namespace decode
+} // namespace data
+} // namespace pv
+
+#endif // PULSEVIEW_PV_VIEW_DECODE_ANNOTATION_H
diff --git a/pv/data/decode/decoder.cpp b/pv/data/decode/decoder.cpp
new file mode 100644
index 0000000..44e474b
--- /dev/null
+++ b/pv/data/decode/decoder.cpp
@@ -0,0 +1,159 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <libsigrok/libsigrok.h>
+#include <libsigrokdecode/libsigrokdecode.h>
+
+#include "decoder.h"
+
+#include <pv/view/logicsignal.h>
+
+using boost::shared_ptr;
+using std::set;
+using std::map;
+using std::string;
+
+namespace pv {
+namespace data {
+namespace decode {
+
+Decoder::Decoder(const srd_decoder *const dec) :
+ _decoder(dec),
+ _shown(true)
+{
+}
+
+Decoder::~Decoder()
+{
+ for (map<string, GVariant*>::const_iterator i = _options.begin();
+ i != _options.end(); i++)
+ g_variant_unref((*i).second);
+}
+
+const srd_decoder* Decoder::decoder() const
+{
+ return _decoder;
+}
+
+bool Decoder::shown() const
+{
+ return _shown;
+}
+
+void Decoder::show(bool show)
+{
+ _shown = show;
+}
+
+const map<const srd_channel*, shared_ptr<view::LogicSignal> >&
+Decoder::channels() const
+{
+ return _probes;
+}
+
+void Decoder::set_probes(std::map<const srd_channel*,
+ boost::shared_ptr<view::LogicSignal> > probes)
+{
+ _probes = probes;
+}
+
+const std::map<std::string, GVariant*>& Decoder::options() const
+{
+ return _options;
+}
+
+void Decoder::set_option(const char *id, GVariant *value)
+{
+ assert(value);
+ g_variant_ref(value);
+ _options[id] = value;
+}
+
+bool Decoder::have_required_probes() const
+{
+ for (GSList *l = _decoder->channels; l; l = l->next) {
+ const srd_channel *const pdch = (const srd_channel*)l->data;
+ assert(pdch);
+ if (_probes.find(pdch) == _probes.end())
+ return false;
+ }
+
+ return true;
+}
+
+set< shared_ptr<pv::data::Logic> > Decoder::get_data()
+{
+ set< shared_ptr<pv::data::Logic> > data;
+ for(map<const srd_channel*, shared_ptr<view::LogicSignal> >::
+ const_iterator i = _probes.begin();
+ i != _probes.end(); i++)
+ {
+ shared_ptr<view::LogicSignal> signal((*i).second);
+ assert(signal);
+ data.insert(signal->logic_data());
+ }
+
+ return data;
+}
+
+srd_decoder_inst* Decoder::create_decoder_inst(srd_session *session, int unit_size) const
+{
+ GHashTable *const opt_hash = g_hash_table_new_full(g_str_hash,
+ g_str_equal, g_free, (GDestroyNotify)g_variant_unref);
+
+ for (map<string, GVariant*>::const_iterator i = _options.begin();
+ i != _options.end(); i++)
+ {
+ GVariant *const value = (*i).second;
+ g_variant_ref(value);
+ g_hash_table_replace(opt_hash, (void*)g_strdup(
+ (*i).first.c_str()), value);
+ }
+
+ srd_decoder_inst *const decoder_inst = srd_inst_new(
+ session, _decoder->id, opt_hash);
+ g_hash_table_destroy(opt_hash);
+
+ if(!decoder_inst)
+ return NULL;
+
+ // Setup the probes
+ GHashTable *const probes = g_hash_table_new_full(g_str_hash,
+ g_str_equal, g_free, (GDestroyNotify)g_variant_unref);
+
+ for(map<const srd_channel*, shared_ptr<view::LogicSignal> >::
+ const_iterator i = _probes.begin();
+ i != _probes.end(); i++)
+ {
+ shared_ptr<view::LogicSignal> signal((*i).second);
+ GVariant *const gvar = g_variant_new_int32(
+ signal->probe()->index);
+ g_variant_ref_sink(gvar);
+ g_hash_table_insert(probes, (*i).first->id, gvar);
+ }
+
+ srd_inst_channel_set_all(decoder_inst, probes, unit_size);
+
+ return decoder_inst;
+}
+
+} // decode
+} // data
+} // pv
diff --git a/pv/data/decode/decoder.h b/pv/data/decode/decoder.h
new file mode 100644
index 0000000..dffefab
--- /dev/null
+++ b/pv/data/decode/decoder.h
@@ -0,0 +1,90 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_DATA_DECODE_DECODER_H
+#define PULSEVIEW_PV_DATA_DECODE_DECODER_H
+
+#include <map>
+#include <set>
+
+#include <boost/shared_ptr.hpp>
+
+#include <glib.h>
+
+struct srd_decoder;
+struct srd_decoder_inst;
+struct srd_channel;
+struct srd_session;
+
+namespace pv {
+
+namespace view {
+class LogicSignal;
+}
+
+namespace data {
+
+class Logic;
+
+namespace decode {
+
+class Decoder
+{
+public:
+ Decoder(const srd_decoder *const decoder);
+
+ virtual ~Decoder();
+
+ const srd_decoder* decoder() const;
+
+ bool shown() const;
+ void show(bool show = true);
+
+ const std::map<const srd_channel*,
+ boost::shared_ptr<view::LogicSignal> >& channels() const;
+ void set_probes(std::map<const srd_channel*,
+ boost::shared_ptr<view::LogicSignal> > probes);
+
+ const std::map<std::string, GVariant*>& options() const;
+
+ void set_option(const char *id, GVariant *value);
+
+ bool have_required_probes() const;
+
+ srd_decoder_inst* create_decoder_inst(
+ srd_session *session, int unit_size) const;
+
+ std::set< boost::shared_ptr<pv::data::Logic> > get_data();
+
+private:
+ const srd_decoder *const _decoder;
+
+ bool _shown;
+
+ std::map<const srd_channel*, boost::shared_ptr<pv::view::LogicSignal> >
+ _probes;
+ std::map<std::string, GVariant*> _options;
+};
+
+} // namespace decode
+} // namespace data
+} // namespace pv
+
+#endif // PULSEVIEW_PV_DATA_DECODE_DECODER_H
diff --git a/pv/data/decode/row.cpp b/pv/data/decode/row.cpp
new file mode 100644
index 0000000..2aabf0f
--- /dev/null
+++ b/pv/data/decode/row.cpp
@@ -0,0 +1,72 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2014 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "row.h"
+
+#include <libsigrokdecode/libsigrokdecode.h>
+
+namespace pv {
+namespace data {
+namespace decode {
+
+Row::Row() :
+ _decoder(NULL),
+ _row(NULL)
+{
+}
+
+Row::Row(const srd_decoder *decoder, const srd_decoder_annotation_row *row) :
+ _decoder(decoder),
+ _row(row)
+{
+}
+
+const srd_decoder* Row::decoder() const
+{
+ return _decoder;
+}
+
+const srd_decoder_annotation_row* Row::row() const
+{
+ return _row;
+}
+
+const QString Row::title() const
+{
+ if (_decoder && _decoder->name && _row && _row->desc)
+ return QString("%1: %2")
+ .arg(QString::fromUtf8(_decoder->name))
+ .arg(QString::fromUtf8(_row->desc));
+ if (_decoder && _decoder->name)
+ return QString::fromUtf8(_decoder->name);
+ if (_row && _row->desc)
+ return QString::fromUtf8(_row->desc);
+ return QString();
+}
+
+bool Row::operator<(const Row &other) const
+{
+ return (_decoder < other._decoder) ||
+ (_decoder == other._decoder && _row < other._row);
+}
+
+} // decode
+} // data
+} // pv
diff --git a/pv/data/decode/row.h b/pv/data/decode/row.h
new file mode 100644
index 0000000..6c05ac4
--- /dev/null
+++ b/pv/data/decode/row.h
@@ -0,0 +1,59 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2014 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_DATA_DECODE_ROW_H
+#define PULSEVIEW_PV_DATA_DECODE_ROW_H
+
+#include <vector>
+
+#include "annotation.h"
+
+struct srd_decoder;
+struct srd_decoder_annotation_row;
+
+namespace pv {
+namespace data {
+namespace decode {
+
+class Row
+{
+public:
+ Row();
+
+ Row(const srd_decoder *decoder,
+ const srd_decoder_annotation_row *row = NULL);
+
+ const srd_decoder* decoder() const;
+ const srd_decoder_annotation_row* row() const;
+
+ const QString title() const;
+
+ bool operator<(const Row &other) const;
+
+private:
+ const srd_decoder *_decoder;
+ const srd_decoder_annotation_row *_row;
+};
+
+} // decode
+} // data
+} // pv
+
+#endif // PULSEVIEW_PV_DATA_DECODE_ROW_H
diff --git a/pv/data/decode/rowdata.cpp b/pv/data/decode/rowdata.cpp
new file mode 100644
index 0000000..87da27c
--- /dev/null
+++ b/pv/data/decode/rowdata.cpp
@@ -0,0 +1,58 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2014 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "rowdata.h"
+
+using std::vector;
+
+namespace pv {
+namespace data {
+namespace decode {
+
+RowData::RowData()
+{
+}
+
+uint64_t RowData::get_max_sample() const
+{
+ if (_annotations.empty())
+ return 0;
+ return _annotations.back().end_sample();
+}
+
+void RowData::get_annotation_subset(
+ vector<pv::data::decode::Annotation> &dest,
+ uint64_t start_sample, uint64_t end_sample) const
+{
+ for (vector<Annotation>::const_iterator i = _annotations.begin();
+ i != _annotations.end(); i++)
+ if ((*i).end_sample() > start_sample &&
+ (*i).start_sample() <= end_sample)
+ dest.push_back(*i);
+}
+
+void RowData::push_annotation(const Annotation &a)
+{
+ _annotations.push_back(a);
+}
+
+} // decode
+} // data
+} // pv
diff --git a/pv/data/decode/rowdata.h b/pv/data/decode/rowdata.h
new file mode 100644
index 0000000..01c65b6
--- /dev/null
+++ b/pv/data/decode/rowdata.h
@@ -0,0 +1,57 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2014 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_DATA_DECODE_ROWDATA_H
+#define PULSEVIEW_PV_DATA_DECODE_ROWDATA_H
+
+#include <vector>
+
+#include "annotation.h"
+
+namespace pv {
+namespace data {
+namespace decode {
+
+class RowData
+{
+public:
+ RowData();
+
+public:
+ uint64_t get_max_sample() const;
+
+ /**
+ * Extracts sorted annotations between two period into a vector.
+ */
+ void get_annotation_subset(
+ std::vector<pv::data::decode::Annotation> &dest,
+ uint64_t start_sample, uint64_t end_sample) const;
+
+ void push_annotation(const Annotation &a);
+
+private:
+ std::vector<Annotation> _annotations;
+};
+
+}
+} // data
+} // pv
+
+#endif // PULSEVIEW_PV_DATA_DECODE_ROWDATA_H
diff --git a/pv/data/decoderstack.cpp b/pv/data/decoderstack.cpp
new file mode 100644
index 0000000..c3bf26d
--- /dev/null
+++ b/pv/data/decoderstack.cpp
@@ -0,0 +1,450 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <libsigrokdecode/libsigrokdecode.h>
+
+#include <boost/foreach.hpp>
+#include <boost/thread/thread.hpp>
+
+#include <stdexcept>
+
+#include <QDebug>
+
+#include "decoderstack.h"
+
+#include <pv/data/logic.h>
+#include <pv/data/logicsnapshot.h>
+#include <pv/data/decode/decoder.h>
+#include <pv/data/decode/annotation.h>
+#include <pv/sigsession.h>
+#include <pv/view/logicsignal.h>
+
+using boost::lock_guard;
+using boost::mutex;
+using boost::optional;
+using boost::shared_ptr;
+using boost::unique_lock;
+using std::deque;
+using std::make_pair;
+using std::max;
+using std::min;
+using std::list;
+using std::map;
+using std::pair;
+using std::vector;
+
+using namespace pv::data::decode;
+
+namespace pv {
+namespace data {
+
+const double DecoderStack::DecodeMargin = 1.0;
+const double DecoderStack::DecodeThreshold = 0.2;
+const int64_t DecoderStack::DecodeChunkLength = 4096;
+const unsigned int DecoderStack::DecodeNotifyPeriod = 65536;
+
+mutex DecoderStack::_global_decode_mutex;
+
+DecoderStack::DecoderStack(pv::SigSession &session,
+ const srd_decoder *const dec) :
+ _session(session),
+ _sample_count(0),
+ _frame_complete(false),
+ _samples_decoded(0)
+{
+ connect(&_session, SIGNAL(frame_began()),
+ this, SLOT(on_new_frame()));
+ connect(&_session, SIGNAL(data_received()),
+ this, SLOT(on_data_received()));
+ connect(&_session, SIGNAL(frame_ended()),
+ this, SLOT(on_frame_ended()));
+
+ _stack.push_back(shared_ptr<decode::Decoder>(
+ new decode::Decoder(dec)));
+}
+
+DecoderStack::~DecoderStack()
+{
+ if (_decode_thread.joinable()) {
+ _decode_thread.interrupt();
+ _decode_thread.join();
+ }
+}
+
+const std::list< boost::shared_ptr<decode::Decoder> >&
+DecoderStack::stack() const
+{
+ return _stack;
+}
+
+void DecoderStack::push(boost::shared_ptr<decode::Decoder> decoder)
+{
+ assert(decoder);
+ _stack.push_back(decoder);
+}
+
+void DecoderStack::remove(int index)
+{
+ assert(index >= 0);
+ assert(index < (int)_stack.size());
+
+ // Find the decoder in the stack
+ list< shared_ptr<Decoder> >::iterator iter = _stack.begin();
+ for(int i = 0; i < index; i++, iter++)
+ assert(iter != _stack.end());
+
+ // Delete the element
+ _stack.erase(iter);
+}
+
+int64_t DecoderStack::samples_decoded() const
+{
+ lock_guard<mutex> decode_lock(_output_mutex);
+ return _samples_decoded;
+}
+
+std::vector<Row> DecoderStack::get_visible_rows() const
+{
+ lock_guard<mutex> lock(_output_mutex);
+
+ vector<Row> rows;
+
+ BOOST_FOREACH (const shared_ptr<decode::Decoder> &dec, _stack)
+ {
+ assert(dec);
+ if (!dec->shown())
+ continue;
+
+ const srd_decoder *const decc = dec->decoder();
+ assert(dec->decoder());
+
+ // Add a row for the decoder if it doesn't have a row list
+ if (!decc->annotation_rows)
+ rows.push_back(Row(decc));
+
+ // Add the decoder rows
+ for (const GSList *l = decc->annotation_rows; l; l = l->next)
+ {
+ const srd_decoder_annotation_row *const ann_row =
+ (srd_decoder_annotation_row *)l->data;
+ assert(ann_row);
+ rows.push_back(Row(decc, ann_row));
+ }
+ }
+
+ return rows;
+}
+
+void DecoderStack::get_annotation_subset(
+ std::vector<pv::data::decode::Annotation> &dest,
+ const Row &row, uint64_t start_sample,
+ uint64_t end_sample) const
+{
+ lock_guard<mutex> lock(_output_mutex);
+
+ std::map<const Row, decode::RowData>::const_iterator iter =
+ _rows.find(row);
+ if (iter != _rows.end())
+ (*iter).second.get_annotation_subset(dest,
+ start_sample, end_sample);
+}
+
+QString DecoderStack::error_message()
+{
+ lock_guard<mutex> lock(_output_mutex);
+ return _error_message;
+}
+
+void DecoderStack::clear()
+{
+ _sample_count = 0;
+ _frame_complete = false;
+ _samples_decoded = 0;
+ _error_message = QString();
+ _rows.clear();
+ _class_rows.clear();
+}
+
+void DecoderStack::begin_decode()
+{
+ shared_ptr<pv::view::LogicSignal> logic_signal;
+ shared_ptr<pv::data::Logic> data;
+
+ if (_decode_thread.joinable()) {
+ _decode_thread.interrupt();
+ _decode_thread.join();
+ }
+
+ clear();
+
+ // Check that all decoders have the required channels
+ BOOST_FOREACH(const shared_ptr<decode::Decoder> &dec, _stack)
+ if (!dec->have_required_probes()) {
+ _error_message = tr("One or more required channels "
+ "have not been specified");
+ return;
+ }
+
+ // Add classes
+ BOOST_FOREACH (const shared_ptr<decode::Decoder> &dec, _stack)
+ {
+ assert(dec);
+ const srd_decoder *const decc = dec->decoder();
+ assert(dec->decoder());
+
+ // Add a row for the decoder if it doesn't have a row list
+ if (!decc->annotation_rows)
+ _rows[Row(decc)] = decode::RowData();
+
+ // Add the decoder rows
+ for (const GSList *l = decc->annotation_rows; l; l = l->next)
+ {
+ const srd_decoder_annotation_row *const ann_row =
+ (srd_decoder_annotation_row *)l->data;
+ assert(ann_row);
+
+ const Row row(decc, ann_row);
+
+ // Add a new empty row data object
+ _rows[row] = decode::RowData();
+
+ // Map out all the classes
+ for (const GSList *ll = ann_row->ann_classes;
+ ll; ll = ll->next)
+ _class_rows[make_pair(decc,
+ GPOINTER_TO_INT(ll->data))] = row;
+ }
+ }
+
+ // We get the logic data of the first channel in the list.
+ // This works because we are currently assuming all
+ // LogicSignals have the same data/snapshot
+ BOOST_FOREACH (const shared_ptr<decode::Decoder> &dec, _stack)
+ if (dec && !dec->channels().empty() &&
+ ((logic_signal = (*dec->channels().begin()).second)) &&
+ ((data = logic_signal->logic_data())))
+ break;
+
+ if (!data)
+ return;
+
+ // Check we have a snapshot of data
+ const deque< shared_ptr<pv::data::LogicSnapshot> > &snapshots =
+ data->get_snapshots();
+ if (snapshots.empty())
+ return;
+ _snapshot = snapshots.front();
+
+ // Get the samplerate and start time
+ _start_time = data->get_start_time();
+ _samplerate = data->samplerate();
+ if (_samplerate == 0.0)
+ _samplerate = 1.0;
+
+ _decode_thread = boost::thread(&DecoderStack::decode_proc, this);
+}
+
+uint64_t DecoderStack::get_max_sample_count() const
+{
+ uint64_t max_sample_count = 0;
+
+ for (map<const Row, RowData>::const_iterator i = _rows.begin();
+ i != _rows.end(); i++)
+ max_sample_count = max(max_sample_count,
+ (*i).second.get_max_sample());
+
+ return max_sample_count;
+}
+
+optional<int64_t> DecoderStack::wait_for_data() const
+{
+ unique_lock<mutex> input_lock(_input_mutex);
+ while(!boost::this_thread::interruption_requested() &&
+ !_frame_complete && _samples_decoded >= _sample_count)
+ _input_cond.wait(input_lock);
+ return boost::make_optional(
+ !boost::this_thread::interruption_requested() &&
+ (_samples_decoded < _sample_count || !_frame_complete),
+ _sample_count);
+}
+
+void DecoderStack::decode_data(
+ const int64_t sample_count, const unsigned int unit_size,
+ srd_session *const session)
+{
+ uint8_t chunk[DecodeChunkLength];
+
+ const unsigned int chunk_sample_count =
+ DecodeChunkLength / _snapshot->unit_size();
+
+ for (int64_t i = 0;
+ !boost::this_thread::interruption_requested() &&
+ i < sample_count;
+ i += chunk_sample_count)
+ {
+ lock_guard<mutex> decode_lock(_global_decode_mutex);
+
+ const int64_t chunk_end = min(
+ i + chunk_sample_count, sample_count);
+ _snapshot->get_samples(chunk, i, chunk_end);
+
+ if (srd_session_send(session, i, i + sample_count, chunk,
+ (chunk_end - i) * unit_size) != SRD_OK) {
+ _error_message = tr("Decoder reported an error");
+ break;
+ }
+
+ {
+ lock_guard<mutex> lock(_output_mutex);
+ _samples_decoded = chunk_end;
+ }
+
+ if (i % DecodeNotifyPeriod == 0)
+ new_decode_data();
+ }
+
+ new_decode_data();
+}
+
+void DecoderStack::decode_proc()
+{
+ optional<int64_t> sample_count;
+ srd_session *session;
+ srd_decoder_inst *prev_di = NULL;
+
+ assert(_snapshot);
+
+ // Create the session
+ srd_session_new(&session);
+ assert(session);
+
+ // Create the decoders
+ const unsigned int unit_size = _snapshot->unit_size();
+
+ BOOST_FOREACH(const shared_ptr<decode::Decoder> &dec, _stack)
+ {
+ srd_decoder_inst *const di = dec->create_decoder_inst(session, unit_size);
+
+ if (!di)
+ {
+ _error_message = tr("Failed to create decoder instance");
+ srd_session_destroy(session);
+ return;
+ }
+
+ if (prev_di)
+ srd_inst_stack (session, prev_di, di);
+
+ prev_di = di;
+ }
+
+ // Get the intial sample count
+ {
+ unique_lock<mutex> input_lock(_input_mutex);
+ sample_count = _sample_count = _snapshot->get_sample_count();
+ }
+
+ // Start the session
+ srd_session_metadata_set(session, SRD_CONF_SAMPLERATE,
+ g_variant_new_uint64((uint64_t)_samplerate));
+
+ srd_pd_output_callback_add(session, SRD_OUTPUT_ANN,
+ DecoderStack::annotation_callback, this);
+
+ srd_session_start(session);
+
+ do {
+ decode_data(*sample_count, unit_size, session);
+ } while(_error_message.isEmpty() && (sample_count = wait_for_data()));
+
+ // Destroy the session
+ srd_session_destroy(session);
+}
+
+void DecoderStack::annotation_callback(srd_proto_data *pdata, void *decoder)
+{
+ assert(pdata);
+ assert(decoder);
+
+ DecoderStack *const d = (DecoderStack*)decoder;
+ assert(d);
+
+ lock_guard<mutex> lock(d->_output_mutex);
+
+ const Annotation a(pdata);
+
+ // Find the row
+ assert(pdata->pdo);
+ assert(pdata->pdo->di);
+ const srd_decoder *const decc = pdata->pdo->di->decoder;
+ assert(decc);
+
+ map<const Row, decode::RowData>::iterator row_iter = d->_rows.end();
+
+ // Try looking up the sub-row of this class
+ const map<pair<const srd_decoder*, int>, Row>::const_iterator r =
+ d->_class_rows.find(make_pair(decc, a.format()));
+ if (r != d->_class_rows.end())
+ row_iter = d->_rows.find((*r).second);
+ else
+ {
+ // Failing that, use the decoder as a key
+ row_iter = d->_rows.find(Row(decc));
+ }
+
+ assert(row_iter != d->_rows.end());
+ if (row_iter == d->_rows.end()) {
+ qDebug() << "Unexpected annotation: decoder = " << decc <<
+ ", format = " << a.format();
+ assert(0);
+ return;
+ }
+
+ // Add the annotation
+ (*row_iter).second.push_annotation(a);
+}
+
+void DecoderStack::on_new_frame()
+{
+ begin_decode();
+}
+
+void DecoderStack::on_data_received()
+{
+ {
+ unique_lock<mutex> lock(_input_mutex);
+ if (_snapshot)
+ _sample_count = _snapshot->get_sample_count();
+ }
+ _input_cond.notify_one();
+}
+
+void DecoderStack::on_frame_ended()
+{
+ {
+ unique_lock<mutex> lock(_input_mutex);
+ if (_snapshot)
+ _frame_complete = true;
+ }
+ _input_cond.notify_one();
+}
+
+} // namespace data
+} // namespace pv
diff --git a/pv/data/decoderstack.h b/pv/data/decoderstack.h
new file mode 100644
index 0000000..2eeaf1c
--- /dev/null
+++ b/pv/data/decoderstack.h
@@ -0,0 +1,165 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_DATA_DECODERSTACK_H
+#define PULSEVIEW_PV_DATA_DECODERSTACK_H
+
+#include "signaldata.h"
+
+#include <list>
+
+#include <boost/optional.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/thread.hpp>
+
+#include <QObject>
+#include <QString>
+
+#include <pv/data/decode/row.h>
+#include <pv/data/decode/rowdata.h>
+
+struct srd_decoder;
+struct srd_decoder_annotation_row;
+struct srd_channel;
+struct srd_proto_data;
+struct srd_session;
+
+namespace DecoderStackTest {
+class TwoDecoderStack;
+}
+
+namespace pv {
+
+class SigSession;
+
+namespace view {
+class LogicSignal;
+}
+
+namespace data {
+
+class LogicSnapshot;
+
+namespace decode {
+class Annotation;
+class Decoder;
+}
+
+class Logic;
+
+class DecoderStack : public QObject, public SignalData
+{
+ Q_OBJECT
+
+private:
+ static const double DecodeMargin;
+ static const double DecodeThreshold;
+ static const int64_t DecodeChunkLength;
+ static const unsigned int DecodeNotifyPeriod;
+
+public:
+ DecoderStack(pv::SigSession &_session,
+ const srd_decoder *const decoder);
+
+ virtual ~DecoderStack();
+
+ const std::list< boost::shared_ptr<decode::Decoder> >& stack() const;
+ void push(boost::shared_ptr<decode::Decoder> decoder);
+ void remove(int index);
+
+ int64_t samples_decoded() const;
+
+ std::vector<decode::Row> get_visible_rows() const;
+
+ /**
+ * Extracts sorted annotations between two period into a vector.
+ */
+ void get_annotation_subset(
+ std::vector<pv::data::decode::Annotation> &dest,
+ const decode::Row &row, uint64_t start_sample,
+ uint64_t end_sample) const;
+
+ QString error_message();
+
+ void clear();
+
+ uint64_t get_max_sample_count() const;
+
+ void begin_decode();
+
+private:
+ boost::optional<int64_t> wait_for_data() const;
+
+ void decode_data(const int64_t sample_count,
+ const unsigned int unit_size, srd_session *const session);
+
+ void decode_proc();
+
+ static void annotation_callback(srd_proto_data *pdata,
+ void *decoder);
+
+private slots:
+ void on_new_frame();
+
+ void on_data_received();
+
+ void on_frame_ended();
+
+signals:
+ void new_decode_data();
+
+private:
+ pv::SigSession &_session;
+
+ /**
+ * This mutex prevents more than one decode operation occuring
+ * concurrently.
+ * @todo A proper solution should be implemented to allow multiple
+ * decode operations.
+ */
+ static boost::mutex _global_decode_mutex;
+
+ std::list< boost::shared_ptr<decode::Decoder> > _stack;
+
+ boost::shared_ptr<pv::data::LogicSnapshot> _snapshot;
+
+ mutable boost::mutex _input_mutex;
+ mutable boost::condition_variable _input_cond;
+ int64_t _sample_count;
+ bool _frame_complete;
+
+ mutable boost::mutex _output_mutex;
+ int64_t _samples_decoded;
+
+ std::map<const decode::Row, decode::RowData> _rows;
+
+ std::map<std::pair<const srd_decoder*, int>, decode::Row> _class_rows;
+
+ QString _error_message;
+
+ boost::thread _decode_thread;
+
+ friend class DecoderStackTest::TwoDecoderStack;
+};
+
+} // namespace data
+} // namespace pv
+
+#endif // PULSEVIEW_PV_DATA_DECODERSTACK_H
diff --git a/pv/data/logic.cpp b/pv/data/logic.cpp
new file mode 100644
index 0000000..167c79b
--- /dev/null
+++ b/pv/data/logic.cpp
@@ -0,0 +1,72 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <boost/foreach.hpp>
+
+#include "logic.h"
+#include "logicsnapshot.h"
+
+using boost::shared_ptr;
+using std::deque;
+using std::max;
+
+namespace pv {
+namespace data {
+
+Logic::Logic(unsigned int num_probes) :
+ SignalData(),
+ _num_probes(num_probes)
+{
+ assert(_num_probes > 0);
+}
+
+int Logic::get_num_probes() const
+{
+ return _num_probes;
+}
+
+void Logic::push_snapshot(
+ shared_ptr<LogicSnapshot> &snapshot)
+{
+ _snapshots.push_front(snapshot);
+}
+
+deque< shared_ptr<LogicSnapshot> >& Logic::get_snapshots()
+{
+ return _snapshots;
+}
+
+void Logic::clear()
+{
+ _snapshots.clear();
+}
+
+uint64_t Logic::get_max_sample_count() const
+{
+ uint64_t l = 0;
+ BOOST_FOREACH(boost::shared_ptr<LogicSnapshot> s, _snapshots) {
+ assert(s);
+ l = max(l, s->get_sample_count());
+ }
+ return l;
+}
+
+} // namespace data
+} // namespace pv
diff --git a/pv/data/logic.h b/pv/data/logic.h
new file mode 100644
index 0000000..3d83394
--- /dev/null
+++ b/pv/data/logic.h
@@ -0,0 +1,59 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_DATA_LOGIC_H
+#define PULSEVIEW_PV_DATA_LOGIC_H
+
+#include "signaldata.h"
+
+#include <boost/shared_ptr.hpp>
+#include <deque>
+
+namespace pv {
+namespace data {
+
+class LogicSnapshot;
+
+class Logic : public SignalData
+{
+public:
+ Logic(unsigned int num_probes);
+
+ int get_num_probes() const;
+
+ void push_snapshot(
+ boost::shared_ptr<LogicSnapshot> &snapshot);
+
+ std::deque< boost::shared_ptr<LogicSnapshot> >&
+ get_snapshots();
+
+ void clear();
+
+ uint64_t get_max_sample_count() const;
+
+private:
+ const unsigned int _num_probes;
+ std::deque< boost::shared_ptr<LogicSnapshot> > _snapshots;
+};
+
+} // namespace data
+} // namespace pv
+
+#endif // PULSEVIEW_PV_DATA_LOGIC_H
diff --git a/pv/data/logicsnapshot.cpp b/pv/data/logicsnapshot.cpp
new file mode 100644
index 0000000..797a00b
--- /dev/null
+++ b/pv/data/logicsnapshot.cpp
@@ -0,0 +1,468 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <extdef.h>
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include <boost/foreach.hpp>
+
+#include "config.h"
+#include "logicsnapshot.h"
+
+using boost::lock_guard;
+using boost::recursive_mutex;
+using std::max;
+using std::min;
+using std::pair;
+
+namespace pv {
+namespace data {
+
+const int LogicSnapshot::MipMapScalePower = 4;
+const int LogicSnapshot::MipMapScaleFactor = 1 << MipMapScalePower;
+const float LogicSnapshot::LogMipMapScaleFactor = logf(MipMapScaleFactor);
+const uint64_t LogicSnapshot::MipMapDataUnit = 64*1024; // bytes
+
+LogicSnapshot::LogicSnapshot(const sr_datafeed_logic &logic,
+ const uint64_t expected_num_samples) :
+ Snapshot(logic.unitsize),
+ _last_append_sample(0)
+{
+ set_capacity(expected_num_samples);
+
+ lock_guard<recursive_mutex> lock(_mutex);
+ memset(_mip_map, 0, sizeof(_mip_map));
+ append_payload(logic);
+}
+
+LogicSnapshot::~LogicSnapshot()
+{
+ lock_guard<recursive_mutex> lock(_mutex);
+ BOOST_FOREACH(MipMapLevel &l, _mip_map)
+ free(l.data);
+}
+
+uint64_t LogicSnapshot::unpack_sample(const uint8_t *ptr) const
+{
+#ifdef HAVE_UNALIGNED_LITTLE_ENDIAN_ACCESS
+ return *(uint64_t*)ptr;
+#else
+ uint64_t value = 0;
+ switch(_unit_size) {
+ default:
+ value |= ((uint64_t)ptr[7]) << 56;
+ /* FALLTHRU */
+ case 7:
+ value |= ((uint64_t)ptr[6]) << 48;
+ /* FALLTHRU */
+ case 6:
+ value |= ((uint64_t)ptr[5]) << 40;
+ /* FALLTHRU */
+ case 5:
+ value |= ((uint64_t)ptr[4]) << 32;
+ /* FALLTHRU */
+ case 4:
+ value |= ((uint32_t)ptr[3]) << 24;
+ /* FALLTHRU */
+ case 3:
+ value |= ((uint32_t)ptr[2]) << 16;
+ /* FALLTHRU */
+ case 2:
+ value |= ptr[1] << 8;
+ /* FALLTHRU */
+ case 1:
+ value |= ptr[0];
+ /* FALLTHRU */
+ case 0:
+ break;
+ }
+ return value;
+#endif
+}
+
+void LogicSnapshot::pack_sample(uint8_t *ptr, uint64_t value)
+{
+#ifdef HAVE_UNALIGNED_LITTLE_ENDIAN_ACCESS
+ *(uint64_t*)ptr = value;
+#else
+ switch(_unit_size) {
+ default:
+ ptr[7] = value >> 56;
+ /* FALLTHRU */
+ case 7:
+ ptr[6] = value >> 48;
+ /* FALLTHRU */
+ case 6:
+ ptr[5] = value >> 40;
+ /* FALLTHRU */
+ case 5:
+ ptr[4] = value >> 32;
+ /* FALLTHRU */
+ case 4:
+ ptr[3] = value >> 24;
+ /* FALLTHRU */
+ case 3:
+ ptr[2] = value >> 16;
+ /* FALLTHRU */
+ case 2:
+ ptr[1] = value >> 8;
+ /* FALLTHRU */
+ case 1:
+ ptr[0] = value;
+ /* FALLTHRU */
+ case 0:
+ break;
+ }
+#endif
+}
+
+void LogicSnapshot::append_payload(
+ const sr_datafeed_logic &logic)
+{
+ assert(_unit_size == logic.unitsize);
+ assert((logic.length % _unit_size) == 0);
+
+ lock_guard<recursive_mutex> lock(_mutex);
+
+ append_data(logic.data, logic.length / _unit_size);
+
+ // Generate the first mip-map from the data
+ append_payload_to_mipmap();
+}
+
+void LogicSnapshot::get_samples(uint8_t *const data,
+ int64_t start_sample, int64_t end_sample) const
+{
+ assert(data);
+ assert(start_sample >= 0);
+ assert(start_sample <= (int64_t)_sample_count);
+ assert(end_sample >= 0);
+ assert(end_sample <= (int64_t)_sample_count);
+ assert(start_sample <= end_sample);
+
+ lock_guard<recursive_mutex> lock(_mutex);
+
+ const size_t size = (end_sample - start_sample) * _unit_size;
+ memcpy(data, (const uint8_t*)_data + start_sample * _unit_size, size);
+}
+
+void LogicSnapshot::reallocate_mipmap_level(MipMapLevel &m)
+{
+ const uint64_t new_data_length = ((m.length + MipMapDataUnit - 1) /
+ MipMapDataUnit) * MipMapDataUnit;
+ if (new_data_length > m.data_length)
+ {
+ m.data_length = new_data_length;
+
+ // Padding is added to allow for the uint64_t write word
+ m.data = realloc(m.data, new_data_length * _unit_size +
+ sizeof(uint64_t));
+ }
+}
+
+void LogicSnapshot::append_payload_to_mipmap()
+{
+ MipMapLevel &m0 = _mip_map[0];
+ uint64_t prev_length;
+ const uint8_t *src_ptr;
+ uint8_t *dest_ptr;
+ uint64_t accumulator;
+ unsigned int diff_counter;
+
+ // Expand the data buffer to fit the new samples
+ prev_length = m0.length;
+ m0.length = _sample_count / MipMapScaleFactor;
+
+ // Break off if there are no new samples to compute
+ if (m0.length == prev_length)
+ return;
+
+ reallocate_mipmap_level(m0);
+
+ dest_ptr = (uint8_t*)m0.data + prev_length * _unit_size;
+
+ // Iterate through the samples to populate the first level mipmap
+ const uint8_t *const end_src_ptr = (uint8_t*)_data +
+ m0.length * _unit_size * MipMapScaleFactor;
+ for (src_ptr = (uint8_t*)_data +
+ prev_length * _unit_size * MipMapScaleFactor;
+ src_ptr < end_src_ptr;)
+ {
+ // Accumulate transitions which have occurred in this sample
+ accumulator = 0;
+ diff_counter = MipMapScaleFactor;
+ while (diff_counter-- > 0)
+ {
+ const uint64_t sample = unpack_sample(src_ptr);
+ accumulator |= _last_append_sample ^ sample;
+ _last_append_sample = sample;
+ src_ptr += _unit_size;
+ }
+
+ pack_sample(dest_ptr, accumulator);
+ dest_ptr += _unit_size;
+ }
+
+ // Compute higher level mipmaps
+ for (unsigned int level = 1; level < ScaleStepCount; level++)
+ {
+ MipMapLevel &m = _mip_map[level];
+ const MipMapLevel &ml = _mip_map[level-1];
+
+ // Expand the data buffer to fit the new samples
+ prev_length = m.length;
+ m.length = ml.length / MipMapScaleFactor;
+
+ // Break off if there are no more samples to computed
+ if (m.length == prev_length)
+ break;
+
+ reallocate_mipmap_level(m);
+
+ // Subsample the level lower level
+ src_ptr = (uint8_t*)ml.data +
+ _unit_size * prev_length * MipMapScaleFactor;
+ const uint8_t *const end_dest_ptr =
+ (uint8_t*)m.data + _unit_size * m.length;
+ for (dest_ptr = (uint8_t*)m.data +
+ _unit_size * prev_length;
+ dest_ptr < end_dest_ptr;
+ dest_ptr += _unit_size)
+ {
+ accumulator = 0;
+ diff_counter = MipMapScaleFactor;
+ while (diff_counter-- > 0)
+ {
+ accumulator |= unpack_sample(src_ptr);
+ src_ptr += _unit_size;
+ }
+
+ pack_sample(dest_ptr, accumulator);
+ }
+ }
+}
+
+uint64_t LogicSnapshot::get_sample(uint64_t index) const
+{
+ assert(_data);
+ assert(index < _sample_count);
+
+ return unpack_sample((uint8_t*)_data + index * _unit_size);
+}
+
+void LogicSnapshot::get_subsampled_edges(
+ std::vector<EdgePair> &edges,
+ uint64_t start, uint64_t end,
+ float min_length, int sig_index)
+{
+ uint64_t index = start;
+ unsigned int level;
+ bool last_sample;
+ bool fast_forward;
+
+ assert(end <= get_sample_count());
+ assert(start <= end);
+ assert(min_length > 0);
+ assert(sig_index >= 0);
+ assert(sig_index < 64);
+
+ lock_guard<recursive_mutex> lock(_mutex);
+
+ const uint64_t block_length = (uint64_t)max(min_length, 1.0f);
+ const unsigned int min_level = max((int)floorf(logf(min_length) /
+ LogMipMapScaleFactor) - 1, 0);
+ const uint64_t sig_mask = 1ULL << sig_index;
+
+ // Store the initial state
+ last_sample = (get_sample(start) & sig_mask) != 0;
+ edges.push_back(pair<int64_t, bool>(index++, last_sample));
+
+ while (index + block_length <= end)
+ {
+ //----- Continue to search -----//
+ level = min_level;
+
+ // We cannot fast-forward if there is no mip-map data at
+ // at the minimum level.
+ fast_forward = (_mip_map[level].data != NULL);
+
+ if (min_length < MipMapScaleFactor)
+ {
+ // Search individual samples up to the beginning of
+ // the next first level mip map block
+ const uint64_t final_index = min(end,
+ pow2_ceil(index, MipMapScalePower));
+
+ for (; index < final_index &&
+ (index & ~(~0 << MipMapScalePower)) != 0;
+ index++)
+ {
+ const bool sample =
+ (get_sample(index) & sig_mask) != 0;
+
+ // If there was a change we cannot fast forward
+ if (sample != last_sample) {
+ fast_forward = false;
+ break;
+ }
+ }
+ }
+ else
+ {
+ // If resolution is less than a mip map block,
+ // round up to the beginning of the mip-map block
+ // for this level of detail
+ const int min_level_scale_power =
+ (level + 1) * MipMapScalePower;
+ index = pow2_ceil(index, min_level_scale_power);
+ if (index >= end)
+ break;
+
+ // We can fast forward only if there was no change
+ const bool sample =
+ (get_sample(index) & sig_mask) != 0;
+ if (last_sample != sample)
+ fast_forward = false;
+ }
+
+ if (fast_forward) {
+
+ // Fast forward: This involves zooming out to higher
+ // levels of the mip map searching for changes, then
+ // zooming in on them to find the point where the edge
+ // begins.
+
+ // Slide right and zoom out at the beginnings of mip-map
+ // blocks until we encounter a change
+ while (1) {
+ const int level_scale_power =
+ (level + 1) * MipMapScalePower;
+ const uint64_t offset =
+ index >> level_scale_power;
+
+ // Check if we reached the last block at this
+ // level, or if there was a change in this block
+ if (offset >= _mip_map[level].length ||
+ (get_subsample(level, offset) &
+ sig_mask))
+ break;
+
+ if ((offset & ~(~0 << MipMapScalePower)) == 0) {
+ // If we are now at the beginning of a
+ // higher level mip-map block ascend one
+ // level
+ if (level + 1 >= ScaleStepCount ||
+ !_mip_map[level + 1].data)
+ break;
+
+ level++;
+ } else {
+ // Slide right to the beginning of the
+ // next mip map block
+ index = pow2_ceil(index + 1,
+ level_scale_power);
+ }
+ }
+
+ // Zoom in, and slide right until we encounter a change,
+ // and repeat until we reach min_level
+ while (1) {
+ assert(_mip_map[level].data);
+
+ const int level_scale_power =
+ (level + 1) * MipMapScalePower;
+ const uint64_t offset =
+ index >> level_scale_power;
+
+ // Check if we reached the last block at this
+ // level, or if there was a change in this block
+ if (offset >= _mip_map[level].length ||
+ (get_subsample(level, offset) &
+ sig_mask)) {
+ // Zoom in unless we reached the minimum
+ // zoom
+ if (level == min_level)
+ break;
+
+ level--;
+ } else {
+ // Slide right to the beginning of the
+ // next mip map block
+ index = pow2_ceil(index + 1,
+ level_scale_power);
+ }
+ }
+
+ // If individual samples within the limit of resolution,
+ // do a linear search for the next transition within the
+ // block
+ if (min_length < MipMapScaleFactor) {
+ for (; index < end; index++) {
+ const bool sample = (get_sample(index) &
+ sig_mask) != 0;
+ if (sample != last_sample)
+ break;
+ }
+ }
+ }
+
+ //----- Store the edge -----//
+
+ // Take the last sample of the quanization block
+ const int64_t final_index = index + block_length;
+ if (index + block_length > end)
+ break;
+
+ // Store the final state
+ const bool final_sample =
+ (get_sample(final_index - 1) & sig_mask) != 0;
+ edges.push_back(pair<int64_t, bool>(index, final_sample));
+
+ index = final_index;
+ last_sample = final_sample;
+ }
+
+ // Add the final state
+ const bool end_sample = get_sample(end) & sig_mask;
+ if (last_sample != end_sample)
+ edges.push_back(pair<int64_t, bool>(end, end_sample));
+ edges.push_back(pair<int64_t, bool>(end + 1, end_sample));
+}
+
+uint64_t LogicSnapshot::get_subsample(int level, uint64_t offset) const
+{
+ assert(level >= 0);
+ assert(_mip_map[level].data);
+ return unpack_sample((uint8_t*)_mip_map[level].data +
+ _unit_size * offset);
+}
+
+uint64_t LogicSnapshot::pow2_ceil(uint64_t x, unsigned int power)
+{
+ const uint64_t p = 1 << power;
+ return (x + p - 1) / p * p;
+}
+
+} // namespace data
+} // namespace pv
diff --git a/pv/data/logicsnapshot.h b/pv/data/logicsnapshot.h
new file mode 100644
index 0000000..0f6f410
--- /dev/null
+++ b/pv/data/logicsnapshot.h
@@ -0,0 +1,115 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_DATA_LOGICSNAPSHOT_H
+#define PULSEVIEW_PV_DATA_LOGICSNAPSHOT_H
+
+#include "snapshot.h"
+
+#include <utility>
+#include <vector>
+
+namespace LogicSnapshotTest {
+class Pow2;
+class Basic;
+class LargeData;
+class Pulses;
+class LongPulses;
+}
+
+namespace pv {
+namespace data {
+
+class LogicSnapshot : public Snapshot
+{
+private:
+ struct MipMapLevel
+ {
+ uint64_t length;
+ uint64_t data_length;
+ void *data;
+ };
+
+private:
+ static const unsigned int ScaleStepCount = 10;
+ static const int MipMapScalePower;
+ static const int MipMapScaleFactor;
+ static const float LogMipMapScaleFactor;
+ static const uint64_t MipMapDataUnit;
+
+public:
+ typedef std::pair<int64_t, bool> EdgePair;
+
+public:
+ LogicSnapshot(const sr_datafeed_logic &logic,
+ uint64_t expected_num_samples = 0);
+
+ virtual ~LogicSnapshot();
+
+ void append_payload(const sr_datafeed_logic &logic);
+
+ void get_samples(uint8_t *const data,
+ int64_t start_sample, int64_t end_sample) const;
+
+private:
+ uint64_t unpack_sample(const uint8_t *ptr) const;
+ void pack_sample(uint8_t *ptr, uint64_t value);
+
+ void reallocate_mipmap_level(MipMapLevel &m);
+
+ void append_payload_to_mipmap();
+
+ uint64_t get_sample(uint64_t index) const;
+
+public:
+ /**
+ * Parses a logic data snapshot to generate a list of transitions
+ * in a time interval to a given level of detail.
+ * @param[out] edges The vector to place the edges into.
+ * @param[in] start The start sample index.
+ * @param[in] end The end sample index.
+ * @param[in] min_length The minimum number of samples that
+ * can be resolved at this level of detail.
+ * @param[in] sig_index The index of the signal.
+ **/
+ void get_subsampled_edges(std::vector<EdgePair> &edges,
+ uint64_t start, uint64_t end,
+ float min_length, int sig_index);
+
+private:
+ uint64_t get_subsample(int level, uint64_t offset) const;
+
+ static uint64_t pow2_ceil(uint64_t x, unsigned int power);
+
+private:
+ struct MipMapLevel _mip_map[ScaleStepCount];
+ uint64_t _last_append_sample;
+
+ friend class LogicSnapshotTest::Pow2;
+ friend class LogicSnapshotTest::Basic;
+ friend class LogicSnapshotTest::LargeData;
+ friend class LogicSnapshotTest::Pulses;
+ friend class LogicSnapshotTest::LongPulses;
+};
+
+} // namespace data
+} // namespace pv
+
+#endif // PULSEVIEW_PV_DATA_LOGICSNAPSHOT_H
diff --git a/pv/data/signaldata.cpp b/pv/data/signaldata.cpp
new file mode 100644
index 0000000..04f1d3f
--- /dev/null
+++ b/pv/data/signaldata.cpp
@@ -0,0 +1,49 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "signaldata.h"
+
+namespace pv {
+namespace data {
+
+SignalData::SignalData() :
+ _start_time(0),
+ _samplerate(0)
+{
+}
+
+double SignalData::samplerate() const
+{
+ return _samplerate;
+}
+
+void SignalData::set_samplerate(double samplerate)
+{
+ _samplerate = samplerate;
+ clear();
+}
+
+double SignalData::get_start_time() const
+{
+ return _start_time;
+}
+
+} // namespace data
+} // namespace pv
diff --git a/pv/data/signaldata.h b/pv/data/signaldata.h
new file mode 100644
index 0000000..0aa3b34
--- /dev/null
+++ b/pv/data/signaldata.h
@@ -0,0 +1,52 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_DATA_SIGNALDATA_H
+#define PULSEVIEW_PV_DATA_SIGNALDATA_H
+
+#include <stdint.h>
+
+namespace pv {
+namespace data {
+
+class SignalData
+{
+public:
+ SignalData();
+
+public:
+ double samplerate() const;
+ void set_samplerate(double samplerate);
+
+ double get_start_time() const;
+
+ virtual void clear() = 0;
+
+ virtual uint64_t get_max_sample_count() const = 0;
+
+protected:
+ double _start_time;
+ double _samplerate;
+};
+
+} // namespace data
+} // namespace pv
+
+#endif // PULSEVIEW_PV_DATA_SIGNALDATA_H
diff --git a/pv/data/snapshot.cpp b/pv/data/snapshot.cpp
new file mode 100644
index 0000000..6ba39d2
--- /dev/null
+++ b/pv/data/snapshot.cpp
@@ -0,0 +1,95 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "snapshot.h"
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+using boost::lock_guard;
+using boost::recursive_mutex;
+
+namespace pv {
+namespace data {
+
+Snapshot::Snapshot(int unit_size) :
+ _data(NULL),
+ _sample_count(0),
+ _capacity(0),
+ _unit_size(unit_size)
+{
+ lock_guard<recursive_mutex> lock(_mutex);
+ assert(_unit_size > 0);
+}
+
+Snapshot::~Snapshot()
+{
+ lock_guard<recursive_mutex> lock(_mutex);
+ free(_data);
+}
+
+uint64_t Snapshot::get_sample_count() const
+{
+ lock_guard<recursive_mutex> lock(_mutex);
+ return _sample_count;
+}
+
+int Snapshot::unit_size() const
+{
+ return _unit_size;
+}
+
+void Snapshot::set_capacity(const uint64_t new_capacity)
+{
+ lock_guard<recursive_mutex> lock(_mutex);
+
+ assert(_capacity >= _sample_count);
+ if (new_capacity > _capacity) {
+ _capacity = new_capacity;
+ _data = realloc(_data, (new_capacity * _unit_size) + sizeof(uint64_t));
+ }
+}
+
+uint64_t Snapshot::capacity() const
+{
+ lock_guard<recursive_mutex> lock(_mutex);
+ return _capacity;
+}
+
+void Snapshot::append_data(void *data, uint64_t samples)
+{
+ lock_guard<recursive_mutex> lock(_mutex);
+
+ assert(_capacity >= _sample_count);
+
+ // Ensure there's enough capacity to copy.
+ const uint64_t free_space = _capacity - _sample_count;
+ if (free_space < samples) {
+ set_capacity(_sample_count + samples);
+ }
+
+ memcpy((uint8_t*)_data + _sample_count * _unit_size,
+ data, samples * _unit_size);
+ _sample_count += samples;
+}
+
+} // namespace data
+} // namespace pv
diff --git a/pv/data/snapshot.h b/pv/data/snapshot.h
new file mode 100644
index 0000000..1f1ca3c
--- /dev/null
+++ b/pv/data/snapshot.h
@@ -0,0 +1,83 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_DATA_SNAPSHOT_H
+#define PULSEVIEW_PV_DATA_SNAPSHOT_H
+
+#include <libsigrok/libsigrok.h>
+
+#include <boost/thread.hpp>
+
+namespace pv {
+namespace data {
+
+class Snapshot
+{
+public:
+ Snapshot(int unit_size);
+
+ virtual ~Snapshot();
+
+ uint64_t get_sample_count() const;
+
+ int unit_size() const;
+
+ /**
+ * @brief Increase the capacity of the snapshot.
+ *
+ * Increasing the capacity allows samples to be appended without needing
+ * to reallocate memory.
+ *
+ * For the best efficiency @c set_capacity() should be called once before
+ * @c append_data() is called to set up the snapshot with the expected number
+ * of samples that will be appended in total.
+ *
+ * @note The capacity will automatically be increased when @c append_data()
+ * is called if there is not enough capacity in the buffer to store the samples.
+ *
+ * @param[in] new_capacity The new capacity of the snapshot. If this value is
+ * smaller or equal than the current capacity then the method has no effect.
+ */
+ void set_capacity(uint64_t new_capacity);
+
+ /**
+ * @brief Get the current capacity of the snapshot.
+ *
+ * The capacity can be increased by calling @c set_capacity().
+ *
+ * @return The current capacity of the snapshot.
+ */
+ uint64_t capacity() const;
+
+protected:
+ void append_data(void *data, uint64_t samples);
+
+protected:
+ mutable boost::recursive_mutex _mutex;
+ void *_data;
+ uint64_t _sample_count;
+ uint64_t _capacity;
+ int _unit_size;
+};
+
+} // namespace data
+} // namespace pv
+
+#endif // PULSEVIEW_PV_DATA_SNAPSHOT_H
diff --git a/pv/device/device.cpp b/pv/device/device.cpp
new file mode 100644
index 0000000..4f9a779
--- /dev/null
+++ b/pv/device/device.cpp
@@ -0,0 +1,104 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2014 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <sstream>
+
+#include <libsigrok/libsigrok.h>
+
+#include "device.h"
+
+using std::ostringstream;
+using std::string;
+
+namespace pv {
+namespace device {
+
+Device::Device(sr_dev_inst *sdi) :
+ _sdi(sdi)
+{
+ assert(_sdi);
+}
+
+sr_dev_inst* Device::dev_inst() const
+{
+ return _sdi;
+}
+
+void Device::use(SigSession *owner) throw(QString)
+{
+ DevInst::use(owner);
+
+ sr_session_new();
+
+ assert(_sdi);
+ sr_dev_open(_sdi);
+ if (sr_session_dev_add(_sdi) != SR_OK)
+ throw QString(tr("Failed to use device."));
+}
+
+void Device::release()
+{
+ if (_owner) {
+ DevInst::release();
+ sr_session_destroy();
+ }
+
+ sr_dev_close(_sdi);
+}
+
+std::string Device::format_device_title() const
+{
+ ostringstream s;
+
+ assert(_sdi);
+
+ if (_sdi->vendor && _sdi->vendor[0]) {
+ s << _sdi->vendor;
+ if ((_sdi->model && _sdi->model[0]) ||
+ (_sdi->version && _sdi->version[0]))
+ s << ' ';
+ }
+
+ if (_sdi->model && _sdi->model[0]) {
+ s << _sdi->model;
+ if (_sdi->version && _sdi->version[0])
+ s << ' ';
+ }
+
+ if (_sdi->version && _sdi->version[0])
+ s << _sdi->version;
+
+ return s.str();
+}
+
+bool Device::is_trigger_enabled() const
+{
+ assert(_sdi);
+ for (const GSList *l = _sdi->channels; l; l = l->next) {
+ const sr_channel *const p = (const sr_channel *)l->data;
+ assert(p);
+ if (p->trigger && p->trigger[0] != '\0')
+ return true;
+ }
+ return false;
+}
+
+} // device
+} // pv
diff --git a/pv/device/device.h b/pv/device/device.h
new file mode 100644
index 0000000..b9e1729
--- /dev/null
+++ b/pv/device/device.h
@@ -0,0 +1,51 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2014 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_DEVICE_DEVICE_H
+#define PULSEVIEW_PV_DEVICE_DEVICE_H
+
+#include "devinst.h"
+
+namespace pv {
+namespace device {
+
+class Device : public DevInst
+{
+public:
+ Device(sr_dev_inst *dev_inst);
+
+ sr_dev_inst* dev_inst() const;
+
+ void use(SigSession *owner) throw(QString);
+
+ void release();
+
+ std::string format_device_title() const;
+
+ bool is_trigger_enabled() const;
+
+private:
+ sr_dev_inst *const _sdi;
+};
+
+} // device
+} // pv
+
+#endif // PULSVIEW_PV_DEVICE_DEVICE_H
diff --git a/pv/device/devinst.cpp b/pv/device/devinst.cpp
new file mode 100644
index 0000000..4543e5c
--- /dev/null
+++ b/pv/device/devinst.cpp
@@ -0,0 +1,139 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2014 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <cassert>
+
+#include <QDebug>
+
+#include <libsigrok/libsigrok.h>
+
+#include "devinst.h"
+
+#include <pv/sigsession.h>
+
+namespace pv {
+namespace device {
+
+DevInst::DevInst() :
+ _owner(NULL)
+{
+}
+
+void DevInst::use(SigSession *owner) throw(QString)
+{
+ assert(owner);
+ assert(!_owner);
+ _owner = owner;
+}
+
+void DevInst::release()
+{
+ if (_owner) {
+ _owner->release_device(this);
+ _owner = NULL;
+ }
+}
+
+SigSession* DevInst::owner() const
+{
+ return _owner;
+}
+
+GVariant* DevInst::get_config(const sr_channel_group *group, int key)
+{
+ GVariant *data = NULL;
+ assert(_owner);
+ sr_dev_inst *const sdi = dev_inst();
+ assert(sdi);
+ if (sr_config_get(sdi->driver, sdi, group, key, &data) != SR_OK)
+ return NULL;
+ return data;
+}
+
+bool DevInst::set_config(const sr_channel_group *group, int key, GVariant *data)
+{
+ assert(_owner);
+ sr_dev_inst *const sdi = dev_inst();
+ assert(sdi);
+ if(sr_config_set(sdi, group, key, data) == SR_OK) {
+ config_changed();
+ return true;
+ }
+ return false;
+}
+
+GVariant* DevInst::list_config(const sr_channel_group *group, int key)
+{
+ GVariant *data = NULL;
+ assert(_owner);
+ sr_dev_inst *const sdi = dev_inst();
+ assert(sdi);
+ if (sr_config_list(sdi->driver, sdi, group, key, &data) != SR_OK)
+ return NULL;
+ return data;
+}
+
+void DevInst::enable_probe(const sr_channel *probe, bool enable)
+{
+ assert(_owner);
+ sr_dev_inst *const sdi = dev_inst();
+ assert(sdi);
+ for (const GSList *p = sdi->channels; p; p = p->next)
+ if (probe == p->data) {
+ const_cast<sr_channel*>(probe)->enabled = enable;
+ config_changed();
+ return;
+ }
+
+ // Probe was not found in the device
+ assert(0);
+}
+
+uint64_t DevInst::get_sample_limit()
+{
+ uint64_t sample_limit;
+ GVariant* gvar = get_config(NULL, SR_CONF_LIMIT_SAMPLES);
+ if (gvar != NULL) {
+ sample_limit = g_variant_get_uint64(gvar);
+ g_variant_unref(gvar);
+ } else {
+ sample_limit = 0U;
+ }
+ return sample_limit;
+}
+
+bool DevInst::is_trigger_enabled() const
+{
+ return false;
+}
+
+void DevInst::start()
+{
+ if (sr_session_start() != SR_OK)
+ throw tr("Failed to start session.");
+}
+
+void DevInst::run()
+{
+ sr_session_run();
+}
+
+} // device
+} // pv
diff --git a/pv/device/devinst.h b/pv/device/devinst.h
new file mode 100644
index 0000000..e6a5140
--- /dev/null
+++ b/pv/device/devinst.h
@@ -0,0 +1,95 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2014 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_DEVICE_DEVINST_H
+#define PULSEVIEW_PV_DEVICE_DEVINST_H
+
+#include <string>
+
+#include <boost/shared_ptr.hpp>
+
+#include <QObject>
+
+#include <glib.h>
+
+#include <stdint.h>
+
+struct sr_dev_inst;
+struct sr_channel;
+struct sr_channel_group;
+
+namespace pv {
+
+class SigSession;
+
+namespace device {
+
+class DevInst : public QObject
+{
+ Q_OBJECT
+
+protected:
+ DevInst();
+
+public:
+ virtual sr_dev_inst* dev_inst() const = 0;
+
+ virtual void use(SigSession *owner) throw(QString);
+
+ virtual void release();
+
+ SigSession* owner() const;
+
+ virtual std::string format_device_title() const = 0;
+
+ GVariant* get_config(const sr_channel_group *group, int key);
+
+ bool set_config(const sr_channel_group *group, int key, GVariant *data);
+
+ GVariant* list_config(const sr_channel_group *group, int key);
+
+ void enable_probe(const sr_channel *probe, bool enable = true);
+
+ /**
+ * @brief Gets the sample limit from the driver.
+ *
+ * @return The returned sample limit from the driver, or 0 if the
+ * sample limit could not be read.
+ */
+ uint64_t get_sample_limit();
+
+ virtual bool is_trigger_enabled() const;
+
+public:
+ virtual void start();
+
+ virtual void run();
+
+signals:
+ void config_changed();
+
+protected:
+ SigSession *_owner;
+};
+
+} // device
+} // pv
+
+#endif // PULSEVIEW_PV_DEVICE_DEVINST_H
diff --git a/pv/device/file.cpp b/pv/device/file.cpp
new file mode 100644
index 0000000..e33be85
--- /dev/null
+++ b/pv/device/file.cpp
@@ -0,0 +1,66 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2014 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "file.h"
+#include "inputfile.h"
+#include "sessionfile.h"
+
+#include <boost/filesystem.hpp>
+
+#include <libsigrok/libsigrok.h>
+
+using std::string;
+
+namespace pv {
+namespace device {
+
+File::File(const std::string path) :
+ _path(path)
+{
+}
+
+std::string File::format_device_title() const
+{
+ return boost::filesystem::path(_path).filename().string();
+}
+
+File* File::create(const string &name)
+{
+ if (sr_session_load(name.c_str()) == SR_OK) {
+ GSList *devlist = NULL;
+ sr_session_dev_list(&devlist);
+ sr_session_destroy();
+
+ if (devlist) {
+ sr_dev_inst *const sdi = (sr_dev_inst*)devlist->data;
+ g_slist_free(devlist);
+ if (sdi) {
+ sr_dev_close(sdi);
+ sr_dev_clear(sdi->driver);
+ return new SessionFile(name);
+ }
+ }
+ }
+
+ return new InputFile(name);
+}
+
+} // device
+} // pv
diff --git a/pv/device/file.h b/pv/device/file.h
new file mode 100644
index 0000000..4a45a82
--- /dev/null
+++ b/pv/device/file.h
@@ -0,0 +1,49 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2014 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_DEVICE_FILE_H
+#define PULSEVIEW_PV_DEVICE_FILE_H
+
+#include <string>
+
+#include "devinst.h"
+
+namespace pv {
+namespace device {
+
+class File : public DevInst
+{
+protected:
+ File(const std::string path);
+
+public:
+ static File* create(const std::string &name);
+
+public:
+ std::string format_device_title() const;
+
+protected:
+ const std::string _path;
+};
+
+} // device
+} // pv
+
+#endif // PULSEVIEW_PV_DEVICE_FILE_H
diff --git a/pv/device/inputfile.cpp b/pv/device/inputfile.cpp
new file mode 100644
index 0000000..55a0fa5
--- /dev/null
+++ b/pv/device/inputfile.cpp
@@ -0,0 +1,143 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2014 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <cassert>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "inputfile.h"
+
+#include <libsigrok/libsigrok.h>
+
+using std::string;
+
+namespace pv {
+namespace device {
+
+InputFile::InputFile(const std::string &path) :
+ File(path),
+ _input(NULL)
+{
+}
+
+sr_dev_inst* InputFile::dev_inst() const
+{
+ assert(_input);
+ return _input->sdi;
+}
+
+void InputFile::use(SigSession *owner) throw(QString)
+{
+ assert(!_input);
+
+ _input = load_input_file_format(_path, NULL);
+ File::use(owner);
+
+ sr_session_new();
+
+ if (sr_session_dev_add(_input->sdi) != SR_OK)
+ throw tr("Failed to add session device.");
+}
+
+void InputFile::release()
+{
+ if (!_owner)
+ return;
+
+ assert(_input);
+ File::release();
+ sr_dev_close(_input->sdi);
+ sr_session_destroy();
+ _input = NULL;
+}
+
+sr_input_format* InputFile::determine_input_file_format(
+ const string &filename)
+{
+ int i;
+
+ /* If there are no input formats, return NULL right away. */
+ sr_input_format *const *const inputs = sr_input_list();
+ if (!inputs) {
+ g_critical("No supported input formats available.");
+ return NULL;
+ }
+
+ /* Otherwise, try to find an input module that can handle this file. */
+ for (i = 0; inputs[i]; i++) {
+ if (inputs[i]->format_match(filename.c_str()))
+ break;
+ }
+
+ /* Return NULL if no input module wanted to touch this. */
+ if (!inputs[i]) {
+ g_critical("Error: no matching input module found.");
+ return NULL;
+ }
+
+ return inputs[i];
+}
+
+sr_input* InputFile::load_input_file_format(const string &filename,
+ sr_input_format *format)
+{
+ struct stat st;
+ sr_input *in;
+
+ if (!format && !(format =
+ determine_input_file_format(filename.c_str()))) {
+ /* The exact cause was already logged. */
+ throw tr("Failed to load file");
+ }
+
+ if (stat(filename.c_str(), &st) == -1)
+ throw tr("Failed to load file");
+
+ /* Initialize the input module. */
+ if (!(in = new sr_input)) {
+ throw tr("Failed to allocate input module.");
+ }
+
+ in->format = format;
+ in->param = NULL;
+ if (in->format->init &&
+ in->format->init(in, filename.c_str()) != SR_OK) {
+ throw tr("Failed to load file");
+ }
+
+ return in;
+}
+
+void InputFile::start()
+{
+}
+
+void InputFile::run()
+{
+ assert(_input);
+ assert(_input->format);
+ assert(_input->format->loadfile);
+ _input->format->loadfile(_input, _path.c_str());
+}
+
+} // device
+} // pv
diff --git a/pv/device/inputfile.h b/pv/device/inputfile.h
new file mode 100644
index 0000000..150418d
--- /dev/null
+++ b/pv/device/inputfile.h
@@ -0,0 +1,68 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2014 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_DEVICE_INPUTFILE_H
+#define PULSEVIEW_PV_DEVICE_INPUTFILE_H
+
+#include "file.h"
+
+#include <string>
+
+struct sr_input;
+struct sr_input_format;
+
+namespace pv {
+namespace device {
+
+class InputFile : public File
+{
+public:
+ InputFile(const std::string &path);
+
+ sr_dev_inst* dev_inst() const;
+
+ virtual void use(SigSession *owner) throw(QString);
+
+ virtual void release();
+
+ virtual void start();
+
+ virtual void run();
+
+private:
+ /**
+ * Attempts to autodetect the format. Failing that
+ * @param filename The filename of the input file.
+ * @return A pointer to the 'struct sr_input_format' that should be used,
+ * or NULL if no input format was selected or auto-detected.
+ */
+ static sr_input_format* determine_input_file_format(
+ const std::string &filename);
+
+ static sr_input* load_input_file_format(const std::string &filename,
+ sr_input_format *format);
+private:
+ sr_input *_input;
+};
+
+} // device
+} // pv
+
+#endif // PULSEVIEW_PV_DEVICE_INPUTFILE_H
diff --git a/pv/device/sessionfile.cpp b/pv/device/sessionfile.cpp
new file mode 100644
index 0000000..ffdeb67
--- /dev/null
+++ b/pv/device/sessionfile.cpp
@@ -0,0 +1,75 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2014 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "sessionfile.h"
+
+#include <libsigrok/libsigrok.h>
+
+namespace pv {
+namespace device {
+
+SessionFile::SessionFile(const std::string &path) :
+ File(path),
+ _sdi(NULL)
+{
+}
+
+sr_dev_inst* SessionFile::dev_inst() const
+{
+ return _sdi;
+}
+
+void SessionFile::use(SigSession *owner) throw(QString)
+{
+ assert(!_sdi);
+
+ if (sr_session_load(_path.c_str()) != SR_OK)
+ throw tr("Failed to open file.\n");
+
+ GSList *devlist = NULL;
+ sr_session_dev_list(&devlist);
+
+ if (!devlist || !devlist->data) {
+ if (devlist)
+ g_slist_free(devlist);
+ throw tr("Failed to start session.");
+ }
+
+ _sdi = (sr_dev_inst*)devlist->data;
+ g_slist_free(devlist);
+
+ File::use(owner);
+}
+
+void SessionFile::release()
+{
+ if (!_owner)
+ return;
+
+ assert(_sdi);
+ File::release();
+ sr_dev_close(_sdi);
+ sr_dev_clear(_sdi->driver);
+ sr_session_destroy();
+ _sdi = NULL;
+}
+
+} // device
+} // pv
diff --git a/pv/device/sessionfile.h b/pv/device/sessionfile.h
new file mode 100644
index 0000000..c0ed958
--- /dev/null
+++ b/pv/device/sessionfile.h
@@ -0,0 +1,47 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2014 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_DEVICE_SESSIONFILE_H
+#define PULSEVIEW_PV_DEVICE_SESSIONFILE_H
+
+#include "file.h"
+
+namespace pv {
+namespace device {
+
+class SessionFile : public File
+{
+public:
+ SessionFile(const std::string &path);
+
+ sr_dev_inst* dev_inst() const;
+
+ virtual void use(SigSession *owner) throw(QString);
+
+ virtual void release();
+
+private:
+ sr_dev_inst *_sdi;
+};
+
+} // device
+} // pv
+
+#endif // PULSEVIEW_PV_DEVICE_SESSIONFILE_H
diff --git a/pv/devicemanager.cpp b/pv/devicemanager.cpp
new file mode 100644
index 0000000..fb487a8
--- /dev/null
+++ b/pv/devicemanager.cpp
@@ -0,0 +1,150 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "devicemanager.h"
+#include "device/device.h"
+#include "sigsession.h"
+
+#include <cassert>
+#include <stdexcept>
+#include <string>
+
+#include <boost/foreach.hpp>
+
+#include <libsigrok/libsigrok.h>
+
+using boost::shared_ptr;
+using std::list;
+using std::map;
+using std::ostringstream;
+using std::runtime_error;
+using std::string;
+
+namespace pv {
+
+DeviceManager::DeviceManager(struct sr_context *sr_ctx) :
+ _sr_ctx(sr_ctx)
+{
+ init_drivers();
+ scan_all_drivers();
+}
+
+DeviceManager::~DeviceManager()
+{
+ release_devices();
+}
+
+const list< shared_ptr<pv::device::Device> >& DeviceManager::devices() const
+{
+ return _devices;
+}
+
+list< shared_ptr<device::Device> > DeviceManager::driver_scan(
+ struct sr_dev_driver *const driver, GSList *const drvopts)
+{
+ list< shared_ptr<device::Device> > driver_devices;
+
+ assert(driver);
+
+ // Remove any device instances from this driver from the device
+ // list. They will not be valid after the scan.
+ list< shared_ptr<device::Device> >::iterator i = _devices.begin();
+ while (i != _devices.end()) {
+ if ((*i)->dev_inst()->driver == driver)
+ i = _devices.erase(i);
+ else
+ i++;
+ }
+
+ // Release this driver and all it's attached devices
+ release_driver(driver);
+
+ // Do the scan
+ GSList *const devices = sr_driver_scan(driver, drvopts);
+ for (GSList *l = devices; l; l = l->next)
+ driver_devices.push_back(shared_ptr<device::Device>(
+ new device::Device((sr_dev_inst*)l->data)));
+ g_slist_free(devices);
+ driver_devices.sort(compare_devices);
+
+ // Add the scanned devices to the main list
+ _devices.insert(_devices.end(), driver_devices.begin(),
+ driver_devices.end());
+ _devices.sort(compare_devices);
+
+ return driver_devices;
+}
+
+void DeviceManager::init_drivers()
+{
+ // Initialise all libsigrok drivers
+ sr_dev_driver **const drivers = sr_driver_list();
+ for (sr_dev_driver **driver = drivers; *driver; driver++) {
+ if (sr_driver_init(_sr_ctx, *driver) != SR_OK) {
+ throw runtime_error(
+ string("Failed to initialize driver ") +
+ string((*driver)->name));
+ }
+ }
+}
+
+void DeviceManager::release_devices()
+{
+ // Release all the used devices
+ BOOST_FOREACH(shared_ptr<device::Device> dev, _devices) {
+ assert(dev);
+ dev->release();
+ }
+
+ // Clear all the drivers
+ sr_dev_driver **const drivers = sr_driver_list();
+ for (sr_dev_driver **driver = drivers; *driver; driver++)
+ sr_dev_clear(*driver);
+}
+
+void DeviceManager::scan_all_drivers()
+{
+ // Scan all drivers for all devices.
+ struct sr_dev_driver **const drivers = sr_driver_list();
+ for (struct sr_dev_driver **driver = drivers; *driver; driver++)
+ driver_scan(*driver);
+}
+
+void DeviceManager::release_driver(struct sr_dev_driver *const driver)
+{
+ BOOST_FOREACH(shared_ptr<device::Device> dev, _devices) {
+ assert(dev);
+ if(dev->dev_inst()->driver == driver)
+ dev->release();
+ }
+
+ // Clear all the old device instances from this driver
+ sr_dev_clear(driver);
+}
+
+bool DeviceManager::compare_devices(shared_ptr<device::Device> a,
+ shared_ptr<device::Device> b)
+{
+ assert(a);
+ assert(b);
+ return a->format_device_title().compare(b->format_device_title()) < 0;
+}
+
+} // namespace pv
diff --git a/pv/devicemanager.h b/pv/devicemanager.h
new file mode 100644
index 0000000..acef8a4
--- /dev/null
+++ b/pv/devicemanager.h
@@ -0,0 +1,75 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_DEVICEMANAGER_H
+#define PULSEVIEW_PV_DEVICEMANAGER_H
+
+#include <glib.h>
+
+#include <list>
+#include <string>
+
+#include <boost/shared_ptr.hpp>
+
+struct sr_context;
+struct sr_dev_driver;
+
+namespace pv {
+
+class SigSession;
+
+namespace device {
+class Device;
+}
+
+class DeviceManager
+{
+public:
+ DeviceManager(struct sr_context *sr_ctx);
+
+ ~DeviceManager();
+
+ const std::list< boost::shared_ptr<pv::device::Device> >&
+ devices() const;
+
+ std::list< boost::shared_ptr<pv::device::Device> > driver_scan(
+ struct sr_dev_driver *const driver,
+ GSList *const drvopts = NULL);
+
+private:
+ void init_drivers();
+
+ void release_devices();
+
+ void scan_all_drivers();
+
+ void release_driver(struct sr_dev_driver *const driver);
+
+ static bool compare_devices(boost::shared_ptr<device::Device> a,
+ boost::shared_ptr<device::Device> b);
+
+private:
+ struct sr_context *const _sr_ctx;
+ std::list< boost::shared_ptr<pv::device::Device> > _devices;
+};
+
+} // namespace pv
+
+#endif // PULSEVIEW_PV_DEVICEMANAGER_H
diff --git a/pv/dialogs/about.cpp b/pv/dialogs/about.cpp
new file mode 100644
index 0000000..fe0f16a
--- /dev/null
+++ b/pv/dialogs/about.cpp
@@ -0,0 +1,121 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef ENABLE_DECODE
+#include <libsigrokdecode/libsigrokdecode.h>
+#endif
+
+#include <QTextDocument>
+
+#include "about.h"
+#include <ui_about.h>
+
+/* __STDC_FORMAT_MACROS is required for PRIu64 and friends (in C++). */
+#define __STDC_FORMAT_MACROS
+#include <glib.h>
+#include <libsigrok/libsigrok.h>
+
+
+namespace pv {
+namespace dialogs {
+
+About::About(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::About)
+{
+ struct sr_dev_driver **drivers;
+ struct sr_input_format **inputs;
+ struct sr_output_format **outputs;
+
+#ifdef ENABLE_DECODE
+ struct srd_decoder *dec;
+#endif
+
+ QString s;
+
+ ui->setupUi(this);
+
+ /* Setup the version field */
+ ui->versionInfo->setText(tr("%1 %2<br />%3<br /><a href=\"%4\">%4</a>")
+ .arg(QApplication::applicationName())
+ .arg(QApplication::applicationVersion())
+ .arg(tr("GNU GPL, version 3 or later"))
+ .arg(QApplication::organizationDomain()));
+ ui->versionInfo->setOpenExternalLinks(true);
+
+ s.append("<table>");
+
+ /* Set up the supported field */
+ s.append("<tr><td colspan=\"2\"><b>" +
+ tr("Supported hardware drivers:") +
+ "</b></td></tr>");
+ drivers = sr_driver_list();
+ for (int i = 0; drivers[i]; ++i) {
+ s.append(QString("<tr><td><i>%1</i></td><td>%2</td></tr>")
+ .arg(QString::fromUtf8(drivers[i]->name))
+ .arg(QString::fromUtf8(drivers[i]->longname)));
+ }
+
+ s.append("<tr><td colspan=\"2\"><b>" +
+ tr("Supported input formats:") +
+ "</b></td></tr>");
+ inputs = sr_input_list();
+ for (int i = 0; inputs[i]; ++i) {
+ s.append(QString("<tr><td><i>%1</i></td><td>%2</td></tr>")
+ .arg(QString::fromUtf8(inputs[i]->id))
+ .arg(QString::fromUtf8(inputs[i]->description)));
+ }
+
+ s.append("<tr><td colspan=\"2\"><b>" +
+ tr("Supported output formats:") +
+ "</b></td></tr>");
+ outputs = sr_output_list();
+ for (int i = 0; outputs[i]; ++i) {
+ s.append(QString("<tr><td><i>%1</i></td><td>%2</td></tr>")
+ .arg(QString::fromUtf8(outputs[i]->id))
+ .arg(QString::fromUtf8(outputs[i]->description)));
+ }
+
+#ifdef ENABLE_DECODE
+ s.append("<tr><td colspan=\"2\"><b>" +
+ tr("Supported protocol decoders:") +
+ "</b></td></tr>");
+ for (const GSList *l = srd_decoder_list(); l; l = l->next) {
+ dec = (struct srd_decoder *)l->data;
+ s.append(QString("<tr><td><i>%1</i></td><td>%2</td></tr>")
+ .arg(QString::fromUtf8(dec->id))
+ .arg(QString::fromUtf8(dec->longname)));
+ }
+#endif
+
+ s.append("</table>");
+
+ supportedDoc.reset(new QTextDocument(this));
+ supportedDoc->setHtml(s);
+ ui->supportList->setDocument(supportedDoc.get());
+}
+
+About::~About()
+{
+ delete ui;
+}
+
+} // namespace dialogs
+} // namespace pv
diff --git a/pv/dialogs/about.h b/pv/dialogs/about.h
new file mode 100644
index 0000000..cc153ad
--- /dev/null
+++ b/pv/dialogs/about.h
@@ -0,0 +1,53 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_ABOUT_H
+#define PULSEVIEW_PV_ABOUT_H
+
+#include <QDialog>
+
+#include <memory>
+
+class QTextDocument;
+
+namespace Ui {
+class About;
+}
+
+namespace pv {
+namespace dialogs {
+
+class About : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit About(QWidget *parent = 0);
+ ~About();
+
+private:
+ Ui::About *ui;
+ std::auto_ptr<QTextDocument> supportedDoc;
+};
+
+} // namespace dialogs
+} // namespace pv
+
+#endif // PULSEVIEW_PV_ABOUT_H
diff --git a/pv/dialogs/about.ui b/pv/dialogs/about.ui
new file mode 100644
index 0000000..10163f9
--- /dev/null
+++ b/pv/dialogs/about.ui
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>About</class>
+ <widget class="QDialog" name="About">
+ <property name="windowModality">
+ <enum>Qt::WindowModal</enum>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>600</width>
+ <height>400</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>About</string>
+ </property>
+ <property name="whatsThis">
+ <string/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="icon">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="pulseview.qrc">:/icons/sigrok-logo-notext.png</pixmap>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="versionInfo">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QTextBrowser" name="supportList"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="pulseview.qrc"/>
+ </resources>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>About</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>About</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/pv/dialogs/connect.cpp b/pv/dialogs/connect.cpp
new file mode 100644
index 0000000..d5ab9c4
--- /dev/null
+++ b/pv/dialogs/connect.cpp
@@ -0,0 +1,241 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012-2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <boost/foreach.hpp>
+
+#include <libsigrok/libsigrok.h>
+
+#include "connect.h"
+
+#include "pv/devicemanager.h"
+#include "pv/device/device.h"
+
+extern "C" {
+/* __STDC_FORMAT_MACROS is required for PRIu64 and friends (in C++). */
+#define __STDC_FORMAT_MACROS
+#include <glib.h>
+#include <libsigrok/libsigrok.h>
+}
+
+using boost::shared_ptr;
+using std::list;
+using std::string;
+
+extern sr_context *sr_ctx;
+
+namespace pv {
+namespace dialogs {
+
+Connect::Connect(QWidget *parent, pv::DeviceManager &device_manager) :
+ QDialog(parent),
+ _device_manager(device_manager),
+ _layout(this),
+ _form(this),
+ _form_layout(&_form),
+ _drivers(&_form),
+ _serial_device(&_form),
+ _scan_button(tr("Scan for Devices"), this),
+ _device_list(this),
+ _button_box(QDialogButtonBox::Ok | QDialogButtonBox::Cancel,
+ Qt::Horizontal, this)
+{
+ setWindowTitle(tr("Connect to Device"));
+
+ connect(&_button_box, SIGNAL(accepted()), this, SLOT(accept()));
+ connect(&_button_box, SIGNAL(rejected()), this, SLOT(reject()));
+
+ populate_drivers();
+ connect(&_drivers, SIGNAL(activated(int)),
+ this, SLOT(device_selected(int)));
+
+ _form.setLayout(&_form_layout);
+ _form_layout.addRow(tr("Driver"), &_drivers);
+
+ _form_layout.addRow(tr("Serial Port"), &_serial_device);
+
+ unset_connection();
+
+ connect(&_scan_button, SIGNAL(pressed()),
+ this, SLOT(scan_pressed()));
+
+ setLayout(&_layout);
+ _layout.addWidget(&_form);
+ _layout.addWidget(&_scan_button);
+ _layout.addWidget(&_device_list);
+ _layout.addWidget(&_button_box);
+}
+
+shared_ptr<device::Device> Connect::get_selected_device() const
+{
+ const QListWidgetItem *const item = _device_list.currentItem();
+ if (!item)
+ return shared_ptr<device::Device>();
+
+ const sr_dev_inst *const sdi = (sr_dev_inst*)item->data(
+ Qt::UserRole).value<void*>();
+ assert(sdi);
+
+ std::map<const sr_dev_inst*, boost::shared_ptr<pv::device::Device> >::
+ const_iterator iter = _device_map.find(sdi);
+ assert(iter != _device_map.end());
+
+ return (*iter).second;
+}
+
+void Connect::populate_drivers()
+{
+ gsize num_opts = 0;
+ const int32_t *hwopts;
+ struct sr_dev_driver **drivers = sr_driver_list();
+ GVariant *gvar_opts;
+
+ for (int i = 0; drivers[i]; ++i) {
+ /**
+ * We currently only support devices that can deliver
+ * samples at a fixed samplerate i.e. oscilloscopes and
+ * logic analysers.
+ * @todo Add support for non-monotonic devices i.e. DMMs
+ * and sensors.
+ */
+ bool supported_device = false;
+ if ((sr_config_list(drivers[i], NULL, NULL,
+ SR_CONF_DEVICE_OPTIONS, &gvar_opts) == SR_OK)) {
+ hwopts = (const int32_t *)g_variant_get_fixed_array(gvar_opts,
+ &num_opts, sizeof(int32_t));
+ for (unsigned int j = 0; j < num_opts; j++)
+ if (hwopts[j] == SR_CONF_SAMPLERATE) {
+ supported_device = true;
+ break;
+ }
+ }
+
+ if (supported_device)
+ _drivers.addItem(QString("%1 (%2)").arg(
+ drivers[i]->longname).arg(drivers[i]->name),
+ qVariantFromValue((void*)drivers[i]));
+ }
+}
+
+void Connect::unset_connection()
+{
+ _device_list.clear();
+ _device_map.clear();
+ _serial_device.hide();
+ _form_layout.labelForField(&_serial_device)->hide();
+ _button_box.button(QDialogButtonBox::Ok)->setDisabled(true);
+}
+
+void Connect::set_serial_connection()
+{
+ _serial_device.show();
+ _form_layout.labelForField(&_serial_device)->show();
+}
+
+void Connect::scan_pressed()
+{
+ _device_list.clear();
+ _device_map.clear();
+
+ const int index = _drivers.currentIndex();
+ if (index == -1)
+ return;
+
+ sr_dev_driver *const driver = (sr_dev_driver*)_drivers.itemData(
+ index).value<void*>();
+
+ GSList *drvopts = NULL;
+
+ if (_serial_device.isVisible()) {
+ sr_config *const src = (sr_config*)g_try_malloc(sizeof(sr_config));
+ src->key = SR_CONF_CONN;
+ const QByteArray byteArray = _serial_device.text().toUtf8();
+ src->data = g_variant_new_string((const gchar*)byteArray.constData());
+ drvopts = g_slist_append(drvopts, src);
+ }
+
+ const list< shared_ptr<device::Device> > devices =
+ _device_manager.driver_scan(driver, drvopts);
+
+ g_slist_free_full(drvopts, (GDestroyNotify)free_drvopts);
+
+ BOOST_FOREACH(shared_ptr<device::Device> dev_inst, devices)
+ {
+ assert(dev_inst);
+ const sr_dev_inst *const sdi = dev_inst->dev_inst();
+ assert(sdi);
+
+ const string title = dev_inst->format_device_title();
+ QString text = QString::fromUtf8(title.c_str());
+
+ if (sdi->channels) {
+ text += QString(" with %1 channels").arg(
+ g_slist_length(sdi->channels));
+ }
+
+ QListWidgetItem *const item = new QListWidgetItem(text,
+ &_device_list);
+ item->setData(Qt::UserRole, qVariantFromValue((void*)sdi));
+ _device_list.addItem(item);
+ _device_map[sdi] = dev_inst;
+ }
+
+ _device_list.setCurrentRow(0);
+ _button_box.button(QDialogButtonBox::Ok)->setDisabled(_device_list.count() == 0);
+}
+
+void Connect::device_selected(int index)
+{
+ gsize num_opts = 0;
+ const int32_t *hwopts;
+ GVariant *gvar_list;
+ sr_dev_driver *const driver = (sr_dev_driver*)_drivers.itemData(
+ index).value<void*>();
+
+ unset_connection();
+
+ if ((sr_config_list(driver, NULL, NULL,
+ SR_CONF_SCAN_OPTIONS, &gvar_list) == SR_OK)) {
+ hwopts = (const int32_t *)g_variant_get_fixed_array(gvar_list,
+ &num_opts, sizeof(int32_t));
+
+ for (unsigned int i = 0; i < num_opts; i++) {
+ switch(hwopts[i]) {
+ case SR_CONF_SERIALCOMM:
+ set_serial_connection();
+ break;
+
+ default:
+ continue;
+ }
+
+ break;
+ }
+ g_variant_unref(gvar_list);
+ }
+}
+
+void Connect::free_drvopts(struct sr_config *src)
+{
+ g_variant_unref(src->data);
+ g_free(src);
+}
+
+} // namespace dialogs
+} // namespace pv
diff --git a/pv/dialogs/connect.h b/pv/dialogs/connect.h
new file mode 100644
index 0000000..1de0ed8
--- /dev/null
+++ b/pv/dialogs/connect.h
@@ -0,0 +1,95 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012-2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_CONNECT_H
+#define PULSEVIEW_PV_CONNECT_H
+
+#include <boost/shared_ptr.hpp>
+
+#include <QComboBox>
+#include <QDialog>
+#include <QDialogButtonBox>
+#include <QFormLayout>
+#include <QLineEdit>
+#include <QListWidget>
+#include <QPushButton>
+#include <QVBoxLayout>
+
+struct sr_config;
+struct sr_dev_inst;
+
+namespace pv {
+
+class DeviceManager;
+
+namespace device {
+class Device;
+}
+
+namespace dialogs {
+
+class Connect : public QDialog
+{
+ Q_OBJECT
+
+public:
+ Connect(QWidget *parent, pv::DeviceManager &device_manager);
+
+ boost::shared_ptr<device::Device> get_selected_device() const;
+
+private:
+ void populate_drivers();
+
+ void unset_connection();
+
+ void set_serial_connection();
+
+private slots:
+ void device_selected(int index);
+
+ void scan_pressed();
+
+private:
+ static void free_drvopts(sr_config *src);
+
+private:
+ pv::DeviceManager &_device_manager;
+
+ QVBoxLayout _layout;
+
+ QWidget _form;
+ QFormLayout _form_layout;
+
+ QComboBox _drivers;
+
+ QLineEdit _serial_device;
+
+ QPushButton _scan_button;
+ QListWidget _device_list;
+ std::map<const sr_dev_inst*, boost::shared_ptr<pv::device::Device> >
+ _device_map;
+
+ QDialogButtonBox _button_box;
+};
+
+} // namespace dialogs
+} // namespace pv
+
+#endif // PULSEVIEW_PV_CONNECT_H
diff --git a/pv/dialogs/storeprogress.cpp b/pv/dialogs/storeprogress.cpp
new file mode 100644
index 0000000..7853173
--- /dev/null
+++ b/pv/dialogs/storeprogress.cpp
@@ -0,0 +1,84 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2014 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "storeprogress.h"
+
+#include <QMessageBox>
+
+namespace pv {
+namespace dialogs {
+
+StoreProgress::StoreProgress(const QString &file_name,
+ const SigSession &session, QWidget *parent) :
+ QProgressDialog(tr("Saving..."), tr("Cancel"), 0, 0, parent),
+ _session(file_name.toStdString(), session)
+{
+ connect(&_session, SIGNAL(progress_updated()),
+ this, SLOT(on_progress_updated()));
+}
+
+StoreProgress::~StoreProgress()
+{
+ _session.wait();
+}
+
+void StoreProgress::run()
+{
+ if (_session.start())
+ show();
+ else
+ show_error();
+}
+
+void StoreProgress::show_error()
+{
+ QMessageBox msg(parentWidget());
+ msg.setText(tr("Failed to save session."));
+ msg.setInformativeText(_session.error());
+ msg.setStandardButtons(QMessageBox::Ok);
+ msg.setIcon(QMessageBox::Warning);
+ msg.exec();
+}
+
+void StoreProgress::closeEvent(QCloseEvent*)
+{
+ _session.cancel();
+}
+
+void StoreProgress::on_progress_updated()
+{
+ const std::pair<uint64_t, uint64_t> p = _session.progress();
+ assert(p.first <= p.second);
+
+ setValue(p.first);
+ setMaximum(p.second);
+
+ const QString err = _session.error();
+ if (!err.isEmpty()) {
+ show_error();
+ close();
+ }
+
+ if (p.first == p.second)
+ close();
+}
+
+} // dialogs
+} // pv
diff --git a/pv/dialogs/storeprogress.h b/pv/dialogs/storeprogress.h
new file mode 100644
index 0000000..61d08d5
--- /dev/null
+++ b/pv/dialogs/storeprogress.h
@@ -0,0 +1,65 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2014 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_DIALOGS_SAVEPROGRESS_H
+#define PULSEVIEW_PV_DIALOGS_SAVEPROGRESS_H
+
+#include <set>
+
+#include <boost/shared_ptr.hpp>
+
+#include <QProgressDialog>
+
+#include <pv/storesession.h>
+
+namespace pv {
+
+class SigSession;
+
+namespace dialogs {
+
+class StoreProgress : public QProgressDialog
+{
+ Q_OBJECT
+
+public:
+ StoreProgress(const QString &file_name, const SigSession &session,
+ QWidget *parent = 0);
+
+ virtual ~StoreProgress();
+
+ void run();
+
+private:
+ void show_error();
+
+ void closeEvent(QCloseEvent*);
+
+private slots:
+ void on_progress_updated();
+
+private:
+ pv::StoreSession _session;
+};
+
+} // dialogs
+} // pv
+
+#endif // PULSEVIEW_PV_DIALOGS_SAVEPROGRESS_H
diff --git a/pv/mainwindow.cpp b/pv/mainwindow.cpp
new file mode 100644
index 0000000..0089b11
--- /dev/null
+++ b/pv/mainwindow.cpp
@@ -0,0 +1,456 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef ENABLE_DECODE
+#include <libsigrokdecode/libsigrokdecode.h>
+#endif
+
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+
+#include <algorithm>
+#include <iterator>
+
+#include <QAction>
+#include <QApplication>
+#include <QButtonGroup>
+#include <QFileDialog>
+#include <QMessageBox>
+#include <QMenu>
+#include <QMenuBar>
+#include <QStatusBar>
+#include <QVBoxLayout>
+#include <QWidget>
+
+#include "mainwindow.h"
+
+#include "devicemanager.h"
+#include "device/device.h"
+#include "dialogs/about.h"
+#include "dialogs/connect.h"
+#include "dialogs/storeprogress.h"
+#include "toolbars/samplingbar.h"
+#include "view/logicsignal.h"
+#include "view/view.h"
+#ifdef ENABLE_DECODE
+#include "widgets/decodermenu.h"
+#endif
+
+/* __STDC_FORMAT_MACROS is required for PRIu64 and friends (in C++). */
+#define __STDC_FORMAT_MACROS
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <glib.h>
+#include <libsigrok/libsigrok.h>
+
+using boost::shared_ptr;
+using std::list;
+
+namespace pv {
+
+namespace view {
+class SelectableItem;
+}
+
+MainWindow::MainWindow(DeviceManager &device_manager,
+ const char *open_file_name,
+ QWidget *parent) :
+ QMainWindow(parent),
+ _device_manager(device_manager),
+ _session(device_manager)
+{
+ setup_ui();
+ if (open_file_name) {
+ const QString s(QString::fromUtf8(open_file_name));
+ QMetaObject::invokeMethod(this, "load_file",
+ Qt::QueuedConnection,
+ Q_ARG(QString, s));
+ }
+}
+
+void MainWindow::setup_ui()
+{
+ setObjectName(QString::fromUtf8("MainWindow"));
+
+ resize(1024, 768);
+
+ // Set the window icon
+ QIcon icon;
+ icon.addFile(QString::fromUtf8(":/icons/sigrok-logo-notext.png"),
+ QSize(), QIcon::Normal, QIcon::Off);
+ setWindowIcon(icon);
+
+ // Setup the central widget
+ _central_widget = new QWidget(this);
+ _vertical_layout = new QVBoxLayout(_central_widget);
+ _vertical_layout->setSpacing(6);
+ _vertical_layout->setContentsMargins(0, 0, 0, 0);
+ setCentralWidget(_central_widget);
+
+ _view = new pv::view::View(_session, this);
+
+ _vertical_layout->addWidget(_view);
+
+ // Setup the menu bar
+ QMenuBar *const menu_bar = new QMenuBar(this);
+ menu_bar->setGeometry(QRect(0, 0, 400, 25));
+
+ // File Menu
+ QMenu *const menu_file = new QMenu;
+ menu_file->setTitle(QApplication::translate(
+ "MainWindow", "&File", 0, QApplication::UnicodeUTF8));
+
+ QAction *const action_open = new QAction(this);
+ action_open->setText(QApplication::translate(
+ "MainWindow", "&Open...", 0, QApplication::UnicodeUTF8));
+ action_open->setIcon(QIcon::fromTheme("document-open",
+ QIcon(":/icons/document-open.png")));
+ action_open->setObjectName(QString::fromUtf8("actionOpen"));
+ menu_file->addAction(action_open);
+
+ QAction *const action_save_as = new QAction(this);
+ action_save_as->setText(QApplication::translate(
+ "MainWindow", "&Save As...", 0, QApplication::UnicodeUTF8));
+ action_save_as->setIcon(QIcon::fromTheme("document-save-as",
+ QIcon(":/icons/document-save-as.png")));
+ action_save_as->setObjectName(QString::fromUtf8("actionSaveAs"));
+ menu_file->addAction(action_save_as);
+
+ menu_file->addSeparator();
+
+ QAction *const action_connect = new QAction(this);
+ action_connect->setText(QApplication::translate(
+ "MainWindow", "&Connect to Device...", 0,
+ QApplication::UnicodeUTF8));
+ action_connect->setObjectName(QString::fromUtf8("actionConnect"));
+ menu_file->addAction(action_connect);
+
+ menu_file->addSeparator();
+
+ QAction *action_quit = new QAction(this);
+ action_quit->setText(QApplication::translate(
+ "MainWindow", "&Quit", 0, QApplication::UnicodeUTF8));
+ action_quit->setIcon(QIcon::fromTheme("application-exit",
+ QIcon(":/icons/application-exit.png")));
+ action_quit->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q));
+ action_quit->setObjectName(QString::fromUtf8("actionQuit"));
+ menu_file->addAction(action_quit);
+
+ // View Menu
+ QMenu *menu_view = new QMenu;
+ menu_view->setTitle(QApplication::translate(
+ "MainWindow", "&View", 0, QApplication::UnicodeUTF8));
+
+ QAction *const action_view_zoom_in = new QAction(this);
+ action_view_zoom_in->setText(QApplication::translate(
+ "MainWindow", "Zoom &In", 0, QApplication::UnicodeUTF8));
+ action_view_zoom_in->setIcon(QIcon::fromTheme("zoom-in",
+ QIcon(":/icons/zoom-in.png")));
+ // simply using Qt::Key_Plus shows no + in the menu
+ action_view_zoom_in->setShortcut(QKeySequence::ZoomIn);
+ action_view_zoom_in->setObjectName(
+ QString::fromUtf8("actionViewZoomIn"));
+ menu_view->addAction(action_view_zoom_in);
+
+ QAction *const action_view_zoom_out = new QAction(this);
+ action_view_zoom_out->setText(QApplication::translate(
+ "MainWindow", "Zoom &Out", 0, QApplication::UnicodeUTF8));
+ action_view_zoom_out->setIcon(QIcon::fromTheme("zoom-out",
+ QIcon(":/icons/zoom-out.png")));
+ action_view_zoom_out->setShortcut(QKeySequence::ZoomOut);
+ action_view_zoom_out->setObjectName(
+ QString::fromUtf8("actionViewZoomOut"));
+ menu_view->addAction(action_view_zoom_out);
+
+ QAction *const action_view_zoom_fit = new QAction(this);
+ action_view_zoom_fit->setText(QApplication::translate(
+ "MainWindow", "Zoom to &Fit", 0, QApplication::UnicodeUTF8));
+ action_view_zoom_fit->setIcon(QIcon::fromTheme("zoom-fit",
+ QIcon(":/icons/zoom-fit.png")));
+ action_view_zoom_fit->setShortcut(QKeySequence(Qt::Key_F));
+ action_view_zoom_fit->setObjectName(
+ QString::fromUtf8("actionViewZoomFit"));
+ menu_view->addAction(action_view_zoom_fit);
+
+ QAction *const action_view_zoom_one_to_one = new QAction(this);
+ action_view_zoom_one_to_one->setText(QApplication::translate(
+ "MainWindow", "Zoom to &One-to-One", 0,
+ QApplication::UnicodeUTF8));
+ action_view_zoom_one_to_one->setIcon(QIcon::fromTheme("zoom-original",
+ QIcon(":/icons/zoom-original.png")));
+ action_view_zoom_one_to_one->setShortcut(QKeySequence(Qt::Key_O));
+ action_view_zoom_one_to_one->setObjectName(
+ QString::fromUtf8("actionViewZoomOneToOne"));
+ menu_view->addAction(action_view_zoom_one_to_one);
+
+ menu_view->addSeparator();
+
+ QAction *action_view_show_cursors = new QAction(this);
+ action_view_show_cursors->setCheckable(true);
+ action_view_show_cursors->setChecked(_view->cursors_shown());
+ action_view_show_cursors->setShortcut(QKeySequence(Qt::Key_C));
+ action_view_show_cursors->setObjectName(
+ QString::fromUtf8("actionViewShowCursors"));
+ action_view_show_cursors->setText(QApplication::translate(
+ "MainWindow", "Show &Cursors", 0, QApplication::UnicodeUTF8));
+ menu_view->addAction(action_view_show_cursors);
+
+ // Decoders Menu
+#ifdef ENABLE_DECODE
+ QMenu *const menu_decoders = new QMenu;
+ menu_decoders->setTitle(QApplication::translate(
+ "MainWindow", "&Decoders", 0, QApplication::UnicodeUTF8));
+
+ pv::widgets::DecoderMenu *const menu_decoders_add =
+ new pv::widgets::DecoderMenu(menu_decoders, true);
+ menu_decoders_add->setTitle(QApplication::translate(
+ "MainWindow", "&Add", 0, QApplication::UnicodeUTF8));
+ connect(menu_decoders_add, SIGNAL(decoder_selected(srd_decoder*)),
+ this, SLOT(add_decoder(srd_decoder*)));
+
+ menu_decoders->addMenu(menu_decoders_add);
+#endif
+
+ // Help Menu
+ QMenu *const menu_help = new QMenu;
+ menu_help->setTitle(QApplication::translate(
+ "MainWindow", "&Help", 0, QApplication::UnicodeUTF8));
+
+ QAction *const action_about = new QAction(this);
+ action_about->setObjectName(QString::fromUtf8("actionAbout"));
+ action_about->setText(QApplication::translate(
+ "MainWindow", "&About...", 0, QApplication::UnicodeUTF8));
+ menu_help->addAction(action_about);
+
+ menu_bar->addAction(menu_file->menuAction());
+ menu_bar->addAction(menu_view->menuAction());
+#ifdef ENABLE_DECODE
+ menu_bar->addAction(menu_decoders->menuAction());
+#endif
+ menu_bar->addAction(menu_help->menuAction());
+
+ setMenuBar(menu_bar);
+ QMetaObject::connectSlotsByName(this);
+
+ // Setup the toolbar
+ QToolBar *const toolbar = new QToolBar(tr("Main Toolbar"), this);
+ toolbar->addAction(action_open);
+ toolbar->addSeparator();
+ toolbar->addAction(action_view_zoom_in);
+ toolbar->addAction(action_view_zoom_out);
+ toolbar->addAction(action_view_zoom_fit);
+ addToolBar(toolbar);
+
+ // Setup the sampling bar
+ _sampling_bar = new toolbars::SamplingBar(_session, this);
+
+ // Populate the device list and select the initially selected device
+ update_device_list();
+
+ connect(_sampling_bar, SIGNAL(run_stop()), this,
+ SLOT(run_stop()));
+ addToolBar(_sampling_bar);
+
+ // Set the title
+ setWindowTitle(QApplication::translate("MainWindow", "PulseView", 0,
+ QApplication::UnicodeUTF8));
+
+ // Setup _session events
+ connect(&_session, SIGNAL(capture_state_changed(int)), this,
+ SLOT(capture_state_changed(int)));
+
+}
+
+void MainWindow::session_error(
+ const QString text, const QString info_text)
+{
+ QMetaObject::invokeMethod(this, "show_session_error",
+ Qt::QueuedConnection, Q_ARG(QString, text),
+ Q_ARG(QString, info_text));
+}
+
+void MainWindow::update_device_list()
+{
+ assert(_sampling_bar);
+
+ shared_ptr<pv::device::DevInst> selected_device = _session.get_device();
+ list< shared_ptr<device::DevInst> > devices;
+ std::copy(_device_manager.devices().begin(),
+ _device_manager.devices().end(), std::back_inserter(devices));
+
+ if (std::find(devices.begin(), devices.end(), selected_device) ==
+ devices.end())
+ devices.push_back(selected_device);
+ assert(selected_device);
+
+ _sampling_bar->set_device_list(devices, selected_device);
+}
+
+void MainWindow::load_file(QString file_name)
+{
+ const QString errorMessage(
+ QString("Failed to load file %1").arg(file_name));
+ const QString infoMessage;
+
+ try {
+ _session.set_file(file_name.toStdString());
+ } catch(QString e) {
+ show_session_error(tr("Failed to load ") + file_name, e);
+ _session.set_default_device();
+ update_device_list();
+ return;
+ }
+
+ update_device_list();
+
+ _session.start_capture(boost::bind(&MainWindow::session_error, this,
+ errorMessage, infoMessage));
+}
+
+void MainWindow::show_session_error(
+ const QString text, const QString info_text)
+{
+ QMessageBox msg(this);
+ msg.setText(text);
+ msg.setInformativeText(info_text);
+ msg.setStandardButtons(QMessageBox::Ok);
+ msg.setIcon(QMessageBox::Warning);
+ msg.exec();
+}
+
+void MainWindow::on_actionOpen_triggered()
+{
+ // Show the dialog
+ const QString file_name = QFileDialog::getOpenFileName(
+ this, tr("Open File"), "", tr(
+ "Sigrok Sessions (*.sr);;"
+ "All Files (*.*)"));
+ if (!file_name.isEmpty())
+ load_file(file_name);
+}
+
+void MainWindow::on_actionSaveAs_triggered()
+{
+ using pv::dialogs::StoreProgress;
+
+ // Stop any currently running capture session
+ _session.stop_capture();
+
+ // Show the dialog
+ const QString file_name = QFileDialog::getSaveFileName(
+ this, tr("Save File"), "", tr("Sigrok Sessions (*.sr)"));
+
+ if (file_name.isEmpty())
+ return;
+
+ StoreProgress *dlg = new StoreProgress(file_name, _session, this);
+ dlg->run();
+}
+
+void MainWindow::on_actionConnect_triggered()
+{
+ // Stop any currently running capture session
+ _session.stop_capture();
+
+ dialogs::Connect dlg(this, _device_manager);
+
+ // If the user selected a device, select it in the device list. Select the
+ // current device otherwise.
+ if (dlg.exec())
+ _session.set_device(dlg.get_selected_device());
+
+ update_device_list();
+}
+
+void MainWindow::on_actionQuit_triggered()
+{
+ close();
+}
+
+void MainWindow::on_actionViewZoomIn_triggered()
+{
+ _view->zoom(1);
+}
+
+void MainWindow::on_actionViewZoomOut_triggered()
+{
+ _view->zoom(-1);
+}
+
+void MainWindow::on_actionViewZoomFit_triggered()
+{
+ _view->zoom_fit();
+}
+
+void MainWindow::on_actionViewZoomOneToOne_triggered()
+{
+ _view->zoom_one_to_one();
+}
+
+void MainWindow::on_actionViewShowCursors_triggered()
+{
+ assert(_view);
+
+ const bool show = !_view->cursors_shown();
+ if(show)
+ _view->centre_cursors();
+
+ _view->show_cursors(show);
+}
+
+void MainWindow::on_actionAbout_triggered()
+{
+ dialogs::About dlg(this);
+ dlg.exec();
+}
+
+void MainWindow::add_decoder(srd_decoder *decoder)
+{
+#ifdef ENABLE_DECODE
+ assert(decoder);
+ _session.add_decoder(decoder);
+#else
+ (void)decoder;
+#endif
+}
+
+void MainWindow::run_stop()
+{
+ switch(_session.get_capture_state()) {
+ case SigSession::Stopped:
+ _session.start_capture(
+ boost::bind(&MainWindow::session_error, this,
+ QString("Capture failed"), _1));
+ break;
+
+ case SigSession::AwaitingTrigger:
+ case SigSession::Running:
+ _session.stop_capture();
+ break;
+ }
+}
+
+void MainWindow::capture_state_changed(int state)
+{
+ _sampling_bar->set_capture_state((pv::SigSession::capture_state)state);
+}
+
+} // namespace pv
diff --git a/pv/mainwindow.h b/pv/mainwindow.h
new file mode 100644
index 0000000..1f9dd52
--- /dev/null
+++ b/pv/mainwindow.h
@@ -0,0 +1,122 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_MAINWINDOW_H
+#define PULSEVIEW_PV_MAINWINDOW_H
+
+#include <list>
+
+#include <boost/weak_ptr.hpp>
+
+#include <QMainWindow>
+
+#include "sigsession.h"
+
+struct srd_decoder;
+
+class QVBoxLayout;
+
+namespace pv {
+
+class DeviceManager;
+
+namespace device {
+class DevInst;
+}
+
+namespace toolbars {
+class ContextBar;
+class SamplingBar;
+}
+
+namespace view {
+class View;
+}
+
+namespace widgets {
+class DecoderMenu;
+}
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ explicit MainWindow(DeviceManager &device_manager,
+ const char *open_file_name = NULL,
+ QWidget *parent = 0);
+
+private:
+ void setup_ui();
+
+ void session_error(const QString text, const QString info_text);
+
+ /**
+ * Updates the device list in the sampling bar
+ */
+ void update_device_list();
+
+private slots:
+ void load_file(QString file_name);
+
+
+ void show_session_error(
+ const QString text, const QString info_text);
+
+ void on_actionOpen_triggered();
+ void on_actionSaveAs_triggered();
+ void on_actionQuit_triggered();
+
+ void on_actionConnect_triggered();
+
+ void on_actionViewZoomIn_triggered();
+
+ void on_actionViewZoomOut_triggered();
+
+ void on_actionViewZoomFit_triggered();
+
+ void on_actionViewZoomOneToOne_triggered();
+
+ void on_actionViewShowCursors_triggered();
+
+ void on_actionAbout_triggered();
+
+ void add_decoder(srd_decoder *decoder);
+
+ void run_stop();
+
+ void capture_state_changed(int state);
+
+private:
+ DeviceManager &_device_manager;
+
+ SigSession _session;
+
+ pv::view::View *_view;
+
+ QWidget *_central_widget;
+ QVBoxLayout *_vertical_layout;
+
+ toolbars::SamplingBar *_sampling_bar;
+};
+
+} // namespace pv
+
+#endif // PULSEVIEW_PV_MAINWINDOW_H
diff --git a/pv/popups/deviceoptions.cpp b/pv/popups/deviceoptions.cpp
new file mode 100644
index 0000000..13055fc
--- /dev/null
+++ b/pv/popups/deviceoptions.cpp
@@ -0,0 +1,53 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "deviceoptions.h"
+
+#include <boost/foreach.hpp>
+
+#include <QFormLayout>
+#include <QListWidget>
+
+#include <pv/prop/property.h>
+
+using boost::shared_ptr;
+
+namespace pv {
+namespace popups {
+
+DeviceOptions::DeviceOptions(shared_ptr<device::DevInst> dev_inst,
+ QWidget *parent) :
+ Popup(parent),
+ _dev_inst(dev_inst),
+ _layout(this),
+ _binding(dev_inst)
+{
+ setLayout(&_layout);
+
+ _layout.addWidget(_binding.get_property_form(this, true));
+}
+
+pv::prop::binding::DeviceOptions& DeviceOptions::binding()
+{
+ return _binding;
+}
+
+} // namespace popups
+} // namespace pv
diff --git a/pv/popups/deviceoptions.h b/pv/popups/deviceoptions.h
new file mode 100644
index 0000000..945959b
--- /dev/null
+++ b/pv/popups/deviceoptions.h
@@ -0,0 +1,54 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_POPUPS_DEVICEOPTIONS_H
+#define PULSEVIEW_PV_POPUPS_DEVICEOPTIONS_H
+
+#include <QGroupBox>
+#include <QVBoxLayout>
+
+#include <pv/prop/binding/deviceoptions.h>
+#include <pv/widgets/popup.h>
+
+namespace pv {
+namespace popups {
+
+class DeviceOptions : public pv::widgets::Popup
+{
+ Q_OBJECT
+
+public:
+ DeviceOptions(boost::shared_ptr<device::DevInst> dev_inst,
+ QWidget *parent);
+
+ pv::prop::binding::DeviceOptions& binding();
+
+private:
+ boost::shared_ptr<device::DevInst> _dev_inst;
+
+ QVBoxLayout _layout;
+
+ pv::prop::binding::DeviceOptions _binding;
+};
+
+} // namespace popups
+} // namespace pv
+
+#endif // PULSEVIEW_PV_POPUPS_DEVICEOPTIONS_H
diff --git a/pv/popups/probes.cpp b/pv/popups/probes.cpp
new file mode 100644
index 0000000..ca1a2fb
--- /dev/null
+++ b/pv/popups/probes.cpp
@@ -0,0 +1,258 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <map>
+
+#include <boost/foreach.hpp>
+
+#include <QCheckBox>
+#include <QFormLayout>
+#include <QGridLayout>
+#include <QLabel>
+
+#include "probes.h"
+
+#include <pv/device/devinst.h>
+#include <pv/prop/binding/deviceoptions.h>
+#include <pv/sigsession.h>
+#include <pv/view/signal.h>
+
+using namespace Qt;
+
+using boost::shared_ptr;
+using std::map;
+using std::set;
+using std::vector;
+
+using pv::view::Signal;
+
+namespace pv {
+namespace popups {
+
+Probes::Probes(SigSession &session, QWidget *parent) :
+ Popup(parent),
+ _session(session),
+ _updating_probes(false),
+ _enable_all_probes(tr("Enable All"), this),
+ _disable_all_probes(tr("Disable All"), this),
+ _check_box_mapper(this)
+{
+ // Create the layout
+ setLayout(&_layout);
+
+ shared_ptr<device::DevInst> dev_inst = _session.get_device();
+ assert(dev_inst);
+ const sr_dev_inst *const sdi = dev_inst->dev_inst();
+ assert(sdi);
+
+ // Collect a set of signals
+ map<const sr_channel*, shared_ptr<Signal> > signal_map;
+ const vector< shared_ptr<Signal> > sigs = _session.get_signals();
+ BOOST_FOREACH(const shared_ptr<Signal> &sig, sigs)
+ signal_map[sig->probe()] = sig;
+
+ // Populate channel groups
+ for (const GSList *g = sdi->channel_groups; g; g = g->next)
+ {
+ const sr_channel_group *const group =
+ (const sr_channel_group*)g->data;
+ assert(group);
+
+ // Make a set of signals, and removed this signals from the
+ // signal map.
+ vector< shared_ptr<Signal> > group_sigs;
+ for (const GSList *p = group->channels; p; p = p->next)
+ {
+ const sr_channel *const probe = (const sr_channel*)p->data;
+ assert(probe);
+
+ const map<const sr_channel*, shared_ptr<Signal> >::
+ iterator iter = signal_map.find(probe);
+ assert(iter != signal_map.end());
+
+ group_sigs.push_back((*iter).second);
+ signal_map.erase(iter);
+ }
+
+ populate_group(group, group_sigs);
+ }
+
+ // Make a vector of the remaining probes
+ vector< shared_ptr<Signal> > global_sigs;
+ for (const GSList *p = sdi->channels; p; p = p->next)
+ {
+ const sr_channel *const probe = (const sr_channel*)p->data;
+ assert(probe);
+
+ const map<const sr_channel*, shared_ptr<Signal> >::
+ const_iterator iter = signal_map.find(probe);
+ if (iter != signal_map.end())
+ global_sigs.push_back((*iter).second);
+ }
+
+ // Create a group
+ populate_group(NULL, global_sigs);
+
+ // Create the enable/disable all buttons
+ connect(&_enable_all_probes, SIGNAL(clicked()),
+ this, SLOT(enable_all_probes()));
+ connect(&_disable_all_probes, SIGNAL(clicked()),
+ this, SLOT(disable_all_probes()));
+
+ _enable_all_probes.setFlat(true);
+ _disable_all_probes.setFlat(true);
+
+ _buttons_bar.addWidget(&_enable_all_probes);
+ _buttons_bar.addWidget(&_disable_all_probes);
+ _buttons_bar.addStretch(1);
+
+ _layout.addRow(&_buttons_bar);
+
+ // Connect the check-box signal mapper
+ connect(&_check_box_mapper, SIGNAL(mapped(QWidget*)),
+ this, SLOT(on_probe_checked(QWidget*)));
+}
+
+void Probes::set_all_probes(bool set)
+{
+ _updating_probes = true;
+
+ for (map<QCheckBox*, shared_ptr<Signal> >::const_iterator i =
+ _check_box_signal_map.begin();
+ i != _check_box_signal_map.end(); i++)
+ {
+ const shared_ptr<Signal> sig = (*i).second;
+ assert(sig);
+
+ sig->enable(set);
+ (*i).first->setChecked(set);
+ }
+
+ _updating_probes = false;
+}
+
+void Probes::populate_group(const sr_channel_group *group,
+ const vector< shared_ptr<pv::view::Signal> > sigs)
+{
+ using pv::prop::binding::DeviceOptions;
+
+ // Only bind options if this is a group. We don't do it for general
+ // options, because these properties are shown in the device config
+ // popup.
+ shared_ptr<DeviceOptions> binding;
+ if (group)
+ binding = shared_ptr<DeviceOptions>(new DeviceOptions(
+ _session.get_device(), group));
+
+ // Create a title if the group is going to have any content
+ if ((!sigs.empty() || (binding && !binding->properties().empty())) &&
+ group && group->name)
+ _layout.addRow(new QLabel(
+ QString("<h3>%1</h3>").arg(group->name)));
+
+ // Create the channel group grid
+ QGridLayout *const probe_grid =
+ create_channel_group_grid(sigs);
+ _layout.addRow(probe_grid);
+
+ // Create the channel group options
+ if (binding)
+ {
+ binding->add_properties_to_form(&_layout, true);
+ _group_bindings.push_back(binding);
+ }
+}
+
+QGridLayout* Probes::create_channel_group_grid(
+ const vector< shared_ptr<pv::view::Signal> > sigs)
+{
+ int row = 0, col = 0;
+ QGridLayout *const grid = new QGridLayout();
+
+ BOOST_FOREACH(const shared_ptr<pv::view::Signal>& sig, sigs)
+ {
+ assert(sig);
+
+ QCheckBox *const checkbox = new QCheckBox(sig->get_name());
+ _check_box_mapper.setMapping(checkbox, checkbox);
+ connect(checkbox, SIGNAL(toggled(bool)),
+ &_check_box_mapper, SLOT(map()));
+
+ grid->addWidget(checkbox, row, col);
+
+ _check_box_signal_map[checkbox] = sig;
+
+ if(++col >= 8)
+ col = 0, row++;
+ }
+
+ return grid;
+}
+
+void Probes::showEvent(QShowEvent *e)
+{
+ pv::widgets::Popup::showEvent(e);
+
+ _updating_probes = true;
+
+ for (map<QCheckBox*, shared_ptr<Signal> >::const_iterator i =
+ _check_box_signal_map.begin();
+ i != _check_box_signal_map.end(); i++)
+ {
+ const shared_ptr<Signal> sig = (*i).second;
+ assert(sig);
+
+ (*i).first->setChecked(sig->enabled());
+ }
+
+ _updating_probes = false;
+}
+
+void Probes::on_probe_checked(QWidget *widget)
+{
+ if (_updating_probes)
+ return;
+
+ QCheckBox *const check_box = (QCheckBox*)widget;
+ assert(check_box);
+
+ // Look up the signal of this check-box
+ map< QCheckBox*, shared_ptr<Signal> >::const_iterator iter =
+ _check_box_signal_map.find((QCheckBox*)check_box);
+ assert(iter != _check_box_signal_map.end());
+
+ const shared_ptr<pv::view::Signal> s = (*iter).second;
+ assert(s);
+
+ s->enable(check_box->isChecked());
+}
+
+void Probes::enable_all_probes()
+{
+ set_all_probes(true);
+}
+
+void Probes::disable_all_probes()
+{
+ set_all_probes(false);
+}
+
+} // popups
+} // pv
diff --git a/pv/popups/probes.h b/pv/popups/probes.h
new file mode 100644
index 0000000..dbf90cd
--- /dev/null
+++ b/pv/popups/probes.h
@@ -0,0 +1,104 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_POPUPS_PROBES_H
+#define PULSEVIEW_PV_POPUPS_PROBES_H
+
+#include <map>
+#include <vector>
+
+#include <boost/shared_ptr.hpp>
+
+#include <QFormLayout>
+#include <QHBoxLayout>
+#include <QPushButton>
+#include <QSignalMapper>
+
+#include <pv/widgets/popup.h>
+
+struct sr_channel_group;
+
+class QCheckBox;
+class QGridLayout;
+
+namespace pv {
+
+class SigSession;
+
+namespace prop {
+namespace binding {
+class DeviceOptions;
+}
+}
+
+namespace view {
+class Signal;
+}
+
+namespace popups {
+
+class Probes : public pv::widgets::Popup
+{
+ Q_OBJECT
+
+public:
+ Probes(SigSession &_session, QWidget *parent);
+
+private:
+ void set_all_probes(bool set);
+
+ void populate_group(const sr_channel_group *group,
+ const std::vector< boost::shared_ptr<pv::view::Signal> > sigs);
+
+ QGridLayout* create_channel_group_grid(
+ const std::vector< boost::shared_ptr<pv::view::Signal> > sigs);
+
+private:
+ void showEvent(QShowEvent *e);
+
+private slots:
+ void on_probe_checked(QWidget *widget);
+
+ void enable_all_probes();
+ void disable_all_probes();
+
+private:
+ pv::SigSession &_session;
+
+ QFormLayout _layout;
+
+ bool _updating_probes;
+
+ std::vector< boost::shared_ptr<pv::prop::binding::DeviceOptions> >
+ _group_bindings;
+ std::map< QCheckBox*, boost::shared_ptr<pv::view::Signal> >
+ _check_box_signal_map;
+
+ QHBoxLayout _buttons_bar;
+ QPushButton _enable_all_probes;
+ QPushButton _disable_all_probes;
+
+ QSignalMapper _check_box_mapper;
+};
+
+} // popups
+} // pv
+
+#endif // PULSEVIEW_PV_POPUPS_PROBES_H
diff --git a/pv/prop/binding/binding.cpp b/pv/prop/binding/binding.cpp
new file mode 100644
index 0000000..3a6c73f
--- /dev/null
+++ b/pv/prop/binding/binding.cpp
@@ -0,0 +1,94 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <boost/foreach.hpp>
+
+#include <QFormLayout>
+
+#include <pv/prop/property.h>
+
+#include "binding.h"
+
+using boost::shared_ptr;
+
+namespace pv {
+namespace prop {
+namespace binding {
+
+const std::vector< boost::shared_ptr<Property> >& Binding::properties()
+{
+ return _properties;
+}
+
+void Binding::commit()
+{
+ BOOST_FOREACH(shared_ptr<pv::prop::Property> p, _properties) {
+ assert(p);
+ p->commit();
+ }
+}
+
+void Binding::add_properties_to_form(QFormLayout *layout,
+ bool auto_commit) const
+{
+ assert(layout);
+
+ BOOST_FOREACH(shared_ptr<pv::prop::Property> p, _properties)
+ {
+ assert(p);
+
+ QWidget *const widget = p->get_widget(layout->parentWidget(),
+ auto_commit);
+ if (p->labeled_widget())
+ layout->addRow(widget);
+ else
+ layout->addRow(p->name(), widget);
+ }
+}
+
+QWidget* Binding::get_property_form(QWidget *parent,
+ bool auto_commit) const
+{
+ QWidget *const form = new QWidget(parent);
+ QFormLayout *const layout = new QFormLayout(form);
+ form->setLayout(layout);
+ add_properties_to_form(layout, auto_commit);
+ return form;
+}
+
+QString Binding::print_gvariant(GVariant *const gvar)
+{
+ QString s;
+
+ if (g_variant_is_of_type(gvar, G_VARIANT_TYPE("s")))
+ s = QString::fromUtf8(g_variant_get_string(gvar, NULL));
+ else
+ {
+ gchar *const text = g_variant_print(gvar, FALSE);
+ s = QString::fromUtf8(text);
+ g_free(text);
+ }
+
+ return s;
+}
+
+} // binding
+} // prop
+} // pv
diff --git a/pv/prop/binding/binding.h b/pv/prop/binding/binding.h
new file mode 100644
index 0000000..89c1acb
--- /dev/null
+++ b/pv/prop/binding/binding.h
@@ -0,0 +1,64 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_PROP_BINDING_BINDING_H
+#define PULSEVIEW_PV_PROP_BINDING_BINDING_H
+
+#include <glib.h>
+
+#include <vector>
+#include <boost/shared_ptr.hpp>
+
+#include <QString>
+
+class QFormLayout;
+class QWidget;
+
+namespace pv {
+namespace prop {
+
+class Property;
+
+namespace binding {
+
+class Binding
+{
+public:
+ const std::vector< boost::shared_ptr<Property> >& properties();
+
+ void commit();
+
+ void add_properties_to_form(QFormLayout *layout,
+ bool auto_commit = false) const;
+
+ QWidget* get_property_form(QWidget *parent,
+ bool auto_commit = false) const;
+
+ static QString print_gvariant(GVariant *const gvar);
+
+protected:
+ std::vector< boost::shared_ptr<Property> > _properties;
+};
+
+} // binding
+} // prop
+} // pv
+
+#endif // PULSEVIEW_PV_PROP_BINDING_BINDING_H
diff --git a/pv/prop/binding/decoderoptions.cpp b/pv/prop/binding/decoderoptions.cpp
new file mode 100644
index 0000000..063daf8
--- /dev/null
+++ b/pv/prop/binding/decoderoptions.cpp
@@ -0,0 +1,151 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <libsigrokdecode/libsigrokdecode.h>
+
+#include "decoderoptions.h"
+
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+#include <boost/none_t.hpp>
+
+#include <pv/data/decoderstack.h>
+#include <pv/data/decode/decoder.h>
+#include <pv/prop/double.h>
+#include <pv/prop/enum.h>
+#include <pv/prop/int.h>
+#include <pv/prop/string.h>
+
+using boost::bind;
+using boost::none;
+using boost::shared_ptr;
+using std::make_pair;
+using std::map;
+using std::pair;
+using std::string;
+using std::vector;
+
+namespace pv {
+namespace prop {
+namespace binding {
+
+DecoderOptions::DecoderOptions(
+ shared_ptr<pv::data::DecoderStack> decoder_stack,
+ shared_ptr<data::decode::Decoder> decoder) :
+ _decoder_stack(decoder_stack),
+ _decoder(decoder)
+{
+ assert(_decoder);
+
+ const srd_decoder *const dec = _decoder->decoder();
+ assert(dec);
+
+ for (GSList *l = dec->options; l; l = l->next)
+ {
+ const srd_decoder_option *const opt =
+ (srd_decoder_option*)l->data;
+
+ const QString name = QString::fromUtf8(opt->desc);
+
+ const Property::Getter getter = bind(
+ &DecoderOptions::getter, this, opt->id);
+ const Property::Setter setter = bind(
+ &DecoderOptions::setter, this, opt->id, _1);
+
+ shared_ptr<Property> prop;
+
+ if (opt->values)
+ prop = bind_enum(name, opt, getter, setter);
+ else if (g_variant_is_of_type(opt->def, G_VARIANT_TYPE("d")))
+ prop = shared_ptr<Property>(new Double(name, 2, "",
+ none, none, getter, setter));
+ else if (g_variant_is_of_type(opt->def, G_VARIANT_TYPE("x")))
+ prop = shared_ptr<Property>(
+ new Int(name, "", none, getter, setter));
+ else if (g_variant_is_of_type(opt->def, G_VARIANT_TYPE("s")))
+ prop = shared_ptr<Property>(
+ new String(name, getter, setter));
+ else
+ continue;
+
+ _properties.push_back(prop);
+ }
+}
+
+shared_ptr<Property> DecoderOptions::bind_enum(
+ const QString &name, const srd_decoder_option *option,
+ Property::Getter getter, Property::Setter setter)
+{
+ vector< pair<GVariant*, QString> > values;
+ for (GSList *l = option->values; l; l = l->next) {
+ GVariant *const var = (GVariant*)l->data;
+ assert(var);
+ values.push_back(make_pair(var, print_gvariant(var)));
+ }
+
+ return shared_ptr<Property>(new Enum(name, values, getter, setter));
+}
+
+GVariant* DecoderOptions::getter(const char *id)
+{
+ GVariant *val = NULL;
+
+ assert(_decoder);
+
+ // Get the value from the hash table if it is already present
+ const map<string, GVariant*>& options = _decoder->options();
+ map<string, GVariant*>::const_iterator iter = options.find(id);
+
+ if (iter != options.end())
+ val = (*iter).second;
+ else
+ {
+ assert(_decoder->decoder());
+
+ // Get the default value if not
+ for (GSList *l = _decoder->decoder()->options; l; l = l->next)
+ {
+ const srd_decoder_option *const opt =
+ (srd_decoder_option*)l->data;
+ if (strcmp(opt->id, id) == 0) {
+ val = opt->def;
+ break;
+ }
+ }
+ }
+
+ if (val)
+ g_variant_ref(val);
+
+ return val;
+}
+
+void DecoderOptions::setter(const char *id, GVariant *value)
+{
+ assert(_decoder);
+ _decoder->set_option(id, value);
+
+ assert(_decoder_stack);
+ _decoder_stack->begin_decode();
+}
+
+} // binding
+} // prop
+} // pv
diff --git a/pv/prop/binding/decoderoptions.h b/pv/prop/binding/decoderoptions.h
new file mode 100644
index 0000000..6dec9c2
--- /dev/null
+++ b/pv/prop/binding/decoderoptions.h
@@ -0,0 +1,66 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_PROP_BINDING_DECODEROPTIONS_H
+#define PULSEVIEW_PV_PROP_BINDING_DECODEROPTIONS_H
+
+#include "binding.h"
+
+#include <pv/prop/property.h>
+
+struct srd_decoder_option;
+
+namespace pv {
+
+namespace data {
+class DecoderStack;
+namespace decode {
+class Decoder;
+}
+}
+
+namespace prop {
+namespace binding {
+
+class DecoderOptions : public Binding
+{
+public:
+ DecoderOptions(boost::shared_ptr<pv::data::DecoderStack> decoder_stack,
+ boost::shared_ptr<pv::data::decode::Decoder> decoder);
+
+private:
+ static boost::shared_ptr<Property> bind_enum(const QString &name,
+ const srd_decoder_option *option,
+ Property::Getter getter, Property::Setter setter);
+
+ GVariant* getter(const char *id);
+
+ void setter(const char *id, GVariant *value);
+
+private:
+ boost::shared_ptr<pv::data::DecoderStack> _decoder_stack;
+ boost::shared_ptr<pv::data::decode::Decoder> _decoder;
+};
+
+} // binding
+} // prop
+} // pv
+
+#endif // PULSEVIEW_PV_PROP_BINDING_DECODEROPTIONS_H
diff --git a/pv/prop/binding/deviceoptions.cpp b/pv/prop/binding/deviceoptions.cpp
new file mode 100644
index 0000000..2dc237a
--- /dev/null
+++ b/pv/prop/binding/deviceoptions.cpp
@@ -0,0 +1,192 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <boost/bind.hpp>
+
+#include <stdint.h>
+
+#include <QDebug>
+
+#include "deviceoptions.h"
+
+#include <pv/device/devinst.h>
+#include <pv/prop/bool.h>
+#include <pv/prop/double.h>
+#include <pv/prop/enum.h>
+#include <pv/prop/int.h>
+
+#include <libsigrok/libsigrok.h>
+
+using boost::bind;
+using boost::function;
+using boost::optional;
+using boost::shared_ptr;
+using std::make_pair;
+using std::pair;
+using std::string;
+using std::vector;
+
+namespace pv {
+namespace prop {
+namespace binding {
+
+DeviceOptions::DeviceOptions(shared_ptr<pv::device::DevInst> dev_inst,
+ const sr_channel_group *group) :
+ _dev_inst(dev_inst),
+ _group(group)
+{
+ assert(dev_inst);
+
+ GVariant *gvar_opts;
+ gsize num_opts;
+
+ if (!(gvar_opts = dev_inst->list_config(group, SR_CONF_DEVICE_OPTIONS)))
+ /* Driver supports no device instance options. */
+ return;
+
+ const int *const options = (const int32_t *)g_variant_get_fixed_array(
+ gvar_opts, &num_opts, sizeof(int32_t));
+ for (unsigned int i = 0; i < num_opts; i++) {
+ const struct sr_config_info *const info =
+ sr_config_info_get(options[i]);
+
+ if (!info)
+ continue;
+
+ const int key = info->key;
+ GVariant *const gvar_list = dev_inst->list_config(group, key);
+
+ const QString name = QString::fromUtf8(info->name);
+
+ switch(key)
+ {
+ case SR_CONF_SAMPLERATE:
+ // Sample rate values are not bound because they are shown
+ // in the SamplingBar
+ break;
+
+ case SR_CONF_CAPTURE_RATIO:
+ bind_int(name, key, "%", pair<int64_t, int64_t>(0, 100));
+ break;
+
+ case SR_CONF_PATTERN_MODE:
+ case SR_CONF_BUFFERSIZE:
+ case SR_CONF_TRIGGER_SOURCE:
+ case SR_CONF_TRIGGER_SLOPE:
+ case SR_CONF_FILTER:
+ case SR_CONF_COUPLING:
+ case SR_CONF_CLOCK_EDGE:
+ bind_enum(name, key, gvar_list);
+ break;
+
+ case SR_CONF_EXTERNAL_CLOCK:
+ case SR_CONF_RLE:
+ bind_bool(name, key);
+ break;
+
+ case SR_CONF_TIMEBASE:
+ bind_enum(name, key, gvar_list, print_timebase);
+ break;
+
+ case SR_CONF_VDIV:
+ bind_enum(name, key, gvar_list, print_vdiv);
+ break;
+
+ case SR_CONF_VOLTAGE_THRESHOLD:
+ bind_enum(name, key, gvar_list, print_voltage_threshold);
+ break;
+ }
+
+ if (gvar_list)
+ g_variant_unref(gvar_list);
+ }
+ g_variant_unref(gvar_opts);
+}
+
+void DeviceOptions::bind_bool(const QString &name, int key)
+{
+ assert(_dev_inst);
+ _properties.push_back(shared_ptr<Property>(new Bool(name,
+ bind(&device::DevInst::get_config, _dev_inst, _group, key),
+ bind(&device::DevInst::set_config, _dev_inst,
+ _group, key, _1))));
+}
+
+void DeviceOptions::bind_enum(const QString &name, int key,
+ GVariant *const gvar_list, function<QString (GVariant*)> printer)
+{
+ GVariant *gvar;
+ GVariantIter iter;
+ vector< pair<GVariant*, QString> > values;
+
+ assert(_dev_inst);
+ if (!gvar_list) {
+ qDebug() << "Config key " << key << " was listed, but no "
+ "options were given";
+ return;
+ }
+
+ g_variant_iter_init (&iter, gvar_list);
+ while ((gvar = g_variant_iter_next_value (&iter)))
+ values.push_back(make_pair(gvar, printer(gvar)));
+
+ _properties.push_back(shared_ptr<Property>(new Enum(name, values,
+ bind(&device::DevInst::get_config, _dev_inst, _group, key),
+ bind(&device::DevInst::set_config, _dev_inst,
+ _group, key, _1))));
+}
+
+void DeviceOptions::bind_int(const QString &name, int key, QString suffix,
+ optional< std::pair<int64_t, int64_t> > range)
+{
+ assert(_dev_inst);
+
+ _properties.push_back(shared_ptr<Property>(new Int(name, suffix, range,
+ bind(&device::DevInst::get_config, _dev_inst, _group, key),
+ bind(&device::DevInst::set_config, _dev_inst, _group, key, _1))));
+}
+
+QString DeviceOptions::print_timebase(GVariant *const gvar)
+{
+ uint64_t p, q;
+ g_variant_get(gvar, "(tt)", &p, &q);
+ return QString::fromUtf8(sr_period_string(p * q));
+}
+
+QString DeviceOptions::print_vdiv(GVariant *const gvar)
+{
+ uint64_t p, q;
+ g_variant_get(gvar, "(tt)", &p, &q);
+ return QString::fromUtf8(sr_voltage_string(p, q));
+}
+
+QString DeviceOptions::print_voltage_threshold(GVariant *const gvar)
+{
+ gdouble lo, hi;
+ char buf[64];
+ g_variant_get(gvar, "(dd)", &lo, &hi);
+ snprintf(buf, sizeof(buf), "L<%.1fV H>%.1fV", lo, hi);
+ return QString::fromUtf8(buf);
+}
+
+} // binding
+} // prop
+} // pv
+
diff --git a/pv/prop/binding/deviceoptions.h b/pv/prop/binding/deviceoptions.h
new file mode 100644
index 0000000..445361f
--- /dev/null
+++ b/pv/prop/binding/deviceoptions.h
@@ -0,0 +1,72 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_PROP_BINDING_DEVICEOPTIONS_H
+#define PULSEVIEW_PV_PROP_BINDING_DEVICEOPTIONS_H
+
+#include <boost/function.hpp>
+#include <boost/optional.hpp>
+
+#include <QString>
+
+#include "binding.h"
+
+#include <glib.h>
+
+struct sr_dev_inst;
+struct sr_channel_group;
+
+namespace pv {
+
+namespace device {
+class DevInst;
+}
+
+namespace prop {
+namespace binding {
+
+class DeviceOptions : public Binding
+{
+public:
+ DeviceOptions(boost::shared_ptr<pv::device::DevInst> dev_inst,
+ const sr_channel_group *group = NULL);
+
+private:
+ void bind_bool(const QString &name, int key);
+ void bind_enum(const QString &name, int key,
+ GVariant *const gvar_list,
+ boost::function<QString (GVariant*)> printer = print_gvariant);
+ void bind_int(const QString &name, int key, QString suffix,
+ boost::optional< std::pair<int64_t, int64_t> > range);
+
+ static QString print_timebase(GVariant *const gvar);
+ static QString print_vdiv(GVariant *const gvar);
+ static QString print_voltage_threshold(GVariant *const gvar);
+
+protected:
+ boost::shared_ptr<device::DevInst> _dev_inst;
+ const sr_channel_group *const _group;
+};
+
+} // binding
+} // prop
+} // pv
+
+#endif // PULSEVIEW_PV_PROP_BINDING_DEVICEOPTIONS_H
diff --git a/pv/prop/bool.cpp b/pv/prop/bool.cpp
new file mode 100644
index 0000000..e0e052f
--- /dev/null
+++ b/pv/prop/bool.cpp
@@ -0,0 +1,83 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <assert.h>
+
+#include <QCheckBox>
+
+#include "bool.h"
+
+namespace pv {
+namespace prop {
+
+Bool::Bool(QString name, Getter getter, Setter setter) :
+ Property(name, getter, setter),
+ _check_box(NULL)
+{
+}
+
+Bool::~Bool()
+{
+}
+
+QWidget* Bool::get_widget(QWidget *parent, bool auto_commit)
+{
+ if (_check_box)
+ return _check_box;
+
+ GVariant *const value = _getter ? _getter() : NULL;
+ if (!value)
+ return NULL;
+
+ _check_box = new QCheckBox(name(), parent);
+ _check_box->setCheckState(g_variant_get_boolean(value) ?
+ Qt::Checked : Qt::Unchecked);
+ g_variant_unref(value);
+
+ if (auto_commit)
+ connect(_check_box, SIGNAL(stateChanged(int)),
+ this, SLOT(on_state_changed(int)));
+
+ return _check_box;
+}
+
+bool Bool::labeled_widget() const
+{
+ return true;
+}
+
+void Bool::commit()
+{
+ assert(_setter);
+
+ if (!_check_box)
+ return;
+
+ _setter(g_variant_new_boolean(
+ _check_box->checkState() == Qt::Checked));
+}
+
+void Bool::on_state_changed(int)
+{
+ commit();
+}
+
+} // prop
+} // pv
diff --git a/pv/prop/bool.h b/pv/prop/bool.h
new file mode 100644
index 0000000..0d8e205
--- /dev/null
+++ b/pv/prop/bool.h
@@ -0,0 +1,55 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_PROP_BOOL_H
+#define PULSEVIEW_PV_PROP_BOOL_H
+
+#include "property.h"
+
+class QCheckBox;
+
+namespace pv {
+namespace prop {
+
+class Bool : public Property
+{
+ Q_OBJECT;
+
+public:
+ Bool(QString name, Getter getter, Setter setter);
+
+ virtual ~Bool();
+
+ QWidget* get_widget(QWidget *parent, bool auto_commit);
+ bool labeled_widget() const;
+
+ void commit();
+
+private slots:
+ void on_state_changed(int);
+
+private:
+ QCheckBox *_check_box;
+};
+
+} // prop
+} // pv
+
+#endif // PULSEVIEW_PV_PROP_BOOL_H
diff --git a/pv/prop/double.cpp b/pv/prop/double.cpp
new file mode 100644
index 0000000..93b45d3
--- /dev/null
+++ b/pv/prop/double.cpp
@@ -0,0 +1,96 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <assert.h>
+
+#include <QDoubleSpinBox>
+
+#include "double.h"
+
+using boost::optional;
+using std::pair;
+
+namespace pv {
+namespace prop {
+
+Double::Double(QString name,
+ int decimals,
+ QString suffix,
+ optional< pair<double, double> > range,
+ optional<double> step,
+ Getter getter,
+ Setter setter) :
+ Property(name, getter, setter),
+ _decimals(decimals),
+ _suffix(suffix),
+ _range(range),
+ _step(step),
+ _spin_box(NULL)
+{
+}
+
+Double::~Double()
+{
+}
+
+QWidget* Double::get_widget(QWidget *parent, bool auto_commit)
+{
+ if (_spin_box)
+ return _spin_box;
+
+ GVariant *const value = _getter ? _getter() : NULL;
+ if (!value)
+ return NULL;
+
+ _spin_box = new QDoubleSpinBox(parent);
+ _spin_box->setDecimals(_decimals);
+ _spin_box->setSuffix(_suffix);
+ if (_range)
+ _spin_box->setRange(_range->first, _range->second);
+ if (_step)
+ _spin_box->setSingleStep(*_step);
+
+ _spin_box->setValue(g_variant_get_double(value));
+ g_variant_unref(value);
+
+ if (auto_commit)
+ connect(_spin_box, SIGNAL(valueChanged(double)),
+ this, SLOT(on_value_changed(double)));
+
+ return _spin_box;
+}
+
+void Double::commit()
+{
+ assert(_setter);
+
+ if (!_spin_box)
+ return;
+
+ _setter(g_variant_new_double(_spin_box->value()));
+}
+
+void Double::on_value_changed(double)
+{
+ commit();
+}
+
+} // prop
+} // pv
diff --git a/pv/prop/double.h b/pv/prop/double.h
new file mode 100644
index 0000000..ba4c595
--- /dev/null
+++ b/pv/prop/double.h
@@ -0,0 +1,67 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_PROP_DOUBLE_H
+#define PULSEVIEW_PV_PROP_DOUBLE_H
+
+#include <utility>
+
+#include <boost/optional.hpp>
+
+#include "property.h"
+
+class QDoubleSpinBox;
+
+namespace pv {
+namespace prop {
+
+class Double : public Property
+{
+ Q_OBJECT
+
+public:
+ Double(QString name, int decimals, QString suffix,
+ boost::optional< std::pair<double, double> > range,
+ boost::optional<double> step,
+ Getter getter,
+ Setter setter);
+
+ virtual ~Double();
+
+ QWidget* get_widget(QWidget *parent, bool auto_commit);
+
+ void commit();
+
+private slots:
+ void on_value_changed(double);
+
+private:
+ const int _decimals;
+ const QString _suffix;
+ const boost::optional< std::pair<double, double> > _range;
+ const boost::optional<double> _step;
+
+ QDoubleSpinBox *_spin_box;
+};
+
+} // prop
+} // pv
+
+#endif // PULSEVIEW_PV_PROP_DOUBLE_H
diff --git a/pv/prop/enum.cpp b/pv/prop/enum.cpp
new file mode 100644
index 0000000..6439a72
--- /dev/null
+++ b/pv/prop/enum.cpp
@@ -0,0 +1,98 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <assert.h>
+
+#include <QComboBox>
+
+#include "enum.h"
+
+using std::pair;
+using std::vector;
+
+namespace pv {
+namespace prop {
+
+Enum::Enum(QString name,
+ vector<pair<GVariant*, QString> > values,
+ Getter getter, Setter setter) :
+ Property(name, getter, setter),
+ _values(values),
+ _selector(NULL)
+{
+ for (vector< pair<GVariant*, QString> >::const_iterator i =
+ _values.begin(); i != _values.end(); i++)
+ g_variant_ref((*i).first);
+}
+
+Enum::~Enum()
+{
+ for (vector< pair<GVariant*, QString> >::const_iterator i =
+ _values.begin(); i != _values.end(); i++)
+ g_variant_unref((*i).first);
+}
+
+QWidget* Enum::get_widget(QWidget *parent, bool auto_commit)
+{
+ if (_selector)
+ return _selector;
+
+ GVariant *const value = _getter ? _getter() : NULL;
+ if (!value)
+ return NULL;
+
+ _selector = new QComboBox(parent);
+ for (unsigned int i = 0; i < _values.size(); i++) {
+ const pair<GVariant*, QString> &v = _values[i];
+ _selector->addItem(v.second, qVariantFromValue((void*)v.first));
+ if (value && g_variant_equal(v.first, value))
+ _selector->setCurrentIndex(i);
+ }
+
+ g_variant_unref(value);
+
+ if (auto_commit)
+ connect(_selector, SIGNAL(currentIndexChanged(int)),
+ this, SLOT(on_current_item_changed(int)));
+
+ return _selector;
+}
+
+void Enum::commit()
+{
+ assert(_setter);
+
+ if (!_selector)
+ return;
+
+ const int index = _selector->currentIndex();
+ if (index < 0)
+ return;
+
+ _setter((GVariant*)_selector->itemData(index).value<void*>());
+}
+
+void Enum::on_current_item_changed(int)
+{
+ commit();
+}
+
+} // prop
+} // pv
diff --git a/pv/prop/enum.h b/pv/prop/enum.h
new file mode 100644
index 0000000..a7e6ed8
--- /dev/null
+++ b/pv/prop/enum.h
@@ -0,0 +1,60 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_PROP_ENUM_H
+#define PULSEVIEW_PV_PROP_ENUM_H
+
+#include <utility>
+#include <vector>
+
+#include "property.h"
+
+class QComboBox;
+
+namespace pv {
+namespace prop {
+
+class Enum : public Property
+{
+ Q_OBJECT;
+
+public:
+ Enum(QString name, std::vector<std::pair<GVariant*, QString> > values,
+ Getter getter, Setter setter);
+
+ virtual ~Enum();
+
+ QWidget* get_widget(QWidget *parent, bool auto_commit);
+
+ void commit();
+
+private slots:
+ void on_current_item_changed(int);
+
+private:
+ const std::vector< std::pair<GVariant*, QString> > _values;
+
+ QComboBox *_selector;
+};
+
+} // prop
+} // pv
+
+#endif // PULSEVIEW_PV_PROP_ENUM_H
diff --git a/pv/prop/int.cpp b/pv/prop/int.cpp
new file mode 100644
index 0000000..8124f9b
--- /dev/null
+++ b/pv/prop/int.cpp
@@ -0,0 +1,186 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdint.h>
+#include <assert.h>
+
+#include <QSpinBox>
+
+#include "int.h"
+
+using boost::optional;
+using std::max;
+using std::min;
+using std::pair;
+
+namespace pv {
+namespace prop {
+
+Int::Int(QString name,
+ QString suffix,
+ optional< pair<int64_t, int64_t> > range,
+ Getter getter,
+ Setter setter) :
+ Property(name, getter, setter),
+ _suffix(suffix),
+ _range(range),
+ _value(NULL),
+ _spin_box(NULL)
+{
+}
+
+Int::~Int()
+{
+ if (_value)
+ g_variant_unref(_value);
+}
+
+QWidget* Int::get_widget(QWidget *parent, bool auto_commit)
+{
+ int64_t int_val = 0, range_min = 0, range_max = 0;
+
+ if (_spin_box)
+ return _spin_box;
+
+ if (_value)
+ g_variant_unref(_value);
+
+ _value = _getter ? _getter() : NULL;
+ if (!_value)
+ return NULL;
+
+ _spin_box = new QSpinBox(parent);
+ _spin_box->setSuffix(_suffix);
+
+ const GVariantType *const type = g_variant_get_type(_value);
+ assert(type);
+
+ if (g_variant_type_equal(type, G_VARIANT_TYPE_BYTE))
+ {
+ int_val = g_variant_get_byte(_value);
+ range_min = 0, range_max = UINT8_MAX;
+ }
+ else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT16))
+ {
+ int_val = g_variant_get_int16(_value);
+ range_min = INT16_MIN, range_max = INT16_MAX;
+ }
+ else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT16))
+ {
+ int_val = g_variant_get_uint16(_value);
+ range_min = 0, range_max = UINT16_MAX;
+ }
+ else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT32))
+ {
+ int_val = g_variant_get_int32(_value);
+ range_min = INT32_MIN, range_max = INT32_MAX;
+ }
+ else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT32))
+ {
+ int_val = g_variant_get_uint32(_value);
+ range_min = 0, range_max = UINT32_MAX;
+ }
+ else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT64))
+ {
+ int_val = g_variant_get_int64(_value);
+ range_min = INT64_MIN, range_max = INT64_MAX;
+ }
+ else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT64))
+ {
+ int_val = g_variant_get_uint64(_value);
+ range_min = 0, range_max = UINT64_MAX;
+ }
+ else
+ {
+ // Unexpected value type.
+ assert(0);
+ }
+
+ // @todo Sigrok supports 64-bit quantities, but Qt does not have a
+ // standard widget to allow the values to be modified over the full
+ // 64-bit range on 32-bit machines. To solve the issue we need a
+ // custom widget.
+
+ range_min = max(range_min, (int64_t)INT_MIN);
+ range_max = min(range_max, (int64_t)INT_MAX);
+
+ if (_range)
+ _spin_box->setRange((int)_range->first, (int)_range->second);
+ else
+ _spin_box->setRange((int)range_min, (int)range_max);
+
+ _spin_box->setValue((int)int_val);
+
+ if (auto_commit)
+ connect(_spin_box, SIGNAL(valueChanged(int)),
+ this, SLOT(on_value_changed(int)));
+
+ return _spin_box;
+}
+
+void Int::commit()
+{
+ assert(_setter);
+
+ if (!_spin_box)
+ return;
+
+ assert(_value);
+
+ GVariant *new_value = NULL;
+ const GVariantType *const type = g_variant_get_type(_value);
+ assert(type);
+
+ if (g_variant_type_equal(type, G_VARIANT_TYPE_BYTE))
+ new_value = g_variant_new_byte(_spin_box->value());
+ else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT16))
+ new_value = g_variant_new_int16(_spin_box->value());
+ else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT16))
+ new_value = g_variant_new_uint16(_spin_box->value());
+ else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT32))
+ new_value = g_variant_new_int32(_spin_box->value());
+ else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT32))
+ new_value = g_variant_new_int32(_spin_box->value());
+ else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT64))
+ new_value = g_variant_new_int64(_spin_box->value());
+ else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT64))
+ new_value = g_variant_new_uint64(_spin_box->value());
+ else
+ {
+ // Unexpected value type.
+ assert(0);
+ }
+
+ assert(new_value);
+
+ g_variant_unref(_value);
+ g_variant_ref(new_value);
+ _value = new_value;
+
+ _setter(new_value);
+}
+
+void Int::on_value_changed(int)
+{
+ commit();
+}
+
+} // prop
+} // pv
diff --git a/pv/prop/int.h b/pv/prop/int.h
new file mode 100644
index 0000000..6d910cd
--- /dev/null
+++ b/pv/prop/int.h
@@ -0,0 +1,64 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_PROP_INT_H
+#define PULSEVIEW_PV_PROP_INT_H
+
+#include <utility>
+
+#include <boost/optional.hpp>
+
+#include "property.h"
+
+class QSpinBox;
+
+namespace pv {
+namespace prop {
+
+class Int : public Property
+{
+ Q_OBJECT;
+
+public:
+ Int(QString name, QString suffix,
+ boost::optional< std::pair<int64_t, int64_t> > range,
+ Getter getter, Setter setter);
+
+ virtual ~Int();
+
+ QWidget* get_widget(QWidget *parent, bool auto_commit);
+
+ void commit();
+
+private slots:
+ void on_value_changed(int);
+
+private:
+ const QString _suffix;
+ const boost::optional< std::pair<int64_t, int64_t> > _range;
+
+ GVariant *_value;
+ QSpinBox *_spin_box;
+};
+
+} // prop
+} // pv
+
+#endif // PULSEVIEW_PV_PROP_INT_H
diff --git a/pv/prop/property.cpp b/pv/prop/property.cpp
new file mode 100644
index 0000000..798b5f1
--- /dev/null
+++ b/pv/prop/property.cpp
@@ -0,0 +1,44 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "property.h"
+
+namespace pv {
+namespace prop {
+
+Property::Property(QString name, Getter getter, Setter setter) :
+ _getter(getter),
+ _setter(setter),
+ _name(name)
+{
+}
+
+const QString& Property::name() const
+{
+ return _name;
+}
+
+bool Property::labeled_widget() const
+{
+ return false;
+}
+
+} // prop
+} // pv
diff --git a/pv/prop/property.h b/pv/prop/property.h
new file mode 100644
index 0000000..0b4bc7b
--- /dev/null
+++ b/pv/prop/property.h
@@ -0,0 +1,67 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_PROP_PROPERTY_H
+#define PULSEVIEW_PV_PROP_PROPERTY_H
+
+#include <glib.h>
+
+#include <boost/function.hpp>
+
+#include <QString>
+#include <QWidget>
+
+class QWidget;
+
+namespace pv {
+namespace prop {
+
+class Property : public QObject
+{
+ Q_OBJECT;
+
+public:
+ typedef boost::function<GVariant* ()> Getter;
+ typedef boost::function<void (GVariant*)> Setter;
+
+protected:
+ Property(QString name, Getter getter, Setter setter);
+
+public:
+ const QString& name() const;
+
+ virtual QWidget* get_widget(QWidget *parent,
+ bool auto_commit = false) = 0;
+ virtual bool labeled_widget() const;
+
+ virtual void commit() = 0;
+
+protected:
+ const Getter _getter;
+ const Setter _setter;
+
+private:
+ QString _name;
+};
+
+} // prop
+} // pv
+
+#endif // PULSEVIEW_PV_PROP_PROPERTY_H
diff --git a/pv/prop/string.cpp b/pv/prop/string.cpp
new file mode 100644
index 0000000..7fcc656
--- /dev/null
+++ b/pv/prop/string.cpp
@@ -0,0 +1,77 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <assert.h>
+
+#include <QLineEdit>
+#include <QSpinBox>
+
+#include "string.h"
+
+namespace pv {
+namespace prop {
+
+String::String(QString name,
+ Getter getter,
+ Setter setter) :
+ Property(name, getter, setter),
+ _line_edit(NULL)
+{
+}
+
+QWidget* String::get_widget(QWidget *parent, bool auto_commit)
+{
+ if (_line_edit)
+ return _line_edit;
+
+ GVariant *const value = _getter ? _getter() : NULL;
+ if (!value)
+ return NULL;
+
+ _line_edit = new QLineEdit(parent);
+ _line_edit->setText(QString::fromUtf8(
+ g_variant_get_string(value, NULL)));
+ g_variant_unref(value);
+
+ if (auto_commit)
+ connect(_line_edit, SIGNAL(textEdited(const QString&)),
+ this, SLOT(on_text_edited(const QString&)));
+
+ return _line_edit;
+}
+
+void String::commit()
+{
+ assert(_setter);
+
+ if (!_line_edit)
+ return;
+
+ QByteArray ba = _line_edit->text().toLocal8Bit();
+ _setter(g_variant_new_string(ba.data()));
+}
+
+void String::on_text_edited(const QString&)
+{
+ commit();
+}
+
+} // prop
+} // pv
diff --git a/pv/prop/string.h b/pv/prop/string.h
new file mode 100644
index 0000000..d54635f
--- /dev/null
+++ b/pv/prop/string.h
@@ -0,0 +1,52 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_PROP_STRING_H
+#define PULSEVIEW_PV_PROP_STRING_H
+
+#include "property.h"
+
+class QLineEdit;
+
+namespace pv {
+namespace prop {
+
+class String : public Property
+{
+ Q_OBJECT;
+
+public:
+ String(QString name, Getter getter, Setter setter);
+
+ QWidget* get_widget(QWidget *parent, bool auto_commit);
+
+ void commit();
+
+private slots:
+ void on_text_edited(const QString&);
+
+private:
+ QLineEdit *_line_edit;
+};
+
+} // prop
+} // pv
+
+#endif // PULSEVIEW_PV_PROP_STRING_H
diff --git a/pv/sigsession.cpp b/pv/sigsession.cpp
new file mode 100644
index 0000000..b441dab
--- /dev/null
+++ b/pv/sigsession.cpp
@@ -0,0 +1,650 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012-14 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef ENABLE_DECODE
+#include <libsigrokdecode/libsigrokdecode.h>
+#endif
+
+#include "sigsession.h"
+
+#include "devicemanager.h"
+#include "device/device.h"
+#include "device/file.h"
+
+#include "data/analog.h"
+#include "data/analogsnapshot.h"
+#include "data/decoderstack.h"
+#include "data/logic.h"
+#include "data/logicsnapshot.h"
+#include "data/decode/decoder.h"
+
+#include "view/analogsignal.h"
+#include "view/decodetrace.h"
+#include "view/logicsignal.h"
+
+#include <assert.h>
+
+#include <stdexcept>
+
+#include <boost/foreach.hpp>
+
+#include <sys/stat.h>
+
+#include <QDebug>
+
+using boost::dynamic_pointer_cast;
+using boost::function;
+using boost::lock_guard;
+using boost::mutex;
+using boost::shared_ptr;
+using std::list;
+using std::map;
+using std::set;
+using std::string;
+using std::vector;
+
+namespace pv {
+
+// TODO: This should not be necessary
+SigSession* SigSession::_session = NULL;
+
+SigSession::SigSession(DeviceManager &device_manager) :
+ _device_manager(device_manager),
+ _capture_state(Stopped)
+{
+ // TODO: This should not be necessary
+ _session = this;
+
+ set_default_device();
+}
+
+SigSession::~SigSession()
+{
+ using pv::device::Device;
+
+ stop_capture();
+
+ if (_sampling_thread.joinable())
+ _sampling_thread.join();
+
+ _dev_inst->release();
+
+ // TODO: This should not be necessary
+ _session = NULL;
+}
+
+shared_ptr<device::DevInst> SigSession::get_device() const
+{
+ return _dev_inst;
+}
+
+void SigSession::set_device(
+ shared_ptr<device::DevInst> dev_inst) throw(QString)
+{
+ using pv::device::Device;
+
+ // Ensure we are not capturing before setting the device
+ stop_capture();
+
+ if (_dev_inst) {
+ sr_session_datafeed_callback_remove_all();
+ _dev_inst->release();
+ }
+
+ _dev_inst = dev_inst;
+ _decode_traces.clear();
+
+ if (dev_inst) {
+ dev_inst->use(this);
+ sr_session_datafeed_callback_add(data_feed_in_proc, NULL);
+ update_signals(dev_inst);
+ }
+}
+
+void SigSession::set_file(const string &name) throw(QString)
+{
+ // Deslect the old device, because file type detection in File::create
+ // destorys the old session inside libsigrok.
+ set_device(shared_ptr<device::DevInst>());
+ set_device(shared_ptr<device::DevInst>(device::File::create(name)));
+}
+
+void SigSession::set_default_device()
+{
+ shared_ptr<pv::device::DevInst> default_device;
+ const list< shared_ptr<device::Device> > &devices =
+ _device_manager.devices();
+
+ if (!devices.empty()) {
+ // Fall back to the first device in the list.
+ default_device = devices.front();
+
+ // Try and find the demo device and select that by default
+ BOOST_FOREACH (shared_ptr<pv::device::Device> dev, devices)
+ if (strcmp(dev->dev_inst()->driver->name,
+ "demo") == 0) {
+ default_device = dev;
+ break;
+ }
+ }
+
+ set_device(default_device);
+}
+
+void SigSession::release_device(device::DevInst *dev_inst)
+{
+ (void)dev_inst;
+ assert(_dev_inst.get() == dev_inst);
+
+ assert(_capture_state == Stopped);
+ _dev_inst = shared_ptr<device::DevInst>();
+}
+
+SigSession::capture_state SigSession::get_capture_state() const
+{
+ lock_guard<mutex> lock(_sampling_mutex);
+ return _capture_state;
+}
+
+void SigSession::start_capture(function<void (const QString)> error_handler)
+{
+ stop_capture();
+
+ // Check that a device instance has been selected.
+ if (!_dev_inst) {
+ qDebug() << "No device selected";
+ return;
+ }
+
+ assert(_dev_inst->dev_inst());
+
+ // Check that at least one probe is enabled
+ const GSList *l;
+ for (l = _dev_inst->dev_inst()->channels; l; l = l->next) {
+ sr_channel *const probe = (sr_channel*)l->data;
+ assert(probe);
+ if (probe->enabled)
+ break;
+ }
+
+ if (!l) {
+ error_handler(tr("No channels enabled."));
+ return;
+ }
+
+ // Begin the session
+ _sampling_thread = boost::thread(
+ &SigSession::sample_thread_proc, this, _dev_inst,
+ error_handler);
+}
+
+void SigSession::stop_capture()
+{
+ if (get_capture_state() == Stopped)
+ return;
+
+ sr_session_stop();
+
+ // Check that sampling stopped
+ if (_sampling_thread.joinable())
+ _sampling_thread.join();
+}
+
+set< shared_ptr<data::SignalData> > SigSession::get_data() const
+{
+ lock_guard<mutex> lock(_signals_mutex);
+ set< shared_ptr<data::SignalData> > data;
+ BOOST_FOREACH(const shared_ptr<view::Signal> sig, _signals) {
+ assert(sig);
+ data.insert(sig->data());
+ }
+
+ return data;
+}
+
+vector< shared_ptr<view::Signal> > SigSession::get_signals() const
+{
+ lock_guard<mutex> lock(_signals_mutex);
+ return _signals;
+}
+
+#ifdef ENABLE_DECODE
+bool SigSession::add_decoder(srd_decoder *const dec)
+{
+ map<const srd_channel*, shared_ptr<view::LogicSignal> > probes;
+ shared_ptr<data::DecoderStack> decoder_stack;
+
+ try
+ {
+ lock_guard<mutex> lock(_signals_mutex);
+
+ // Create the decoder
+ decoder_stack = shared_ptr<data::DecoderStack>(
+ new data::DecoderStack(*this, dec));
+
+ // Make a list of all the probes
+ std::vector<const srd_channel*> all_probes;
+ for(const GSList *i = dec->channels; i; i = i->next)
+ all_probes.push_back((const srd_channel*)i->data);
+ for(const GSList *i = dec->opt_channels; i; i = i->next)
+ all_probes.push_back((const srd_channel*)i->data);
+
+ // Auto select the initial probes
+ BOOST_FOREACH(const srd_channel *pdch, all_probes)
+ BOOST_FOREACH(shared_ptr<view::Signal> s, _signals)
+ {
+ shared_ptr<view::LogicSignal> l =
+ dynamic_pointer_cast<view::LogicSignal>(s);
+ if (l && QString::fromUtf8(pdch->name).
+ toLower().contains(
+ l->get_name().toLower()))
+ probes[pdch] = l;
+ }
+
+ assert(decoder_stack);
+ assert(!decoder_stack->stack().empty());
+ assert(decoder_stack->stack().front());
+ decoder_stack->stack().front()->set_probes(probes);
+
+ // Create the decode signal
+ shared_ptr<view::DecodeTrace> d(
+ new view::DecodeTrace(*this, decoder_stack,
+ _decode_traces.size()));
+ _decode_traces.push_back(d);
+ }
+ catch(std::runtime_error e)
+ {
+ return false;
+ }
+
+ signals_changed();
+
+ // Do an initial decode
+ decoder_stack->begin_decode();
+
+ return true;
+}
+
+vector< shared_ptr<view::DecodeTrace> > SigSession::get_decode_signals() const
+{
+ lock_guard<mutex> lock(_signals_mutex);
+ return _decode_traces;
+}
+
+void SigSession::remove_decode_signal(view::DecodeTrace *signal)
+{
+ for (vector< shared_ptr<view::DecodeTrace> >::iterator i =
+ _decode_traces.begin();
+ i != _decode_traces.end();
+ i++)
+ if ((*i).get() == signal)
+ {
+ _decode_traces.erase(i);
+ signals_changed();
+ return;
+ }
+}
+#endif
+
+void SigSession::set_capture_state(capture_state state)
+{
+ lock_guard<mutex> lock(_sampling_mutex);
+ const bool changed = _capture_state != state;
+ _capture_state = state;
+ if(changed)
+ capture_state_changed(state);
+}
+
+void SigSession::update_signals(shared_ptr<device::DevInst> dev_inst)
+{
+ assert(dev_inst);
+ assert(_capture_state == Stopped);
+
+ unsigned int logic_probe_count = 0;
+
+ // Clear the decode traces
+ _decode_traces.clear();
+
+ // Detect what data types we will receive
+ if(dev_inst) {
+ assert(dev_inst->dev_inst());
+ for (const GSList *l = dev_inst->dev_inst()->channels;
+ l; l = l->next) {
+ const sr_channel *const probe = (const sr_channel *)l->data;
+ if (!probe->enabled)
+ continue;
+
+ switch(probe->type) {
+ case SR_CHANNEL_LOGIC:
+ logic_probe_count++;
+ break;
+ }
+ }
+ }
+
+ // Create data containers for the logic data snapshots
+ {
+ lock_guard<mutex> data_lock(_data_mutex);
+
+ _logic_data.reset();
+ if (logic_probe_count != 0) {
+ _logic_data.reset(new data::Logic(
+ logic_probe_count));
+ assert(_logic_data);
+ }
+ }
+
+ // Make the Signals list
+ do {
+ lock_guard<mutex> lock(_signals_mutex);
+
+ _signals.clear();
+
+ if(!dev_inst)
+ break;
+
+ assert(dev_inst->dev_inst());
+ for (const GSList *l = dev_inst->dev_inst()->channels;
+ l; l = l->next) {
+ shared_ptr<view::Signal> signal;
+ sr_channel *const probe = (sr_channel *)l->data;
+ assert(probe);
+
+ switch(probe->type) {
+ case SR_CHANNEL_LOGIC:
+ signal = shared_ptr<view::Signal>(
+ new view::LogicSignal(dev_inst,
+ probe, _logic_data));
+ break;
+
+ case SR_CHANNEL_ANALOG:
+ {
+ shared_ptr<data::Analog> data(
+ new data::Analog());
+ signal = shared_ptr<view::Signal>(
+ new view::AnalogSignal(dev_inst,
+ probe, data));
+ break;
+ }
+
+ default:
+ assert(0);
+ break;
+ }
+
+ assert(signal);
+ _signals.push_back(signal);
+ }
+
+ } while(0);
+
+ signals_changed();
+}
+
+shared_ptr<view::Signal> SigSession::signal_from_probe(
+ const sr_channel *probe) const
+{
+ lock_guard<mutex> lock(_signals_mutex);
+ BOOST_FOREACH(shared_ptr<view::Signal> sig, _signals) {
+ assert(sig);
+ if (sig->probe() == probe)
+ return sig;
+ }
+ return shared_ptr<view::Signal>();
+}
+
+void SigSession::read_sample_rate(const sr_dev_inst *const sdi)
+{
+ GVariant *gvar;
+ uint64_t sample_rate = 0;
+
+ // Read out the sample rate
+ if(sdi->driver)
+ {
+ const int ret = sr_config_get(sdi->driver, sdi, NULL,
+ SR_CONF_SAMPLERATE, &gvar);
+ if (ret != SR_OK) {
+ qDebug("Failed to get samplerate\n");
+ return;
+ }
+
+ sample_rate = g_variant_get_uint64(gvar);
+ g_variant_unref(gvar);
+ }
+
+ // Set the sample rate of all data
+ const set< shared_ptr<data::SignalData> > data_set = get_data();
+ BOOST_FOREACH(shared_ptr<data::SignalData> data, data_set) {
+ assert(data);
+ data->set_samplerate(sample_rate);
+ }
+}
+
+void SigSession::sample_thread_proc(shared_ptr<device::DevInst> dev_inst,
+ function<void (const QString)> error_handler)
+{
+ assert(dev_inst);
+ assert(dev_inst->dev_inst());
+ assert(error_handler);
+
+ read_sample_rate(dev_inst->dev_inst());
+
+ try {
+ dev_inst->start();
+ } catch(const QString e) {
+ error_handler(e);
+ return;
+ }
+
+ set_capture_state(dev_inst->is_trigger_enabled() ?
+ AwaitingTrigger : Running);
+
+ dev_inst->run();
+ set_capture_state(Stopped);
+
+ // Confirm that SR_DF_END was received
+ if (_cur_logic_snapshot)
+ {
+ qDebug("SR_DF_END was not received.");
+ assert(0);
+ }
+}
+
+void SigSession::feed_in_header(const sr_dev_inst *sdi)
+{
+ read_sample_rate(sdi);
+}
+
+void SigSession::feed_in_meta(const sr_dev_inst *sdi,
+ const sr_datafeed_meta &meta)
+{
+ (void)sdi;
+
+ for (const GSList *l = meta.config; l; l = l->next) {
+ const sr_config *const src = (const sr_config*)l->data;
+ switch (src->key) {
+ case SR_CONF_SAMPLERATE:
+ /// @todo handle samplerate changes
+ /// samplerate = (uint64_t *)src->value;
+ break;
+ default:
+ // Unknown metadata is not an error.
+ break;
+ }
+ }
+
+ signals_changed();
+}
+
+void SigSession::feed_in_frame_begin()
+{
+ if (_cur_logic_snapshot || !_cur_analog_snapshots.empty())
+ frame_began();
+}
+
+void SigSession::feed_in_logic(const sr_datafeed_logic &logic)
+{
+ lock_guard<mutex> lock(_data_mutex);
+
+ if (!_logic_data)
+ {
+ qDebug() << "Unexpected logic packet";
+ return;
+ }
+
+ if (!_cur_logic_snapshot)
+ {
+ // This could be the first packet after a trigger
+ set_capture_state(Running);
+
+ // Create a new data snapshot
+ _cur_logic_snapshot = shared_ptr<data::LogicSnapshot>(
+ new data::LogicSnapshot(logic, _dev_inst->get_sample_limit()));
+ _logic_data->push_snapshot(_cur_logic_snapshot);
+
+ // @todo Putting this here means that only listeners querying
+ // for logic will be notified. Currently the only user of
+ // frame_began is DecoderStack, but in future we need to signal
+ // this after both analog and logic sweeps have begun.
+ frame_began();
+ }
+ else
+ {
+ // Append to the existing data snapshot
+ _cur_logic_snapshot->append_payload(logic);
+ }
+
+ data_received();
+}
+
+void SigSession::feed_in_analog(const sr_datafeed_analog &analog)
+{
+ lock_guard<mutex> lock(_data_mutex);
+
+ const unsigned int probe_count = g_slist_length(analog.channels);
+ const size_t sample_count = analog.num_samples / probe_count;
+ const float *data = analog.data;
+ bool sweep_beginning = false;
+
+ for (GSList *p = analog.channels; p; p = p->next)
+ {
+ shared_ptr<data::AnalogSnapshot> snapshot;
+
+ sr_channel *const probe = (sr_channel*)p->data;
+ assert(probe);
+
+ // Try to get the snapshot of the probe
+ const map< const sr_channel*, shared_ptr<data::AnalogSnapshot> >::
+ iterator iter = _cur_analog_snapshots.find(probe);
+ if (iter != _cur_analog_snapshots.end())
+ snapshot = (*iter).second;
+ else
+ {
+ // If no snapshot was found, this means we havn't
+ // created one yet. i.e. this is the first packet
+ // in the sweep containing this snapshot.
+ sweep_beginning = true;
+
+ // Create a snapshot, keep it in the maps of probes
+ snapshot = shared_ptr<data::AnalogSnapshot>(
+ new data::AnalogSnapshot(_dev_inst->get_sample_limit()));
+ _cur_analog_snapshots[probe] = snapshot;
+
+ // Find the annalog data associated with the probe
+ shared_ptr<view::AnalogSignal> sig =
+ dynamic_pointer_cast<view::AnalogSignal>(
+ signal_from_probe(probe));
+ assert(sig);
+
+ shared_ptr<data::Analog> data(sig->analog_data());
+ assert(data);
+
+ // Push the snapshot into the analog data.
+ data->push_snapshot(snapshot);
+ }
+
+ assert(snapshot);
+
+ // Append the samples in the snapshot
+ snapshot->append_interleaved_samples(data++, sample_count,
+ probe_count);
+ }
+
+ if (sweep_beginning) {
+ // This could be the first packet after a trigger
+ set_capture_state(Running);
+ }
+
+ data_received();
+}
+
+void SigSession::data_feed_in(const struct sr_dev_inst *sdi,
+ const struct sr_datafeed_packet *packet)
+{
+ assert(sdi);
+ assert(packet);
+
+ switch (packet->type) {
+ case SR_DF_HEADER:
+ feed_in_header(sdi);
+ break;
+
+ case SR_DF_META:
+ assert(packet->payload);
+ feed_in_meta(sdi,
+ *(const sr_datafeed_meta*)packet->payload);
+ break;
+
+ case SR_DF_FRAME_BEGIN:
+ feed_in_frame_begin();
+ break;
+
+ case SR_DF_LOGIC:
+ assert(packet->payload);
+ feed_in_logic(*(const sr_datafeed_logic*)packet->payload);
+ break;
+
+ case SR_DF_ANALOG:
+ assert(packet->payload);
+ feed_in_analog(*(const sr_datafeed_analog*)packet->payload);
+ break;
+
+ case SR_DF_END:
+ {
+ {
+ lock_guard<mutex> lock(_data_mutex);
+ _cur_logic_snapshot.reset();
+ _cur_analog_snapshots.clear();
+ }
+ frame_ended();
+ break;
+ }
+ }
+}
+
+void SigSession::data_feed_in_proc(const struct sr_dev_inst *sdi,
+ const struct sr_datafeed_packet *packet, void *cb_data)
+{
+ (void) cb_data;
+ assert(_session);
+ _session->data_feed_in(sdi, packet);
+}
+
+} // namespace pv
diff --git a/pv/sigsession.h b/pv/sigsession.h
new file mode 100644
index 0000000..1221750
--- /dev/null
+++ b/pv/sigsession.h
@@ -0,0 +1,204 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012-14 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_SIGSESSION_H
+#define PULSEVIEW_PV_SIGSESSION_H
+
+#include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/thread.hpp>
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <QObject>
+#include <QString>
+
+#include <libsigrok/libsigrok.h>
+
+struct srd_decoder;
+struct srd_channel;
+
+namespace pv {
+
+class DeviceManager;
+
+namespace data {
+class Analog;
+class AnalogSnapshot;
+class Logic;
+class LogicSnapshot;
+class SignalData;
+}
+
+namespace device {
+class DevInst;
+}
+
+namespace view {
+class DecodeTrace;
+class LogicSignal;
+class Signal;
+}
+
+class SigSession : public QObject
+{
+ Q_OBJECT
+
+public:
+ enum capture_state {
+ Stopped,
+ AwaitingTrigger,
+ Running
+ };
+
+public:
+ SigSession(DeviceManager &device_manager);
+
+ ~SigSession();
+
+ boost::shared_ptr<device::DevInst> get_device() const;
+
+ /**
+ * Sets device instance that will be used in the next capture session.
+ */
+ void set_device(boost::shared_ptr<device::DevInst> dev_inst)
+ throw(QString);
+
+ void set_file(const std::string &name)
+ throw(QString);
+
+ void set_default_device();
+
+ void release_device(device::DevInst *dev_inst);
+
+ capture_state get_capture_state() const;
+
+ void start_capture(boost::function<void (const QString)> error_handler);
+
+ void stop_capture();
+
+ std::set< boost::shared_ptr<data::SignalData> > get_data() const;
+
+ std::vector< boost::shared_ptr<view::Signal> >
+ get_signals() const;
+
+#ifdef ENABLE_DECODE
+ bool add_decoder(srd_decoder *const dec);
+
+ std::vector< boost::shared_ptr<view::DecodeTrace> >
+ get_decode_signals() const;
+
+ void remove_decode_signal(view::DecodeTrace *signal);
+#endif
+
+private:
+ void set_capture_state(capture_state state);
+
+ void update_signals(boost::shared_ptr<device::DevInst> dev_inst);
+
+ boost::shared_ptr<view::Signal> signal_from_probe(
+ const sr_channel *probe) const;
+
+ void read_sample_rate(const sr_dev_inst *const sdi);
+
+private:
+ /**
+ * Attempts to autodetect the format. Failing that
+ * @param filename The filename of the input file.
+ * @return A pointer to the 'struct sr_input_format' that should be
+ * used, or NULL if no input format was selected or
+ * auto-detected.
+ */
+ static sr_input_format* determine_input_file_format(
+ const std::string &filename);
+
+ static sr_input* load_input_file_format(
+ const std::string &filename,
+ boost::function<void (const QString)> error_handler,
+ sr_input_format *format = NULL);
+
+ void sample_thread_proc(boost::shared_ptr<device::DevInst> dev_inst,
+ boost::function<void (const QString)> error_handler);
+
+ void feed_in_header(const sr_dev_inst *sdi);
+
+ void feed_in_meta(const sr_dev_inst *sdi,
+ const sr_datafeed_meta &meta);
+
+ void feed_in_frame_begin();
+
+ void feed_in_logic(const sr_datafeed_logic &logic);
+
+ void feed_in_analog(const sr_datafeed_analog &analog);
+
+ void data_feed_in(const struct sr_dev_inst *sdi,
+ const struct sr_datafeed_packet *packet);
+
+ static void data_feed_in_proc(const struct sr_dev_inst *sdi,
+ const struct sr_datafeed_packet *packet, void *cb_data);
+
+private:
+ DeviceManager &_device_manager;
+
+ /**
+ * The device instance that will be used in the next capture session.
+ */
+ boost::shared_ptr<device::DevInst> _dev_inst;
+
+ std::vector< boost::shared_ptr<view::DecodeTrace> > _decode_traces;
+
+ mutable boost::mutex _sampling_mutex;
+ capture_state _capture_state;
+
+ mutable boost::mutex _signals_mutex;
+ std::vector< boost::shared_ptr<view::Signal> > _signals;
+
+ mutable boost::mutex _data_mutex;
+ boost::shared_ptr<data::Logic> _logic_data;
+ boost::shared_ptr<data::LogicSnapshot> _cur_logic_snapshot;
+ std::map< const sr_channel*, boost::shared_ptr<data::AnalogSnapshot> >
+ _cur_analog_snapshots;
+
+ boost::thread _sampling_thread;
+
+signals:
+ void capture_state_changed(int state);
+
+ void signals_changed();
+
+ void frame_began();
+
+ void data_received();
+
+ void frame_ended();
+
+private:
+ // TODO: This should not be necessary. Multiple concurrent
+ // sessions should should be supported and it should be
+ // possible to associate a pointer with a sr_session.
+ static SigSession *_session;
+};
+
+} // namespace pv
+
+#endif // PULSEVIEW_PV_SIGSESSION_H
diff --git a/pv/storesession.cpp b/pv/storesession.cpp
new file mode 100644
index 0000000..3d0f12f
--- /dev/null
+++ b/pv/storesession.cpp
@@ -0,0 +1,196 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2014 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "storesession.h"
+
+#include <pv/sigsession.h>
+#include <pv/data/logic.h>
+#include <pv/data/logicsnapshot.h>
+#include <pv/view/signal.h>
+
+using boost::dynamic_pointer_cast;
+using boost::mutex;
+using boost::shared_ptr;
+using boost::thread;
+using boost::lock_guard;
+using std::deque;
+using std::make_pair;
+using std::min;
+using std::pair;
+using std::set;
+using std::string;
+using std::vector;
+
+namespace pv {
+
+const size_t StoreSession::BlockSize = 1024 * 1024;
+
+StoreSession::StoreSession(const std::string &file_name,
+ const SigSession &session) :
+ _file_name(file_name),
+ _session(session),
+ _units_stored(0),
+ _unit_count(0)
+{
+}
+
+StoreSession::~StoreSession()
+{
+ wait();
+}
+
+pair<uint64_t, uint64_t> StoreSession::progress() const
+{
+ lock_guard<mutex> lock(_mutex);
+ return make_pair(_units_stored, _unit_count);
+}
+
+const QString& StoreSession::error() const
+{
+ lock_guard<mutex> lock(_mutex);
+ return _error;
+}
+
+bool StoreSession::start()
+{
+ set< shared_ptr<data::SignalData> > data_set =
+ _session.get_data();
+ const vector< shared_ptr<view::Signal> > sigs =
+ _session.get_signals();
+
+ // Check we have logic data
+ if (data_set.empty() || sigs.empty()) {
+ _error = tr("No data to save.");
+ return false;
+ }
+
+ if (data_set.size() > 1) {
+ _error = tr("PulseView currently only has support for "
+ "storing a single data stream.");
+ return false;
+ }
+
+ // Get the logic data
+ //shared_ptr<data::SignalData
+ shared_ptr<data::Logic> data;
+ if (!(data = dynamic_pointer_cast<data::Logic>(*data_set.begin()))) {
+ _error = tr("PulseView currently only has support for "
+ "storing a logic data.");
+ return false;
+ }
+
+ // Get the snapshot
+ const deque< shared_ptr<data::LogicSnapshot> > &snapshots =
+ data->get_snapshots();
+
+ if (snapshots.empty()) {
+ _error = tr("No snapshots to save.");
+ return false;
+ }
+
+ const shared_ptr<data::LogicSnapshot> snapshot(snapshots.front());
+ assert(snapshot);
+
+ // Make a list of probes
+ char **const probes = new char*[sigs.size() + 1];
+ for (size_t i = 0; i < sigs.size(); i++) {
+ shared_ptr<view::Signal> sig(sigs[i]);
+ assert(sig);
+ probes[i] = strdup(sig->get_name().toUtf8().constData());
+ }
+ probes[sigs.size()] = NULL;
+
+ // Begin storing
+ if (sr_session_save_init(_file_name.c_str(),
+ data->samplerate(), probes) != SR_OK) {
+ _error = tr("Error while saving.");
+ return false;
+ }
+
+ // Delete the probes array
+ for (size_t i = 0; i <= sigs.size(); i++)
+ free(probes[i]);
+ delete[] probes;
+
+ _thread = boost::thread(&StoreSession::store_proc, this, snapshot);
+ return true;
+}
+
+void StoreSession::wait()
+{
+ if (_thread.joinable())
+ _thread.join();
+}
+
+void StoreSession::cancel()
+{
+ _thread.interrupt();
+}
+
+void StoreSession::store_proc(shared_ptr<data::LogicSnapshot> snapshot)
+{
+ assert(snapshot);
+
+ uint64_t start_sample = 0;
+
+ /// TODO: Wrap this in a std::unique_ptr when we transition to C++11
+ uint8_t *const data = new uint8_t[BlockSize];
+ assert(data);
+
+ const int unit_size = snapshot->unit_size();
+ assert(unit_size != 0);
+
+ {
+ lock_guard<mutex> lock(_mutex);
+ _unit_count = snapshot->get_sample_count();
+ }
+
+ const unsigned int samples_per_block = BlockSize / unit_size;
+
+ while (!boost::this_thread::interruption_requested() &&
+ start_sample < _unit_count)
+ {
+ progress_updated();
+
+ const uint64_t end_sample = min(
+ start_sample + samples_per_block, _unit_count);
+ snapshot->get_samples(data, start_sample, end_sample);
+
+ if(sr_session_append(_file_name.c_str(), data, unit_size,
+ end_sample - start_sample) != SR_OK)
+ {
+ _error = tr("Error while saving.");
+ break;
+ }
+
+ start_sample = end_sample;
+
+ {
+ lock_guard<mutex> lock(_mutex);
+ _units_stored = start_sample;
+ }
+ }
+
+ progress_updated();
+
+ delete[] data;
+}
+
+} // pv
diff --git a/pv/storesession.h b/pv/storesession.h
new file mode 100644
index 0000000..5ef92a3
--- /dev/null
+++ b/pv/storesession.h
@@ -0,0 +1,83 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2014 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_STORESESSION_H
+#define PULSEVIEW_PV_STORESESSION_H
+
+#include <stdint.h>
+
+#include <string>
+
+#include <boost/thread.hpp>
+
+#include <QObject>
+
+namespace pv {
+
+class SigSession;
+
+namespace data {
+class LogicSnapshot;
+}
+
+class StoreSession : public QObject
+{
+ Q_OBJECT
+
+private:
+ static const size_t BlockSize;
+
+public:
+ StoreSession(const std::string &file_name,
+ const SigSession &session);
+
+ ~StoreSession();
+
+ std::pair<uint64_t, uint64_t> progress() const;
+
+ const QString& error() const;
+
+ bool start();
+
+ void wait();
+
+ void cancel();
+
+private:
+ void store_proc(boost::shared_ptr<pv::data::LogicSnapshot> snapshot);
+
+signals:
+ void progress_updated();
+
+private:
+ const std::string _file_name;
+ const SigSession &_session;
+
+ boost::thread _thread;
+
+ mutable boost::mutex _mutex;
+ uint64_t _units_stored;
+ uint64_t _unit_count;
+ QString _error;
+};
+
+} // pv
+
+#endif // PULSEVIEW_PV_STORESESSION_H
diff --git a/pv/toolbars/samplingbar.cpp b/pv/toolbars/samplingbar.cpp
new file mode 100644
index 0000000..91c4b07
--- /dev/null
+++ b/pv/toolbars/samplingbar.cpp
@@ -0,0 +1,450 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <extdef.h>
+
+#include <assert.h>
+
+#include <boost/foreach.hpp>
+
+#include <QAction>
+#include <QDebug>
+
+#include "samplingbar.h"
+
+#include <pv/devicemanager.h>
+#include <pv/device/devinst.h>
+#include <pv/popups/deviceoptions.h>
+#include <pv/popups/probes.h>
+
+using boost::shared_ptr;
+using std::map;
+using std::max;
+using std::min;
+using std::string;
+
+namespace pv {
+namespace toolbars {
+
+const uint64_t SamplingBar::MinSampleCount = 100ULL;
+const uint64_t SamplingBar::MaxSampleCount = 1000000000000ULL;
+const uint64_t SamplingBar::DefaultSampleCount = 1000000;
+
+SamplingBar::SamplingBar(SigSession &session, QWidget *parent) :
+ QToolBar("Sampling Bar", parent),
+ _session(session),
+ _device_selector(this),
+ _updating_device_selector(false),
+ _configure_button(this),
+ _configure_button_action(NULL),
+ _probes_button(this),
+ _sample_count(" samples", this),
+ _sample_rate("Hz", this),
+ _updating_sample_rate(false),
+ _updating_sample_count(false),
+ _sample_count_supported(false),
+ _icon_red(":/icons/status-red.svg"),
+ _icon_green(":/icons/status-green.svg"),
+ _icon_grey(":/icons/status-grey.svg"),
+ _run_stop_button(this)
+{
+ connect(&_run_stop_button, SIGNAL(clicked()),
+ this, SLOT(on_run_stop()));
+ connect(&_device_selector, SIGNAL(currentIndexChanged (int)),
+ this, SLOT(on_device_selected()));
+ connect(&_sample_count, SIGNAL(value_changed()),
+ this, SLOT(on_sample_count_changed()));
+ connect(&_sample_rate, SIGNAL(value_changed()),
+ this, SLOT(on_sample_rate_changed()));
+
+ _sample_count.show_min_max_step(0, UINT64_MAX, 1);
+
+ set_capture_state(pv::SigSession::Stopped);
+
+ _configure_button.setIcon(QIcon::fromTheme("configure",
+ QIcon(":/icons/configure.png")));
+
+ _probes_button.setIcon(QIcon::fromTheme("probes",
+ QIcon(":/icons/probes.svg")));
+
+ _run_stop_button.setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
+
+ addWidget(&_device_selector);
+ _configure_button_action = addWidget(&_configure_button);
+ addWidget(&_probes_button);
+ addWidget(&_sample_count);
+ addWidget(&_sample_rate);
+
+ addWidget(&_run_stop_button);
+}
+
+void SamplingBar::set_device_list(
+ const std::list< shared_ptr<pv::device::DevInst> > &devices,
+ shared_ptr<pv::device::DevInst> selected)
+{
+ int selected_index = -1;
+
+ assert(selected);
+
+ _updating_device_selector = true;
+
+ _device_selector.clear();
+ _device_selector_map.clear();
+
+ BOOST_FOREACH (shared_ptr<pv::device::DevInst> dev_inst, devices) {
+ assert(dev_inst);
+ const string title = dev_inst->format_device_title();
+ const sr_dev_inst *sdi = dev_inst->dev_inst();
+ assert(sdi);
+
+ if (selected == dev_inst)
+ selected_index = _device_selector.count();
+
+ _device_selector_map[sdi] = dev_inst;
+ _device_selector.addItem(title.c_str(),
+ qVariantFromValue((void*)sdi));
+ }
+
+ // The selected device should have been in the list
+ assert(selected_index != -1);
+ _device_selector.setCurrentIndex(selected_index);
+
+ update_device_config_widgets();
+
+ _updating_device_selector = false;
+}
+
+shared_ptr<pv::device::DevInst> SamplingBar::get_selected_device() const
+{
+ const int index = _device_selector.currentIndex();
+ if (index < 0)
+ return shared_ptr<pv::device::DevInst>();
+
+ const sr_dev_inst *const sdi =
+ (const sr_dev_inst*)_device_selector.itemData(
+ index).value<void*>();
+ assert(sdi);
+
+ map<const sr_dev_inst*, boost::weak_ptr<device::DevInst> >::
+ const_iterator iter = _device_selector_map.find(sdi);
+ if (iter == _device_selector_map.end())
+ return shared_ptr<pv::device::DevInst>();
+
+ return shared_ptr<pv::device::DevInst>((*iter).second);
+}
+
+void SamplingBar::set_capture_state(pv::SigSession::capture_state state)
+{
+ const QIcon *icons[] = {&_icon_grey, &_icon_red, &_icon_green};
+ _run_stop_button.setIcon(*icons[state]);
+ _run_stop_button.setText((state == pv::SigSession::Stopped) ?
+ tr("Run") : tr("Stop"));
+}
+
+void SamplingBar::update_sample_rate_selector()
+{
+ GVariant *gvar_dict, *gvar_list;
+ const uint64_t *elements = NULL;
+ gsize num_elements;
+
+ if (_updating_sample_rate)
+ return;
+
+ const shared_ptr<device::DevInst> dev_inst = get_selected_device();
+ if (!dev_inst)
+ return;
+
+ assert(!_updating_sample_rate);
+ _updating_sample_rate = true;
+
+ if (!(gvar_dict = dev_inst->list_config(NULL, SR_CONF_SAMPLERATE)))
+ {
+ _sample_rate.show_none();
+ _updating_sample_rate = false;
+ return;
+ }
+
+ if ((gvar_list = g_variant_lookup_value(gvar_dict,
+ "samplerate-steps", G_VARIANT_TYPE("at"))))
+ {
+ elements = (const uint64_t *)g_variant_get_fixed_array(
+ gvar_list, &num_elements, sizeof(uint64_t));
+
+ const uint64_t min = elements[0];
+ const uint64_t max = elements[1];
+ const uint64_t step = elements[2];
+
+ g_variant_unref(gvar_list);
+
+ assert(min > 0);
+ assert(max > 0);
+ assert(max > min);
+ assert(step > 0);
+
+ if (step == 1)
+ _sample_rate.show_125_list(min, max);
+ else
+ {
+ // When the step is not 1, we cam't make a 1-2-5-10
+ // list of sample rates, because we may not be able to
+ // make round numbers. Therefore in this case, show a
+ // spin box.
+ _sample_rate.show_min_max_step(min, max, step);
+ }
+ }
+ else if ((gvar_list = g_variant_lookup_value(gvar_dict,
+ "samplerates", G_VARIANT_TYPE("at"))))
+ {
+ elements = (const uint64_t *)g_variant_get_fixed_array(
+ gvar_list, &num_elements, sizeof(uint64_t));
+ _sample_rate.show_list(elements, num_elements);
+ g_variant_unref(gvar_list);
+ }
+ _updating_sample_rate = false;
+
+ g_variant_unref(gvar_dict);
+ update_sample_rate_selector_value();
+}
+
+void SamplingBar::update_sample_rate_selector_value()
+{
+ GVariant *gvar;
+ uint64_t samplerate;
+
+ if (_updating_sample_rate)
+ return;
+
+ const shared_ptr<device::DevInst> dev_inst = get_selected_device();
+ if (!dev_inst)
+ return;
+
+ if (!(gvar = dev_inst->get_config(NULL, SR_CONF_SAMPLERATE))) {
+ qDebug() << "WARNING: Failed to get value of sample rate";
+ return;
+ }
+ samplerate = g_variant_get_uint64(gvar);
+ g_variant_unref(gvar);
+
+ assert(!_updating_sample_rate);
+ _updating_sample_rate = true;
+ _sample_rate.set_value(samplerate);
+ _updating_sample_rate = false;
+}
+
+void SamplingBar::update_sample_count_selector()
+{
+ GVariant *gvar;
+
+ if (_updating_sample_count)
+ return;
+
+ const shared_ptr<device::DevInst> dev_inst = get_selected_device();
+ if (!dev_inst)
+ return;
+
+ assert(!_updating_sample_count);
+ _updating_sample_count = true;
+
+ if (_sample_count_supported)
+ {
+ uint64_t sample_count = _sample_count.value();
+ uint64_t min_sample_count = 0;
+ uint64_t max_sample_count = MaxSampleCount;
+
+ if (sample_count == 0)
+ sample_count = DefaultSampleCount;
+
+ if ((gvar = dev_inst->list_config(NULL, SR_CONF_LIMIT_SAMPLES)))
+ {
+ g_variant_get(gvar, "(tt)",
+ &min_sample_count, &max_sample_count);
+ g_variant_unref(gvar);
+ }
+
+ min_sample_count = min(max(min_sample_count, MinSampleCount),
+ max_sample_count);
+
+ _sample_count.show_125_list(
+ min_sample_count, max_sample_count);
+
+ if ((gvar = dev_inst->get_config(NULL, SR_CONF_LIMIT_SAMPLES)))
+ {
+ sample_count = g_variant_get_uint64(gvar);
+ if (sample_count == 0)
+ sample_count = DefaultSampleCount;
+ sample_count = min(max(sample_count, MinSampleCount),
+ max_sample_count);
+
+ g_variant_unref(gvar);
+ }
+
+ _sample_count.set_value(sample_count);
+ }
+ else
+ _sample_count.show_none();
+
+ _updating_sample_count = false;
+}
+
+void SamplingBar::update_device_config_widgets()
+{
+ GVariant *gvar;
+
+ using namespace pv::popups;
+
+ const shared_ptr<device::DevInst> dev_inst = get_selected_device();
+ if (!dev_inst)
+ return;
+
+ // Update the configure popup
+ DeviceOptions *const opts = new DeviceOptions(dev_inst, this);
+ _configure_button_action->setVisible(
+ !opts->binding().properties().empty());
+ _configure_button.set_popup(opts);
+
+ // Update the probes popup
+ Probes *const probes = new Probes(_session, this);
+ _probes_button.set_popup(probes);
+
+ // Update supported options.
+ _sample_count_supported = false;
+
+ if ((gvar = dev_inst->list_config(NULL, SR_CONF_DEVICE_OPTIONS)))
+ {
+ gsize num_opts;
+ const int *const options =
+ (const int32_t *)g_variant_get_fixed_array(
+ gvar, &num_opts, sizeof(int32_t));
+ for (unsigned int i = 0; i < num_opts; i++)
+ {
+ switch (options[i]) {
+ case SR_CONF_LIMIT_SAMPLES:
+ _sample_count_supported = true;
+ break;
+ case SR_CONF_LIMIT_FRAMES:
+ dev_inst->set_config(NULL, SR_CONF_LIMIT_FRAMES,
+ g_variant_new_uint64(1));
+ break;
+ }
+ }
+ }
+
+ // Add notification of reconfigure events
+ disconnect(this, SLOT(on_config_changed()));
+ connect(dev_inst.get(), SIGNAL(config_changed()),
+ this, SLOT(on_config_changed()));
+
+ // Update sweep timing widgets.
+ update_sample_count_selector();
+ update_sample_rate_selector();
+}
+
+void SamplingBar::commit_sample_count()
+{
+ uint64_t sample_count = 0;
+
+ if (_updating_sample_count)
+ return;
+
+ const shared_ptr<device::DevInst> dev_inst = get_selected_device();
+ if (!dev_inst)
+ return;
+
+ sample_count = _sample_count.value();
+
+ // Set the sample count
+ assert(!_updating_sample_count);
+ _updating_sample_count = true;
+ if (_sample_count_supported &&
+ !dev_inst->set_config(NULL, SR_CONF_LIMIT_SAMPLES,
+ g_variant_new_uint64(sample_count))) {
+ qDebug() << "Failed to configure sample count.";
+ return;
+ }
+ _updating_sample_count = false;
+}
+
+void SamplingBar::commit_sample_rate()
+{
+ uint64_t sample_rate = 0;
+
+ if (_updating_sample_rate)
+ return;
+
+ const shared_ptr<device::DevInst> dev_inst = get_selected_device();
+ if (!dev_inst)
+ return;
+
+ sample_rate = _sample_rate.value();
+ if (sample_rate == 0)
+ return;
+
+ // Set the samplerate
+ assert(!_updating_sample_rate);
+ _updating_sample_rate = true;
+ if (!dev_inst->set_config(NULL, SR_CONF_SAMPLERATE,
+ g_variant_new_uint64(sample_rate))) {
+ qDebug() << "Failed to configure samplerate.";
+ return;
+ }
+ _updating_sample_rate = false;
+}
+
+void SamplingBar::on_device_selected()
+{
+ if (_updating_device_selector)
+ return;
+
+ const shared_ptr<device::DevInst> dev_inst = get_selected_device();
+ if (!dev_inst)
+ return;
+
+ _session.set_device(dev_inst);
+
+ update_device_config_widgets();
+}
+
+void SamplingBar::on_sample_count_changed()
+{
+ commit_sample_count();
+}
+
+void SamplingBar::on_sample_rate_changed()
+{
+ commit_sample_rate();
+}
+
+void SamplingBar::on_run_stop()
+{
+ commit_sample_count();
+ commit_sample_rate();
+ run_stop();
+}
+
+void SamplingBar::on_config_changed()
+{
+ commit_sample_count();
+ update_sample_count_selector();
+ commit_sample_rate();
+ update_sample_rate_selector();
+}
+
+} // namespace toolbars
+} // namespace pv
diff --git a/pv/toolbars/samplingbar.h b/pv/toolbars/samplingbar.h
new file mode 100644
index 0000000..e59d2f9
--- /dev/null
+++ b/pv/toolbars/samplingbar.h
@@ -0,0 +1,121 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_TOOLBARS_SAMPLINGBAR_H
+#define PULSEVIEW_PV_TOOLBARS_SAMPLINGBAR_H
+
+#include <stdint.h>
+
+#include <list>
+#include <map>
+
+#include <boost/shared_ptr.hpp>
+
+#include <QComboBox>
+#include <QDoubleSpinBox>
+#include <QToolBar>
+#include <QToolButton>
+
+#include <pv/sigsession.h>
+#include <pv/widgets/popuptoolbutton.h>
+#include <pv/widgets/sweeptimingwidget.h>
+
+class QAction;
+
+namespace pv {
+
+class SigSession;
+
+namespace device {
+class DevInst;
+}
+
+namespace toolbars {
+
+class SamplingBar : public QToolBar
+{
+ Q_OBJECT
+
+private:
+ static const uint64_t MinSampleCount;
+ static const uint64_t MaxSampleCount;
+ static const uint64_t DefaultSampleCount;
+
+public:
+ SamplingBar(SigSession &session, QWidget *parent);
+
+ void set_device_list(
+ const std::list< boost::shared_ptr<pv::device::DevInst> >
+ &devices,
+ boost::shared_ptr<pv::device::DevInst> selected);
+
+ boost::shared_ptr<pv::device::DevInst> get_selected_device() const;
+
+ void set_capture_state(pv::SigSession::capture_state state);
+
+signals:
+ void run_stop();
+
+private:
+ void update_sample_rate_selector();
+ void update_sample_rate_selector_value();
+ void update_sample_count_selector();
+ void update_device_config_widgets();
+ void commit_sample_rate();
+ void commit_sample_count();
+
+private slots:
+ void on_device_selected();
+ void on_sample_count_changed();
+ void on_sample_rate_changed();
+ void on_run_stop();
+
+ void on_config_changed();
+
+private:
+ SigSession &_session;
+
+ QComboBox _device_selector;
+ std::map<const sr_dev_inst*, boost::weak_ptr<device::DevInst> >
+ _device_selector_map;
+ bool _updating_device_selector;
+
+ pv::widgets::PopupToolButton _configure_button;
+ QAction *_configure_button_action;
+
+ pv::widgets::PopupToolButton _probes_button;
+
+ pv::widgets::SweepTimingWidget _sample_count;
+ pv::widgets::SweepTimingWidget _sample_rate;
+ bool _updating_sample_rate;
+ bool _updating_sample_count;
+
+ bool _sample_count_supported;
+
+ QIcon _icon_red;
+ QIcon _icon_green;
+ QIcon _icon_grey;
+ QToolButton _run_stop_button;
+};
+
+} // namespace toolbars
+} // namespace pv
+
+#endif // PULSEVIEW_PV_TOOLBARS_SAMPLINGBAR_H
diff --git a/pv/view/analogsignal.cpp b/pv/view/analogsignal.cpp
new file mode 100644
index 0000000..c3f2738
--- /dev/null
+++ b/pv/view/analogsignal.cpp
@@ -0,0 +1,202 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <extdef.h>
+
+#include <math.h>
+
+#include "analogsignal.h"
+#include "pv/data/analog.h"
+#include "pv/data/analogsnapshot.h"
+#include "pv/view/view.h"
+
+using boost::shared_ptr;
+using std::max;
+using std::min;
+using std::deque;
+
+namespace pv {
+namespace view {
+
+const QColor AnalogSignal::SignalColours[4] = {
+ QColor(0xC4, 0xA0, 0x00), // Yellow
+ QColor(0x87, 0x20, 0x7A), // Magenta
+ QColor(0x20, 0x4A, 0x87), // Blue
+ QColor(0x4E, 0x9A, 0x06) // Green
+};
+
+const float AnalogSignal::EnvelopeThreshold = 256.0f;
+
+AnalogSignal::AnalogSignal(shared_ptr<pv::device::DevInst> dev_inst,
+ const sr_channel *const probe, shared_ptr<data::Analog> data) :
+ Signal(dev_inst, probe),
+ _data(data),
+ _scale(1.0f)
+{
+ _colour = SignalColours[probe->index % countof(SignalColours)];
+}
+
+AnalogSignal::~AnalogSignal()
+{
+}
+
+shared_ptr<pv::data::SignalData> AnalogSignal::data() const
+{
+ return _data;
+}
+
+shared_ptr<pv::data::Analog> AnalogSignal::analog_data() const
+{
+ return _data;
+}
+
+void AnalogSignal::set_scale(float scale)
+{
+ _scale = scale;
+}
+
+void AnalogSignal::paint_back(QPainter &p, int left, int right)
+{
+ if (_probe->enabled)
+ paint_axis(p, get_y(), left, right);
+}
+
+void AnalogSignal::paint_mid(QPainter &p, int left, int right)
+{
+ assert(_data);
+ assert(right >= left);
+
+ assert(_view);
+ const int y = _v_offset - _view->v_offset();
+
+ const double scale = _view->scale();
+ assert(scale > 0);
+
+ const double offset = _view->offset();
+
+ if (!_probe->enabled)
+ return;
+
+ const deque< shared_ptr<pv::data::AnalogSnapshot> > &snapshots =
+ _data->get_snapshots();
+ if (snapshots.empty())
+ return;
+
+ const shared_ptr<pv::data::AnalogSnapshot> &snapshot =
+ snapshots.front();
+
+ const double pixels_offset = offset / scale;
+ const double samplerate = _data->samplerate();
+ const double start_time = _data->get_start_time();
+ const int64_t last_sample = snapshot->get_sample_count() - 1;
+ const double samples_per_pixel = samplerate * scale;
+ const double start = samplerate * (offset - start_time);
+ const double end = start + samples_per_pixel * (right - left);
+
+ const int64_t start_sample = min(max((int64_t)floor(start),
+ (int64_t)0), last_sample);
+ const int64_t end_sample = min(max((int64_t)ceil(end) + 1,
+ (int64_t)0), last_sample);
+
+ if (samples_per_pixel < EnvelopeThreshold)
+ paint_trace(p, snapshot, y, left,
+ start_sample, end_sample,
+ pixels_offset, samples_per_pixel);
+ else
+ paint_envelope(p, snapshot, y, left,
+ start_sample, end_sample,
+ pixels_offset, samples_per_pixel);
+}
+
+void AnalogSignal::paint_trace(QPainter &p,
+ const shared_ptr<pv::data::AnalogSnapshot> &snapshot,
+ int y, int left, const int64_t start, const int64_t end,
+ const double pixels_offset, const double samples_per_pixel)
+{
+ const int64_t sample_count = end - start;
+
+ const float *const samples = snapshot->get_samples(start, end);
+ assert(samples);
+
+ p.setPen(_colour);
+
+ QPointF *points = new QPointF[sample_count];
+ QPointF *point = points;
+
+ for (int64_t sample = start; sample != end; sample++) {
+ const float x = (sample / samples_per_pixel -
+ pixels_offset) + left;
+ *point++ = QPointF(x,
+ y - samples[sample - start] * _scale);
+ }
+
+ p.drawPolyline(points, point - points);
+
+ delete[] samples;
+ delete[] points;
+}
+
+void AnalogSignal::paint_envelope(QPainter &p,
+ const shared_ptr<pv::data::AnalogSnapshot> &snapshot,
+ int y, int left, const int64_t start, const int64_t end,
+ const double pixels_offset, const double samples_per_pixel)
+{
+ using pv::data::AnalogSnapshot;
+
+ AnalogSnapshot::EnvelopeSection e;
+ snapshot->get_envelope_section(e, start, end, samples_per_pixel);
+
+ if (e.length < 2)
+ return;
+
+ p.setPen(QPen(Qt::NoPen));
+ p.setBrush(_colour);
+
+ QRectF *const rects = new QRectF[e.length];
+ QRectF *rect = rects;
+
+ for(uint64_t sample = 0; sample < e.length-1; sample++) {
+ const float x = ((e.scale * sample + e.start) /
+ samples_per_pixel - pixels_offset) + left;
+ const AnalogSnapshot::EnvelopeSample *const s =
+ e.samples + sample;
+
+ // We overlap this sample with the next so that vertical
+ // gaps do not appear during steep rising or falling edges
+ const float b = y - max(s->max, (s+1)->min) * _scale;
+ const float t = y - min(s->min, (s+1)->max) * _scale;
+
+ float h = b - t;
+ if(h >= 0.0f && h <= 1.0f)
+ h = 1.0f;
+ if(h <= 0.0f && h >= -1.0f)
+ h = -1.0f;
+
+ *rect++ = QRectF(x, t, 1.0f, h);
+ }
+
+ p.drawRects(rects, e.length);
+
+ delete[] rects;
+ delete[] e.samples;
+}
+
+} // namespace view
+} // namespace pv
diff --git a/pv/view/analogsignal.h b/pv/view/analogsignal.h
new file mode 100644
index 0000000..f22d128
--- /dev/null
+++ b/pv/view/analogsignal.h
@@ -0,0 +1,92 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_VIEW_ANALOGSIGNAL_H
+#define PULSEVIEW_PV_VIEW_ANALOGSIGNAL_H
+
+#include "signal.h"
+
+#include <boost/shared_ptr.hpp>
+
+namespace pv {
+
+namespace data {
+class Analog;
+class AnalogSnapshot;
+}
+
+namespace view {
+
+class AnalogSignal : public Signal
+{
+private:
+ static const QColor SignalColours[4];
+
+ static const float EnvelopeThreshold;
+
+public:
+ AnalogSignal(boost::shared_ptr<pv::device::DevInst> dev_inst,
+ const sr_channel *const probe,
+ boost::shared_ptr<pv::data::Analog> data);
+
+ virtual ~AnalogSignal();
+
+ boost::shared_ptr<pv::data::SignalData> data() const;
+
+ boost::shared_ptr<pv::data::Analog> analog_data() const;
+
+ void set_scale(float scale);
+
+ /**
+ * Paints the background layer of the signal with a QPainter
+ * @param p the QPainter to paint into.
+ * @param left the x-coordinate of the left edge of the signal.
+ * @param right the x-coordinate of the right edge of the signal.
+ **/
+ void paint_back(QPainter &p, int left, int right);
+
+ /**
+ * Paints the mid-layer of the signal with a QPainter
+ * @param p the QPainter to paint into.
+ * @param left the x-coordinate of the left edge of the signal.
+ * @param right the x-coordinate of the right edge of the signal.
+ **/
+ void paint_mid(QPainter &p, int left, int right);
+
+private:
+ void paint_trace(QPainter &p,
+ const boost::shared_ptr<pv::data::AnalogSnapshot> &snapshot,
+ int y, int left, const int64_t start, const int64_t end,
+ const double pixels_offset, const double samples_per_pixel);
+
+ void paint_envelope(QPainter &p,
+ const boost::shared_ptr<pv::data::AnalogSnapshot> &snapshot,
+ int y, int left, const int64_t start, const int64_t end,
+ const double pixels_offset, const double samples_per_pixel);
+
+private:
+ boost::shared_ptr<pv::data::Analog> _data;
+ float _scale;
+};
+
+} // namespace view
+} // namespace pv
+
+#endif // PULSEVIEW_PV_VIEW_ANALOGSIGNAL_H
diff --git a/pv/view/cursor.cpp b/pv/view/cursor.cpp
new file mode 100644
index 0000000..ca22eef
--- /dev/null
+++ b/pv/view/cursor.cpp
@@ -0,0 +1,159 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "cursor.h"
+
+#include "ruler.h"
+#include "view.h"
+
+#include <QBrush>
+#include <QPainter>
+#include <QPointF>
+#include <QRect>
+#include <QRectF>
+
+#include <stdio.h>
+
+#include <extdef.h>
+
+using boost::shared_ptr;
+
+namespace pv {
+namespace view {
+
+const QColor Cursor::LineColour(32, 74, 135);
+const QColor Cursor::FillColour(52, 101, 164);
+const QColor Cursor::HighlightColour(83, 130, 186);
+const QColor Cursor::TextColour(Qt::white);
+
+const int Cursor::Offset = 1;
+
+const int Cursor::ArrowSize = 4;
+
+Cursor::Cursor(View &view, double time) :
+ TimeMarker(view, LineColour, time)
+{
+}
+
+QRectF Cursor::get_label_rect(const QRect &rect) const
+{
+ const shared_ptr<Cursor> other(get_other_cursor());
+ assert(other);
+
+ const float x = (_time - _view.offset()) / _view.scale();
+
+ const QSizeF label_size(
+ _text_size.width() + View::LabelPadding.width() * 2,
+ _text_size.height() + View::LabelPadding.height() * 2);
+ const float top = rect.height() - label_size.height() -
+ Cursor::Offset - Cursor::ArrowSize - 0.5f;
+ const float height = label_size.height();
+
+ if (_time > other->time())
+ return QRectF(x, top, label_size.width(), height);
+ else
+ return QRectF(x - label_size.width(), top,
+ label_size.width(), height);
+}
+
+void Cursor::paint_label(QPainter &p, const QRect &rect,
+ unsigned int prefix)
+{
+ const shared_ptr<Cursor> other(get_other_cursor());
+ assert(other);
+
+ compute_text_size(p, prefix);
+ const QRectF r(get_label_rect(rect));
+
+ const QPointF left_points[] = {
+ r.topLeft(),
+ r.topRight(),
+ r.bottomRight(),
+ QPointF(r.left() + ArrowSize, r.bottom()),
+ QPointF(r.left(), rect.bottom()),
+ };
+
+ const QPointF right_points[] = {
+ r.topRight(),
+ r.topLeft(),
+ r.bottomLeft(),
+ QPointF(r.right() - ArrowSize, r.bottom()),
+ QPointF(r.right(), rect.bottom()),
+ };
+
+ const QPointF left_highlight_points[] = {
+ QPointF(r.left() + 1, r.top() + 1),
+ QPointF(r.right() - 1, r.top() + 1),
+ QPointF(r.right() - 1, r.bottom() - 1),
+ QPointF(r.left() + ArrowSize - 1, r.bottom() - 1),
+ QPointF(r.left() + 1, rect.bottom() - 1),
+ };
+
+ const QPointF right_highlight_points[] = {
+ QPointF(r.right() - 1, r.top() + 1),
+ QPointF(r.left() + 1, r.top() + 1),
+ QPointF(r.left() + 1, r.bottom() - 1),
+ QPointF(r.right() - ArrowSize + 1, r.bottom() - 1),
+ QPointF(r.right() - 1, rect.bottom() - 1),
+ };
+
+ const QPointF *const points = (_time > other->time()) ?
+ left_points : right_points;
+ const QPointF *const highlight_points = (_time > other->time()) ?
+ left_highlight_points : right_highlight_points;
+
+ if (selected()) {
+ p.setPen(highlight_pen());
+ p.setBrush(Qt::transparent);
+ p.drawPolygon(points, countof(left_points));
+ }
+
+ p.setPen(Qt::transparent);
+ p.setBrush(FillColour);
+ p.drawPolygon(points, countof(left_points));
+
+ p.setPen(HighlightColour);
+ p.setBrush(Qt::transparent);
+ p.drawPolygon(highlight_points, countof(left_highlight_points));
+
+ p.setPen(LineColour);
+ p.setBrush(Qt::transparent);
+ p.drawPolygon(points, countof(left_points));
+
+ p.setPen(TextColour);
+ p.drawText(r, Qt::AlignCenter | Qt::AlignVCenter,
+ Ruler::format_time(_time, prefix, 2));
+}
+
+void Cursor::compute_text_size(QPainter &p, unsigned int prefix)
+{
+ _text_size = p.boundingRect(QRectF(), 0,
+ Ruler::format_time(_time, prefix, 2)).size();
+}
+
+shared_ptr<Cursor> Cursor::get_other_cursor() const
+{
+ const CursorPair &cursors = _view.cursors();
+ return (cursors.first().get() == this) ?
+ cursors.second() : cursors.first();
+}
+
+} // namespace view
+} // namespace pv
diff --git a/pv/view/cursor.h b/pv/view/cursor.h
new file mode 100644
index 0000000..290365e
--- /dev/null
+++ b/pv/view/cursor.h
@@ -0,0 +1,86 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_VIEW_CURSOR_H
+#define PULSEVIEW_PV_VIEW_CURSOR_H
+
+#include "timemarker.h"
+
+#include <boost/shared_ptr.hpp>
+
+#include <QSizeF>
+
+class QPainter;
+
+namespace pv {
+namespace view {
+
+class Cursor : public TimeMarker
+{
+ Q_OBJECT
+
+public:
+ static const QColor LineColour;
+ static const QColor FillColour;
+ static const QColor HighlightColour;
+ static const QColor TextColour;
+
+ static const int Offset;
+
+ static const int ArrowSize;
+
+public:
+ /**
+ * Constructor.
+ * @param view A reference to the view that owns this cursor pair.
+ * @param time The time to set the flag to.
+ */
+ Cursor(View &view, double time);
+
+public:
+ /**
+ * Gets the marker label rectangle.
+ * @param rect The rectangle of the ruler client area.
+ * @return Returns the label rectangle.
+ */
+ QRectF get_label_rect(const QRect &rect) const;
+
+ /**
+ * Paints the cursor's label to the ruler.
+ * @param p The painter to draw with.
+ * @param rect The rectangle of the ruler client area.
+ * @param prefix The index of the SI prefix to use.
+ */
+ void paint_label(QPainter &p, const QRect &rect,
+ unsigned int prefix);
+
+private:
+ void compute_text_size(QPainter &p, unsigned int prefix);
+
+ boost::shared_ptr<Cursor> get_other_cursor() const;
+
+private:
+ QSizeF _text_size;
+};
+
+} // namespace view
+} // namespace pv
+
+#endif // PULSEVIEW_PV_VIEW_CURSOR_H
diff --git a/pv/view/cursorpair.cpp b/pv/view/cursorpair.cpp
new file mode 100644
index 0000000..ed8829d
--- /dev/null
+++ b/pv/view/cursorpair.cpp
@@ -0,0 +1,155 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "cursorpair.h"
+
+#include "ruler.h"
+#include "view.h"
+
+#include <algorithm>
+
+using boost::shared_ptr;
+using std::max;
+using std::make_pair;
+using std::min;
+using std::pair;
+
+namespace pv {
+namespace view {
+
+const int CursorPair::DeltaPadding = 8;
+
+CursorPair::CursorPair(View &view) :
+ _first(new Cursor(view, 0.0)),
+ _second(new Cursor(view, 1.0)),
+ _view(view)
+{
+}
+
+shared_ptr<Cursor> CursorPair::first() const
+{
+ return _first;
+}
+
+shared_ptr<Cursor> CursorPair::second() const
+{
+ return _second;
+}
+
+QRectF CursorPair::get_label_rect(const QRect &rect) const
+{
+ const QSizeF label_size(
+ _text_size.width() + View::LabelPadding.width() * 2,
+ _text_size.height() + View::LabelPadding.height() * 2);
+ const pair<float, float> offsets(get_cursor_offsets());
+ const pair<float, float> normal_offsets(
+ (offsets.first < offsets.second) ? offsets :
+ make_pair(offsets.second, offsets.first));
+
+ const float height = label_size.height();
+ const float left = max(normal_offsets.first + DeltaPadding, -height);
+ const float right = min(normal_offsets.second - DeltaPadding,
+ (float)rect.width() + height);
+
+ return QRectF(left, rect.height() - label_size.height() -
+ Cursor::ArrowSize - Cursor::Offset - 0.5f,
+ right - left, height);
+}
+
+void CursorPair::draw_markers(QPainter &p,
+ const QRect &rect, unsigned int prefix)
+{
+ assert(_first);
+ assert(_second);
+
+ compute_text_size(p, prefix);
+ QRectF delta_rect(get_label_rect(rect));
+
+ const int radius = delta_rect.height() / 2;
+ const QRectF text_rect(delta_rect.intersected(
+ rect).adjusted(radius, 0, -radius, 0));
+ if(text_rect.width() >= _text_size.width())
+ {
+ const int highlight_radius = delta_rect.height() / 2 - 2;
+
+ p.setBrush(Cursor::FillColour);
+ p.setPen(Cursor::LineColour);
+ p.drawRoundedRect(delta_rect, radius, radius);
+
+ delta_rect.adjust(1, 1, -1, -1);
+ p.setPen(Cursor::HighlightColour);
+ p.drawRoundedRect(delta_rect, highlight_radius, highlight_radius);
+
+ p.setPen(Cursor::TextColour);
+ p.drawText(text_rect, Qt::AlignCenter | Qt::AlignVCenter,
+ Ruler::format_time(_second->time() - _first->time(), prefix, 2));
+ }
+
+ // Paint the cursor markers
+ _first->paint_label(p, rect, prefix);
+ _second->paint_label(p, rect, prefix);
+}
+
+void CursorPair::draw_viewport_background(QPainter &p,
+ const QRect &rect)
+{
+ p.setPen(Qt::NoPen);
+ p.setBrush(QBrush(View::CursorAreaColour));
+
+ const pair<float, float> offsets(get_cursor_offsets());
+ const int l = (int)max(min(
+ offsets.first, offsets.second), 0.0f);
+ const int r = (int)min(max(
+ offsets.first, offsets.second), (float)rect.width());
+
+ p.drawRect(l, 0, r - l, rect.height());
+}
+
+void CursorPair::draw_viewport_foreground(QPainter &p,
+ const QRect &rect)
+{
+ assert(_first);
+ assert(_second);
+
+ _first->paint(p, rect);
+ _second->paint(p, rect);
+}
+
+void CursorPair::compute_text_size(QPainter &p, unsigned int prefix)
+{
+ assert(_first);
+ assert(_second);
+
+ _text_size = p.boundingRect(QRectF(), 0, Ruler::format_time(
+ _second->time() - _first->time(), prefix, 2)).size();
+}
+
+pair<float, float> CursorPair::get_cursor_offsets() const
+{
+ assert(_first);
+ assert(_second);
+
+ return pair<float, float>(
+ (_first->time() - _view.offset()) / _view.scale(),
+ (_second->time() - _view.offset()) / _view.scale());
+}
+
+} // namespace view
+} // namespace pv
diff --git a/pv/view/cursorpair.h b/pv/view/cursorpair.h
new file mode 100644
index 0000000..815276e
--- /dev/null
+++ b/pv/view/cursorpair.h
@@ -0,0 +1,81 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_VIEW_CURSORPAIR_H
+#define PULSEVIEW_PV_VIEW_CURSORPAIR_H
+
+#include "cursor.h"
+
+#include <boost/shared_ptr.hpp>
+
+#include <QPainter>
+
+class QPainter;
+
+namespace pv {
+namespace view {
+
+class CursorPair
+{
+private:
+ static const int DeltaPadding;
+
+public:
+ /**
+ * Constructor.
+ * @param view A reference to the view that owns this cursor pair.
+ */
+ CursorPair(View &view);
+
+ /**
+ * Returns a pointer to the first cursor.
+ */
+ boost::shared_ptr<Cursor> first() const;
+
+ /**
+ * Returns a pointer to the second cursor.
+ */
+ boost::shared_ptr<Cursor> second() const;
+
+public:
+ QRectF get_label_rect(const QRect &rect) const;
+
+ void draw_markers(QPainter &p,
+ const QRect &rect, unsigned int prefix);
+
+ void draw_viewport_background(QPainter &p, const QRect &rect);
+
+ void draw_viewport_foreground(QPainter &p, const QRect &rect);
+
+ void compute_text_size(QPainter &p, unsigned int prefix);
+
+ std::pair<float, float> get_cursor_offsets() const;
+
+private:
+ boost::shared_ptr<Cursor> _first, _second;
+ const View &_view;
+
+ QSizeF _text_size;
+};
+
+} // namespace view
+} // namespace pv
+
+#endif // PULSEVIEW_PV_VIEW_CURSORPAIR_H
diff --git a/pv/view/decodetrace.cpp b/pv/view/decodetrace.cpp
new file mode 100644
index 0000000..618de9a
--- /dev/null
+++ b/pv/view/decodetrace.cpp
@@ -0,0 +1,725 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+extern "C" {
+#include <libsigrokdecode/libsigrokdecode.h>
+}
+
+#include <extdef.h>
+
+#include <boost/foreach.hpp>
+#include <boost/functional/hash.hpp>
+
+#include <QAction>
+#include <QApplication>
+#include <QComboBox>
+#include <QFormLayout>
+#include <QLabel>
+#include <QMenu>
+#include <QPushButton>
+
+#include "decodetrace.h"
+
+#include <pv/sigsession.h>
+#include <pv/data/decoderstack.h>
+#include <pv/data/decode/decoder.h>
+#include <pv/data/logic.h>
+#include <pv/data/logicsnapshot.h>
+#include <pv/data/decode/annotation.h>
+#include <pv/view/logicsignal.h>
+#include <pv/view/view.h>
+#include <pv/widgets/decodergroupbox.h>
+#include <pv/widgets/decodermenu.h>
+
+using boost::dynamic_pointer_cast;
+using boost::shared_ptr;
+using std::list;
+using std::max;
+using std::map;
+using std::min;
+using std::vector;
+
+namespace pv {
+namespace view {
+
+const QColor DecodeTrace::DecodeColours[4] = {
+ QColor(0xEF, 0x29, 0x29), // Red
+ QColor(0xFC, 0xE9, 0x4F), // Yellow
+ QColor(0x8A, 0xE2, 0x34), // Green
+ QColor(0x72, 0x9F, 0xCF) // Blue
+};
+
+const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
+const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
+
+const int DecodeTrace::ArrowSize = 4;
+const double DecodeTrace::EndCapWidth = 5;
+const int DecodeTrace::DrawPadding = 100;
+
+const QColor DecodeTrace::Colours[16] = {
+ QColor(0xEF, 0x29, 0x29),
+ QColor(0xF6, 0x6A, 0x32),
+ QColor(0xFC, 0xAE, 0x3E),
+ QColor(0xFB, 0xCA, 0x47),
+ QColor(0xFC, 0xE9, 0x4F),
+ QColor(0xCD, 0xF0, 0x40),
+ QColor(0x8A, 0xE2, 0x34),
+ QColor(0x4E, 0xDC, 0x44),
+ QColor(0x55, 0xD7, 0x95),
+ QColor(0x64, 0xD1, 0xD2),
+ QColor(0x72, 0x9F, 0xCF),
+ QColor(0xD4, 0x76, 0xC4),
+ QColor(0x9D, 0x79, 0xB9),
+ QColor(0xAD, 0x7F, 0xA8),
+ QColor(0xC2, 0x62, 0x9B),
+ QColor(0xD7, 0x47, 0x6F)
+};
+
+const QColor DecodeTrace::OutlineColours[16] = {
+ QColor(0x77, 0x14, 0x14),
+ QColor(0x7B, 0x35, 0x19),
+ QColor(0x7E, 0x57, 0x1F),
+ QColor(0x7D, 0x65, 0x23),
+ QColor(0x7E, 0x74, 0x27),
+ QColor(0x66, 0x78, 0x20),
+ QColor(0x45, 0x71, 0x1A),
+ QColor(0x27, 0x6E, 0x22),
+ QColor(0x2A, 0x6B, 0x4A),
+ QColor(0x32, 0x68, 0x69),
+ QColor(0x39, 0x4F, 0x67),
+ QColor(0x6A, 0x3B, 0x62),
+ QColor(0x4E, 0x3C, 0x5C),
+ QColor(0x56, 0x3F, 0x54),
+ QColor(0x61, 0x31, 0x4D),
+ QColor(0x6B, 0x23, 0x37)
+};
+
+DecodeTrace::DecodeTrace(pv::SigSession &session,
+ boost::shared_ptr<pv::data::DecoderStack> decoder_stack, int index) :
+ Trace(QString::fromUtf8(
+ decoder_stack->stack().front()->decoder()->name)),
+ _session(session),
+ _decoder_stack(decoder_stack),
+ _delete_mapper(this),
+ _show_hide_mapper(this)
+{
+ assert(_decoder_stack);
+
+ _colour = DecodeColours[index % countof(DecodeColours)];
+
+ connect(_decoder_stack.get(), SIGNAL(new_decode_data()),
+ this, SLOT(on_new_decode_data()));
+ connect(&_delete_mapper, SIGNAL(mapped(int)),
+ this, SLOT(on_delete_decoder(int)));
+ connect(&_show_hide_mapper, SIGNAL(mapped(int)),
+ this, SLOT(on_show_hide_decoder(int)));
+}
+
+bool DecodeTrace::enabled() const
+{
+ return true;
+}
+
+const boost::shared_ptr<pv::data::DecoderStack>& DecodeTrace::decoder() const
+{
+ return _decoder_stack;
+}
+
+void DecodeTrace::set_view(pv::view::View *view)
+{
+ assert(view);
+ Trace::set_view(view);
+}
+
+void DecodeTrace::paint_back(QPainter &p, int left, int right)
+{
+ Trace::paint_back(p, left, right);
+ paint_axis(p, get_y(), left, right);
+}
+
+void DecodeTrace::paint_mid(QPainter &p, int left, int right)
+{
+ using namespace pv::data::decode;
+
+ const double scale = _view->scale();
+ assert(scale > 0);
+
+ double samplerate = _decoder_stack->samplerate();
+
+ _cur_row_headings.clear();
+
+ // Show sample rate as 1Hz when it is unknown
+ if (samplerate == 0.0)
+ samplerate = 1.0;
+
+ const double pixels_offset = (_view->offset() -
+ _decoder_stack->get_start_time()) / scale;
+ const double samples_per_pixel = samplerate * scale;
+
+ const uint64_t start_sample = (uint64_t)max((left + pixels_offset) *
+ samples_per_pixel, 0.0);
+ const uint64_t end_sample = (uint64_t)max((right + pixels_offset) *
+ samples_per_pixel, 0.0);
+
+ QFontMetrics m(QApplication::font());
+ const int text_height = m.boundingRect(QRect(), 0, "Tg").height();
+ const int annotation_height = (text_height * 5) / 4;
+ const int row_height = (text_height * 6) / 4;
+
+ assert(_decoder_stack);
+ const QString err = _decoder_stack->error_message();
+ if (!err.isEmpty())
+ {
+ draw_unresolved_period(p, annotation_height, left, right,
+ samples_per_pixel, pixels_offset);
+ draw_error(p, err, left, right);
+ return;
+ }
+
+ // Iterate through the rows
+ assert(_view);
+ int y = get_y();
+
+ assert(_decoder_stack);
+
+ const vector<Row> rows(_decoder_stack->get_visible_rows());
+ for (size_t i = 0; i < rows.size(); i++)
+ {
+ const Row &row = rows[i];
+
+ size_t base_colour = 0x13579BDF;
+ boost::hash_combine(base_colour, this);
+ boost::hash_combine(base_colour, row.decoder());
+ boost::hash_combine(base_colour, row.row());
+ base_colour >>= 16;
+
+ vector<Annotation> annotations;
+ _decoder_stack->get_annotation_subset(annotations, row,
+ start_sample, end_sample);
+ if (!annotations.empty()) {
+ BOOST_FOREACH(const Annotation &a, annotations)
+ draw_annotation(a, p, get_text_colour(),
+ annotation_height, left, right,
+ samples_per_pixel, pixels_offset, y,
+ base_colour);
+ y += row_height;
+
+ _cur_row_headings.push_back(row.title());
+ }
+ }
+
+ // Draw the hatching
+ draw_unresolved_period(p, annotation_height, left, right,
+ samples_per_pixel, pixels_offset);
+}
+
+void DecodeTrace::paint_fore(QPainter &p, int left, int right)
+{
+ using namespace pv::data::decode;
+
+ (void)right;
+
+ QFontMetrics m(QApplication::font());
+ const int text_height = m.boundingRect(QRect(), 0, "Tg").height();
+ const int row_height = (text_height * 6) / 4;
+
+ for (size_t i = 0; i < _cur_row_headings.size(); i++)
+ {
+ const int y = i * row_height + get_y();
+
+ p.setPen(QPen(Qt::NoPen));
+ p.setBrush(QApplication::palette().brush(QPalette::WindowText));
+
+ if (i != 0)
+ {
+ const QPointF points[] = {
+ QPointF(left, y - ArrowSize),
+ QPointF(left + ArrowSize, y),
+ QPointF(left, y + ArrowSize)
+ };
+ p.drawPolygon(points, countof(points));
+ }
+
+ const QRect r(left + ArrowSize * 2, y - row_height / 2,
+ right - left, row_height);
+ const QString h(_cur_row_headings[i]);
+ const int f = Qt::AlignLeft | Qt::AlignVCenter |
+ Qt::TextDontClip;
+
+ // Draw the outline
+ p.setPen(QApplication::palette().color(QPalette::Base));
+ for (int dx = -1; dx <= 1; dx++)
+ for (int dy = -1; dy <= 1; dy++)
+ if (dx != 0 && dy != 0)
+ p.drawText(r.translated(dx, dy), f, h);
+
+ // Draw the text
+ p.setPen(QApplication::palette().color(QPalette::WindowText));
+ p.drawText(r, f, h);
+ }
+}
+
+void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
+{
+ using pv::data::decode::Decoder;
+
+ assert(form);
+ assert(parent);
+ assert(_decoder_stack);
+
+ // Add the standard options
+ Trace::populate_popup_form(parent, form);
+
+ // Add the decoder options
+ _bindings.clear();
+ _probe_selectors.clear();
+ _decoder_forms.clear();
+
+ const list< shared_ptr<Decoder> >& stack = _decoder_stack->stack();
+
+ if (stack.empty())
+ {
+ QLabel *const l = new QLabel(
+ tr("<p><i>No decoders in the stack</i></p>"));
+ l->setAlignment(Qt::AlignCenter);
+ form->addRow(l);
+ }
+ else
+ {
+ list< shared_ptr<Decoder> >::const_iterator iter =
+ stack.begin();
+ for (int i = 0; i < (int)stack.size(); i++, iter++) {
+ shared_ptr<Decoder> dec(*iter);
+ create_decoder_form(i, dec, parent, form);
+ }
+
+ form->addRow(new QLabel(
+ tr("<i>* Required channels</i>"), parent));
+ }
+
+ // Add stacking button
+ pv::widgets::DecoderMenu *const decoder_menu =
+ new pv::widgets::DecoderMenu(parent);
+ connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
+ this, SLOT(on_stack_decoder(srd_decoder*)));
+
+ QPushButton *const stack_button =
+ new QPushButton(tr("Stack Decoder"), parent);
+ stack_button->setMenu(decoder_menu);
+
+ QHBoxLayout *stack_button_box = new QHBoxLayout;
+ stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
+ form->addRow(stack_button_box);
+}
+
+QMenu* DecodeTrace::create_context_menu(QWidget *parent)
+{
+ QMenu *const menu = Trace::create_context_menu(parent);
+
+ menu->addSeparator();
+
+ QAction *const del = new QAction(tr("Delete"), this);
+ del->setShortcuts(QKeySequence::Delete);
+ connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
+ menu->addAction(del);
+
+ return menu;
+}
+
+void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
+ QPainter &p, QColor text_color, int h, int left, int right,
+ double samples_per_pixel, double pixels_offset, int y,
+ size_t base_colour) const
+{
+ const double start = a.start_sample() / samples_per_pixel -
+ pixels_offset;
+ const double end = a.end_sample() / samples_per_pixel -
+ pixels_offset;
+
+ const size_t colour = (base_colour + a.format()) % countof(Colours);
+ const QColor &fill = Colours[colour];
+ const QColor &outline = OutlineColours[colour];
+
+ if (start > right + DrawPadding || end < left - DrawPadding)
+ return;
+
+ if (a.start_sample() == a.end_sample())
+ draw_instant(a, p, fill, outline, text_color, h,
+ start, y);
+ else
+ draw_range(a, p, fill, outline, text_color, h,
+ start, end, y);
+}
+
+void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
+ QColor fill, QColor outline, QColor text_color, int h, double x, int y) const
+{
+ const QString text = a.annotations().empty() ?
+ QString() : a.annotations().back();
+ const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
+ 0.0) + h;
+ const QRectF rect(x - w / 2, y - h / 2, w, h);
+
+ p.setPen(outline);
+ p.setBrush(fill);
+ p.drawRoundedRect(rect, h / 2, h / 2);
+
+ p.setPen(text_color);
+ p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
+}
+
+void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
+ QColor fill, QColor outline, QColor text_color, int h, double start,
+ double end, int y) const
+{
+ const double top = y + .5 - h / 2;
+ const double bottom = y + .5 + h / 2;
+ const vector<QString> annotations = a.annotations();
+
+ p.setPen(outline);
+ p.setBrush(fill);
+
+ // If the two ends are within 1 pixel, draw a vertical line
+ if (start + 1.0 > end)
+ {
+ p.drawLine(QPointF(start, top), QPointF(start, bottom));
+ return;
+ }
+
+ const double cap_width = min((end - start) / 4, EndCapWidth);
+
+ QPointF pts[] = {
+ QPointF(start, y + .5f),
+ QPointF(start + cap_width, top),
+ QPointF(end - cap_width, top),
+ QPointF(end, y + .5f),
+ QPointF(end - cap_width, bottom),
+ QPointF(start + cap_width, bottom)
+ };
+
+ p.drawConvexPolygon(pts, countof(pts));
+
+ if (annotations.empty())
+ return;
+
+ QRectF rect(start + cap_width, y - h / 2,
+ end - start - cap_width * 2, h);
+ if (rect.width() <= 4)
+ return;
+
+ p.setPen(text_color);
+
+ // Try to find an annotation that will fit
+ QString best_annotation;
+ int best_width = 0;
+
+ BOOST_FOREACH(const QString &a, annotations) {
+ const int w = p.boundingRect(QRectF(), 0, a).width();
+ if (w <= rect.width() && w > best_width)
+ best_annotation = a, best_width = w;
+ }
+
+ if (best_annotation.isEmpty())
+ best_annotation = annotations.back();
+
+ // If not ellide the last in the list
+ p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
+ best_annotation, Qt::ElideRight, rect.width()));
+}
+
+void DecodeTrace::draw_error(QPainter &p, const QString &message,
+ int left, int right)
+{
+ const int y = get_y();
+
+ p.setPen(ErrorBgColour.darker());
+ p.setBrush(ErrorBgColour);
+
+ const QRectF bounding_rect =
+ QRectF(left, INT_MIN / 2 + y, right - left, INT_MAX);
+ const QRectF text_rect = p.boundingRect(bounding_rect,
+ Qt::AlignCenter, message);
+ const float r = text_rect.height() / 4;
+
+ p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
+ Qt::AbsoluteSize);
+
+ p.setPen(get_text_colour());
+ p.drawText(text_rect, message);
+}
+
+void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
+ int right, double samples_per_pixel, double pixels_offset)
+{
+ using namespace pv::data;
+ using pv::data::decode::Decoder;
+
+ assert(_decoder_stack);
+
+ shared_ptr<Logic> data;
+ shared_ptr<LogicSignal> logic_signal;
+
+ const list< shared_ptr<Decoder> > &stack = _decoder_stack->stack();
+
+ // We get the logic data of the first probe in the list.
+ // This works because we are currently assuming all
+ // LogicSignals have the same data/snapshot
+ BOOST_FOREACH (const shared_ptr<Decoder> &dec, stack)
+ if (dec && !dec->channels().empty() &&
+ ((logic_signal = (*dec->channels().begin()).second)) &&
+ ((data = logic_signal->logic_data())))
+ break;
+
+ if (!data || data->get_snapshots().empty())
+ return;
+
+ const shared_ptr<LogicSnapshot> snapshot =
+ data->get_snapshots().front();
+ assert(snapshot);
+ const int64_t sample_count = (int64_t)snapshot->get_sample_count();
+ if (sample_count == 0)
+ return;
+
+ const int64_t samples_decoded = _decoder_stack->samples_decoded();
+ if (sample_count == samples_decoded)
+ return;
+
+ const int y = get_y();
+ const double start = max(samples_decoded /
+ samples_per_pixel - pixels_offset, left - 1.0);
+ const double end = min(sample_count / samples_per_pixel -
+ pixels_offset, right + 1.0);
+ const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
+
+ p.setPen(QPen(Qt::NoPen));
+ p.setBrush(Qt::white);
+ p.drawRect(no_decode_rect);
+
+ p.setPen(NoDecodeColour);
+ p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
+ p.drawRect(no_decode_rect);
+}
+
+void DecodeTrace::create_decoder_form(int index,
+ shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
+ QFormLayout *form)
+{
+ const GSList *l;
+
+ assert(dec);
+ const srd_decoder *const decoder = dec->decoder();
+ assert(decoder);
+
+ pv::widgets::DecoderGroupBox *const group =
+ new pv::widgets::DecoderGroupBox(
+ QString::fromUtf8(decoder->name));
+ group->set_decoder_visible(dec->shown());
+
+ _delete_mapper.setMapping(group, index);
+ connect(group, SIGNAL(delete_decoder()), &_delete_mapper, SLOT(map()));
+
+ _show_hide_mapper.setMapping(group, index);
+ connect(group, SIGNAL(show_hide_decoder()),
+ &_show_hide_mapper, SLOT(map()));
+
+ QFormLayout *const decoder_form = new QFormLayout;
+ group->add_layout(decoder_form);
+
+ // Add the mandatory channels
+ for(l = decoder->channels; l; l = l->next) {
+ const struct srd_channel *const pdch =
+ (struct srd_channel *)l->data;
+ QComboBox *const combo = create_probe_selector(parent, dec, pdch);
+ connect(combo, SIGNAL(currentIndexChanged(int)),
+ this, SLOT(on_probe_selected(int)));
+ decoder_form->addRow(tr("<b>%1</b> (%2) *")
+ .arg(QString::fromUtf8(pdch->name))
+ .arg(QString::fromUtf8(pdch->desc)), combo);
+
+ const ProbeSelector s = {combo, dec, pdch};
+ _probe_selectors.push_back(s);
+ }
+
+ // Add the optional channels
+ for(l = decoder->opt_channels; l; l = l->next) {
+ const struct srd_channel *const pdch =
+ (struct srd_channel *)l->data;
+ QComboBox *const combo = create_probe_selector(parent, dec, pdch);
+ connect(combo, SIGNAL(currentIndexChanged(int)),
+ this, SLOT(on_probe_selected(int)));
+ decoder_form->addRow(tr("<b>%1</b> (%2)")
+ .arg(QString::fromUtf8(pdch->name))
+ .arg(QString::fromUtf8(pdch->desc)), combo);
+
+ const ProbeSelector s = {combo, dec, pdch};
+ _probe_selectors.push_back(s);
+ }
+
+ // Add the options
+ shared_ptr<prop::binding::DecoderOptions> binding(
+ new prop::binding::DecoderOptions(_decoder_stack, dec));
+ binding->add_properties_to_form(decoder_form, true);
+
+ _bindings.push_back(binding);
+
+ form->addRow(group);
+ _decoder_forms.push_back(group);
+}
+
+QComboBox* DecodeTrace::create_probe_selector(
+ QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
+ const srd_channel *const pdch)
+{
+ assert(dec);
+
+ const vector< shared_ptr<Signal> > sigs = _session.get_signals();
+
+ assert(_decoder_stack);
+ const map<const srd_channel*,
+ shared_ptr<LogicSignal> >::const_iterator probe_iter =
+ dec->channels().find(pdch);
+
+ QComboBox *selector = new QComboBox(parent);
+
+ selector->addItem("-", qVariantFromValue((void*)NULL));
+
+ if (probe_iter == dec->channels().end())
+ selector->setCurrentIndex(0);
+
+ for(size_t i = 0; i < sigs.size(); i++) {
+ const shared_ptr<view::Signal> s(sigs[i]);
+ assert(s);
+
+ if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled())
+ {
+ selector->addItem(s->get_name(),
+ qVariantFromValue((void*)s.get()));
+ if ((*probe_iter).second == s)
+ selector->setCurrentIndex(i + 1);
+ }
+ }
+
+ return selector;
+}
+
+void DecodeTrace::commit_decoder_probes(shared_ptr<data::decode::Decoder> &dec)
+{
+ assert(dec);
+
+ map<const srd_channel*, shared_ptr<LogicSignal> > probe_map;
+ const vector< shared_ptr<Signal> > sigs = _session.get_signals();
+
+ BOOST_FOREACH(const ProbeSelector &s, _probe_selectors)
+ {
+ if(s._decoder != dec)
+ break;
+
+ const LogicSignal *const selection =
+ (LogicSignal*)s._combo->itemData(
+ s._combo->currentIndex()).value<void*>();
+
+ BOOST_FOREACH(shared_ptr<Signal> sig, sigs)
+ if(sig.get() == selection) {
+ probe_map[s._pdch] =
+ dynamic_pointer_cast<LogicSignal>(sig);
+ break;
+ }
+ }
+
+ dec->set_probes(probe_map);
+}
+
+void DecodeTrace::commit_probes()
+{
+ assert(_decoder_stack);
+ BOOST_FOREACH(shared_ptr<data::decode::Decoder> dec,
+ _decoder_stack->stack())
+ commit_decoder_probes(dec);
+
+ _decoder_stack->begin_decode();
+}
+
+void DecodeTrace::on_new_decode_data()
+{
+ if (_view)
+ _view->update_viewport();
+}
+
+void DecodeTrace::delete_pressed()
+{
+ on_delete();
+}
+
+void DecodeTrace::on_delete()
+{
+ _session.remove_decode_signal(this);
+}
+
+void DecodeTrace::on_probe_selected(int)
+{
+ commit_probes();
+}
+
+void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
+{
+ assert(decoder);
+ assert(_decoder_stack);
+ _decoder_stack->push(shared_ptr<data::decode::Decoder>(
+ new data::decode::Decoder(decoder)));
+ _decoder_stack->begin_decode();
+
+ create_popup_form();
+}
+
+void DecodeTrace::on_delete_decoder(int index)
+{
+ _decoder_stack->remove(index);
+
+ // Update the popup
+ create_popup_form();
+
+ _decoder_stack->begin_decode();
+}
+
+void DecodeTrace::on_show_hide_decoder(int index)
+{
+ using pv::data::decode::Decoder;
+
+ const list< shared_ptr<Decoder> > stack(_decoder_stack->stack());
+
+ // Find the decoder in the stack
+ list< shared_ptr<Decoder> >::const_iterator iter = stack.begin();
+ for(int i = 0; i < index; i++, iter++)
+ assert(iter != stack.end());
+
+ shared_ptr<Decoder> dec = *iter;
+ assert(dec);
+
+ const bool show = !dec->shown();
+ dec->show(show);
+
+ assert(index < (int)_decoder_forms.size());
+ _decoder_forms[index]->set_decoder_visible(show);
+
+ _view->update_viewport();
+}
+
+} // namespace view
+} // namespace pv
diff --git a/pv/view/decodetrace.h b/pv/view/decodetrace.h
new file mode 100644
index 0000000..9a991fc
--- /dev/null
+++ b/pv/view/decodetrace.h
@@ -0,0 +1,191 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_VIEW_DECODETRACE_H
+#define PULSEVIEW_PV_VIEW_DECODETRACE_H
+
+#include "trace.h"
+
+#include <list>
+#include <map>
+
+#include <QSignalMapper>
+
+#include <boost/shared_ptr.hpp>
+
+#include <pv/prop/binding/decoderoptions.h>
+
+struct srd_channel;
+struct srd_decoder;
+
+class QComboBox;
+
+namespace pv {
+
+class SigSession;
+
+namespace data {
+class DecoderStack;
+
+namespace decode {
+class Annotation;
+class Decoder;
+class Row;
+}
+}
+
+namespace widgets {
+class DecoderGroupBox;
+}
+
+namespace view {
+
+class DecodeTrace : public Trace
+{
+ Q_OBJECT
+
+private:
+ struct ProbeSelector
+ {
+ const QComboBox *_combo;
+ const boost::shared_ptr<pv::data::decode::Decoder> _decoder;
+ const srd_channel *_pdch;
+ };
+
+private:
+ static const QColor DecodeColours[4];
+ static const QColor ErrorBgColour;
+ static const QColor NoDecodeColour;
+
+ static const int ArrowSize;
+ static const double EndCapWidth;
+ static const int DrawPadding;
+
+ static const QColor Colours[16];
+ static const QColor OutlineColours[16];
+
+public:
+ DecodeTrace(pv::SigSession &session,
+ boost::shared_ptr<pv::data::DecoderStack> decoder_stack,
+ int index);
+
+ bool enabled() const;
+
+ const boost::shared_ptr<pv::data::DecoderStack>& decoder() const;
+
+ void set_view(pv::view::View *view);
+
+ /**
+ * Paints the background layer of the trace with a QPainter
+ * @param p the QPainter to paint into.
+ * @param left the x-coordinate of the left edge of the signal.
+ * @param right the x-coordinate of the right edge of the signal.
+ **/
+ void paint_back(QPainter &p, int left, int right);
+
+ /**
+ * Paints the mid-layer of the trace with a QPainter
+ * @param p the QPainter to paint into.
+ * @param left the x-coordinate of the left edge of the signal
+ * @param right the x-coordinate of the right edge of the signal
+ **/
+ void paint_mid(QPainter &p, int left, int right);
+
+ /**
+ * Paints the foreground layer of the trace with a QPainter
+ * @param p the QPainter to paint into.
+ * @param left the x-coordinate of the left edge of the signal
+ * @param right the x-coordinate of the right edge of the signal
+ **/
+ void paint_fore(QPainter &p, int left, int right);
+
+ void populate_popup_form(QWidget *parent, QFormLayout *form);
+
+ QMenu* create_context_menu(QWidget *parent);
+
+ void delete_pressed();
+
+private:
+ void draw_annotation(const pv::data::decode::Annotation &a, QPainter &p,
+ QColor text_colour, int text_height, int left, int right,
+ double samples_per_pixel, double pixels_offset, int y,
+ size_t base_colour) const;
+
+ void draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
+ QColor fill, QColor outline, QColor text_color, int h, double x,
+ int y) const;
+
+ void draw_range(const pv::data::decode::Annotation &a, QPainter &p,
+ QColor fill, QColor outline, QColor text_color, int h, double start,
+ double end, int y) const;
+
+ void draw_error(QPainter &p, const QString &message,
+ int left, int right);
+
+ void draw_unresolved_period(QPainter &p, int h, int left,
+ int right, double samples_per_pixel, double pixels_offset);
+
+ void create_decoder_form(int index,
+ boost::shared_ptr<pv::data::decode::Decoder> &dec,
+ QWidget *parent, QFormLayout *form);
+
+ QComboBox* create_probe_selector(QWidget *parent,
+ const boost::shared_ptr<pv::data::decode::Decoder> &dec,
+ const srd_channel *const pdch);
+
+ void commit_decoder_probes(
+ boost::shared_ptr<data::decode::Decoder> &dec);
+
+ void commit_probes();
+
+private slots:
+ void on_new_decode_data();
+
+ void on_delete();
+
+ void on_probe_selected(int);
+
+ void on_stack_decoder(srd_decoder *decoder);
+
+ void on_delete_decoder(int index);
+
+ void on_show_hide_decoder(int index);
+
+private:
+ pv::SigSession &_session;
+ boost::shared_ptr<pv::data::DecoderStack> _decoder_stack;
+
+ uint64_t _decode_start, _decode_end;
+
+ std::list< boost::shared_ptr<pv::prop::binding::DecoderOptions> >
+ _bindings;
+
+ std::list<ProbeSelector> _probe_selectors;
+ std::vector<pv::widgets::DecoderGroupBox*> _decoder_forms;
+
+ std::vector<QString> _cur_row_headings;
+
+ QSignalMapper _delete_mapper, _show_hide_mapper;
+};
+
+} // namespace view
+} // namespace pv
+
+#endif // PULSEVIEW_PV_VIEW_DECODETRACE_H
diff --git a/pv/view/header.cpp b/pv/view/header.cpp
new file mode 100644
index 0000000..a790252
--- /dev/null
+++ b/pv/view/header.cpp
@@ -0,0 +1,304 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "header.h"
+#include "view.h"
+
+#include "signal.h"
+#include "../sigsession.h"
+
+#include <assert.h>
+
+#include <boost/foreach.hpp>
+
+#include <QApplication>
+#include <QMenu>
+#include <QMouseEvent>
+#include <QPainter>
+#include <QRect>
+
+#include <pv/widgets/popup.h>
+
+using boost::shared_ptr;
+using std::max;
+using std::make_pair;
+using std::pair;
+using std::vector;
+
+namespace pv {
+namespace view {
+
+const int Header::Padding = 12;
+
+Header::Header(View &parent) :
+ MarginWidget(parent),
+ _dragging(false)
+{
+ setFocusPolicy(Qt::ClickFocus);
+ setMouseTracking(true);
+
+ connect(&_view.session(), SIGNAL(signals_changed()),
+ this, SLOT(on_signals_changed()));
+
+ connect(&_view, SIGNAL(signals_moved()),
+ this, SLOT(on_signals_moved()));
+
+ // Trigger the initial event manually. The default device has signals
+ // which were created before this object came into being
+ on_signals_changed();
+}
+
+QSize Header::sizeHint() const
+{
+ int max_width = 0;
+
+ const vector< shared_ptr<Trace> > traces(_view.get_traces());
+ BOOST_FOREACH(shared_ptr<Trace> t, traces) {
+ assert(t);
+ max_width = max(max_width, (int)t->get_label_rect(0).width());
+ }
+
+ return QSize(max_width + Padding, 0);
+}
+
+shared_ptr<Trace> Header::get_mouse_over_trace(const QPoint &pt)
+{
+ const int w = width();
+ const vector< shared_ptr<Trace> > traces(_view.get_traces());
+
+ BOOST_FOREACH(const shared_ptr<Trace> t, traces)
+ {
+ assert(t);
+ if (t->pt_in_label_rect(0, w, pt))
+ return t;
+ }
+
+ return shared_ptr<Trace>();
+}
+
+void Header::clear_selection()
+{
+ const vector< shared_ptr<Trace> > traces(_view.get_traces());
+ BOOST_FOREACH(const shared_ptr<Trace> t, traces) {
+ assert(t);
+ t->select(false);
+ }
+
+ update();
+}
+
+void Header::paintEvent(QPaintEvent*)
+{
+ const int w = width();
+ const vector< shared_ptr<Trace> > traces(_view.get_traces());
+
+ QPainter painter(this);
+ painter.setRenderHint(QPainter::Antialiasing);
+
+ const bool dragging = !_drag_traces.empty();
+ BOOST_FOREACH(const shared_ptr<Trace> t, traces)
+ {
+ assert(t);
+
+ const bool highlight = !dragging && t->pt_in_label_rect(
+ 0, w, _mouse_point);
+ t->paint_label(painter, w, highlight);
+ }
+
+ painter.end();
+}
+
+void Header::mousePressEvent(QMouseEvent *event)
+{
+ assert(event);
+
+ const vector< shared_ptr<Trace> > traces(_view.get_traces());
+
+ if (event->button() & Qt::LeftButton) {
+ _mouse_down_point = event->pos();
+
+ // Save the offsets of any signals which will be dragged
+ BOOST_FOREACH(const shared_ptr<Trace> t, traces)
+ if (t->selected())
+ _drag_traces.push_back(
+ make_pair(t, t->get_v_offset()));
+ }
+
+ // Select the signal if it has been clicked
+ const shared_ptr<Trace> mouse_over_trace =
+ get_mouse_over_trace(event->pos());
+ if (mouse_over_trace) {
+ if (mouse_over_trace->selected())
+ mouse_over_trace->select(false);
+ else {
+ mouse_over_trace->select(true);
+
+ if (~QApplication::keyboardModifiers() &
+ Qt::ControlModifier)
+ _drag_traces.clear();
+
+ // Add the signal to the drag list
+ if (event->button() & Qt::LeftButton)
+ _drag_traces.push_back(
+ make_pair(mouse_over_trace,
+ mouse_over_trace->get_v_offset()));
+ }
+ }
+
+ if (~QApplication::keyboardModifiers() & Qt::ControlModifier) {
+ // Unselect all other signals because the Ctrl is not
+ // pressed
+ BOOST_FOREACH(const shared_ptr<Trace> t, traces)
+ if (t != mouse_over_trace)
+ t->select(false);
+ }
+
+ selection_changed();
+ update();
+}
+
+void Header::mouseReleaseEvent(QMouseEvent *event)
+{
+ using pv::widgets::Popup;
+
+ assert(event);
+ if (event->button() == Qt::LeftButton) {
+ if (_dragging)
+ _view.normalize_layout();
+ else
+ {
+ const shared_ptr<Trace> mouse_over_trace =
+ get_mouse_over_trace(event->pos());
+ if (mouse_over_trace) {
+ Popup *const p =
+ mouse_over_trace->create_popup(&_view);
+ p->set_position(mapToGlobal(QPoint(width(),
+ mouse_over_trace->get_y())),
+ Popup::Right);
+ p->show();
+ }
+ }
+
+ _dragging = false;
+ _drag_traces.clear();
+ }
+}
+
+void Header::mouseMoveEvent(QMouseEvent *event)
+{
+ assert(event);
+ _mouse_point = event->pos();
+
+ if (!(event->buttons() & Qt::LeftButton))
+ return;
+
+ if ((event->pos() - _mouse_down_point).manhattanLength() <
+ QApplication::startDragDistance())
+ return;
+
+ // Move the signals if we are dragging
+ if (!_drag_traces.empty())
+ {
+ _dragging = true;
+
+ const int delta = event->pos().y() - _mouse_down_point.y();
+
+ for (std::list<std::pair<boost::weak_ptr<Trace>,
+ int> >::iterator i = _drag_traces.begin();
+ i != _drag_traces.end(); i++) {
+ const boost::shared_ptr<Trace> trace((*i).first);
+ if (trace) {
+ const int y = (*i).second + delta;
+ const int y_snap =
+ ((y + View::SignalSnapGridSize / 2) /
+ View::SignalSnapGridSize) *
+ View::SignalSnapGridSize;
+ trace->set_v_offset(y_snap);
+
+ // Ensure the trace is selected
+ trace->select();
+ }
+
+ }
+
+ signals_moved();
+ }
+
+ update();
+}
+
+void Header::leaveEvent(QEvent*)
+{
+ _mouse_point = QPoint(-1, -1);
+ update();
+}
+
+void Header::contextMenuEvent(QContextMenuEvent *event)
+{
+ const shared_ptr<Trace> t = get_mouse_over_trace(_mouse_point);
+
+ if (t)
+ t->create_context_menu(this)->exec(event->globalPos());
+}
+
+void Header::keyPressEvent(QKeyEvent *e)
+{
+ assert(e);
+
+ switch (e->key())
+ {
+ case Qt::Key_Delete:
+ {
+ const vector< shared_ptr<Trace> > traces(_view.get_traces());
+ BOOST_FOREACH(const shared_ptr<Trace> t, traces)
+ if (t->selected())
+ t->delete_pressed();
+ break;
+ }
+ }
+}
+
+void Header::on_signals_changed()
+{
+ const vector< shared_ptr<Trace> > traces(_view.get_traces());
+ BOOST_FOREACH(shared_ptr<Trace> t, traces) {
+ assert(t);
+ connect(t.get(), SIGNAL(visibility_changed()),
+ this, SLOT(update()));
+ connect(t.get(), SIGNAL(text_changed()),
+ this, SLOT(on_trace_text_changed()));
+ connect(t.get(), SIGNAL(colour_changed()),
+ this, SLOT(update()));
+ }
+}
+
+void Header::on_signals_moved()
+{
+ update();
+}
+
+void Header::on_trace_text_changed()
+{
+ update();
+ geometry_updated();
+}
+
+} // namespace view
+} // namespace pv
diff --git a/pv/view/header.h b/pv/view/header.h
new file mode 100644
index 0000000..5474f10
--- /dev/null
+++ b/pv/view/header.h
@@ -0,0 +1,94 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_VIEW_HEADER_H
+#define PULSEVIEW_PV_VIEW_HEADER_H
+
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+
+#include <list>
+#include <utility>
+
+#include "marginwidget.h"
+
+namespace pv {
+namespace view {
+
+class Trace;
+class View;
+
+class Header : public MarginWidget
+{
+ Q_OBJECT
+
+private:
+ static const int Padding;
+
+public:
+ Header(View &parent);
+
+ QSize sizeHint() const;
+
+private:
+ boost::shared_ptr<pv::view::Trace> get_mouse_over_trace(
+ const QPoint &pt);
+
+ void clear_selection();
+
+private:
+ void paintEvent(QPaintEvent *event);
+
+private:
+ void mousePressEvent(QMouseEvent * event);
+
+ void mouseReleaseEvent(QMouseEvent *event);
+
+ void mouseMoveEvent(QMouseEvent *event);
+
+ void leaveEvent(QEvent *event);
+
+ void contextMenuEvent(QContextMenuEvent *event);
+
+ void keyPressEvent(QKeyEvent *e);
+
+private slots:
+ void on_signals_changed();
+
+ void on_signals_moved();
+
+ void on_trace_text_changed();
+
+signals:
+ void signals_moved();
+
+private:
+ QPoint _mouse_point;
+ QPoint _mouse_down_point;
+ bool _dragging;
+
+ std::list<std::pair<boost::weak_ptr<Trace>, int> >
+ _drag_traces;
+};
+
+} // namespace view
+} // namespace pv
+
+#endif // PULSEVIEW_PV_VIEW_HEADER_H
diff --git a/pv/view/logicsignal.cpp b/pv/view/logicsignal.cpp
new file mode 100644
index 0000000..0b44264
--- /dev/null
+++ b/pv/view/logicsignal.cpp
@@ -0,0 +1,353 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <extdef.h>
+
+#include <math.h>
+
+#include <QFormLayout>
+#include <QToolBar>
+
+#include "logicsignal.h"
+#include "view.h"
+
+#include <pv/sigsession.h>
+#include <pv/device/devinst.h>
+#include <pv/data/logic.h>
+#include <pv/data/logicsnapshot.h>
+#include <pv/view/view.h>
+
+using boost::shared_ptr;
+using std::deque;
+using std::max;
+using std::min;
+using std::pair;
+using std::vector;
+
+namespace pv {
+namespace view {
+
+const float LogicSignal::Oversampling = 2.0f;
+
+const QColor LogicSignal::EdgeColour(0x80, 0x80, 0x80);
+const QColor LogicSignal::HighColour(0x00, 0xC0, 0x00);
+const QColor LogicSignal::LowColour(0xC0, 0x00, 0x00);
+
+const QColor LogicSignal::SignalColours[10] = {
+ QColor(0x16, 0x19, 0x1A), // Black
+ QColor(0x8F, 0x52, 0x02), // Brown
+ QColor(0xCC, 0x00, 0x00), // Red
+ QColor(0xF5, 0x79, 0x00), // Orange
+ QColor(0xED, 0xD4, 0x00), // Yellow
+ QColor(0x73, 0xD2, 0x16), // Green
+ QColor(0x34, 0x65, 0xA4), // Blue
+ QColor(0x75, 0x50, 0x7B), // Violet
+ QColor(0x88, 0x8A, 0x85), // Grey
+ QColor(0xEE, 0xEE, 0xEC), // White
+};
+
+LogicSignal::LogicSignal(shared_ptr<pv::device::DevInst> dev_inst,
+ const sr_channel *const probe, shared_ptr<data::Logic> data) :
+ Signal(dev_inst, probe),
+ _data(data),
+ _trigger_none(NULL),
+ _trigger_rising(NULL),
+ _trigger_high(NULL),
+ _trigger_falling(NULL),
+ _trigger_low(NULL),
+ _trigger_change(NULL)
+{
+ _colour = SignalColours[probe->index % countof(SignalColours)];
+}
+
+LogicSignal::~LogicSignal()
+{
+}
+
+shared_ptr<pv::data::SignalData> LogicSignal::data() const
+{
+ return _data;
+}
+
+shared_ptr<pv::data::Logic> LogicSignal::logic_data() const
+{
+ return _data;
+}
+
+void LogicSignal::paint_back(QPainter &p, int left, int right)
+{
+ if (_probe->enabled)
+ paint_axis(p, get_y(), left, right);
+}
+
+void LogicSignal::paint_mid(QPainter &p, int left, int right)
+{
+ using pv::view::View;
+
+ QLineF *line;
+
+ vector< pair<int64_t, bool> > edges;
+
+ assert(_probe);
+ assert(_data);
+ assert(right >= left);
+
+ assert(_view);
+ const int y = _v_offset - _view->v_offset();
+
+ const double scale = _view->scale();
+ assert(scale > 0);
+
+ const double offset = _view->offset();
+
+ if (!_probe->enabled)
+ return;
+
+ const float high_offset = y - View::SignalHeight + 0.5f;
+ const float low_offset = y + 0.5f;
+
+ const deque< shared_ptr<pv::data::LogicSnapshot> > &snapshots =
+ _data->get_snapshots();
+ if (snapshots.empty())
+ return;
+
+ const shared_ptr<pv::data::LogicSnapshot> &snapshot =
+ snapshots.front();
+
+ double samplerate = _data->samplerate();
+
+ // Show sample rate as 1Hz when it is unknown
+ if (samplerate == 0.0)
+ samplerate = 1.0;
+
+ const double pixels_offset = offset / scale;
+ const double start_time = _data->get_start_time();
+ const int64_t last_sample = snapshot->get_sample_count() - 1;
+ const double samples_per_pixel = samplerate * scale;
+ const double start = samplerate * (offset - start_time);
+ const double end = start + samples_per_pixel * (right - left);
+
+ snapshot->get_subsampled_edges(edges,
+ min(max((int64_t)floor(start), (int64_t)0), last_sample),
+ min(max((int64_t)ceil(end), (int64_t)0), last_sample),
+ samples_per_pixel / Oversampling, _probe->index);
+ assert(edges.size() >= 2);
+
+ // Paint the edges
+ const unsigned int edge_count = edges.size() - 2;
+ QLineF *const edge_lines = new QLineF[edge_count];
+ line = edge_lines;
+
+ for (vector<pv::data::LogicSnapshot::EdgePair>::const_iterator i =
+ edges.begin() + 1;
+ i != edges.end() - 1; i++) {
+ const float x = ((*i).first / samples_per_pixel -
+ pixels_offset) + left;
+ *line++ = QLineF(x, high_offset, x, low_offset);
+ }
+
+ p.setPen(EdgeColour);
+ p.drawLines(edge_lines, edge_count);
+ delete[] edge_lines;
+
+ // Paint the caps
+ const unsigned int max_cap_line_count = edges.size();
+ QLineF *const cap_lines = new QLineF[max_cap_line_count];
+
+ p.setPen(HighColour);
+ paint_caps(p, cap_lines, edges, true, samples_per_pixel,
+ pixels_offset, left, high_offset);
+ p.setPen(LowColour);
+ paint_caps(p, cap_lines, edges, false, samples_per_pixel,
+ pixels_offset, left, low_offset);
+
+ delete[] cap_lines;
+}
+
+void LogicSignal::paint_caps(QPainter &p, QLineF *const lines,
+ vector< pair<int64_t, bool> > &edges, bool level,
+ double samples_per_pixel, double pixels_offset, float x_offset,
+ float y_offset)
+{
+ QLineF *line = lines;
+
+ for (vector<pv::data::LogicSnapshot::EdgePair>::const_iterator i =
+ edges.begin(); i != (edges.end() - 1); i++)
+ if ((*i).second == level) {
+ *line++ = QLineF(
+ ((*i).first / samples_per_pixel -
+ pixels_offset) + x_offset, y_offset,
+ ((*(i+1)).first / samples_per_pixel -
+ pixels_offset) + x_offset, y_offset);
+ }
+
+ p.drawLines(lines, line - lines);
+}
+
+void LogicSignal::init_trigger_actions(QWidget *parent)
+{
+ _trigger_none = new QAction(QIcon(":/icons/trigger-none.svg"),
+ tr("No trigger"), parent);
+ _trigger_none->setCheckable(true);
+ connect(_trigger_none, SIGNAL(triggered()),
+ this, SLOT(on_trigger_none()));
+
+ _trigger_rising = new QAction(QIcon(":/icons/trigger-rising.svg"),
+ tr("Trigger on rising edge"), parent);
+ _trigger_rising->setCheckable(true);
+ connect(_trigger_rising, SIGNAL(triggered()),
+ this, SLOT(on_trigger_rising()));
+
+ _trigger_high = new QAction(QIcon(":/icons/trigger-high.svg"),
+ tr("Trigger on high level"), parent);
+ _trigger_high->setCheckable(true);
+ connect(_trigger_high, SIGNAL(triggered()),
+ this, SLOT(on_trigger_high()));
+
+ _trigger_falling = new QAction(QIcon(":/icons/trigger-falling.svg"),
+ tr("Trigger on falling edge"), parent);
+ _trigger_falling->setCheckable(true);
+ connect(_trigger_falling, SIGNAL(triggered()),
+ this, SLOT(on_trigger_falling()));
+
+ _trigger_low = new QAction(QIcon(":/icons/trigger-low.svg"),
+ tr("Trigger on low level"), parent);
+ _trigger_low->setCheckable(true);
+ connect(_trigger_low, SIGNAL(triggered()),
+ this, SLOT(on_trigger_low()));
+
+ _trigger_change = new QAction(QIcon(":/icons/trigger-change.svg"),
+ tr("Trigger on rising or falling edge"), parent);
+ _trigger_change->setCheckable(true);
+ connect(_trigger_change, SIGNAL(triggered()),
+ this, SLOT(on_trigger_change()));
+}
+
+void LogicSignal::populate_popup_form(QWidget *parent, QFormLayout *form)
+{
+ GVariant *gvar;
+
+ Signal::populate_popup_form(parent, form);
+
+ // Add the trigger actions
+ assert(_dev_inst);
+ if ((gvar = _dev_inst->list_config(NULL, SR_CONF_TRIGGER_TYPE)))
+ {
+ const char *const trig_types =
+ g_variant_get_string(gvar, NULL);
+
+ if (trig_types && trig_types[0] != '\0')
+ {
+ _trigger_bar = new QToolBar(parent);
+
+ init_trigger_actions(_trigger_bar);
+ _trigger_bar->addAction(_trigger_none);
+ add_trigger_action(trig_types, 'r', _trigger_rising);
+ add_trigger_action(trig_types, '1', _trigger_high);
+ add_trigger_action(trig_types, 'f', _trigger_falling);
+ add_trigger_action(trig_types, '0', _trigger_low);
+ add_trigger_action(trig_types, 'c', _trigger_change);
+
+ update_trigger_actions();
+
+ form->addRow(tr("Trigger"), _trigger_bar);
+ }
+
+ g_variant_unref(gvar);
+ }
+}
+
+void LogicSignal::add_trigger_action(const char *trig_types, char type,
+ QAction *action)
+{
+ while(*trig_types)
+ if(*trig_types++ == type) {
+ _trigger_bar->addAction(action);
+ break;
+ }
+}
+
+void LogicSignal::update_trigger_actions()
+{
+ const char cur_trigger = _probe->trigger ?
+ _probe->trigger[0] : '\0';
+ _trigger_none->setChecked(cur_trigger == '\0');
+ _trigger_rising->setChecked(cur_trigger == 'r');
+ _trigger_high->setChecked(cur_trigger == '1');
+ _trigger_falling->setChecked(cur_trigger == 'f');
+ _trigger_low->setChecked(cur_trigger == '0');
+ _trigger_change->setChecked(cur_trigger == 'c');
+}
+
+void LogicSignal::set_trigger(char type)
+{
+ const char trigger_type_string[2] = {type, 0};
+ const char *const trigger_string =
+ (type != 0) ? trigger_type_string : NULL;
+
+ assert(_dev_inst);
+ const sr_dev_inst *const sdi = _dev_inst->dev_inst();
+ assert(sdi);
+
+ const int probe_count = g_slist_length(sdi->channels);
+ assert(probe_count > 0);
+
+ assert(_probe && _probe->index < probe_count);
+
+ for (int i = 0; i < probe_count; i++) {
+ sr_dev_trigger_set(sdi, i, (i == _probe->index) ?
+ trigger_string : NULL);
+ }
+
+ update_trigger_actions();
+}
+
+void LogicSignal::on_trigger_none()
+{
+ set_trigger('\0');
+}
+
+void LogicSignal::on_trigger_rising()
+{
+ set_trigger('r');
+}
+
+void LogicSignal::on_trigger_high()
+{
+ set_trigger('1');
+}
+
+void LogicSignal::on_trigger_falling()
+{
+ set_trigger('f');
+}
+
+void LogicSignal::on_trigger_low()
+{
+ set_trigger('0');
+}
+
+void LogicSignal::on_trigger_change()
+{
+ set_trigger('c');
+}
+
+} // namespace view
+} // namespace pv
diff --git a/pv/view/logicsignal.h b/pv/view/logicsignal.h
new file mode 100644
index 0000000..c3e1666
--- /dev/null
+++ b/pv/view/logicsignal.h
@@ -0,0 +1,119 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_VIEW_LOGICSIGNAL_H
+#define PULSEVIEW_PV_VIEW_LOGICSIGNAL_H
+
+#include "signal.h"
+
+#include <boost/shared_ptr.hpp>
+
+class QToolBar;
+
+namespace pv {
+
+namespace data {
+class Logic;
+}
+
+namespace view {
+
+class LogicSignal : public Signal
+{
+ Q_OBJECT
+
+private:
+ static const float Oversampling;
+
+ static const QColor EdgeColour;
+ static const QColor HighColour;
+ static const QColor LowColour;
+
+ static const QColor SignalColours[10];
+
+public:
+ LogicSignal(boost::shared_ptr<pv::device::DevInst> dev_inst,
+ const sr_channel *const probe,
+ boost::shared_ptr<pv::data::Logic> data);
+
+ virtual ~LogicSignal();
+
+ boost::shared_ptr<pv::data::SignalData> data() const;
+
+ boost::shared_ptr<pv::data::Logic> logic_data() const;
+
+ /**
+ * Paints the background layer of the signal with a QPainter
+ * @param p the QPainter to paint into.
+ * @param left the x-coordinate of the left edge of the signal.
+ * @param right the x-coordinate of the right edge of the signal.
+ **/
+ void paint_back(QPainter &p, int left, int right);
+
+ /**
+ * Paints the mid-layer of the signal with a QPainter
+ * @param p the QPainter to paint into.
+ * @param left the x-coordinate of the left edge of the signal.
+ * @param right the x-coordinate of the right edge of the signal.
+ **/
+ void paint_mid(QPainter &p, int left, int right);
+
+private:
+
+ void paint_caps(QPainter &p, QLineF *const lines,
+ std::vector< std::pair<int64_t, bool> > &edges,
+ bool level, double samples_per_pixel, double pixels_offset,
+ float x_offset, float y_offset);
+
+ void init_trigger_actions(QWidget *parent);
+
+ void populate_popup_form(QWidget *parent, QFormLayout *form);
+
+ void add_trigger_action(const char *trig_types, char type,
+ QAction *action);
+
+ void update_trigger_actions();
+
+ void set_trigger(char type);
+
+private slots:
+ void on_trigger_none();
+ void on_trigger_rising();
+ void on_trigger_high();
+ void on_trigger_falling();
+ void on_trigger_low();
+ void on_trigger_change();
+
+private:
+ boost::shared_ptr<pv::data::Logic> _data;
+
+ QToolBar *_trigger_bar;
+ QAction *_trigger_none;
+ QAction *_trigger_rising;
+ QAction *_trigger_high;
+ QAction *_trigger_falling;
+ QAction *_trigger_low;
+ QAction *_trigger_change;
+};
+
+} // namespace view
+} // namespace pv
+
+#endif // PULSEVIEW_PV_VIEW_LOGICSIGNAL_H
diff --git a/pv/view/marginwidget.cpp b/pv/view/marginwidget.cpp
new file mode 100644
index 0000000..539551d
--- /dev/null
+++ b/pv/view/marginwidget.cpp
@@ -0,0 +1,35 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "view.h"
+
+#include "marginwidget.h"
+
+namespace pv {
+namespace view {
+
+MarginWidget::MarginWidget(View &parent) :
+ QWidget(&parent),
+ _view(parent)
+{
+}
+
+} // namespace view
+} // namespace pv
diff --git a/pv/view/marginwidget.h b/pv/view/marginwidget.h
new file mode 100644
index 0000000..42dffa7
--- /dev/null
+++ b/pv/view/marginwidget.h
@@ -0,0 +1,53 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_MARGINWIDGET_H
+#define PULSEVIEW_PV_MARGINWIDGET_H
+
+#include <QWidget>
+
+namespace pv {
+namespace view {
+
+class View;
+
+class MarginWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ MarginWidget(pv::view::View &parent);
+
+public slots:
+ virtual void clear_selection() = 0;
+
+signals:
+ void selection_changed();
+
+ void geometry_updated();
+
+protected:
+ pv::view::View &_view;
+};
+
+} // namespace view
+} // namespace pv
+
+#endif // PULSEVIEW_PV_MARGINWIDGET_H
diff --git a/pv/view/ruler.cpp b/pv/view/ruler.cpp
new file mode 100644
index 0000000..aec3de9
--- /dev/null
+++ b/pv/view/ruler.cpp
@@ -0,0 +1,275 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "ruler.h"
+
+#include "cursor.h"
+#include "view.h"
+#include "viewport.h"
+
+#include <extdef.h>
+
+#include <assert.h>
+#include <math.h>
+#include <limits.h>
+
+#include <QApplication>
+#include <QMouseEvent>
+#include <QPainter>
+#include <QTextStream>
+
+#include <pv/widgets/popup.h>
+
+using namespace Qt;
+using boost::shared_ptr;
+
+namespace pv {
+namespace view {
+
+const int Ruler::RulerHeight = 30;
+const int Ruler::MinorTickSubdivision = 4;
+const int Ruler::ScaleUnits[3] = {1, 2, 5};
+
+const QString Ruler::SIPrefixes[9] =
+ {"f", "p", "n", QChar(0x03BC), "m", "", "k", "M", "G"};
+const int Ruler::FirstSIPrefixPower = -15;
+
+const int Ruler::HoverArrowSize = 5;
+
+Ruler::Ruler(View &parent) :
+ MarginWidget(parent),
+ _dragging(false)
+{
+ setMouseTracking(true);
+
+ connect(&_view, SIGNAL(hover_point_changed()),
+ this, SLOT(hover_point_changed()));
+}
+
+void Ruler::clear_selection()
+{
+ CursorPair &cursors = _view.cursors();
+ cursors.first()->select(false);
+ cursors.second()->select(false);
+ update();
+}
+
+QString Ruler::format_time(double t, unsigned int prefix,
+ unsigned int precision)
+{
+ const double multiplier = pow(10.0,
+ (int)- prefix * 3 - FirstSIPrefixPower);
+
+ QString s;
+ QTextStream ts(&s);
+ ts.setRealNumberPrecision(precision);
+ ts << fixed << forcesign << (t * multiplier) <<
+ SIPrefixes[prefix] << "s";
+ return s;
+}
+
+QSize Ruler::sizeHint() const
+{
+ return QSize(0, RulerHeight);
+}
+
+void Ruler::paintEvent(QPaintEvent*)
+{
+
+ const double SpacingIncrement = 32.0f;
+ const double MinValueSpacing = 32.0f;
+ const int ValueMargin = 3;
+
+ QPainter p(this);
+ p.setRenderHint(QPainter::Antialiasing);
+
+ double min_width = SpacingIncrement, typical_width;
+ double tick_period;
+ unsigned int prefix;
+
+ // Find tick spacing, and number formatting that does not cause
+ // value to collide.
+ do
+ {
+ const double min_period = _view.scale() * min_width;
+
+ const int order = (int)floorf(log10f(min_period));
+ const double order_decimal = pow(10.0, order);
+
+ unsigned int unit = 0;
+
+ do
+ {
+ tick_period = order_decimal * ScaleUnits[unit++];
+ } while (tick_period < min_period && unit < countof(ScaleUnits));
+
+ prefix = (order - FirstSIPrefixPower) / 3;
+ assert(prefix < countof(SIPrefixes));
+
+
+ typical_width = p.boundingRect(0, 0, INT_MAX, INT_MAX,
+ AlignLeft | AlignTop, format_time(_view.offset(),
+ prefix)).width() + MinValueSpacing;
+
+ min_width += SpacingIncrement;
+
+ } while(typical_width > tick_period / _view.scale());
+
+ const int text_height = p.boundingRect(0, 0, INT_MAX, INT_MAX,
+ AlignLeft | AlignTop, "8").height();
+
+ // Draw the tick marks
+ p.setPen(palette().color(foregroundRole()));
+
+ const double minor_tick_period = tick_period / MinorTickSubdivision;
+ const double first_major_division =
+ floor(_view.offset() / tick_period);
+ const double first_minor_division =
+ ceil(_view.offset() / minor_tick_period);
+ const double t0 = first_major_division * tick_period;
+
+ int division = (int)round(first_minor_division -
+ first_major_division * MinorTickSubdivision) - 1;
+
+ const int major_tick_y1 = text_height + ValueMargin * 2;
+ const int tick_y2 = height();
+ const int minor_tick_y1 = (major_tick_y1 + tick_y2) / 2;
+
+ double x;
+
+ do {
+ const double t = t0 + division * minor_tick_period;
+ x = (t - _view.offset()) / _view.scale();
+
+ if (division % MinorTickSubdivision == 0)
+ {
+ // Draw a major tick
+ p.drawText(x, ValueMargin, 0, text_height,
+ AlignCenter | AlignTop | TextDontClip,
+ format_time(t, prefix));
+ p.drawLine(QPointF(x, major_tick_y1),
+ QPointF(x, tick_y2));
+ }
+ else
+ {
+ // Draw a minor tick
+ p.drawLine(QPointF(x, minor_tick_y1),
+ QPointF(x, tick_y2));
+ }
+
+ division++;
+
+ } while (x < width());
+
+ // Draw the cursors
+ if (_view.cursors_shown())
+ _view.cursors().draw_markers(p, rect(), prefix);
+
+ // Draw the hover mark
+ draw_hover_mark(p);
+
+ p.end();
+}
+
+void Ruler::mouseMoveEvent(QMouseEvent *e)
+{
+ if (!(e->buttons() & Qt::LeftButton))
+ return;
+
+ if ((e->pos() - _mouse_down_point).manhattanLength() <
+ QApplication::startDragDistance())
+ return;
+
+ _dragging = true;
+
+ if (shared_ptr<TimeMarker> m = _grabbed_marker.lock())
+ m->set_time(_view.offset() +
+ ((double)e->x() + 0.5) * _view.scale());
+}
+
+void Ruler::mousePressEvent(QMouseEvent *e)
+{
+ if (e->buttons() & Qt::LeftButton)
+ {
+ _mouse_down_point = e->pos();
+
+ _grabbed_marker.reset();
+
+ clear_selection();
+
+ if (_view.cursors_shown()) {
+ CursorPair &cursors = _view.cursors();
+ if (cursors.first()->get_label_rect(
+ rect()).contains(e->pos()))
+ _grabbed_marker = cursors.first();
+ else if (cursors.second()->get_label_rect(
+ rect()).contains(e->pos()))
+ _grabbed_marker = cursors.second();
+ }
+
+ if (shared_ptr<TimeMarker> m = _grabbed_marker.lock())
+ m->select();
+
+ selection_changed();
+ }
+}
+
+void Ruler::mouseReleaseEvent(QMouseEvent *)
+{
+ using pv::widgets::Popup;
+
+ if (!_dragging)
+ if (shared_ptr<TimeMarker> m = _grabbed_marker.lock()) {
+ Popup *const p = m->create_popup(&_view);
+ p->set_position(mapToGlobal(QPoint(m->get_x(),
+ height())), Popup::Bottom);
+ p->show();
+ }
+
+ _dragging = false;
+ _grabbed_marker.reset();
+}
+
+void Ruler::draw_hover_mark(QPainter &p)
+{
+ const int x = _view.hover_point().x();
+
+ if (x == -1 || _dragging)
+ return;
+
+ p.setPen(QPen(Qt::NoPen));
+ p.setBrush(QBrush(palette().color(foregroundRole())));
+
+ const int b = height() - 1;
+ const QPointF points[] = {
+ QPointF(x, b),
+ QPointF(x - HoverArrowSize, b - HoverArrowSize),
+ QPointF(x + HoverArrowSize, b - HoverArrowSize)
+ };
+ p.drawPolygon(points, countof(points));
+}
+
+void Ruler::hover_point_changed()
+{
+ update();
+}
+
+} // namespace view
+} // namespace pv
diff --git a/pv/view/ruler.h b/pv/view/ruler.h
new file mode 100644
index 0000000..dc4e7bb
--- /dev/null
+++ b/pv/view/ruler.h
@@ -0,0 +1,84 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_VIEW_RULER_H
+#define PULSEVIEW_PV_VIEW_RULER_H
+
+#include <boost/weak_ptr.hpp>
+
+#include "marginwidget.h"
+
+namespace pv {
+namespace view {
+
+class TimeMarker;
+class View;
+
+class Ruler : public MarginWidget
+{
+ Q_OBJECT
+
+private:
+ static const int RulerHeight;
+ static const int MinorTickSubdivision;
+ static const int ScaleUnits[3];
+
+ static const QString SIPrefixes[9];
+ static const int FirstSIPrefixPower;
+
+ static const int HoverArrowSize;
+
+public:
+ Ruler(View &parent);
+
+ void clear_selection();
+
+ static QString format_time(double t, unsigned int prefix,
+ unsigned precision = 0);
+
+public:
+ QSize sizeHint() const;
+
+private:
+ void paintEvent(QPaintEvent *event);
+
+ void mouseMoveEvent(QMouseEvent *e);
+ void mousePressEvent(QMouseEvent *e);
+ void mouseReleaseEvent(QMouseEvent *);
+
+private:
+ /**
+ * Draw a hover arrow under the cursor position.
+ */
+ void draw_hover_mark(QPainter &p);
+
+private slots:
+ void hover_point_changed();
+
+private:
+ boost::weak_ptr<TimeMarker> _grabbed_marker;
+ QPoint _mouse_down_point;
+ bool _dragging;
+};
+
+} // namespace view
+} // namespace pv
+
+#endif // PULSEVIEW_PV_VIEW_RULER_H
diff --git a/pv/view/selectableitem.cpp b/pv/view/selectableitem.cpp
new file mode 100644
index 0000000..3f4f6da
--- /dev/null
+++ b/pv/view/selectableitem.cpp
@@ -0,0 +1,66 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "selectableitem.h"
+
+#include <QApplication>
+#include <QMenu>
+#include <QPalette>
+
+namespace pv {
+namespace view {
+
+const int SelectableItem::HighlightRadius = 6;
+
+SelectableItem::SelectableItem() :
+ _context_parent(NULL),
+ _selected(false)
+{
+}
+
+bool SelectableItem::selected() const
+{
+ return _selected;
+}
+
+void SelectableItem::select(bool select)
+{
+ _selected = select;
+}
+
+QMenu* SelectableItem::create_context_menu(QWidget *parent)
+{
+ _context_parent = parent;
+ return new QMenu(parent);
+}
+
+void SelectableItem::delete_pressed()
+{
+}
+
+QPen SelectableItem::highlight_pen()
+{
+ return QPen(QApplication::palette().brush(
+ QPalette::Highlight), HighlightRadius,
+ Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
+}
+
+} // namespace view
+} // namespace pv
diff --git a/pv/view/selectableitem.h b/pv/view/selectableitem.h
new file mode 100644
index 0000000..d321775
--- /dev/null
+++ b/pv/view/selectableitem.h
@@ -0,0 +1,81 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_SELECTABLEITEM_H
+#define PULSEVIEW_PV_SELECTABLEITEM_H
+
+#include <list>
+
+#include <QPen>
+
+class QAction;
+class QMenu;
+class QWidget;
+
+namespace pv {
+
+namespace widgets {
+class Popup;
+}
+
+namespace view {
+
+class SelectableItem : public QObject
+{
+ Q_OBJECT
+
+private:
+ static const int HighlightRadius;
+
+public:
+ SelectableItem();
+
+public:
+ /**
+ * Returns true if the signal has been selected by the user.
+ */
+ bool selected() const;
+
+ /**
+ * Selects or deselects the signal.
+ */
+ void select(bool select = true);
+
+public:
+ virtual QMenu* create_context_menu(QWidget *parent);
+
+ virtual pv::widgets::Popup* create_popup(QWidget *parent) = 0;
+
+ virtual void delete_pressed();
+
+protected:
+ static QPen highlight_pen();
+
+protected:
+ QWidget *_context_parent;
+
+private:
+ bool _selected;
+};
+
+} // namespace view
+} // namespace pv
+
+#endif // PULSEVIEW_PV_SELECTABLEITEM_H
diff --git a/pv/view/signal.cpp b/pv/view/signal.cpp
new file mode 100644
index 0000000..3312c59
--- /dev/null
+++ b/pv/view/signal.cpp
@@ -0,0 +1,136 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <extdef.h>
+
+#include <assert.h>
+#include <math.h>
+
+#include <QApplication>
+#include <QFormLayout>
+#include <QMenu>
+
+#include <libsigrok/libsigrok.h>
+
+#include "signal.h"
+#include "view.h"
+
+#include <pv/device/devinst.h>
+
+using boost::shared_ptr;
+
+namespace pv {
+namespace view {
+
+const char *const ProbeNames[] = {
+ "CLK",
+ "DATA",
+ "IN",
+ "OUT",
+ "RST",
+ "Tx",
+ "Rx",
+ "EN",
+ "SCLK",
+ "MOSI",
+ "MISO",
+ "/SS",
+ "SDA",
+ "SCL"
+};
+
+Signal::Signal(shared_ptr<pv::device::DevInst> dev_inst,
+ const sr_channel *const probe) :
+ Trace(probe->name),
+ _dev_inst(dev_inst),
+ _probe(probe),
+ _name_widget(NULL),
+ _updating_name_widget(false)
+{
+ assert(_probe);
+}
+
+void Signal::set_name(QString name)
+{
+ Trace::set_name(name);
+ _updating_name_widget = true;
+ _name_widget->setEditText(name);
+ _updating_name_widget = false;
+}
+
+bool Signal::enabled() const
+{
+ return _probe->enabled;
+}
+
+void Signal::enable(bool enable)
+{
+ _dev_inst->enable_probe(_probe, enable);
+ visibility_changed();
+}
+
+const sr_channel* Signal::probe() const
+{
+ return _probe;
+}
+
+void Signal::populate_popup_form(QWidget *parent, QFormLayout *form)
+{
+ _name_widget = new QComboBox(parent);
+ _name_widget->setEditable(true);
+
+ for(unsigned int i = 0; i < countof(ProbeNames); i++)
+ _name_widget->insertItem(i, ProbeNames[i]);
+ _name_widget->setEditText(_probe->name);
+
+ connect(_name_widget, SIGNAL(editTextChanged(const QString&)),
+ this, SLOT(on_text_changed(const QString&)));
+
+ form->addRow(tr("Name"), _name_widget);
+
+ add_colour_option(parent, form);
+}
+
+QMenu* Signal::create_context_menu(QWidget *parent)
+{
+ QMenu *const menu = Trace::create_context_menu(parent);
+
+ menu->addSeparator();
+
+ QAction *const disable = new QAction(tr("Disable"), this);
+ disable->setShortcuts(QKeySequence::Delete);
+ connect(disable, SIGNAL(triggered()), this, SLOT(on_disable()));
+ menu->addAction(disable);
+
+ return menu;
+}
+
+void Signal::delete_pressed()
+{
+ on_disable();
+}
+
+void Signal::on_disable()
+{
+ enable(false);
+}
+
+} // namespace view
+} // namespace pv
diff --git a/pv/view/signal.h b/pv/view/signal.h
new file mode 100644
index 0000000..32d1817
--- /dev/null
+++ b/pv/view/signal.h
@@ -0,0 +1,92 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_VIEW_SIGNAL_H
+#define PULSEVIEW_PV_VIEW_SIGNAL_H
+
+#include <boost/shared_ptr.hpp>
+
+#include <QComboBox>
+#include <QWidgetAction>
+
+#include <stdint.h>
+
+#include "trace.h"
+
+struct sr_channel;
+
+namespace pv {
+
+namespace data {
+class SignalData;
+}
+
+namespace device {
+class DevInst;
+}
+
+namespace view {
+
+class Signal : public Trace
+{
+ Q_OBJECT
+
+protected:
+ Signal(boost::shared_ptr<pv::device::DevInst> dev_inst,
+ const sr_channel *const probe);
+
+public:
+ /**
+ * Sets the name of the signal.
+ */
+ void set_name(QString name);
+
+ virtual boost::shared_ptr<pv::data::SignalData> data() const = 0;
+
+ /**
+ * Returns true if the trace is visible and enabled.
+ */
+ bool enabled() const;
+
+ void enable(bool enable = true);
+
+ const sr_channel* probe() const;
+
+ virtual void populate_popup_form(QWidget *parent, QFormLayout *form);
+
+ QMenu* create_context_menu(QWidget *parent);
+
+ void delete_pressed();
+
+private slots:
+ void on_disable();
+
+protected:
+ boost::shared_ptr<pv::device::DevInst> _dev_inst;
+ const sr_channel *const _probe;
+
+ QComboBox *_name_widget;
+ bool _updating_name_widget;
+};
+
+} // namespace view
+} // namespace pv
+
+#endif // PULSEVIEW_PV_VIEW_SIGNAL_H
diff --git a/pv/view/timemarker.cpp b/pv/view/timemarker.cpp
new file mode 100644
index 0000000..a5d280b
--- /dev/null
+++ b/pv/view/timemarker.cpp
@@ -0,0 +1,104 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "timemarker.h"
+
+#include "view.h"
+
+#include <QFormLayout>
+#include <QPainter>
+
+#include <pv/widgets/popup.h>
+
+namespace pv {
+namespace view {
+
+TimeMarker::TimeMarker(View &view, const QColor &colour, double time) :
+ _view(view),
+ _colour(colour),
+ _time(time),
+ _value_action(NULL),
+ _value_widget(NULL),
+ _updating_value_widget(false)
+{
+}
+
+double TimeMarker::time() const
+{
+ return _time;
+}
+
+float TimeMarker::get_x() const
+{
+ return (_time - _view.offset()) / _view.scale();
+}
+
+void TimeMarker::set_time(double time)
+{
+ _time = time;
+
+ if (_value_widget) {
+ _updating_value_widget = true;
+ _value_widget->setValue(time);
+ _updating_value_widget = false;
+ }
+
+ time_changed();
+}
+
+void TimeMarker::paint(QPainter &p, const QRect &rect)
+{
+ const float x = get_x();
+ p.setPen(_colour);
+ p.drawLine(QPointF(x, rect.top()), QPointF(x, rect.bottom()));
+}
+
+pv::widgets::Popup* TimeMarker::create_popup(QWidget *parent)
+{
+ using pv::widgets::Popup;
+
+ Popup *const popup = new Popup(parent);
+ QFormLayout *const form = new QFormLayout(popup);
+ popup->setLayout(form);
+
+ _value_widget = new QDoubleSpinBox(parent);
+ _value_widget->setDecimals(9);
+ _value_widget->setSuffix("s");
+ _value_widget->setSingleStep(1e-6);
+ _value_widget->setValue(_time);
+
+ connect(_value_widget, SIGNAL(valueChanged(double)),
+ this, SLOT(on_value_changed(double)));
+
+ form->addRow(tr("Time"), _value_widget);
+
+ return popup;
+}
+
+void TimeMarker::on_value_changed(double value)
+{
+ if (!_updating_value_widget) {
+ _time = value;
+ time_changed();
+ }
+}
+
+} // namespace view
+} // namespace pv
diff --git a/pv/view/timemarker.h b/pv/view/timemarker.h
new file mode 100644
index 0000000..fc058c2
--- /dev/null
+++ b/pv/view/timemarker.h
@@ -0,0 +1,113 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_VIEW_MARKER_H
+#define PULSEVIEW_PV_VIEW_MARKER_H
+
+#include <QColor>
+#include <QDoubleSpinBox>
+#include <QObject>
+#include <QRectF>
+#include <QWidgetAction>
+
+#include "selectableitem.h"
+
+class QPainter;
+class QRect;
+
+namespace pv {
+namespace view {
+
+class View;
+
+class TimeMarker : public SelectableItem
+{
+ Q_OBJECT
+
+protected:
+ /**
+ * Constructor.
+ * @param view A reference to the view that owns this marker.
+ * @param colour A reference to the colour of this cursor.
+ * @param time The time to set the flag to.
+ */
+ TimeMarker(View &view, const QColor &colour, double time);
+
+public:
+ /**
+ * Gets the time of the marker.
+ */
+ double time() const;
+
+ /**
+ * Sets the time of the marker.
+ */
+ void set_time(double time);
+
+ float get_x() const;
+
+ /**
+ * Paints the marker to the viewport.
+ * @param p The painter to draw with.
+ * @param rect The rectangle of the viewport client area.
+ */
+ virtual void paint(QPainter &p, const QRect &rect);
+
+ /**
+ * Gets the marker label rectangle.
+ * @param rect The rectangle of the ruler client area.
+ * @return Returns the label rectangle.
+ */
+ virtual QRectF get_label_rect(const QRect &rect) const = 0;
+
+ /**
+ * Paints the marker's label to the ruler.
+ * @param p The painter to draw with.
+ * @param rect The rectangle of the ruler client area.
+ * @param prefix The SI prefix to paint time value with.
+ */
+ virtual void paint_label(QPainter &p, const QRect &rect,
+ unsigned int prefix) = 0;
+
+ pv::widgets::Popup* create_popup(QWidget *parent);
+
+private slots:
+ void on_value_changed(double value);
+
+signals:
+ void time_changed();
+
+protected:
+ View &_view;
+ const QColor &_colour;
+
+ double _time;
+
+ QSizeF _text_size;
+
+ QWidgetAction *_value_action;
+ QDoubleSpinBox *_value_widget;
+ bool _updating_value_widget;
+};
+
+} // namespace view
+} // namespace pv
+
+#endif // PULSEVIEW_PV_VIEW_MARKER_H
diff --git a/pv/view/trace.cpp b/pv/view/trace.cpp
new file mode 100644
index 0000000..d10448f
--- /dev/null
+++ b/pv/view/trace.cpp
@@ -0,0 +1,292 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <extdef.h>
+
+#include <assert.h>
+#include <math.h>
+
+#include <QApplication>
+#include <QFormLayout>
+#include <QLineEdit>
+
+#include "trace.h"
+#include "tracepalette.h"
+#include "view.h"
+
+#include <pv/widgets/colourbutton.h>
+#include <pv/widgets/popup.h>
+
+namespace pv {
+namespace view {
+
+const QPen Trace::AxisPen(QColor(128, 128, 128, 64));
+const int Trace::LabelHitPadding = 2;
+
+Trace::Trace(QString name) :
+ _name(name),
+ _v_offset(0),
+ _popup(NULL),
+ _popup_form(NULL)
+{
+}
+
+QString Trace::get_name() const
+{
+ return _name;
+}
+
+void Trace::set_name(QString name)
+{
+ _name = name;
+}
+
+QColor Trace::get_colour() const
+{
+ return _colour;
+}
+
+void Trace::set_colour(QColor colour)
+{
+ _colour = colour;
+}
+
+int Trace::get_v_offset() const
+{
+ return _v_offset;
+}
+
+void Trace::set_v_offset(int v_offset)
+{
+ _v_offset = v_offset;
+}
+
+void Trace::set_view(pv::view::View *view)
+{
+ assert(view);
+ _view = view;
+}
+
+void Trace::paint_back(QPainter &p, int left, int right)
+{
+ (void)p;
+ (void)left;
+ (void)right;
+}
+
+void Trace::paint_mid(QPainter &p, int left, int right)
+{
+ (void)p;
+ (void)left;
+ (void)right;
+}
+
+void Trace::paint_fore(QPainter &p, int left, int right)
+{
+ (void)p;
+ (void)left;
+ (void)right;
+}
+
+void Trace::paint_label(QPainter &p, int right, bool hover)
+{
+ assert(_view);
+ const int y = _v_offset - _view->v_offset();
+
+ p.setBrush(_colour);
+
+ if (!enabled())
+ return;
+
+ const QColor colour = get_colour();
+
+ const QRectF label_rect = get_label_rect(right);
+
+ // Paint the label
+ const QPointF points[] = {
+ label_rect.topLeft(),
+ label_rect.topRight(),
+ QPointF(right, y),
+ label_rect.bottomRight(),
+ label_rect.bottomLeft()
+ };
+
+ const QPointF highlight_points[] = {
+ QPointF(label_rect.left() + 1, label_rect.top() + 1),
+ QPointF(label_rect.right(), label_rect.top() + 1),
+ QPointF(right - 1, y),
+ QPointF(label_rect.right(), label_rect.bottom() - 1),
+ QPointF(label_rect.left() + 1, label_rect.bottom() - 1)
+ };
+
+ if (selected()) {
+ p.setPen(highlight_pen());
+ p.setBrush(Qt::transparent);
+ p.drawPolygon(points, countof(points));
+ }
+
+ p.setPen(Qt::transparent);
+ p.setBrush(hover ? colour.lighter() : colour);
+ p.drawPolygon(points, countof(points));
+
+ p.setPen(colour.lighter());
+ p.setBrush(Qt::transparent);
+ p.drawPolygon(highlight_points, countof(highlight_points));
+
+ p.setPen(colour.darker());
+ p.setBrush(Qt::transparent);
+ p.drawPolygon(points, countof(points));
+
+ // Paint the text
+ p.setPen(get_text_colour());
+ p.setFont(QApplication::font());
+ p.drawText(label_rect, Qt::AlignCenter | Qt::AlignVCenter, _name);
+}
+
+bool Trace::pt_in_label_rect(int left, int right, const QPoint &point)
+{
+ (void)left;
+
+ const QRectF label = get_label_rect(right);
+ return enabled() && QRectF(
+ QPointF(label.left() - LabelHitPadding,
+ label.top() - LabelHitPadding),
+ QPointF(right, label.bottom() + LabelHitPadding)
+ ).contains(point);
+}
+
+QMenu* Trace::create_context_menu(QWidget *parent)
+{
+ QMenu *const menu = SelectableItem::create_context_menu(parent);
+
+ return menu;
+}
+
+pv::widgets::Popup* Trace::create_popup(QWidget *parent)
+{
+ using pv::widgets::Popup;
+
+ _popup = new Popup(parent);
+
+ create_popup_form();
+
+ connect(_popup, SIGNAL(closed()),
+ this, SLOT(on_popup_closed()));
+
+ return _popup;
+}
+
+int Trace::get_y() const
+{
+ return _v_offset - _view->v_offset();
+}
+
+QRectF Trace::get_label_rect(int right)
+{
+ using pv::view::View;
+
+ assert(_view);
+
+ QFontMetrics m(QApplication::font());
+ const QSize text_size(
+ m.boundingRect(QRect(), 0, _name).width(),
+ m.boundingRect(QRect(), 0, "Tg").height());
+ const QSizeF label_size(
+ text_size.width() + View::LabelPadding.width() * 2,
+ ceilf((text_size.height() + View::LabelPadding.height() * 2) / 2) * 2);
+ const float label_arrow_length = label_size.height() / 2;
+ return QRectF(
+ right - label_arrow_length - label_size.width() - 0.5,
+ get_y() + 0.5f - label_size.height() / 2,
+ label_size.width(), label_size.height());
+}
+
+QColor Trace::get_text_colour() const
+{
+ return (_colour.lightness() > 64) ? Qt::black : Qt::white;
+}
+
+void Trace::paint_axis(QPainter &p, int y, int left, int right)
+{
+ p.setPen(AxisPen);
+ p.drawLine(QPointF(left, y + 0.5f), QPointF(right, y + 0.5f));
+}
+
+void Trace::add_colour_option(QWidget *parent, QFormLayout *form)
+{
+ using pv::widgets::ColourButton;
+
+ ColourButton *const colour_button = new ColourButton(
+ TracePalette::Rows, TracePalette::Cols, parent);
+ colour_button->set_palette(TracePalette::Colours);
+ colour_button->set_colour(_colour);
+ connect(colour_button, SIGNAL(selected(const QColor&)),
+ this, SLOT(on_colour_changed(const QColor&)));
+
+ form->addRow(tr("Colour"), colour_button);
+}
+
+void Trace::create_popup_form()
+{
+ // Clear the layout
+
+ // Transfer the layout and the child widgets to a temporary widget
+ // which then goes out of scope destroying the layout and all the child
+ // widgets.
+ if (_popup_form)
+ QWidget().setLayout(_popup_form);
+
+ // Repopulate the popup
+ _popup_form = new QFormLayout(_popup);
+ _popup->setLayout(_popup_form);
+ populate_popup_form(_popup, _popup_form);
+}
+
+void Trace::populate_popup_form(QWidget *parent, QFormLayout *form)
+{
+ QLineEdit *const name_edit = new QLineEdit(parent);
+ name_edit->setText(_name);
+ connect(name_edit, SIGNAL(textChanged(const QString&)),
+ this, SLOT(on_text_changed(const QString&)));
+ form->addRow(tr("Name"), name_edit);
+
+ add_colour_option(parent, form);
+}
+
+void Trace::on_popup_closed()
+{
+ _popup = NULL;
+ _popup_form = NULL;
+}
+
+void Trace::on_text_changed(const QString &text)
+{
+ set_name(text);
+ text_changed();
+}
+
+void Trace::on_colour_changed(const QColor &colour)
+{
+ set_colour(colour);
+ colour_changed();
+}
+
+} // namespace view
+} // namespace pv
diff --git a/pv/view/trace.h b/pv/view/trace.h
new file mode 100644
index 0000000..ace93d5
--- /dev/null
+++ b/pv/view/trace.h
@@ -0,0 +1,203 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_VIEW_TRACE_H
+#define PULSEVIEW_PV_VIEW_TRACE_H
+
+#include <QColor>
+#include <QPainter>
+#include <QPen>
+#include <QRect>
+#include <QString>
+
+#include <stdint.h>
+
+#include "selectableitem.h"
+
+class QFormLayout;
+
+namespace pv {
+namespace view {
+
+class View;
+
+class Trace : public SelectableItem
+{
+ Q_OBJECT
+
+private:
+ static const QPen AxisPen;
+ static const int LabelHitPadding;
+
+protected:
+ Trace(QString name);
+
+public:
+ /**
+ * Gets the name of this signal.
+ */
+ QString get_name() const;
+
+ /**
+ * Sets the name of the signal.
+ */
+ virtual void set_name(QString name);
+
+ /**
+ * Get the colour of the signal.
+ */
+ QColor get_colour() const;
+
+ /**
+ * Set the colour of the signal.
+ */
+ void set_colour(QColor colour);
+
+ /**
+ * Gets the vertical layout offset of this signal.
+ */
+ int get_v_offset() const;
+
+ /**
+ * Sets the vertical layout offset of this signal.
+ */
+ void set_v_offset(int v_offset);
+
+ /**
+ * Returns true if the trace is visible and enabled.
+ */
+ virtual bool enabled() const = 0;
+
+ virtual void set_view(pv::view::View *view);
+
+ /**
+ * Paints the background layer of the trace with a QPainter
+ * @param p the QPainter to paint into.
+ * @param left the x-coordinate of the left edge of the signal
+ * @param right the x-coordinate of the right edge of the signal
+ **/
+ virtual void paint_back(QPainter &p, int left, int right);
+
+ /**
+ * Paints the mid-layer of the trace with a QPainter
+ * @param p the QPainter to paint into.
+ * @param left the x-coordinate of the left edge of the signal
+ * @param right the x-coordinate of the right edge of the signal
+ **/
+ virtual void paint_mid(QPainter &p, int left, int right);
+
+ /**
+ * Paints the foreground layer of the trace with a QPainter
+ * @param p the QPainter to paint into.
+ * @param left the x-coordinate of the left edge of the signal
+ * @param right the x-coordinate of the right edge of the signal
+ **/
+ virtual void paint_fore(QPainter &p, int left, int right);
+
+ /**
+ * Paints the signal label into a QGLWidget.
+ * @param p the QPainter to paint into.
+ * @param right the x-coordinate of the right edge of the header
+ * area.
+ * @param hover true if the label is being hovered over by the mouse.
+ */
+ virtual void paint_label(QPainter &p, int right, bool hover);
+
+ /**
+ * Determines if a point is in the header label rect.
+ * @param left the x-coordinate of the left edge of the header
+ * area.
+ * @param right the x-coordinate of the right edge of the header
+ * area.
+ * @param point the point to test.
+ */
+ bool pt_in_label_rect(int left, int right, const QPoint &point);
+
+ virtual QMenu* create_context_menu(QWidget *parent);
+
+ pv::widgets::Popup* create_popup(QWidget *parent);
+
+ /**
+ * Gets the y-offset of the axis.
+ */
+ int get_y() const;
+
+ /**
+ * Computes the outline rectangle of a label.
+ * @param p the QPainter to lay out text with.
+ * @param right the x-coordinate of the right edge of the header
+ * area.
+ * @return Returns the rectangle of the signal label.
+ */
+ QRectF get_label_rect(int right);
+
+protected:
+
+ /**
+ * Gets the text colour.
+ * @remarks This colour is computed by comparing the lightness
+ * of the trace colour against a threshold to determine whether
+ * white or black would be more visible.
+ */
+ QColor get_text_colour() const;
+
+ /**
+ * Paints a zero axis across the viewport.
+ * @param p the QPainter to paint into.
+ * @param y the y-offset of the axis.
+ * @param left the x-coordinate of the left edge of the view.
+ * @param right the x-coordinate of the right edge of the view.
+ */
+ void paint_axis(QPainter &p, int y, int left, int right);
+
+ void add_colour_option(QWidget *parent, QFormLayout *form);
+
+ void create_popup_form();
+
+ virtual void populate_popup_form(QWidget *parent, QFormLayout *form);
+
+private slots:
+ void on_text_changed(const QString &text);
+
+ void on_colour_changed(const QColor &colour);
+
+ void on_popup_closed();
+
+signals:
+ void visibility_changed();
+ void text_changed();
+ void colour_changed();
+
+protected:
+ pv::view::View *_view;
+
+ QString _name;
+ QColor _colour;
+ int _v_offset;
+
+private:
+ pv::widgets::Popup *_popup;
+ QFormLayout *_popup_form;
+};
+
+} // namespace view
+} // namespace pv
+
+#endif // PULSEVIEW_PV_VIEW_TRACE_H
diff --git a/pv/view/tracepalette.cpp b/pv/view/tracepalette.cpp
new file mode 100644
index 0000000..cefb952
--- /dev/null
+++ b/pv/view/tracepalette.cpp
@@ -0,0 +1,70 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "tracepalette.h"
+
+namespace pv {
+namespace view {
+
+const QColor TracePalette::Colours[Cols * Rows] = {
+
+ // Light Colours
+ QColor(0xFC, 0xE9, 0x4F), // Butter
+ QColor(0xFC, 0xAF, 0x3E), // Orange
+ QColor(0xE9, 0xB9, 0x6E), // Chocolate
+ QColor(0x8A, 0xE2, 0x34), // Chameleon
+ QColor(0x72, 0x9F, 0xCF), // Sky Blue
+ QColor(0xAD, 0x7F, 0xA8), // Plum
+ QColor(0xCF, 0x72, 0xC3), // Magenta
+ QColor(0xEF, 0x29, 0x29), // Scarlet Red
+
+ // Mid Colours
+ QColor(0xED, 0xD4, 0x00), // Butter
+ QColor(0xF5, 0x79, 0x00), // Orange
+ QColor(0xC1, 0x7D, 0x11), // Chocolate
+ QColor(0x73, 0xD2, 0x16), // Chameleon
+ QColor(0x34, 0x65, 0xA4), // Sky Blue
+ QColor(0x75, 0x50, 0x7B), // Plum
+ QColor(0xA3, 0x34, 0x96), // Magenta
+ QColor(0xCC, 0x00, 0x00), // Scarlet Red
+
+ // Dark Colours
+ QColor(0xC4, 0xA0, 0x00), // Butter
+ QColor(0xCE, 0x5C, 0x00), // Orange
+ QColor(0x8F, 0x59, 0x02), // Chocolate
+ QColor(0x4E, 0x9A, 0x06), // Chameleon
+ QColor(0x20, 0x4A, 0x87), // Sky Blue
+ QColor(0x5C, 0x35, 0x66), // Plum
+ QColor(0x87, 0x20, 0x7A), // Magenta
+ QColor(0xA4, 0x00, 0x00), // Scarlet Red
+
+ // Greys
+ QColor(0x16, 0x19, 0x1A), // Black
+ QColor(0x2E, 0x34, 0x36), // Grey 1
+ QColor(0x55, 0x57, 0x53), // Grey 2
+ QColor(0x88, 0x8A, 0x8F), // Grey 3
+ QColor(0xBA, 0xBD, 0xB6), // Grey 4
+ QColor(0xD3, 0xD7, 0xCF), // Grey 5
+ QColor(0xEE, 0xEE, 0xEC), // Grey 6
+ QColor(0xFF, 0xFF, 0xFF), // White
+};
+
+} // view
+} // pv
diff --git a/pv/view/tracepalette.h b/pv/view/tracepalette.h
new file mode 100644
index 0000000..ae3c69a
--- /dev/null
+++ b/pv/view/tracepalette.h
@@ -0,0 +1,41 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_TRACEPALETTE_H
+#define PULSEVIEW_PV_TRACEPALETTE_H
+
+#include <QColor>
+
+namespace pv {
+namespace view {
+
+class TracePalette
+{
+public:
+ static const unsigned int Cols = 8;
+ static const unsigned int Rows = 4;
+ static const QColor Colours[Cols * Rows];
+};
+
+
+} // view
+} // pv
+
+#endif // PULSEVIEW_PV_VIEW_TRACEPALETTE_H
diff --git a/pv/view/view.cpp b/pv/view/view.cpp
new file mode 100644
index 0000000..d117489
--- /dev/null
+++ b/pv/view/view.cpp
@@ -0,0 +1,539 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef ENABLE_DECODE
+#include <libsigrokdecode/libsigrokdecode.h>
+#endif
+
+#include <assert.h>
+#include <limits.h>
+#include <math.h>
+
+#include <boost/foreach.hpp>
+
+#include <QEvent>
+#include <QMouseEvent>
+#include <QScrollBar>
+
+#include "decodetrace.h"
+#include "header.h"
+#include "ruler.h"
+#include "signal.h"
+#include "view.h"
+#include "viewport.h"
+
+#include "pv/sigsession.h"
+#include "pv/data/logic.h"
+#include "pv/data/logicsnapshot.h"
+
+using boost::shared_ptr;
+using boost::weak_ptr;
+using pv::data::SignalData;
+using std::deque;
+using std::list;
+using std::max;
+using std::make_pair;
+using std::min;
+using std::pair;
+using std::set;
+using std::vector;
+
+namespace pv {
+namespace view {
+
+const double View::MaxScale = 1e9;
+const double View::MinScale = 1e-15;
+
+const int View::MaxScrollValue = INT_MAX / 2;
+
+const int View::SignalHeight = 30;
+const int View::SignalMargin = 10;
+const int View::SignalSnapGridSize = 10;
+
+const QColor View::CursorAreaColour(220, 231, 243);
+
+const QSizeF View::LabelPadding(4, 0);
+
+View::View(SigSession &session, QWidget *parent) :
+ QAbstractScrollArea(parent),
+ _session(session),
+ _viewport(new Viewport(*this)),
+ _ruler(new Ruler(*this)),
+ _header(new Header(*this)),
+ _scale(1e-6),
+ _offset(0),
+ _v_offset(0),
+ _updating_scroll(false),
+ _show_cursors(false),
+ _cursors(*this),
+ _hover_point(-1, -1)
+{
+ connect(horizontalScrollBar(), SIGNAL(valueChanged(int)),
+ this, SLOT(h_scroll_value_changed(int)));
+ connect(verticalScrollBar(), SIGNAL(valueChanged(int)),
+ this, SLOT(v_scroll_value_changed(int)));
+
+ connect(&_session, SIGNAL(signals_changed()),
+ this, SLOT(signals_changed()));
+ connect(&_session, SIGNAL(data_received()),
+ this, SLOT(data_updated()));
+ connect(&_session, SIGNAL(frame_ended()),
+ this, SLOT(data_updated()));
+
+ connect(_cursors.first().get(), SIGNAL(time_changed()),
+ this, SLOT(marker_time_changed()));
+ connect(_cursors.second().get(), SIGNAL(time_changed()),
+ this, SLOT(marker_time_changed()));
+
+ connect(_header, SIGNAL(geometry_updated()),
+ this, SLOT(on_geometry_updated()));
+ connect(_header, SIGNAL(signals_moved()),
+ this, SLOT(on_signals_moved()));
+
+ connect(_header, SIGNAL(selection_changed()),
+ _ruler, SLOT(clear_selection()));
+ connect(_ruler, SIGNAL(selection_changed()),
+ _header, SLOT(clear_selection()));
+
+ connect(_header, SIGNAL(selection_changed()),
+ this, SIGNAL(selection_changed()));
+ connect(_ruler, SIGNAL(selection_changed()),
+ this, SIGNAL(selection_changed()));
+
+ setViewport(_viewport);
+
+ _viewport->installEventFilter(this);
+ _ruler->installEventFilter(this);
+ _header->installEventFilter(this);
+
+ // Trigger the initial event manually. The default device has signals
+ // which were created before this object came into being
+ signals_changed();
+}
+
+SigSession& View::session()
+{
+ return _session;
+}
+
+const SigSession& View::session() const
+{
+ return _session;
+}
+
+double View::scale() const
+{
+ return _scale;
+}
+
+double View::offset() const
+{
+ return _offset;
+}
+
+int View::v_offset() const
+{
+ return _v_offset;
+}
+
+void View::zoom(double steps)
+{
+ zoom(steps, _viewport->width() / 2);
+}
+
+void View::zoom(double steps, int offset)
+{
+ set_zoom(_scale * pow(3.0/2.0, -steps), offset);
+}
+
+void View::zoom_fit()
+{
+ const pair<double, double> extents = get_time_extents();
+ const double delta = extents.second - extents.first;
+ if (delta < 1e-12)
+ return;
+
+ assert(_viewport);
+ const int w = _viewport->width();
+ if (w <= 0)
+ return;
+
+ const double scale = max(min(delta / w, MaxScale), MinScale);
+ set_scale_offset(scale, extents.first);
+}
+
+void View::zoom_one_to_one()
+{
+ using pv::data::SignalData;
+
+ const vector< shared_ptr<Signal> > sigs(
+ session().get_signals());
+
+ // Make a set of all the visible data objects
+ set< shared_ptr<SignalData> > visible_data = get_visible_data();
+ if (visible_data.empty())
+ return;
+
+ double samplerate = 0.0;
+ BOOST_FOREACH(const shared_ptr<SignalData> d, visible_data) {
+ assert(d);
+ samplerate = max(samplerate, d->samplerate());
+ }
+
+ if (samplerate == 0.0)
+ return;
+
+ assert(_viewport);
+ const int w = _viewport->width();
+ if (w <= 0)
+ return;
+
+ set_zoom(1.0 / samplerate, w / 2);
+}
+
+void View::set_scale_offset(double scale, double offset)
+{
+ _scale = scale;
+ _offset = offset;
+
+ update_scroll();
+ _ruler->update();
+ _viewport->update();
+ scale_offset_changed();
+}
+
+vector< shared_ptr<Trace> > View::get_traces() const
+{
+ const vector< shared_ptr<Signal> > sigs(
+ session().get_signals());
+#ifdef ENABLE_DECODE
+ const vector< shared_ptr<DecodeTrace> > decode_sigs(
+ session().get_decode_signals());
+ vector< shared_ptr<Trace> > traces(
+ sigs.size() + decode_sigs.size());
+#else
+ vector< shared_ptr<Trace> > traces(sigs.size());
+#endif
+
+ vector< shared_ptr<Trace> >::iterator i = traces.begin();
+ i = copy(sigs.begin(), sigs.end(), i);
+#ifdef ENABLE_DECODE
+ i = copy(decode_sigs.begin(), decode_sigs.end(), i);
+#endif
+
+ stable_sort(traces.begin(), traces.end(), compare_trace_v_offsets);
+ return traces;
+}
+
+list<weak_ptr<SelectableItem> > View::selected_items() const
+{
+ list<weak_ptr<SelectableItem> > items;
+
+ // List the selected signals
+ const vector< shared_ptr<Trace> > traces(get_traces());
+ BOOST_FOREACH (shared_ptr<Trace> t, traces) {
+ assert(t);
+ if (t->selected())
+ items.push_back(t);
+ }
+
+ // List the selected cursors
+ if (_cursors.first()->selected())
+ items.push_back(_cursors.first());
+ if (_cursors.second()->selected())
+ items.push_back(_cursors.second());
+
+ return items;
+}
+
+set< shared_ptr<SignalData> > View::get_visible_data() const
+{
+ const vector< shared_ptr<Signal> > sigs(
+ session().get_signals());
+
+ // Make a set of all the visible data objects
+ set< shared_ptr<SignalData> > visible_data;
+ BOOST_FOREACH(const shared_ptr<Signal> sig, sigs)
+ if (sig->enabled())
+ visible_data.insert(sig->data());
+
+ return visible_data;
+}
+
+pair<double, double> View::get_time_extents() const
+{
+ const set< shared_ptr<SignalData> > visible_data = get_visible_data();
+ if (visible_data.empty())
+ return make_pair(0.0, 0.0);
+
+ double left_time = DBL_MAX, right_time = DBL_MIN;
+ BOOST_FOREACH(const shared_ptr<SignalData> d, visible_data)
+ {
+ const double start_time = d->get_start_time();
+ double samplerate = d->samplerate();
+ samplerate = (samplerate <= 0.0) ? 1.0 : samplerate;
+
+ left_time = min(left_time, start_time);
+ right_time = max(right_time, start_time +
+ d->get_max_sample_count() / samplerate);
+ }
+
+ assert(left_time < right_time);
+ return make_pair(left_time, right_time);
+}
+
+bool View::cursors_shown() const
+{
+ return _show_cursors;
+}
+
+void View::show_cursors(bool show)
+{
+ _show_cursors = show;
+ _ruler->update();
+ _viewport->update();
+}
+
+void View::centre_cursors()
+{
+ const double time_width = _scale * _viewport->width();
+ _cursors.first()->set_time(_offset + time_width * 0.4);
+ _cursors.second()->set_time(_offset + time_width * 0.6);
+ _ruler->update();
+ _viewport->update();
+}
+
+CursorPair& View::cursors()
+{
+ return _cursors;
+}
+
+const CursorPair& View::cursors() const
+{
+ return _cursors;
+}
+
+const QPoint& View::hover_point() const
+{
+ return _hover_point;
+}
+
+void View::normalize_layout()
+{
+ const vector< shared_ptr<Trace> > traces(get_traces());
+
+ int v_min = INT_MAX;
+ BOOST_FOREACH(const shared_ptr<Trace> t, traces)
+ v_min = min(t->get_v_offset(), v_min);
+
+ const int delta = -min(v_min, 0);
+ BOOST_FOREACH(shared_ptr<Trace> t, traces)
+ t->set_v_offset(t->get_v_offset() + delta);
+
+ verticalScrollBar()->setSliderPosition(_v_offset + delta);
+ v_scroll_value_changed(verticalScrollBar()->sliderPosition());
+}
+
+void View::update_viewport()
+{
+ assert(_viewport);
+ _viewport->update();
+}
+
+void View::get_scroll_layout(double &length, double &offset) const
+{
+ const pair<double, double> extents = get_time_extents();
+ length = (extents.second - extents.first) / _scale;
+ offset = _offset / _scale;
+}
+
+void View::set_zoom(double scale, int offset)
+{
+ const double cursor_offset = _offset + _scale * offset;
+ const double new_scale = max(min(scale, MaxScale), MinScale);
+ const double new_offset = cursor_offset - new_scale * offset;
+ set_scale_offset(new_scale, new_offset);
+}
+
+void View::update_scroll()
+{
+ assert(_viewport);
+
+ const QSize areaSize = _viewport->size();
+
+ // Set the horizontal scroll bar
+ double length = 0, offset = 0;
+ get_scroll_layout(length, offset);
+ length = max(length - areaSize.width(), 0.0);
+
+ horizontalScrollBar()->setPageStep(areaSize.width() / 2);
+
+ _updating_scroll = true;
+
+ if (length < MaxScrollValue) {
+ horizontalScrollBar()->setRange(0, length);
+ horizontalScrollBar()->setSliderPosition(offset);
+ } else {
+ horizontalScrollBar()->setRange(0, MaxScrollValue);
+ horizontalScrollBar()->setSliderPosition(
+ _offset * MaxScrollValue / (_scale * length));
+ }
+
+ _updating_scroll = false;
+
+ // Set the vertical scrollbar
+ verticalScrollBar()->setPageStep(areaSize.height());
+ verticalScrollBar()->setRange(0,
+ _viewport->get_total_height() + SignalMargin -
+ areaSize.height());
+}
+
+void View::update_layout()
+{
+ setViewportMargins(_header->sizeHint().width(),
+ _ruler->sizeHint().height(), 0, 0);
+ _ruler->setGeometry(_viewport->x(), 0,
+ _viewport->width(), _viewport->y());
+ _header->setGeometry(0, _viewport->y(),
+ _viewport->x(), _viewport->height());
+ update_scroll();
+}
+
+bool View::compare_trace_v_offsets(const shared_ptr<Trace> &a,
+ const shared_ptr<Trace> &b)
+{
+ assert(a);
+ assert(b);
+ return a->get_v_offset() < b->get_v_offset();
+}
+
+bool View::eventFilter(QObject *object, QEvent *event)
+{
+ const QEvent::Type type = event->type();
+ if (type == QEvent::MouseMove) {
+
+ const QMouseEvent *const mouse_event = (QMouseEvent*)event;
+ if (object == _viewport)
+ _hover_point = mouse_event->pos();
+ else if (object == _ruler)
+ _hover_point = QPoint(mouse_event->x(), 0);
+ else if (object == _header)
+ _hover_point = QPoint(0, mouse_event->y());
+ else
+ _hover_point = QPoint(-1, -1);
+
+ hover_point_changed();
+
+ } else if (type == QEvent::Leave) {
+ _hover_point = QPoint(-1, -1);
+ hover_point_changed();
+ }
+
+ return QObject::eventFilter(object, event);
+}
+
+bool View::viewportEvent(QEvent *e)
+{
+ switch(e->type()) {
+ case QEvent::Paint:
+ case QEvent::MouseButtonPress:
+ case QEvent::MouseButtonRelease:
+ case QEvent::MouseButtonDblClick:
+ case QEvent::MouseMove:
+ case QEvent::Wheel:
+ return false;
+
+ default:
+ return QAbstractScrollArea::viewportEvent(e);
+ }
+}
+
+void View::resizeEvent(QResizeEvent*)
+{
+ update_layout();
+}
+
+void View::h_scroll_value_changed(int value)
+{
+ if (_updating_scroll)
+ return;
+
+ const int range = horizontalScrollBar()->maximum();
+ if (range < MaxScrollValue)
+ _offset = _scale * value;
+ else {
+ double length = 0, offset;
+ get_scroll_layout(length, offset);
+ _offset = _scale * length * value / MaxScrollValue;
+ }
+
+ _ruler->update();
+ _viewport->update();
+}
+
+void View::v_scroll_value_changed(int value)
+{
+ _v_offset = value;
+ _header->update();
+ _viewport->update();
+}
+
+void View::signals_changed()
+{
+ int offset = SignalMargin + SignalHeight;
+ const vector< shared_ptr<Trace> > traces(get_traces());
+ BOOST_FOREACH(shared_ptr<Trace> t, traces) {
+ t->set_view(this);
+ t->set_v_offset(offset);
+ offset += SignalHeight + 2 * SignalMargin;
+ }
+
+ update_layout();
+ normalize_layout();
+}
+
+void View::data_updated()
+{
+ // Update the scroll bars
+ update_scroll();
+
+ // Repaint the view
+ _viewport->update();
+}
+
+void View::marker_time_changed()
+{
+ _ruler->update();
+ _viewport->update();
+}
+
+void View::on_signals_moved()
+{
+ update_scroll();
+ signals_moved();
+}
+
+void View::on_geometry_updated()
+{
+ update_layout();
+}
+
+} // namespace view
+} // namespace pv
diff --git a/pv/view/view.h b/pv/view/view.h
new file mode 100644
index 0000000..0661637
--- /dev/null
+++ b/pv/view/view.h
@@ -0,0 +1,214 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_VIEW_VIEW_H
+#define PULSEVIEW_PV_VIEW_VIEW_H
+
+#include <stdint.h>
+
+#include <set>
+#include <vector>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+
+#include <QAbstractScrollArea>
+#include <QSizeF>
+
+#include <pv/data/signaldata.h>
+
+#include "cursorpair.h"
+
+namespace pv {
+
+class SigSession;
+
+namespace view {
+
+class Header;
+class Ruler;
+class Trace;
+class Viewport;
+
+class View : public QAbstractScrollArea {
+ Q_OBJECT
+
+private:
+ static const double MaxScale;
+ static const double MinScale;
+
+ static const int MaxScrollValue;
+
+public:
+ static const int SignalHeight;
+ static const int SignalMargin;
+ static const int SignalSnapGridSize;
+
+ static const QColor CursorAreaColour;
+
+ static const QSizeF LabelPadding;
+
+public:
+ explicit View(SigSession &session, QWidget *parent = 0);
+
+ SigSession& session();
+ const SigSession& session() const;
+
+ /**
+ * Returns the view time scale in seconds per pixel.
+ */
+ double scale() const;
+
+ /**
+ * Returns the time offset of the left edge of the view in
+ * seconds.
+ */
+ double offset() const;
+ int v_offset() const;
+
+ void zoom(double steps);
+ void zoom(double steps, int offset);
+
+ void zoom_fit();
+
+ void zoom_one_to_one();
+
+ /**
+ * Sets the scale and offset.
+ * @param scale The new view scale in seconds per pixel.
+ * @param offset The view time offset in seconds.
+ */
+ void set_scale_offset(double scale, double offset);
+
+ std::vector< boost::shared_ptr<Trace> > get_traces() const;
+
+ std::list<boost::weak_ptr<SelectableItem> > selected_items() const;
+
+ std::set< boost::shared_ptr<pv::data::SignalData> >
+ get_visible_data() const;
+
+ std::pair<double, double> get_time_extents() const;
+
+ /**
+ * Returns true if cursors are displayed. false otherwise.
+ */
+ bool cursors_shown() const;
+
+ /**
+ * Shows or hides the cursors.
+ */
+ void show_cursors(bool show = true);
+
+ /**
+ * Moves the cursors to a convenient position in the view.
+ */
+ void centre_cursors();
+
+ /**
+ * Returns a reference to the pair of cursors.
+ */
+ CursorPair& cursors();
+
+ /**
+ * Returns a reference to the pair of cursors.
+ */
+ const CursorPair& cursors() const;
+
+ const QPoint& hover_point() const;
+
+ void normalize_layout();
+
+ void update_viewport();
+
+signals:
+ void hover_point_changed();
+
+ void signals_moved();
+
+ void selection_changed();
+
+ void scale_offset_changed();
+
+private:
+ void get_scroll_layout(double &length, double &offset) const;
+
+ /**
+ * Simultaneously sets the zoom and offset.
+ * @param scale The scale to set the view to in seconds per pixel. This
+ * value is clamped between MinScale and MaxScale.
+ * @param offset The offset of the left edge of the view in seconds.
+ */
+ void set_zoom(double scale, int offset);
+
+ void update_scroll();
+
+ void update_layout();
+
+ static bool compare_trace_v_offsets(
+ const boost::shared_ptr<pv::view::Trace> &a,
+ const boost::shared_ptr<pv::view::Trace> &b);
+
+private:
+ bool eventFilter(QObject *object, QEvent *event);
+
+ bool viewportEvent(QEvent *e);
+
+ void resizeEvent(QResizeEvent *e);
+
+private slots:
+
+ void h_scroll_value_changed(int value);
+ void v_scroll_value_changed(int value);
+
+ void signals_changed();
+ void data_updated();
+
+ void marker_time_changed();
+
+ void on_signals_moved();
+
+ void on_geometry_updated();
+
+private:
+ SigSession &_session;
+
+ Viewport *_viewport;
+ Ruler *_ruler;
+ Header *_header;
+
+ /// The view time scale in seconds per pixel.
+ double _scale;
+
+ /// The view time offset in seconds.
+ double _offset;
+
+ int _v_offset;
+ bool _updating_scroll;
+
+ bool _show_cursors;
+ CursorPair _cursors;
+
+ QPoint _hover_point;
+};
+
+} // namespace view
+} // namespace pv
+
+#endif // PULSEVIEW_PV_VIEW_VIEW_H
diff --git a/pv/view/viewport.cpp b/pv/view/viewport.cpp
new file mode 100644
index 0000000..3b06cf9
--- /dev/null
+++ b/pv/view/viewport.cpp
@@ -0,0 +1,161 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "view.h"
+#include "viewport.h"
+
+#include "signal.h"
+#include "../sigsession.h"
+
+#include <QMouseEvent>
+
+#include <boost/foreach.hpp>
+
+using boost::shared_ptr;
+using std::max;
+using std::min;
+using std::vector;
+
+namespace pv {
+namespace view {
+
+Viewport::Viewport(View &parent) :
+ QWidget(&parent),
+ _view(parent)
+{
+ setMouseTracking(true);
+ setAutoFillBackground(true);
+ setBackgroundRole(QPalette::Base);
+
+ connect(&_view.session(), SIGNAL(signals_changed()),
+ this, SLOT(on_signals_changed()));
+
+ connect(&_view, SIGNAL(signals_moved()),
+ this, SLOT(on_signals_moved()));
+
+ // Trigger the initial event manually. The default device has signals
+ // which were created before this object came into being
+ on_signals_changed();
+}
+
+int Viewport::get_total_height() const
+{
+ int h = 0;
+ const vector< shared_ptr<Trace> > traces(_view.get_traces());
+ BOOST_FOREACH(const shared_ptr<Trace> t, traces) {
+ assert(t);
+ h = max(t->get_v_offset() + View::SignalHeight, h);
+ }
+
+ return h;
+}
+
+void Viewport::paintEvent(QPaintEvent*)
+{
+ const vector< shared_ptr<Trace> > traces(_view.get_traces());
+
+ QPainter p(this);
+ p.setRenderHint(QPainter::Antialiasing);
+
+ if (_view.cursors_shown())
+ _view.cursors().draw_viewport_background(p, rect());
+
+ // Plot the signal
+ BOOST_FOREACH(const shared_ptr<Trace> t, traces)
+ {
+ assert(t);
+ t->paint_back(p, 0, width());
+ }
+
+ BOOST_FOREACH(const shared_ptr<Trace> t, traces)
+ t->paint_mid(p, 0, width());
+
+ BOOST_FOREACH(const shared_ptr<Trace> t, traces)
+ t->paint_fore(p, 0, width());
+
+ if (_view.cursors_shown())
+ _view.cursors().draw_viewport_foreground(p, rect());
+
+ p.end();
+}
+
+void Viewport::mousePressEvent(QMouseEvent *event)
+{
+ assert(event);
+
+ _mouse_down_point = event->pos();
+ _mouse_down_offset = _view.offset();
+}
+
+void Viewport::mouseMoveEvent(QMouseEvent *event)
+{
+ assert(event);
+
+ if (event->buttons() & Qt::LeftButton)
+ {
+ _view.set_scale_offset(_view.scale(),
+ _mouse_down_offset +
+ (_mouse_down_point - event->pos()).x() *
+ _view.scale());
+ }
+}
+
+void Viewport::mouseDoubleClickEvent(QMouseEvent *event)
+{
+ assert(event);
+
+ if (event->buttons() & Qt::LeftButton)
+ _view.zoom(2.0, event->x());
+ else if (event->buttons() & Qt::RightButton)
+ _view.zoom(-2.0, event->x());
+}
+
+void Viewport::wheelEvent(QWheelEvent *event)
+{
+ assert(event);
+
+ if (event->orientation() == Qt::Vertical) {
+ // Vertical scrolling is interpreted as zooming in/out
+ _view.zoom(event->delta() / 120, event->x());
+ } else if (event->orientation() == Qt::Horizontal) {
+ // Horizontal scrolling is interpreted as moving left/right
+ _view.set_scale_offset(_view.scale(),
+ event->delta() * _view.scale()
+ + _view.offset());
+ }
+}
+
+void Viewport::on_signals_changed()
+{
+ const vector< shared_ptr<Trace> > traces(_view.get_traces());
+ BOOST_FOREACH(shared_ptr<Trace> t, traces) {
+ assert(t);
+ connect(t.get(), SIGNAL(visibility_changed()),
+ this, SLOT(update()));
+ }
+}
+
+void Viewport::on_signals_moved()
+{
+ update();
+}
+
+} // namespace view
+} // namespace pv
diff --git a/pv/view/viewport.h b/pv/view/viewport.h
new file mode 100644
index 0000000..0474b7e
--- /dev/null
+++ b/pv/view/viewport.h
@@ -0,0 +1,68 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_VIEW_VIEWPORT_H
+#define PULSEVIEW_PV_VIEW_VIEWPORT_H
+
+#include <QTimer>
+#include <QWidget>
+
+class QPainter;
+class QPaintEvent;
+class SigSession;
+
+namespace pv {
+namespace view {
+
+class View;
+
+class Viewport : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit Viewport(View &parent);
+
+ int get_total_height() const;
+
+protected:
+ void paintEvent(QPaintEvent *event);
+
+private:
+ void mousePressEvent(QMouseEvent *event);
+ void mouseMoveEvent(QMouseEvent *event);
+ void mouseDoubleClickEvent(QMouseEvent * event);
+ void wheelEvent(QWheelEvent *event);
+
+private slots:
+ void on_signals_changed();
+ void on_signals_moved();
+
+private:
+ View &_view;
+
+ QPoint _mouse_down_point;
+ double _mouse_down_offset;
+};
+
+} // namespace view
+} // namespace pv
+
+#endif // PULSEVIEW_PV_VIEW_VIEWPORT_H
diff --git a/pv/widgets/colourbutton.cpp b/pv/widgets/colourbutton.cpp
new file mode 100644
index 0000000..fd417c1
--- /dev/null
+++ b/pv/widgets/colourbutton.cpp
@@ -0,0 +1,109 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "colourbutton.h"
+
+#include <assert.h>
+
+#include <QApplication>
+#include <QPainter>
+
+namespace pv {
+namespace widgets {
+
+const int ColourButton::SwatchMargin = 7;
+
+ColourButton::ColourButton(int rows, int cols, QWidget *parent) :
+ QPushButton("", parent),
+ _popup(rows, cols, this)
+{
+ connect(this, SIGNAL(clicked(bool)), this, SLOT(on_clicked(bool)));
+ connect(&_popup, SIGNAL(selected(int, int)),
+ this, SLOT(on_selected(int, int)));
+}
+
+ColourPopup& ColourButton::popup()
+{
+ return _popup;
+}
+
+const QColor& ColourButton::colour() const
+{
+ return _cur_colour;
+}
+
+void ColourButton::set_colour(QColor colour)
+{
+ _cur_colour = colour;
+
+ const unsigned int rows = _popup.well_array().numRows();
+ const unsigned int cols = _popup.well_array().numCols();
+
+ for (unsigned int r = 0; r < rows; r++)
+ for (unsigned int c = 0; c < cols; c++)
+ if (_popup.well_array().cellBrush(r, c).color() ==
+ colour)
+ {
+ _popup.well_array().setSelected(r, c);
+ _popup.well_array().setCurrent(r, c);
+ return;
+ }
+}
+
+void ColourButton::set_palette(const QColor *const palette)
+{
+ assert(palette);
+
+ const unsigned int rows = _popup.well_array().numRows();
+ const unsigned int cols = _popup.well_array().numCols();
+
+ for (unsigned int r = 0; r < rows; r++)
+ for (unsigned int c = 0; c < cols; c++)
+ _popup.well_array().setCellBrush(r, c,
+ QBrush(palette[r * cols + c]));
+}
+
+void ColourButton::on_clicked(bool)
+{
+ _popup.set_position(mapToGlobal(rect().center()), Popup::Bottom);
+ _popup.show();
+}
+
+void ColourButton::on_selected(int row, int col)
+{
+ _cur_colour = _popup.well_array().cellBrush(row, col).color();
+ selected(_cur_colour);
+}
+
+void ColourButton::paintEvent(QPaintEvent *e)
+{
+ QPushButton::paintEvent(e);
+
+ QPainter p(this);
+
+ const QRect r = rect().adjusted(SwatchMargin, SwatchMargin,
+ -SwatchMargin, -SwatchMargin);
+ p.setPen(QApplication::palette().color(QPalette::Dark));
+ p.setBrush(QBrush(_cur_colour));
+ p.drawRect(r);
+}
+
+} // widgets
+} // pv
diff --git a/pv/widgets/colourbutton.h b/pv/widgets/colourbutton.h
new file mode 100644
index 0000000..45a3fff
--- /dev/null
+++ b/pv/widgets/colourbutton.h
@@ -0,0 +1,68 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_WIDGETS_COLOURBUTTON_H
+#define PULSEVIEW_PV_WIDGETS_COLOURBUTTON_H
+
+#include <QPushButton>
+
+#include "colourpopup.h"
+
+namespace pv {
+namespace widgets {
+
+class ColourButton : public QPushButton
+{
+ Q_OBJECT;
+
+private:
+ static const int SwatchMargin;
+
+public:
+ ColourButton(int rows, int cols, QWidget *parent);
+
+ ColourPopup& popup();
+
+ const QColor& colour() const;
+
+ void set_colour(QColor colour);
+
+ void set_palette(const QColor *const palette);
+
+private:
+ void paintEvent(QPaintEvent *e);
+
+private slots:
+ void on_clicked(bool);
+
+ void on_selected(int row, int col);
+
+signals:
+ void selected(const QColor &colour);
+
+private:
+ ColourPopup _popup;
+ QColor _cur_colour;
+};
+
+} // widgets
+} // pv
+
+#endif // PULSEVIEW_PV_WIDGETS_COLOURBUTTON_H
diff --git a/pv/widgets/colourpopup.cpp b/pv/widgets/colourpopup.cpp
new file mode 100644
index 0000000..816a4c5
--- /dev/null
+++ b/pv/widgets/colourpopup.cpp
@@ -0,0 +1,51 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "colourpopup.h"
+
+namespace pv {
+namespace widgets {
+
+ColourPopup::ColourPopup(int rows, int cols, QWidget *parent) :
+ Popup(parent),
+ _well_array(rows, cols, this),
+ _layout(this)
+{
+ _layout.addWidget(&_well_array);
+ setLayout(&_layout);
+
+ connect(&_well_array, SIGNAL(selected(int, int)),
+ this, SIGNAL(selected(int, int)));
+ connect(&_well_array, SIGNAL(selected(int, int)),
+ this, SLOT(colour_selected(int, int)));
+}
+
+QWellArray& ColourPopup::well_array()
+{
+ return _well_array;
+}
+
+void ColourPopup::colour_selected(int, int)
+{
+ close();
+}
+
+} // widgets
+} // pv
diff --git a/pv/widgets/colourpopup.h b/pv/widgets/colourpopup.h
new file mode 100644
index 0000000..4f5c52e
--- /dev/null
+++ b/pv/widgets/colourpopup.h
@@ -0,0 +1,55 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_WIDGETS_COLOURPOPUP_H
+#define PULSEVIEW_PV_WIDGETS_COLOURPOPUP_H
+
+#include "popup.h"
+#include "wellarray.h"
+
+#include <QVBoxLayout>
+
+namespace pv {
+namespace widgets {
+
+class ColourPopup : public Popup
+{
+ Q_OBJECT
+
+public:
+ ColourPopup(int rows, int cols, QWidget *partent);
+
+ QWellArray& well_array();
+
+signals:
+ void selected(int row, int col);
+
+private slots:
+ void colour_selected(int, int);
+
+private:
+ QWellArray _well_array;
+ QVBoxLayout _layout;
+};
+
+} // widgets
+} // pv
+
+#endif // PULSEVIEW_PV_WIDGETS_COLOURPOPUP_H
diff --git a/pv/widgets/decodergroupbox.cpp b/pv/widgets/decodergroupbox.cpp
new file mode 100644
index 0000000..2cbd532
--- /dev/null
+++ b/pv/widgets/decodergroupbox.cpp
@@ -0,0 +1,77 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "decodergroupbox.h"
+
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QPushButton>
+#include <QVBoxLayout>
+
+#include <assert.h>
+
+namespace pv {
+namespace widgets {
+
+DecoderGroupBox::DecoderGroupBox(QString title, QWidget *parent) :
+ QWidget(parent),
+ _layout(new QGridLayout),
+ _show_hide_button(QIcon(":/icons/decoder-shown.svg"), QString(), this)
+{
+ _layout->setContentsMargins(0, 0, 0, 0);
+ setLayout(_layout);
+
+ _layout->addWidget(new QLabel(QString("<h3>%1</h3>").arg(title)),
+ 0, 0);
+ _layout->setColumnStretch(0, 1);
+
+ QHBoxLayout *const toolbar = new QHBoxLayout;
+ _layout->addLayout(toolbar, 0, 1);
+
+ _show_hide_button.setFlat(true);
+ _show_hide_button.setIconSize(QSize(16, 16));
+ connect(&_show_hide_button, SIGNAL(clicked()),
+ this, SIGNAL(show_hide_decoder()));
+ toolbar->addWidget(&_show_hide_button);
+
+ QPushButton *const delete_button = new QPushButton(
+ QIcon(":/icons/decoder-delete.svg"), QString(), this);
+ delete_button->setFlat(true);
+ delete_button->setIconSize(QSize(16, 16));
+ connect(delete_button, SIGNAL(clicked()),
+ this, SIGNAL(delete_decoder()));
+ toolbar->addWidget(delete_button);
+}
+
+void DecoderGroupBox::add_layout(QLayout *layout)
+{
+ assert(layout);
+ _layout->addLayout(layout, 1, 0, 1, 2);
+}
+
+void DecoderGroupBox::set_decoder_visible(bool visible)
+{
+ _show_hide_button.setIcon(QIcon(visible ?
+ ":/icons/decoder-shown.svg" :
+ ":/icons/decoder-hidden.svg"));
+}
+
+} // widgets
+} // pv
diff --git a/pv/widgets/decodergroupbox.h b/pv/widgets/decodergroupbox.h
new file mode 100644
index 0000000..2302a97
--- /dev/null
+++ b/pv/widgets/decodergroupbox.h
@@ -0,0 +1,56 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_WIDGETS_DECODERGROUPBOX_H
+#define PULSEVIEW_PV_WIDGETS_DECODERGROUPBOX_H
+
+#include <QPushButton>
+
+class QGridLayout;
+class QToolBar;
+
+namespace pv {
+namespace widgets {
+
+class DecoderGroupBox : public QWidget
+{
+ Q_OBJECT
+
+public:
+ DecoderGroupBox(QString title, QWidget *parent = NULL);
+
+ void add_layout(QLayout *layout);
+
+ void set_decoder_visible(bool visible);
+
+signals:
+ void delete_decoder();
+
+ void show_hide_decoder();
+
+private:
+ QGridLayout *const _layout;
+ QPushButton _show_hide_button;
+};
+
+} // widgets
+} // pv
+
+#endif // PULSEVIEW_PV_WIDGETS_DECODERGROUPBOX_H
diff --git a/pv/widgets/decodermenu.cpp b/pv/widgets/decodermenu.cpp
new file mode 100644
index 0000000..2f828a2
--- /dev/null
+++ b/pv/widgets/decodermenu.cpp
@@ -0,0 +1,72 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <libsigrokdecode/libsigrokdecode.h>
+
+#include "decodermenu.h"
+
+namespace pv {
+namespace widgets {
+
+DecoderMenu::DecoderMenu(QWidget *parent, bool first_level_decoder) :
+ QMenu(parent),
+ _mapper(this)
+{
+ GSList *l = g_slist_sort(g_slist_copy(
+ (GSList*)srd_decoder_list()), decoder_name_cmp);
+ for(; l; l = l->next)
+ {
+ const srd_decoder *const d = (srd_decoder*)l->data;
+ assert(d);
+
+ const bool have_probes = (d->channels || d->opt_channels) != 0;
+ if (first_level_decoder == have_probes) {
+ QAction *const action =
+ addAction(QString::fromUtf8(d->name));
+ action->setData(qVariantFromValue(l->data));
+ _mapper.setMapping(action, action);
+ connect(action, SIGNAL(triggered()),
+ &_mapper, SLOT(map()));
+ }
+ }
+ g_slist_free(l);
+
+ connect(&_mapper, SIGNAL(mapped(QObject*)),
+ this, SLOT(on_action(QObject*)));
+}
+
+int DecoderMenu::decoder_name_cmp(const void *a, const void *b)
+{
+ return strcmp(((const srd_decoder*)a)->name,
+ ((const srd_decoder*)b)->name);
+}
+
+void DecoderMenu::on_action(QObject *action)
+{
+ assert(action);
+ srd_decoder *const dec =
+ (srd_decoder*)((QAction*)action)->data().value<void*>();
+ assert(dec);
+
+ decoder_selected(dec);
+}
+
+} // widgets
+} // pv
diff --git a/pv/widgets/decodermenu.h b/pv/widgets/decodermenu.h
new file mode 100644
index 0000000..20dd412
--- /dev/null
+++ b/pv/widgets/decodermenu.h
@@ -0,0 +1,56 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_WIDGETS_DECODERMENU_H
+#define PULSEVIEW_PV_WIDGETS_DECODERMENU_H
+
+#include <QMenu>
+#include <QSignalMapper>
+
+struct srd_decoder;
+
+namespace pv {
+namespace widgets {
+
+class DecoderMenu : public QMenu
+{
+ Q_OBJECT;
+
+public:
+ DecoderMenu(QWidget *parent, bool first_level_decoder = false);
+
+private:
+ static int decoder_name_cmp(const void *a, const void *b);
+
+
+private slots:
+ void on_action(QObject *action);
+
+signals:
+ void decoder_selected(srd_decoder *decoder);
+
+private:
+ QSignalMapper _mapper;
+};
+
+} // widgets
+} // pv
+
+#endif // PULSEVIEW_PV_WIDGETS_DECODERMENU_H
diff --git a/pv/widgets/popup.cpp b/pv/widgets/popup.cpp
new file mode 100644
index 0000000..6857358
--- /dev/null
+++ b/pv/widgets/popup.cpp
@@ -0,0 +1,269 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <algorithm>
+
+#include <assert.h>
+
+#include <QtGui>
+
+#include "popup.h"
+
+using std::max;
+using std::min;
+
+namespace pv {
+namespace widgets {
+
+const unsigned int Popup::ArrowLength = 10;
+const unsigned int Popup::ArrowOverlap = 3;
+const unsigned int Popup::MarginWidth = 6;
+
+Popup::Popup(QWidget *parent) :
+ QWidget(parent, Qt::Popup | Qt::FramelessWindowHint),
+ _point(),
+ _pos(Left)
+{
+}
+
+const QPoint& Popup::point() const
+{
+ return _point;
+}
+
+Popup::Position Popup::position() const
+{
+ return _pos;
+}
+
+void Popup::set_position(const QPoint point, Position pos)
+{
+ _point = point, _pos = pos;
+
+ setContentsMargins(
+ MarginWidth + ((pos == Right) ? ArrowLength : 0),
+ MarginWidth + ((pos == Bottom) ? ArrowLength : 0),
+ MarginWidth + ((pos == Left) ? ArrowLength : 0),
+ MarginWidth + ((pos == Top) ? ArrowLength : 0));
+
+}
+
+bool Popup::space_for_arrow() const
+{
+ // Check if there is room for the arrow
+ switch (_pos) {
+ case Right:
+ if (_point.x() > x())
+ return false;
+ return true;
+
+ case Bottom:
+ if (_point.y() > y())
+ return false;
+ return true;
+
+ case Left:
+ if (_point.x() < (x() + width()))
+ return false;
+ return true;
+
+ case Top:
+ if (_point.y() < (y() + height()))
+ return false;
+ return true;
+ }
+
+ return true;
+}
+
+QPolygon Popup::arrow_polygon() const
+{
+ QPolygon poly;
+
+ const QPoint p = mapFromGlobal(_point);
+ const int l = ArrowLength + ArrowOverlap;
+
+ switch (_pos)
+ {
+ case Right:
+ poly << QPoint(p.x() + l, p.y() - l);
+ break;
+
+ case Bottom:
+ poly << QPoint(p.x() - l, p.y() + l);
+ break;
+
+ case Left:
+ case Top:
+ poly << QPoint(p.x() - l, p.y() - l);
+ break;
+ }
+
+ poly << p;
+
+ switch (_pos)
+ {
+ case Right:
+ case Bottom:
+ poly << QPoint(p.x() + l, p.y() + l);
+ break;
+
+ case Left:
+ poly << QPoint(p.x() - l, p.y() + l);
+ break;
+
+ case Top:
+ poly << QPoint(p.x() + l, p.y() - l);
+ break;
+ }
+
+ return poly;
+}
+
+QRegion Popup::arrow_region() const
+{
+ return QRegion(arrow_polygon());
+}
+
+QRect Popup::bubble_rect() const
+{
+ return QRect(
+ QPoint((_pos == Right) ? ArrowLength : 0,
+ (_pos == Bottom) ? ArrowLength : 0),
+ QSize(width() - ((_pos == Left || _pos == Right) ?
+ ArrowLength : 0),
+ height() - ((_pos == Top || _pos == Bottom) ?
+ ArrowLength : 0)));
+}
+
+QRegion Popup::bubble_region() const
+{
+ const QRect rect(bubble_rect());
+
+ const unsigned int r = MarginWidth;
+ const unsigned int d = 2 * r;
+ return QRegion(rect.adjusted(r, 0, -r, 0)).united(
+ QRegion(rect.adjusted(0, r, 0, -r))).united(
+ QRegion(rect.left(), rect.top(), d, d,
+ QRegion::Ellipse)).united(
+ QRegion(rect.right() - d, rect.top(), d, d,
+ QRegion::Ellipse)).united(
+ QRegion(rect.left(), rect.bottom() - d, d, d,
+ QRegion::Ellipse)).united(
+ QRegion(rect.right() - d, rect.bottom() - d, d, d,
+ QRegion::Ellipse));
+}
+
+QRegion Popup::popup_region() const
+{
+ if (space_for_arrow())
+ return arrow_region().united(bubble_region());
+ else
+ return bubble_region();
+}
+
+void Popup::reposition_widget()
+{
+ QPoint o;
+
+ const QRect screen_rect = QApplication::desktop()->availableGeometry(
+ QApplication::desktop()->screenNumber(_point));
+
+ if (_pos == Right || _pos == Left)
+ o.ry() = -height() / 2;
+ else
+ o.rx() = -width() / 2;
+
+ if (_pos == Left)
+ o.rx() = -width();
+ else if(_pos == Top)
+ o.ry() = -height();
+
+ o += _point;
+ move(max(min(o.x(), screen_rect.right() - width()),
+ screen_rect.left()),
+ max(min(o.y(), screen_rect.bottom() - height()),
+ screen_rect.top()));
+}
+
+void Popup::closeEvent(QCloseEvent*)
+{
+ closed();
+}
+
+void Popup::paintEvent(QPaintEvent*)
+{
+ QPainter painter(this);
+ painter.setRenderHint(QPainter::Antialiasing);
+
+ const QColor outline_color(QApplication::palette().color(
+ QPalette::Dark));
+
+ // Draw the bubble
+ const QRegion b = bubble_region();
+ const QRegion bubble_outline = QRegion(rect()).subtracted(
+ b.translated(1, 0).intersected(b.translated(0, 1).intersected(
+ b.translated(-1, 0).intersected(b.translated(0, -1)))));
+
+ painter.setPen(Qt::NoPen);
+ painter.setBrush(QApplication::palette().brush(QPalette::Window));
+ painter.drawRect(rect());
+
+ // Draw the arrow
+ if (!space_for_arrow())
+ return;
+
+ const QPoint ArrowOffsets[] = {
+ QPoint(1, 0), QPoint(0, -1), QPoint(-1, 0), QPoint(0, 1)};
+
+ const QRegion a(arrow_region());
+ const QRegion arrow_outline = a.subtracted(
+ a.translated(ArrowOffsets[_pos]));
+
+ painter.setClipRegion(bubble_outline.subtracted(a).united(
+ arrow_outline));
+ painter.setBrush(outline_color);
+ painter.drawRect(rect());
+}
+
+void Popup::resizeEvent(QResizeEvent*)
+{
+ reposition_widget();
+ setMask(popup_region());
+}
+
+void Popup::mouseReleaseEvent(QMouseEvent *e)
+{
+ assert(e);
+
+ // We need our own out-of-bounds click handler because QWidget counts
+ // the drop-shadow region as inside the widget
+ if(!bubble_rect().contains(e->pos()))
+ close();
+}
+
+void Popup::showEvent(QShowEvent*)
+{
+ reposition_widget();
+}
+
+} // namespace widgets
+} // namespace pv
+
diff --git a/pv/widgets/popup.h b/pv/widgets/popup.h
new file mode 100644
index 0000000..0bc6d0f
--- /dev/null
+++ b/pv/widgets/popup.h
@@ -0,0 +1,93 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_WIDGETS_POPUP_H
+#define PULSEVIEW_PV_WIDGETS_POPUP_H
+
+#include <QWidget>
+
+namespace pv {
+namespace widgets {
+
+class Popup : public QWidget
+{
+ Q_OBJECT
+
+public:
+ enum Position
+ {
+ Right,
+ Top,
+ Left,
+ Bottom
+ };
+
+private:
+ static const unsigned int ArrowLength;
+ static const unsigned int ArrowOverlap;
+ static const unsigned int MarginWidth;
+
+public:
+ Popup(QWidget *parent);
+
+ const QPoint& point() const;
+ Position position() const;
+
+ void set_position(const QPoint point, Position pos);
+
+private:
+ bool space_for_arrow() const;
+
+ QPolygon arrow_polygon() const;
+
+ QRegion arrow_region() const;
+
+ QRect bubble_rect() const;
+
+ QRegion bubble_region() const;
+
+ QRegion popup_region() const;
+
+ void reposition_widget();
+
+private:
+ void closeEvent(QCloseEvent*);
+
+ void paintEvent(QPaintEvent*);
+
+ void resizeEvent(QResizeEvent*);
+
+ void mouseReleaseEvent(QMouseEvent *e);
+
+protected:
+ void showEvent(QShowEvent *e);
+
+signals:
+ void closed();
+
+private:
+ QPoint _point;
+ Position _pos;
+};
+
+} // namespace widgets
+} // namespace pv
+
+#endif // PULSEVIEW_PV_WIDGETS_POPUP_H
diff --git a/pv/widgets/popuptoolbutton.cpp b/pv/widgets/popuptoolbutton.cpp
new file mode 100644
index 0000000..74743ed
--- /dev/null
+++ b/pv/widgets/popuptoolbutton.cpp
@@ -0,0 +1,58 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <assert.h>
+
+#include "popuptoolbutton.h"
+
+namespace pv {
+namespace widgets {
+
+PopupToolButton::PopupToolButton(QWidget *parent) :
+ QToolButton(parent),
+ _popup(NULL)
+{
+ connect(this, SIGNAL(clicked(bool)), this, SLOT(on_clicked(bool)));
+}
+
+Popup* PopupToolButton::popup() const
+{
+ return _popup;
+}
+
+void PopupToolButton::set_popup(Popup *popup)
+{
+ assert(popup);
+ _popup = popup;
+}
+
+void PopupToolButton::on_clicked(bool)
+{
+ if(!_popup)
+ return;
+
+ const QRect r = rect();
+ _popup->set_position(mapToGlobal(QPoint((r.left() + r.right()) / 2,
+ ((r.top() + r.bottom() * 3) / 4))), Popup::Bottom);
+ _popup->show();
+}
+
+} // widgets
+} // pv
diff --git a/pv/widgets/popuptoolbutton.h b/pv/widgets/popuptoolbutton.h
new file mode 100644
index 0000000..b619b6f
--- /dev/null
+++ b/pv/widgets/popuptoolbutton.h
@@ -0,0 +1,52 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_WIDGETS_POPUPTOOLBUTTON_H
+#define PULSEVIEW_PV_WIDGETS_POPUPTOOLBUTTON_H
+
+#include "popup.h"
+
+#include <QToolButton>
+
+namespace pv {
+namespace widgets {
+
+class PopupToolButton : public QToolButton
+{
+ Q_OBJECT;
+
+public:
+ PopupToolButton(QWidget *parent);
+
+ Popup* popup() const;
+
+ void set_popup(Popup *popup);
+
+private slots:
+ void on_clicked(bool);
+
+private:
+ Popup *_popup;
+};
+
+} // widgets
+} // pv
+
+#endif // PULSEVIEW_PV_WIDGETS_POPUPTOOLBUTTON_H
diff --git a/pv/widgets/sweeptimingwidget.cpp b/pv/widgets/sweeptimingwidget.cpp
new file mode 100644
index 0000000..00ebb76
--- /dev/null
+++ b/pv/widgets/sweeptimingwidget.cpp
@@ -0,0 +1,184 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "sweeptimingwidget.h"
+
+#include <vector>
+
+#include <assert.h>
+
+#include <extdef.h>
+
+using std::vector;
+
+namespace pv {
+namespace widgets {
+
+SweepTimingWidget::SweepTimingWidget(const char *suffix,
+ QWidget *parent) :
+ QWidget(parent),
+ _suffix(suffix),
+ _layout(this),
+ _value(this),
+ _list(this),
+ _value_type(None)
+{
+ setContentsMargins(0, 0, 0, 0);
+
+ _value.setDecimals(0);
+ _value.setSuffix(QString::fromUtf8(suffix));
+
+ connect(&_list, SIGNAL(currentIndexChanged(int)),
+ this, SIGNAL(value_changed()));
+ connect(&_value, SIGNAL(editingFinished()),
+ this, SIGNAL(value_changed()));
+
+ setLayout(&_layout);
+ _layout.setMargin(0);
+ _layout.addWidget(&_list);
+ _layout.addWidget(&_value);
+
+ show_none();
+}
+
+void SweepTimingWidget::show_none()
+{
+ _value_type = None;
+ _value.hide();
+ _list.hide();
+}
+
+void SweepTimingWidget::show_min_max_step(uint64_t min, uint64_t max,
+ uint64_t step)
+{
+ assert(max > min);
+ assert(step > 0);
+
+ _value_type = MinMaxStep;
+
+ _value.setRange(min, max);
+ _value.setSingleStep(step);
+
+ _value.show();
+ _list.hide();
+}
+
+void SweepTimingWidget::show_list(const uint64_t *vals, size_t count)
+{
+ _value_type = List;
+
+ _list.clear();
+ for (size_t i = 0; i < count; i++)
+ {
+ char *const s = sr_si_string_u64(vals[i], _suffix);
+ _list.addItem(QString::fromUtf8(s),
+ qVariantFromValue(vals[i]));
+ g_free(s);
+ }
+
+ _value.hide();
+ _list.show();
+}
+
+void SweepTimingWidget::show_125_list(uint64_t min, uint64_t max)
+{
+ assert(max > min);
+
+ // Create a 1-2-5-10 list of entries.
+ const unsigned int FineScales[] = {1, 2, 5};
+ uint64_t value, decade;
+ unsigned int fine;
+ vector<uint64_t> values;
+
+ // Compute the starting decade
+ for (decade = 1; decade * 10 <= min; decade *= 10);
+
+ // Compute the first entry
+ for (fine = 0; fine < countof(FineScales); fine++)
+ if (FineScales[fine] * decade >= min)
+ break;
+
+ assert(fine < countof(FineScales));
+
+ // Add the minimum entry if it's not on the 1-2-5 progression
+ if (min != FineScales[fine] * decade)
+ values.push_back(min);
+
+ while ((value = FineScales[fine] * decade) < max) {
+ values.push_back(value);
+ if (++fine >= countof(FineScales))
+ fine = 0, decade *= 10;
+ }
+
+ // Add the max value
+ values.push_back(max);
+
+ // Make a C array, and give it to the sweep timing widget
+ uint64_t *const values_array = new uint64_t[values.size()];
+ copy(values.begin(), values.end(), values_array);
+ show_list(values_array, values.size());
+ delete[] values_array;
+}
+
+uint64_t SweepTimingWidget::value() const
+{
+ switch(_value_type)
+ {
+ case None:
+ return 0;
+
+ case MinMaxStep:
+ return (uint64_t)_value.value();
+
+ case List:
+ {
+ const int index = _list.currentIndex();
+ return (index >= 0) ? _list.itemData(
+ index).value<uint64_t>() : 0;
+ }
+
+ default:
+ // Unexpected value type
+ assert(0);
+ return 0;
+ }
+}
+
+void SweepTimingWidget::set_value(uint64_t value)
+{
+ _value.setValue(value);
+
+ int best_match = _list.count() - 1;
+ int64_t best_variance = INT64_MAX;
+
+ for (int i = 0; i < _list.count(); i++) {
+ const int64_t this_variance = abs(
+ (int64_t)value - _list.itemData(i).value<int64_t>());
+ if (this_variance < best_variance) {
+ best_variance = this_variance;
+ best_match = i;
+ }
+ }
+
+ _list.setCurrentIndex(best_match);
+}
+
+} // widgets
+} // pv
diff --git a/pv/widgets/sweeptimingwidget.h b/pv/widgets/sweeptimingwidget.h
new file mode 100644
index 0000000..b66188f
--- /dev/null
+++ b/pv/widgets/sweeptimingwidget.h
@@ -0,0 +1,75 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PULSEVIEW_PV_WIDGETS_SWEEPTIMINGWIDGET_H
+#define PULSEVIEW_PV_WIDGETS_SWEEPTIMINGWIDGET_H
+
+#include <libsigrok/libsigrok.h>
+
+#include <QComboBox>
+#include <QDoubleSpinBox>
+#include <QHBoxLayout>
+#include <QLineEdit>
+#include <QWidget>
+
+namespace pv {
+namespace widgets {
+
+class SweepTimingWidget : public QWidget
+{
+ Q_OBJECT
+
+private:
+ enum ValueType
+ {
+ None,
+ MinMaxStep,
+ List
+ };
+
+public:
+ SweepTimingWidget(const char *suffix, QWidget *parent = NULL);
+
+ void show_none();
+ void show_min_max_step(uint64_t min, uint64_t max, uint64_t step);
+ void show_list(const uint64_t *vals, size_t count);
+ void show_125_list(uint64_t min, uint64_t max);
+
+ uint64_t value() const;
+ void set_value(uint64_t value);
+
+signals:
+ void value_changed();
+
+private:
+ const char *const _suffix;
+
+ QHBoxLayout _layout;
+
+ QDoubleSpinBox _value;
+ QComboBox _list;
+
+ ValueType _value_type;
+};
+
+} // widgets
+} // pv
+
+#endif // PULSEVIEW_PV_WIDGETS_SWEEPTIMINGWIDGET_H
diff --git a/pv/widgets/wellarray.cpp b/pv/widgets/wellarray.cpp
new file mode 100644
index 0000000..dcc3b41
--- /dev/null
+++ b/pv/widgets/wellarray.cpp
@@ -0,0 +1,290 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QPainter>
+#include <QPaintEvent>
+#include <QStyle>
+#include <QStyleOptionFrame>
+
+#include "wellarray.h"
+
+void QWellArray::paintEvent(QPaintEvent *e)
+{
+ QRect r = e->rect();
+ int cx = r.x();
+ int cy = r.y();
+ int ch = r.height();
+ int cw = r.width();
+ int colfirst = columnAt(cx);
+ int collast = columnAt(cx + cw);
+ int rowfirst = rowAt(cy);
+ int rowlast = rowAt(cy + ch);
+
+ if (isRightToLeft()) {
+ int t = colfirst;
+ colfirst = collast;
+ collast = t;
+ }
+
+ QPainter painter(this);
+ QPainter *p = &painter;
+ QRect rect(0, 0, cellWidth(), cellHeight());
+
+
+ if (collast < 0 || collast >= ncols)
+ collast = ncols-1;
+ if (rowlast < 0 || rowlast >= nrows)
+ rowlast = nrows-1;
+
+ // Go through the rows
+ for (int r = rowfirst; r <= rowlast; ++r) {
+ // get row position and height
+ int rowp = rowY(r);
+
+ // Go through the columns in the row r
+ // if we know from where to where, go through [colfirst, collast],
+ // else go through all of them
+ for (int c = colfirst; c <= collast; ++c) {
+ // get position and width of column c
+ int colp = columnX(c);
+ // Translate painter and draw the cell
+ rect.translate(colp, rowp);
+ paintCell(p, r, c, rect);
+ rect.translate(-colp, -rowp);
+ }
+ }
+}
+
+struct QWellArrayData {
+ QBrush *brush;
+};
+
+QWellArray::QWellArray(int rows, int cols, QWidget *parent)
+ : QWidget(parent)
+ ,nrows(rows), ncols(cols)
+{
+ d = 0;
+ setFocusPolicy(Qt::StrongFocus);
+ cellw = 28;
+ cellh = 24;
+ curCol = 0;
+ curRow = 0;
+ selCol = -1;
+ selRow = -1;
+}
+
+QSize QWellArray::sizeHint() const
+{
+ ensurePolished();
+ return gridSize().boundedTo(QSize(640, 480));
+}
+
+
+void QWellArray::paintCell(QPainter* p, int row, int col, const QRect &rect)
+{
+ int b = 3; //margin
+
+ const QPalette & g = palette();
+ QStyleOptionFrame opt;
+ int dfw = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
+ opt.lineWidth = dfw;
+ opt.midLineWidth = 1;
+ opt.rect = rect.adjusted(b, b, -b, -b);
+ opt.palette = g;
+ opt.state = QStyle::State_Enabled | QStyle::State_Sunken;
+ style()->drawPrimitive(QStyle::PE_Frame, &opt, p, this);
+ b += dfw;
+
+ if ((row == curRow) && (col == curCol)) {
+ if (hasFocus()) {
+ QStyleOptionFocusRect opt;
+ opt.palette = g;
+ opt.rect = rect;
+ opt.state = QStyle::State_None | QStyle::State_KeyboardFocusChange;
+ style()->drawPrimitive(QStyle::PE_FrameFocusRect, &opt, p, this);
+ }
+ }
+ paintCellContents(p, row, col, opt.rect.adjusted(dfw, dfw, -dfw, -dfw));
+}
+
+/*!
+ Reimplement this function to change the contents of the well array.
+ */
+void QWellArray::paintCellContents(QPainter *p, int row, int col, const QRect &r)
+{
+ if (d) {
+ p->fillRect(r, d->brush[row*numCols()+col]);
+ } else {
+ p->fillRect(r, Qt::white);
+ p->setPen(Qt::black);
+ p->drawLine(r.topLeft(), r.bottomRight());
+ p->drawLine(r.topRight(), r.bottomLeft());
+ }
+}
+
+void QWellArray::mousePressEvent(QMouseEvent *e)
+{
+ // The current cell marker is set to the cell the mouse is pressed in
+ QPoint pos = e->pos();
+ setCurrent(rowAt(pos.y()), columnAt(pos.x()));
+}
+
+void QWellArray::mouseReleaseEvent(QMouseEvent * /* event */)
+{
+ // The current cell marker is set to the cell the mouse is clicked in
+ setSelected(curRow, curCol);
+}
+
+
+/*
+ Sets the cell currently having the focus. This is not necessarily
+ the same as the currently selected cell.
+*/
+
+void QWellArray::setCurrent(int row, int col)
+{
+ if ((curRow == row) && (curCol == col))
+ return;
+
+ if (row < 0 || col < 0)
+ row = col = -1;
+
+ int oldRow = curRow;
+ int oldCol = curCol;
+
+ curRow = row;
+ curCol = col;
+
+ updateCell(oldRow, oldCol);
+ updateCell(curRow, curCol);
+}
+
+/*
+ Sets the currently selected cell to \a row, \a column. If \a row or
+ \a column are less than zero, the current cell is unselected.
+
+ Does not set the position of the focus indicator.
+*/
+void QWellArray::setSelected(int row, int col)
+{
+ int oldRow = selRow;
+ int oldCol = selCol;
+
+ if (row < 0 || col < 0)
+ row = col = -1;
+
+ selCol = col;
+ selRow = row;
+
+ updateCell(oldRow, oldCol);
+ updateCell(selRow, selCol);
+ if (row >= 0)
+ emit selected(row, col);
+}
+
+void QWellArray::focusInEvent(QFocusEvent*)
+{
+ updateCell(curRow, curCol);
+}
+
+void QWellArray::setCellBrush(int row, int col, const QBrush &b)
+{
+ if (!d) {
+ d = new QWellArrayData;
+ int i = numRows()*numCols();
+ d->brush = new QBrush[i];
+ }
+ if (row >= 0 && row < numRows() && col >= 0 && col < numCols())
+ d->brush[row*numCols()+col] = b;
+}
+
+/*
+ Returns the brush set for the cell at \a row, \a column. If no brush is
+ set, Qt::NoBrush is returned.
+*/
+
+QBrush QWellArray::cellBrush(int row, int col)
+{
+ if (d && row >= 0 && row < numRows() && col >= 0 && col < numCols())
+ return d->brush[row*numCols()+col];
+ return Qt::NoBrush;
+}
+
+
+
+/*!\reimp
+*/
+
+void QWellArray::focusOutEvent(QFocusEvent*)
+{
+ updateCell(curRow, curCol);
+}
+
+/*\reimp
+*/
+void QWellArray::keyPressEvent(QKeyEvent* e)
+{
+ switch(e->key()) { // Look at the key code
+ case Qt::Key_Left: // If 'left arrow'-key,
+ if(curCol > 0) // and cr't not in leftmost col
+ setCurrent(curRow, curCol - 1); // set cr't to next left column
+ break;
+ case Qt::Key_Right: // Correspondingly...
+ if(curCol < numCols()-1)
+ setCurrent(curRow, curCol + 1);
+ break;
+ case Qt::Key_Up:
+ if(curRow > 0)
+ setCurrent(curRow - 1, curCol);
+ break;
+ case Qt::Key_Down:
+ if(curRow < numRows()-1)
+ setCurrent(curRow + 1, curCol);
+ break;
+ case Qt::Key_Space:
+ setSelected(curRow, curCol);
+ break;
+ default: // If not an interesting key,
+ e->ignore(); // we don't accept the event
+ return;
+ }
+
+}
diff --git a/pv/widgets/wellarray.h b/pv/widgets/wellarray.h
new file mode 100644
index 0000000..dad088c
--- /dev/null
+++ b/pv/widgets/wellarray.h
@@ -0,0 +1,133 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QWidget>
+
+struct QWellArrayData;
+
+class QWellArray : public QWidget
+{
+ Q_OBJECT
+ Q_PROPERTY(int selectedColumn READ selectedColumn)
+ Q_PROPERTY(int selectedRow READ selectedRow)
+
+public:
+ QWellArray(int rows, int cols, QWidget* parent=0);
+ QString cellContent(int row, int col) const;
+
+ int selectedColumn() const { return selCol; }
+ int selectedRow() const { return selRow; }
+
+ virtual void setCurrent(int row, int col);
+ virtual void setSelected(int row, int col);
+
+ QSize sizeHint() const;
+
+ virtual void setCellBrush(int row, int col, const QBrush &);
+ QBrush cellBrush(int row, int col);
+
+ inline int cellWidth() const
+ { return cellw; }
+
+ inline int cellHeight() const
+ { return cellh; }
+
+ inline int rowAt(int y) const
+ { return y / cellh; }
+
+ inline int columnAt(int x) const
+ { if (isRightToLeft()) return ncols - (x / cellw) - 1; return x / cellw; }
+
+ inline int rowY(int row) const
+ { return cellh * row; }
+
+ inline int columnX(int column) const
+ { if (isRightToLeft()) return cellw * (ncols - column - 1); return cellw * column; }
+
+ inline int numRows() const
+ { return nrows; }
+
+ inline int numCols() const
+ {return ncols; }
+
+ inline QRect cellRect() const
+ { return QRect(0, 0, cellw, cellh); }
+
+ inline QSize gridSize() const
+ { return QSize(ncols * cellw, nrows * cellh); }
+
+ QRect cellGeometry(int row, int column)
+ {
+ QRect r;
+ if (row >= 0 && row < nrows && column >= 0 && column < ncols)
+ r.setRect(columnX(column), rowY(row), cellw, cellh);
+ return r;
+ }
+
+ inline void updateCell(int row, int column) { update(cellGeometry(row, column)); }
+
+signals:
+ void selected(int row, int col);
+
+protected:
+ virtual void paintCell(QPainter *, int row, int col, const QRect&);
+ virtual void paintCellContents(QPainter *, int row, int col, const QRect&);
+
+ void mousePressEvent(QMouseEvent*);
+ void mouseReleaseEvent(QMouseEvent*);
+ void keyPressEvent(QKeyEvent*);
+ void focusInEvent(QFocusEvent*);
+ void focusOutEvent(QFocusEvent*);
+ void paintEvent(QPaintEvent *);
+
+private:
+ Q_DISABLE_COPY(QWellArray)
+
+ int nrows;
+ int ncols;
+ int cellw;
+ int cellh;
+ int curRow;
+ int curCol;
+ int selRow;
+ int selCol;
+ QWellArrayData *d;
+};
diff --git a/signalhandler.cpp b/signalhandler.cpp
new file mode 100644
index 0000000..8c9244b
--- /dev/null
+++ b/signalhandler.cpp
@@ -0,0 +1,95 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Adam Reichold
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "signalhandler.h"
+
+#include <assert.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <QDebug>
+#include <QSocketNotifier>
+
+int SignalHandler::_sockets[2];
+
+bool SignalHandler::prepare_signals()
+{
+ if(socketpair(AF_UNIX, SOCK_STREAM, 0, _sockets) != 0)
+ return false;
+
+ struct sigaction sig_action;
+
+ sig_action.sa_handler = SignalHandler::handle_signals;
+ sigemptyset(&sig_action.sa_mask);
+ sig_action.sa_flags = SA_RESTART;
+
+ if(sigaction(SIGINT, &sig_action, 0) != 0 ||
+ sigaction(SIGTERM, &sig_action, 0) != 0) {
+ close(_sockets[0]);
+ close(_sockets[1]);
+ return false;
+ }
+
+ return true;
+}
+
+SignalHandler::SignalHandler(QObject* parent) : QObject(parent),
+ _socket_notifier(0)
+{
+ _socket_notifier = new QSocketNotifier(_sockets[1],
+ QSocketNotifier::Read, this);
+ connect(_socket_notifier, SIGNAL(activated(int)),
+ SLOT(on_socket_notifier_activated()));
+}
+
+void SignalHandler::on_socket_notifier_activated()
+{
+ _socket_notifier->setEnabled(false);
+
+ int sig_number;
+ if(read(_sockets[1], &sig_number, sizeof(int)) !=
+ sizeof(int)) {
+ qDebug() << "Failed to catch signal";
+ abort();
+ }
+
+ switch(sig_number)
+ {
+ case SIGINT:
+ emit int_received();
+ break;
+ case SIGTERM:
+ emit term_received();
+ break;
+ }
+
+ _socket_notifier->setEnabled(true);
+}
+
+void SignalHandler::handle_signals(int sig_number)
+{
+ if(write(_sockets[0], &sig_number, sizeof(int)) !=
+ sizeof(int)) {
+ // Failed to handle signal
+ abort();
+ }
+}
diff --git a/signalhandler.h b/signalhandler.h
new file mode 100644
index 0000000..7ecd612
--- /dev/null
+++ b/signalhandler.h
@@ -0,0 +1,55 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Adam Reichold
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef SIGNALHANDLER_H
+#define SIGNALHANDLER_H
+
+#include <QObject>
+
+class QSocketNotifier;
+
+class SignalHandler : public QObject
+{
+ Q_OBJECT
+
+public:
+ static bool prepare_signals();
+
+public:
+ explicit SignalHandler(QObject* parent = NULL);
+
+signals:
+ void int_received();
+ void term_received();
+
+private slots:
+ void on_socket_notifier_activated();
+
+private:
+ static void handle_signals(int sig_number);
+
+private:
+ QSocketNotifier* _socket_notifier;
+
+private:
+ static int _sockets[2];
+};
+
+#endif // SIGNALHANDLER_H
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
new file mode 100644
index 0000000..0ea69f0
--- /dev/null
+++ b/test/CMakeLists.txt
@@ -0,0 +1,176 @@
+##
+## This file is part of the PulseView project.
+##
+## Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+## Copyright (C) 2012 Alexandru Gagniuc <mr.nuke.me at gmail.com>
+##
+## This program is free software: you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+##
+
+option(ENABLE_DECODE "Build with libsigrokdecode" FALSE)
+
+list(APPEND PKGDEPS libsigrok>=0.3.0)
+
+if(ENABLE_DECODE)
+ list(APPEND PKGDEPS libsigrokdecode>=0.3.0)
+endif()
+
+find_package(PkgConfig)
+pkg_check_modules(PKGDEPS REQUIRED ${PKGDEPS})
+
+# Find the platform's thread library (needed for boost-thread).
+# This will set ${CMAKE_THREAD_LIBS_INIT} to the correct, OS-specific value.
+find_package(Threads)
+
+if(WIN32)
+ # On Windows/MinGW we need to use 'thread_win32' instead of 'thread'.
+ # The library is named libboost_thread_win32* (not libboost_thread*).
+ find_package(Boost 1.42 COMPONENTS filesystem system thread_win32 unit_test_framework REQUIRED)
+else()
+ find_package(Boost 1.42 COMPONENTS filesystem system thread unit_test_framework REQUIRED)
+endif()
+
+
+find_program(QT_QMAKE_EXECUTABLE NAMES qmake4 qmake-qt4 qmake-mac)
+find_package(Qt4 REQUIRED)
+
+set(pulseview_TEST_SOURCES
+ ${PROJECT_SOURCE_DIR}/pv/devicemanager.cpp
+ ${PROJECT_SOURCE_DIR}/pv/sigsession.cpp
+ ${PROJECT_SOURCE_DIR}/pv/view/cursorpair.cpp
+ ${PROJECT_SOURCE_DIR}/pv/data/analog.cpp
+ ${PROJECT_SOURCE_DIR}/pv/data/analogsnapshot.cpp
+ ${PROJECT_SOURCE_DIR}/pv/data/logic.cpp
+ ${PROJECT_SOURCE_DIR}/pv/data/logicsnapshot.cpp
+ ${PROJECT_SOURCE_DIR}/pv/data/snapshot.cpp
+ ${PROJECT_SOURCE_DIR}/pv/data/signaldata.cpp
+ ${PROJECT_SOURCE_DIR}/pv/device/device.cpp
+ ${PROJECT_SOURCE_DIR}/pv/device/devinst.cpp
+ ${PROJECT_SOURCE_DIR}/pv/device/file.cpp
+ ${PROJECT_SOURCE_DIR}/pv/device/inputfile.cpp
+ ${PROJECT_SOURCE_DIR}/pv/device/sessionfile.cpp
+ ${PROJECT_SOURCE_DIR}/pv/prop/double.cpp
+ ${PROJECT_SOURCE_DIR}/pv/prop/enum.cpp
+ ${PROJECT_SOURCE_DIR}/pv/prop/int.cpp
+ ${PROJECT_SOURCE_DIR}/pv/prop/property.cpp
+ ${PROJECT_SOURCE_DIR}/pv/prop/string.cpp
+ ${PROJECT_SOURCE_DIR}/pv/prop/binding/binding.cpp
+ ${PROJECT_SOURCE_DIR}/pv/view/analogsignal.cpp
+ ${PROJECT_SOURCE_DIR}/pv/view/cursor.cpp
+ ${PROJECT_SOURCE_DIR}/pv/view/cursorpair.cpp
+ ${PROJECT_SOURCE_DIR}/pv/view/header.cpp
+ ${PROJECT_SOURCE_DIR}/pv/view/logicsignal.cpp
+ ${PROJECT_SOURCE_DIR}/pv/view/marginwidget.cpp
+ ${PROJECT_SOURCE_DIR}/pv/view/ruler.cpp
+ ${PROJECT_SOURCE_DIR}/pv/view/selectableitem.cpp
+ ${PROJECT_SOURCE_DIR}/pv/view/signal.cpp
+ ${PROJECT_SOURCE_DIR}/pv/view/timemarker.cpp
+ ${PROJECT_SOURCE_DIR}/pv/view/trace.cpp
+ ${PROJECT_SOURCE_DIR}/pv/view/tracepalette.cpp
+ ${PROJECT_SOURCE_DIR}/pv/view/view.cpp
+ ${PROJECT_SOURCE_DIR}/pv/view/viewport.cpp
+ ${PROJECT_SOURCE_DIR}/pv/widgets/colourbutton.cpp
+ ${PROJECT_SOURCE_DIR}/pv/widgets/colourpopup.cpp
+ ${PROJECT_SOURCE_DIR}/pv/widgets/popup.cpp
+ ${PROJECT_SOURCE_DIR}/pv/widgets/wellarray.cpp
+ data/analogsnapshot.cpp
+ data/logicsnapshot.cpp
+ test.cpp
+)
+
+# This list includes only QObject derived class headers.
+set(pulseview_TEST_HEADERS
+ ${PROJECT_SOURCE_DIR}/pv/sigsession.h
+ ${PROJECT_SOURCE_DIR}/pv/device/devinst.h
+ ${PROJECT_SOURCE_DIR}/pv/prop/double.h
+ ${PROJECT_SOURCE_DIR}/pv/prop/enum.h
+ ${PROJECT_SOURCE_DIR}/pv/prop/int.h
+ ${PROJECT_SOURCE_DIR}/pv/prop/property.h
+ ${PROJECT_SOURCE_DIR}/pv/prop/string.h
+ ${PROJECT_SOURCE_DIR}/pv/view/cursor.h
+ ${PROJECT_SOURCE_DIR}/pv/view/header.h
+ ${PROJECT_SOURCE_DIR}/pv/view/logicsignal.h
+ ${PROJECT_SOURCE_DIR}/pv/view/marginwidget.h
+ ${PROJECT_SOURCE_DIR}/pv/view/ruler.h
+ ${PROJECT_SOURCE_DIR}/pv/view/selectableitem.h
+ ${PROJECT_SOURCE_DIR}/pv/view/signal.h
+ ${PROJECT_SOURCE_DIR}/pv/view/timemarker.h
+ ${PROJECT_SOURCE_DIR}/pv/view/trace.h
+ ${PROJECT_SOURCE_DIR}/pv/view/view.h
+ ${PROJECT_SOURCE_DIR}/pv/view/viewport.h
+ ${PROJECT_SOURCE_DIR}/pv/widgets/colourbutton.h
+ ${PROJECT_SOURCE_DIR}/pv/widgets/colourpopup.h
+ ${PROJECT_SOURCE_DIR}/pv/widgets/popup.h
+ ${PROJECT_SOURCE_DIR}/pv/widgets/wellarray.h
+)
+
+if(ENABLE_DECODE)
+ list(APPEND pulseview_TEST_SOURCES
+ ${PROJECT_SOURCE_DIR}/pv/data/decoderstack.cpp
+ ${PROJECT_SOURCE_DIR}/pv/data/decode/annotation.cpp
+ ${PROJECT_SOURCE_DIR}/pv/data/decode/decoder.cpp
+ ${PROJECT_SOURCE_DIR}/pv/data/decode/row.cpp
+ ${PROJECT_SOURCE_DIR}/pv/data/decode/rowdata.cpp
+ ${PROJECT_SOURCE_DIR}/pv/prop/binding/decoderoptions.cpp
+ ${PROJECT_SOURCE_DIR}/pv/view/decodetrace.cpp
+ ${PROJECT_SOURCE_DIR}/pv/widgets/decodergroupbox.cpp
+ ${PROJECT_SOURCE_DIR}/pv/widgets/decodermenu.cpp
+ data/decoderstack.cpp
+ )
+
+ list(APPEND pulseview_TEST_HEADERS
+ ${PROJECT_SOURCE_DIR}/pv/data/decoderstack.h
+ ${PROJECT_SOURCE_DIR}/pv/view/decodetrace.h
+ ${PROJECT_SOURCE_DIR}/pv/widgets/decodergroupbox.h
+ ${PROJECT_SOURCE_DIR}/pv/widgets/decodermenu.h
+ )
+endif()
+
+qt4_wrap_cpp(pulseview_TEST_HEADERS_MOC ${pulseview_TEST_HEADERS})
+
+if(ENABLE_DECODE)
+ add_definitions(-DENABLE_DECODE)
+endif()
+
+# On MinGW we need to use static linking.
+if(NOT WIN32)
+add_definitions(-DBOOST_TEST_DYN_LINK)
+endif()
+
+add_definitions(${QT_DEFINITIONS})
+
+include_directories(
+ ${Boost_INCLUDE_DIRS}
+ ${PKGDEPS_INCLUDE_DIRS}
+)
+
+set(PULSEVIEW_LINK_LIBS
+ ${Boost_LIBRARIES}
+ ${CMAKE_THREAD_LIBS_INIT}
+ ${PKGDEPS_LIBRARIES}
+ ${QT_LIBRARIES}
+)
+
+if(WIN32)
+ # Workaround for a MinGW linking issue.
+ list(APPEND PULSEVIEW_LINK_LIBS "-llzma -llcms2")
+endif()
+
+add_executable(pulseview-test
+ ${pulseview_TEST_SOURCES}
+ ${pulseview_TEST_HEADERS_MOC}
+)
+
+target_link_libraries(pulseview-test ${PULSEVIEW_LINK_LIBS})
+
diff --git a/test/data/analogsnapshot.cpp b/test/data/analogsnapshot.cpp
new file mode 100644
index 0000000..200d262
--- /dev/null
+++ b/test/data/analogsnapshot.cpp
@@ -0,0 +1,114 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <extdef.h>
+
+#include <stdint.h>
+
+#include <boost/test/unit_test.hpp>
+
+#include "../../pv/data/analogsnapshot.h"
+
+using pv::data::AnalogSnapshot;
+
+BOOST_AUTO_TEST_SUITE(AnalogSnapshotTest)
+
+void push_analog(AnalogSnapshot &s, unsigned int num_samples,
+ float value)
+{
+ float *const data = new float[num_samples];
+ for (unsigned int i = 0; i < num_samples; i++)
+ data[i] = value;
+
+ s.append_interleaved_samples(data, num_samples, 1);
+ delete[] data;
+}
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+ // Create an empty AnalogSnapshot object
+ AnalogSnapshot s;
+
+ //----- Test AnalogSnapshot::push_analog -----//
+
+ BOOST_CHECK(s.get_sample_count() == 0);
+ for (unsigned int i = 0; i < AnalogSnapshot::ScaleStepCount; i++)
+ {
+ const AnalogSnapshot::Envelope &m = s._envelope_levels[i];
+ BOOST_CHECK_EQUAL(m.length, 0);
+ BOOST_CHECK_EQUAL(m.data_length, 0);
+ BOOST_CHECK(m.samples == NULL);
+ }
+
+ // Push 8 samples of all zeros
+ push_analog(s, 8, 0.0f);
+
+ BOOST_CHECK(s.get_sample_count() == 8);
+
+ // There should not be enough samples to have a single mip map sample
+ for (unsigned int i = 0; i < AnalogSnapshot::ScaleStepCount; i++)
+ {
+ const AnalogSnapshot::Envelope &m = s._envelope_levels[i];
+ BOOST_CHECK_EQUAL(m.length, 0);
+ BOOST_CHECK_EQUAL(m.data_length, 0);
+ BOOST_CHECK(m.samples == NULL);
+ }
+
+ // Push 8 samples of 1.0s to bring the total up to 16
+ push_analog(s, 8, 1.0f);
+
+ // There should now be enough data for exactly one sample
+ // in mip map level 0, and that sample should be 0
+ const AnalogSnapshot::Envelope &e0 = s._envelope_levels[0];
+ BOOST_CHECK_EQUAL(e0.length, 1);
+ BOOST_CHECK_EQUAL(e0.data_length, AnalogSnapshot::EnvelopeDataUnit);
+ BOOST_REQUIRE(e0.samples != NULL);
+ BOOST_CHECK_EQUAL(e0.samples[0].min, 0.0f);
+ BOOST_CHECK_EQUAL(e0.samples[0].max, 1.0f);
+
+ // The higher levels should still be empty
+ for (unsigned int i = 1; i < AnalogSnapshot::ScaleStepCount; i++)
+ {
+ const AnalogSnapshot::Envelope &m = s._envelope_levels[i];
+ BOOST_CHECK_EQUAL(m.length, 0);
+ BOOST_CHECK_EQUAL(m.data_length, 0);
+ BOOST_CHECK(m.samples == NULL);
+ }
+
+ // Push 240 samples of all zeros to bring the total up to 256
+ push_analog(s, 240, -1.0f);
+
+ BOOST_CHECK_EQUAL(e0.length, 16);
+ BOOST_CHECK_EQUAL(e0.data_length, AnalogSnapshot::EnvelopeDataUnit);
+
+ for (unsigned int i = 1; i < e0.length; i++) {
+ BOOST_CHECK_EQUAL(e0.samples[i].min, -1.0f);
+ BOOST_CHECK_EQUAL(e0.samples[i].max, -1.0f);
+ }
+
+ const AnalogSnapshot::Envelope &e1 = s._envelope_levels[1];
+ BOOST_CHECK_EQUAL(e1.length, 1);
+ BOOST_CHECK_EQUAL(e1.data_length, AnalogSnapshot::EnvelopeDataUnit);
+ BOOST_REQUIRE(e1.samples != NULL);
+ BOOST_CHECK_EQUAL(e1.samples[0].min, -1.0f);
+ BOOST_CHECK_EQUAL(e1.samples[0].max, 1.0f);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/data/decoderstack.cpp b/test/data/decoderstack.cpp
new file mode 100644
index 0000000..d6e4f75
--- /dev/null
+++ b/test/data/decoderstack.cpp
@@ -0,0 +1,86 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <libsigrokdecode/libsigrokdecode.h> /* First, so we avoid a _POSIX_C_SOURCE warning. */
+#include <boost/test/unit_test.hpp>
+
+#include <libsigrok/libsigrok.h>
+
+#include "../../pv/data/decoderstack.h"
+#include "../../pv/devicemanager.h"
+#include "../../pv/sigsession.h"
+#include "../../pv/view/decodetrace.h"
+
+using boost::shared_ptr;
+using pv::data::DecoderStack;
+using pv::data::decode::Decoder;
+using pv::view::DecodeTrace;
+using std::vector;
+
+BOOST_AUTO_TEST_SUITE(DecoderStackTest)
+
+BOOST_AUTO_TEST_CASE(TwoDecoderStack)
+{
+ sr_context *ctx = NULL;
+
+ BOOST_REQUIRE(sr_init(&ctx) == SR_OK);
+ BOOST_REQUIRE(ctx);
+
+ BOOST_REQUIRE(srd_init(NULL) == SRD_OK);
+
+ srd_decoder_load_all();
+
+ {
+ pv::DeviceManager dm(ctx);
+ pv::SigSession ss(dm);
+
+ const GSList *l = srd_decoder_list();
+ BOOST_REQUIRE(l);
+ srd_decoder *const dec = (struct srd_decoder*)l->data;
+ BOOST_REQUIRE(dec);
+
+ ss.add_decoder(dec);
+ ss.add_decoder(dec);
+
+ // Check the signals were created
+ const vector< shared_ptr<DecodeTrace> > sigs =
+ ss.get_decode_signals();
+
+ shared_ptr<DecoderStack> dec0 = sigs[0]->decoder();
+ BOOST_REQUIRE(dec0);
+
+ shared_ptr<DecoderStack> dec1 = sigs[0]->decoder();
+ BOOST_REQUIRE(dec1);
+
+ // Wait for the decode threads to complete
+ dec0->_decode_thread.join();
+ dec1->_decode_thread.join();
+
+ // Check there were no errors
+ BOOST_CHECK_EQUAL(dec0->error_message().isEmpty(), true);
+ BOOST_CHECK_EQUAL(dec1->error_message().isEmpty(), true);
+ }
+
+
+ srd_exit();
+ sr_exit(ctx);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/data/logicsnapshot.cpp b/test/data/logicsnapshot.cpp
new file mode 100644
index 0000000..5fc1721
--- /dev/null
+++ b/test/data/logicsnapshot.cpp
@@ -0,0 +1,534 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <extdef.h>
+
+#include <stdint.h>
+
+#include <boost/test/unit_test.hpp>
+
+#include "../../pv/data/logicsnapshot.h"
+
+using pv::data::LogicSnapshot;
+using std::vector;
+
+BOOST_AUTO_TEST_SUITE(LogicSnapshotTest)
+
+void push_logic(LogicSnapshot &s, unsigned int length, uint8_t value)
+{
+ sr_datafeed_logic logic;
+ logic.unitsize = 1;
+ logic.length = length;
+ logic.data = new uint8_t[length];
+ memset(logic.data, value, length * logic.unitsize);
+ s.append_payload(logic);
+ delete[] (uint8_t*)logic.data;
+}
+
+BOOST_AUTO_TEST_CASE(Pow2)
+{
+ BOOST_CHECK_EQUAL(LogicSnapshot::pow2_ceil(0, 0), 0);
+ BOOST_CHECK_EQUAL(LogicSnapshot::pow2_ceil(1, 0), 1);
+ BOOST_CHECK_EQUAL(LogicSnapshot::pow2_ceil(2, 0), 2);
+
+ BOOST_CHECK_EQUAL(
+ LogicSnapshot::pow2_ceil(INT64_MIN, 0), INT64_MIN);
+ BOOST_CHECK_EQUAL(
+ LogicSnapshot::pow2_ceil(INT64_MAX, 0), INT64_MAX);
+
+ BOOST_CHECK_EQUAL(LogicSnapshot::pow2_ceil(0, 1), 0);
+ BOOST_CHECK_EQUAL(LogicSnapshot::pow2_ceil(1, 1), 2);
+ BOOST_CHECK_EQUAL(LogicSnapshot::pow2_ceil(2, 1), 2);
+ BOOST_CHECK_EQUAL(LogicSnapshot::pow2_ceil(3, 1), 4);
+}
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+ // Create an empty LogicSnapshot object
+ sr_datafeed_logic logic;
+ logic.length = 0;
+ logic.unitsize = 1;
+ logic.data = NULL;
+
+ LogicSnapshot s(logic);
+
+ //----- Test LogicSnapshot::push_logic -----//
+
+ BOOST_CHECK(s.get_sample_count() == 0);
+ for (unsigned int i = 0; i < LogicSnapshot::ScaleStepCount; i++)
+ {
+ const LogicSnapshot::MipMapLevel &m = s._mip_map[i];
+ BOOST_CHECK_EQUAL(m.length, 0);
+ BOOST_CHECK_EQUAL(m.data_length, 0);
+ BOOST_CHECK(m.data == NULL);
+ }
+
+ // Push 8 samples of all zeros
+ push_logic(s, 8, 0);
+
+ BOOST_CHECK(s.get_sample_count() == 8);
+
+ // There should not be enough samples to have a single mip map sample
+ for (unsigned int i = 0; i < LogicSnapshot::ScaleStepCount; i++)
+ {
+ const LogicSnapshot::MipMapLevel &m = s._mip_map[i];
+ BOOST_CHECK_EQUAL(m.length, 0);
+ BOOST_CHECK_EQUAL(m.data_length, 0);
+ BOOST_CHECK(m.data == NULL);
+ }
+
+ // Push 8 samples of 0x11s to bring the total up to 16
+ push_logic(s, 8, 0x11);
+
+ // There should now be enough data for exactly one sample
+ // in mip map level 0, and that sample should be 0
+ const LogicSnapshot::MipMapLevel &m0 = s._mip_map[0];
+ BOOST_CHECK_EQUAL(m0.length, 1);
+ BOOST_CHECK_EQUAL(m0.data_length, LogicSnapshot::MipMapDataUnit);
+ BOOST_REQUIRE(m0.data != NULL);
+ BOOST_CHECK_EQUAL(((uint8_t*)m0.data)[0], 0x11);
+
+ // The higher levels should still be empty
+ for (unsigned int i = 1; i < LogicSnapshot::ScaleStepCount; i++)
+ {
+ const LogicSnapshot::MipMapLevel &m = s._mip_map[i];
+ BOOST_CHECK_EQUAL(m.length, 0);
+ BOOST_CHECK_EQUAL(m.data_length, 0);
+ BOOST_CHECK(m.data == NULL);
+ }
+
+ // Push 240 samples of all zeros to bring the total up to 256
+ push_logic(s, 240, 0);
+
+ BOOST_CHECK_EQUAL(m0.length, 16);
+ BOOST_CHECK_EQUAL(m0.data_length, LogicSnapshot::MipMapDataUnit);
+
+ BOOST_CHECK_EQUAL(((uint8_t*)m0.data)[1], 0x11);
+ for (unsigned int i = 2; i < m0.length; i++)
+ BOOST_CHECK_EQUAL(((uint8_t*)m0.data)[i], 0);
+
+ const LogicSnapshot::MipMapLevel &m1 = s._mip_map[1];
+ BOOST_CHECK_EQUAL(m1.length, 1);
+ BOOST_CHECK_EQUAL(m1.data_length, LogicSnapshot::MipMapDataUnit);
+ BOOST_REQUIRE(m1.data != NULL);
+ BOOST_CHECK_EQUAL(((uint8_t*)m1.data)[0], 0x11);
+
+ //----- Test LogicSnapshot::get_subsampled_edges -----//
+
+ // Test a full view at full zoom.
+ vector<LogicSnapshot::EdgePair> edges;
+ s.get_subsampled_edges(edges, 0, 255, 1, 0);
+ BOOST_REQUIRE_EQUAL(edges.size(), 4);
+
+ BOOST_CHECK_EQUAL(edges[0].first, 0);
+ BOOST_CHECK_EQUAL(edges[1].first, 8);
+ BOOST_CHECK_EQUAL(edges[2].first, 16);
+ BOOST_CHECK_EQUAL(edges[3].first, 256);
+
+ // Test a subset at high zoom
+ edges.clear();
+ s.get_subsampled_edges(edges, 6, 17, 0.05f, 0);
+ BOOST_REQUIRE_EQUAL(edges.size(), 4);
+
+ BOOST_CHECK_EQUAL(edges[0].first, 6);
+ BOOST_CHECK_EQUAL(edges[1].first, 8);
+ BOOST_CHECK_EQUAL(edges[2].first, 16);
+ BOOST_CHECK_EQUAL(edges[3].first, 18);
+}
+
+BOOST_AUTO_TEST_CASE(LargeData)
+{
+ uint8_t prev_sample;
+ const unsigned int Length = 1000000;
+
+ sr_datafeed_logic logic;
+ logic.unitsize = 1;
+ logic.length = Length;
+ logic.data = new uint8_t[Length];
+ uint8_t *data = (uint8_t*)logic.data;
+
+ for (unsigned int i = 0; i < Length; i++)
+ *data++ = (uint8_t)(i >> 8);
+
+ LogicSnapshot s(logic);
+ delete[] (uint8_t*)logic.data;
+
+ BOOST_CHECK(s.get_sample_count() == Length);
+
+ // Check mip map level 0
+ BOOST_CHECK_EQUAL(s._mip_map[0].length, 62500);
+ BOOST_CHECK_EQUAL(s._mip_map[0].data_length,
+ LogicSnapshot::MipMapDataUnit);
+ BOOST_REQUIRE(s._mip_map[0].data != NULL);
+
+ prev_sample = 0;
+ for (unsigned int i = 0; i < s._mip_map[0].length;)
+ {
+ BOOST_TEST_MESSAGE("Testing mip_map[0].data[" << i << "]");
+
+ const uint8_t sample = (uint8_t)((i*16) >> 8);
+ BOOST_CHECK_EQUAL(s.get_subsample(0, i++) & 0xFF,
+ prev_sample ^ sample);
+ prev_sample = sample;
+
+ for (int j = 1; i < s._mip_map[0].length && j < 16; j++)
+ {
+ BOOST_TEST_MESSAGE("Testing mip_map[0].data[" << i << "]");
+ BOOST_CHECK_EQUAL(s.get_subsample(0, i++) & 0xFF, 0);
+ }
+ }
+
+ // Check mip map level 1
+ BOOST_CHECK_EQUAL(s._mip_map[1].length, 3906);
+ BOOST_CHECK_EQUAL(s._mip_map[1].data_length,
+ LogicSnapshot::MipMapDataUnit);
+ BOOST_REQUIRE(s._mip_map[1].data != NULL);
+
+ prev_sample = 0;
+ for (unsigned int i = 0; i < s._mip_map[1].length; i++)
+ {
+ BOOST_TEST_MESSAGE("Testing mip_map[1].data[" << i << "]");
+
+ const uint8_t sample = i;
+ const uint8_t expected = sample ^ prev_sample;
+ prev_sample = i;
+
+ BOOST_CHECK_EQUAL(s.get_subsample(1, i) & 0xFF, expected);
+ }
+
+ // Check mip map level 2
+ BOOST_CHECK_EQUAL(s._mip_map[2].length, 244);
+ BOOST_CHECK_EQUAL(s._mip_map[2].data_length,
+ LogicSnapshot::MipMapDataUnit);
+ BOOST_REQUIRE(s._mip_map[2].data != NULL);
+
+ prev_sample = 0;
+ for (unsigned int i = 0; i < s._mip_map[2].length; i++)
+ {
+ BOOST_TEST_MESSAGE("Testing mip_map[2].data[" << i << "]");
+
+ const uint8_t sample = i << 4;
+ const uint8_t expected = (sample ^ prev_sample) | 0x0F;
+ prev_sample = sample;
+
+ BOOST_CHECK_EQUAL(s.get_subsample(2, i) & 0xFF, expected);
+ }
+
+ // Check mip map level 3
+ BOOST_CHECK_EQUAL(s._mip_map[3].length, 15);
+ BOOST_CHECK_EQUAL(s._mip_map[3].data_length,
+ LogicSnapshot::MipMapDataUnit);
+ BOOST_REQUIRE(s._mip_map[3].data != NULL);
+
+ for (unsigned int i = 0; i < s._mip_map[3].length; i++)
+ BOOST_CHECK_EQUAL(*((uint8_t*)s._mip_map[3].data + i),
+ 0xFF);
+
+ // Check the higher levels
+ for (unsigned int i = 4; i < LogicSnapshot::ScaleStepCount; i++)
+ {
+ const LogicSnapshot::MipMapLevel &m = s._mip_map[i];
+ BOOST_CHECK_EQUAL(m.length, 0);
+ BOOST_CHECK_EQUAL(m.data_length, 0);
+ BOOST_CHECK(m.data == NULL);
+ }
+
+ //----- Test LogicSnapshot::get_subsampled_edges -----//
+ // Check in normal case
+ vector<LogicSnapshot::EdgePair> edges;
+ s.get_subsampled_edges(edges, 0, Length-1, 1, 7);
+
+ BOOST_CHECK_EQUAL(edges.size(), 32);
+
+ for (unsigned int i = 0; i < edges.size() - 1; i++)
+ {
+ BOOST_CHECK_EQUAL(edges[i].first, i * 32768);
+ BOOST_CHECK_EQUAL(edges[i].second, i & 1);
+ }
+
+ BOOST_CHECK_EQUAL(edges[31].first, 1000000);
+
+ // Check in very low zoom case
+ edges.clear();
+ s.get_subsampled_edges(edges, 0, Length-1, 50e6f, 7);
+
+ BOOST_CHECK_EQUAL(edges.size(), 2);
+}
+
+BOOST_AUTO_TEST_CASE(Pulses)
+{
+ const int Cycles = 3;
+ const int Period = 64;
+ const int Length = Cycles * Period;
+
+ vector<LogicSnapshot::EdgePair> edges;
+
+ //----- Create a LogicSnapshot -----//
+ sr_datafeed_logic logic;
+ logic.unitsize = 1;
+ logic.length = Length;
+ logic.data = (uint64_t*)new uint8_t[Length];
+ uint8_t *p = (uint8_t*)logic.data;
+
+ for (int i = 0; i < Cycles; i++) {
+ *p++ = 0xFF;
+ for (int j = 1; j < Period; j++)
+ *p++ = 0x00;
+ }
+
+ LogicSnapshot s(logic);
+ delete[] (uint8_t*)logic.data;
+
+ //----- Check the mip-map -----//
+ // Check mip map level 0
+ BOOST_CHECK_EQUAL(s._mip_map[0].length, 12);
+ BOOST_CHECK_EQUAL(s._mip_map[0].data_length,
+ LogicSnapshot::MipMapDataUnit);
+ BOOST_REQUIRE(s._mip_map[0].data != NULL);
+
+ for (unsigned int i = 0; i < s._mip_map[0].length;) {
+ BOOST_TEST_MESSAGE("Testing mip_map[0].data[" << i << "]");
+ BOOST_CHECK_EQUAL(s.get_subsample(0, i++) & 0xFF, 0xFF);
+
+ for (int j = 1;
+ i < s._mip_map[0].length &&
+ j < Period/LogicSnapshot::MipMapScaleFactor; j++) {
+ BOOST_TEST_MESSAGE(
+ "Testing mip_map[0].data[" << i << "]");
+ BOOST_CHECK_EQUAL(s.get_subsample(0, i++) & 0xFF, 0x00);
+ }
+ }
+
+ // Check the higher levels are all inactive
+ for (unsigned int i = 1; i < LogicSnapshot::ScaleStepCount; i++) {
+ const LogicSnapshot::MipMapLevel &m = s._mip_map[i];
+ BOOST_CHECK_EQUAL(m.length, 0);
+ BOOST_CHECK_EQUAL(m.data_length, 0);
+ BOOST_CHECK(m.data == NULL);
+ }
+
+ //----- Test get_subsampled_edges at reduced scale -----//
+ s.get_subsampled_edges(edges, 0, Length-1, 16.0f, 2);
+ BOOST_REQUIRE_EQUAL(edges.size(), Cycles + 2);
+
+ BOOST_CHECK_EQUAL(0, false);
+ for (unsigned int i = 1; i < edges.size(); i++)
+ BOOST_CHECK_EQUAL(edges[i].second, false);
+}
+
+BOOST_AUTO_TEST_CASE(LongPulses)
+{
+ const int Cycles = 3;
+ const int Period = 64;
+ const int PulseWidth = 16;
+ const int Length = Cycles * Period;
+
+ int j;
+ vector<LogicSnapshot::EdgePair> edges;
+
+ //----- Create a LogicSnapshot -----//
+ sr_datafeed_logic logic;
+ logic.unitsize = 8;
+ logic.length = Length * 8;
+ logic.data = (uint64_t*)new uint64_t[Length];
+ uint64_t *p = (uint64_t*)logic.data;
+
+ for (int i = 0; i < Cycles; i++) {
+ for (j = 0; j < PulseWidth; j++)
+ *p++ = ~0;
+ for (; j < Period; j++)
+ *p++ = 0;
+ }
+
+ LogicSnapshot s(logic);
+ delete[] (uint64_t*)logic.data;
+
+ //----- Check the mip-map -----//
+ // Check mip map level 0
+ BOOST_CHECK_EQUAL(s._mip_map[0].length, 12);
+ BOOST_CHECK_EQUAL(s._mip_map[0].data_length,
+ LogicSnapshot::MipMapDataUnit);
+ BOOST_REQUIRE(s._mip_map[0].data != NULL);
+
+ for (unsigned int i = 0; i < s._mip_map[0].length;) {
+ for (j = 0; i < s._mip_map[0].length && j < 2; j++) {
+ BOOST_TEST_MESSAGE(
+ "Testing mip_map[0].data[" << i << "]");
+ BOOST_CHECK_EQUAL(s.get_subsample(0, i++), ~0);
+ }
+
+ for (; i < s._mip_map[0].length &&
+ j < Period/LogicSnapshot::MipMapScaleFactor; j++) {
+ BOOST_TEST_MESSAGE(
+ "Testing mip_map[0].data[" << i << "]");
+ BOOST_CHECK_EQUAL(s.get_subsample(0, i++), 0);
+ }
+ }
+
+ // Check the higher levels are all inactive
+ for (unsigned int i = 1; i < LogicSnapshot::ScaleStepCount; i++) {
+ const LogicSnapshot::MipMapLevel &m = s._mip_map[i];
+ BOOST_CHECK_EQUAL(m.length, 0);
+ BOOST_CHECK_EQUAL(m.data_length, 0);
+ BOOST_CHECK(m.data == NULL);
+ }
+
+ //----- Test get_subsampled_edges at a full scale -----//
+ s.get_subsampled_edges(edges, 0, Length-1, 16.0f, 2);
+ BOOST_REQUIRE_EQUAL(edges.size(), Cycles * 2 + 1);
+
+ for (int i = 0; i < Cycles; i++) {
+ BOOST_CHECK_EQUAL(edges[i*2].first, i * Period);
+ BOOST_CHECK_EQUAL(edges[i*2].second, true);
+ BOOST_CHECK_EQUAL(edges[i*2+1].first, i * Period + PulseWidth);
+ BOOST_CHECK_EQUAL(edges[i*2+1].second, false);
+ }
+
+ BOOST_CHECK_EQUAL(edges.back().first, Length);
+ BOOST_CHECK_EQUAL(edges.back().second, false);
+
+ //----- Test get_subsampled_edges at a simplified scale -----//
+ edges.clear();
+ s.get_subsampled_edges(edges, 0, Length-1, 17.0f, 2);
+
+ BOOST_CHECK_EQUAL(edges[0].first, 0);
+ BOOST_CHECK_EQUAL(edges[0].second, true);
+ BOOST_CHECK_EQUAL(edges[1].first, 16);
+ BOOST_CHECK_EQUAL(edges[1].second, false);
+
+ for (int i = 1; i < Cycles; i++) {
+ BOOST_CHECK_EQUAL(edges[i+1].first, i * Period);
+ BOOST_CHECK_EQUAL(edges[i+1].second, false);
+ }
+
+ BOOST_CHECK_EQUAL(edges.back().first, Length);
+ BOOST_CHECK_EQUAL(edges.back().second, false);
+}
+
+BOOST_AUTO_TEST_CASE(LisaMUsbHid)
+{
+ /* This test was created from the beginning of the USB_DM signal in
+ * sigrok-dumps-usb/lisa_m_usbhid/lisa_m_usbhid.sr
+ */
+
+ const int Edges[] = {
+ 7028, 7033, 7036, 7041, 7044, 7049, 7053, 7066, 7073, 7079,
+ 7086, 7095, 7103, 7108, 7111, 7116, 7119, 7124, 7136, 7141,
+ 7148, 7162, 7500
+ };
+ const int Length = Edges[countof(Edges) - 1];
+
+ bool state = false;
+ int lastEdgePos = 0;
+
+ //----- Create a LogicSnapshot -----//
+ sr_datafeed_logic logic;
+ logic.unitsize = 1;
+ logic.length = Length;
+ logic.data = new uint8_t[Length];
+ uint8_t *data = (uint8_t*)logic.data;
+
+ for (unsigned int i = 0; i < countof(Edges); i++) {
+ const int edgePos = Edges[i];
+ memset(&data[lastEdgePos], state ? 0x02 : 0,
+ edgePos - lastEdgePos - 1);
+
+ lastEdgePos = edgePos;
+ state = !state;
+ }
+
+ LogicSnapshot s(logic);
+ delete[] (uint64_t*)logic.data;
+
+ vector<LogicSnapshot::EdgePair> edges;
+
+
+ /* The trailing edge of the pulse train is falling in the source data.
+ * Check this is always true at different scales
+ */
+
+ edges.clear();
+ s.get_subsampled_edges(edges, 0, Length-1, 33.333332f, 1);
+ BOOST_CHECK_EQUAL(edges[edges.size() - 2].second, false);
+}
+
+/*
+ * This test checks the rendering of wide data (more than 8 probes)
+ * Probe signals are either all-high, or all-low, but are interleaved such that
+ * they would toggle during every sample if treated like 8 probes.
+ * The packet contains a large number of samples, so the mipmap generation kicks
+ * in.
+ *
+ * The signals should not toggle (have exactly two edges: the start and end)
+ */
+BOOST_AUTO_TEST_CASE(WideData)
+{
+ const int Length = 512<<10;
+ uint16_t *data = new uint16_t[Length];
+
+ sr_datafeed_logic logic;
+ logic.unitsize = sizeof(data[0]);
+ logic.length = Length * sizeof(data[0]);
+ logic.data = data;
+
+ for (int i = 0; i < Length; i++)
+ data[i] = 0x0FF0;
+
+ LogicSnapshot s(logic);
+
+ vector<LogicSnapshot::EdgePair> edges;
+
+ edges.clear();
+ s.get_subsampled_edges(edges, 0, Length-1, 1, 0);
+ BOOST_CHECK_EQUAL(edges.size(), 2);
+
+ edges.clear();
+ s.get_subsampled_edges(edges, 0, Length-1, 1, 8);
+ BOOST_CHECK_EQUAL(edges.size(), 2);
+
+ // Cleanup
+ delete [] data;
+}
+
+/*
+ * This test is a replica of sixteen.sr attached to Bug #33.
+ */
+BOOST_AUTO_TEST_CASE(Sixteen)
+{
+ const int Length = 8;
+ uint16_t data[Length];
+
+ sr_datafeed_logic logic;
+ logic.unitsize = sizeof(data[0]);
+ logic.length = Length * sizeof(data[0]);
+ logic.data = data;
+
+ for (int i = 0; i < Length; i++)
+ data[i] = 0xFFFE;
+
+ LogicSnapshot s(logic);
+
+ vector<LogicSnapshot::EdgePair> edges;
+ s.get_subsampled_edges(edges, 0, 2, 0.0004, 1);
+
+ BOOST_CHECK_EQUAL(edges.size(), 2);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/test.cpp b/test/test.cpp
new file mode 100644
index 0000000..04c9981
--- /dev/null
+++ b/test/test.cpp
@@ -0,0 +1,22 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2012 Joel Holdsworth <joel at airwebreathe.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#define BOOST_TEST_MAIN
+#include <boost/test/unit_test.hpp>
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-science/packages/pulseview.git
More information about the debian-science-commits
mailing list