]> sigrok.org Git - pulseview.git/commitdiff
Backport recent changes from mainline.
authorUwe Hermann <redacted>
Tue, 13 Jun 2017 14:49:11 +0000 (16:49 +0200)
committerUwe Hermann <redacted>
Mon, 29 Oct 2018 10:40:26 +0000 (11:40 +0100)
This includes all changes from

  0adee2d93430b5fb879388e66f3bd44fd11b3b22
  manual: Bump date.

up to

  10906507eb3d32346506a12d4387205f7c7c6e72
  HACKING: Update URL to Linux kernel coding style.

264 files changed:
.gitignore
CMakeLists.txt
HACKING
INSTALL
README
android/loghandler.cpp
contrib/org.sigrok.PulseView.desktop
contrib/pulseview_cross.nsi.in
doc/pulseview.1
icons/help-browser.png [new file with mode: 0644]
icons/media-playback-pause.png [new file with mode: 0644]
icons/media-playback-start.png [new file with mode: 0644]
icons/settings-general.png [new file with mode: 0644]
icons/view-displaymode-last_complete_segment.svg [new file with mode: 0644]
icons/view-displaymode-last_segment.svg [new file with mode: 0644]
icons/view-displaymode-single_segment.svg [new file with mode: 0644]
icons/zoom-original.png [deleted file]
main.cpp
manual/CMakeLists.txt [new file with mode: 0644]
manual/acquisition.txt [new file with mode: 0644]
manual/analysis.txt [new file with mode: 0644]
manual/asciidoctor-stylesheet-factory/LICENSE [new file with mode: 0644]
manual/asciidoctor-stylesheet-factory/stylesheets/readthedocs.css [new file with mode: 0644]
manual/cli.txt [new file with mode: 0644]
manual/decoders.txt [new file with mode: 0644]
manual/images/_callouts.xcf [new file with mode: 0644]
manual/images/device_selector.png [new file with mode: 0644]
manual/images/device_selector_dropdown.png [new file with mode: 0644]
manual/images/device_selector_scan.png [new file with mode: 0644]
manual/images/pv_after_startup.png [new file with mode: 0644]
manual/images/pv_after_startup.xcf [new file with mode: 0644]
manual/images/pv_analysis.png [new file with mode: 0644]
manual/images/pv_analysis.xcf [new file with mode: 0644]
manual/images/pv_conversion_a2l.png [new file with mode: 0644]
manual/images/pv_cursors_markers.png [new file with mode: 0644]
manual/images/pv_cursors_markers.xcf [new file with mode: 0644]
manual/images/pv_decoders_1.png [new file with mode: 0644]
manual/images/pv_decoders_1.xcf [new file with mode: 0644]
manual/images/pv_decoders_2.png [new file with mode: 0644]
manual/images/pv_decoders_3.png [new file with mode: 0644]
manual/images/pv_decoders_3.xcf [new file with mode: 0644]
manual/images/pv_decoders_4.png [new file with mode: 0644]
manual/images/pv_device_config.png [new file with mode: 0644]
manual/images/pv_device_config.xcf [new file with mode: 0644]
manual/images/pv_import.png [new file with mode: 0644]
manual/images/pv_nodevice.png [new file with mode: 0644]
manual/images/top_bar.png [new file with mode: 0644]
manual/import_export.txt [new file with mode: 0644]
manual/installation.txt [new file with mode: 0644]
manual/license.txt [new file with mode: 0644]
manual/manual.txt [new file with mode: 0644]
manual/overview.txt [new file with mode: 0644]
pulseview.qrc
pv/application.cpp
pv/application.hpp
pv/binding/binding.cpp
pv/binding/binding.hpp
pv/binding/decoder.cpp
pv/binding/decoder.hpp
pv/binding/device.cpp
pv/binding/device.hpp
pv/binding/inputoutput.cpp
pv/data/analog.cpp
pv/data/analog.hpp
pv/data/analogsegment.cpp
pv/data/analogsegment.hpp
pv/data/decode/annotation.cpp
pv/data/decode/annotation.hpp
pv/data/decode/decoder.cpp
pv/data/decode/decoder.hpp
pv/data/decode/row.cpp
pv/data/decode/row.hpp
pv/data/decode/rowdata.cpp
pv/data/decode/rowdata.hpp
pv/data/decoderstack.cpp [deleted file]
pv/data/decoderstack.hpp [deleted file]
pv/data/decodesignal.cpp [new file with mode: 0644]
pv/data/decodesignal.hpp [new file with mode: 0644]
pv/data/logic.cpp
pv/data/logic.hpp
pv/data/logicsegment.cpp
pv/data/logicsegment.hpp
pv/data/segment.cpp
pv/data/segment.hpp
pv/data/signalbase.cpp
pv/data/signalbase.hpp
pv/data/signaldata.hpp
pv/devicemanager.cpp
pv/devicemanager.hpp
pv/devices/device.cpp
pv/devices/file.hpp
pv/devices/hardwaredevice.cpp
pv/devices/inputfile.cpp
pv/devices/inputfile.hpp
pv/dialogs/connect.cpp
pv/dialogs/connect.hpp
pv/dialogs/settings.cpp
pv/dialogs/settings.hpp
pv/dialogs/storeprogress.cpp
pv/globalsettings.cpp
pv/globalsettings.hpp
pv/logging.cpp [new file with mode: 0644]
pv/logging.hpp [new file with mode: 0644]
pv/mainwindow.cpp
pv/mainwindow.hpp
pv/popups/channels.cpp
pv/popups/channels.hpp
pv/popups/deviceoptions.cpp
pv/popups/deviceoptions.hpp
pv/prop/bool.cpp
pv/prop/bool.hpp
pv/prop/double.cpp
pv/prop/double.hpp
pv/prop/enum.cpp
pv/prop/enum.hpp
pv/prop/int.cpp
pv/prop/int.hpp
pv/prop/property.hpp
pv/prop/string.cpp
pv/prop/string.hpp
pv/session.cpp
pv/session.hpp
pv/storesession.cpp
pv/toolbars/mainbar.cpp
pv/toolbars/mainbar.hpp
pv/util.cpp
pv/util.hpp
pv/views/trace/analogsignal.cpp
pv/views/trace/analogsignal.hpp
pv/views/trace/cursor.cpp
pv/views/trace/cursor.hpp
pv/views/trace/cursorpair.cpp
pv/views/trace/cursorpair.hpp
pv/views/trace/decodetrace.cpp
pv/views/trace/decodetrace.hpp
pv/views/trace/flag.cpp
pv/views/trace/flag.hpp
pv/views/trace/header.cpp
pv/views/trace/header.hpp
pv/views/trace/logicsignal.cpp
pv/views/trace/logicsignal.hpp
pv/views/trace/marginwidget.cpp
pv/views/trace/rowitem.cpp
pv/views/trace/rowitem.hpp
pv/views/trace/ruler.cpp
pv/views/trace/ruler.hpp
pv/views/trace/signal.cpp
pv/views/trace/signal.hpp
pv/views/trace/signalscalehandle.cpp [deleted file]
pv/views/trace/signalscalehandle.hpp [deleted file]
pv/views/trace/standardbar.cpp
pv/views/trace/standardbar.hpp
pv/views/trace/timeitem.cpp
pv/views/trace/timeitem.hpp
pv/views/trace/timemarker.cpp
pv/views/trace/timemarker.hpp
pv/views/trace/trace.cpp
pv/views/trace/trace.hpp
pv/views/trace/tracegroup.cpp
pv/views/trace/tracegroup.hpp
pv/views/trace/tracepalette.cpp
pv/views/trace/tracepalette.hpp
pv/views/trace/tracetreeitem.cpp
pv/views/trace/tracetreeitem.hpp
pv/views/trace/tracetreeitemowner.cpp
pv/views/trace/triggermarker.cpp
pv/views/trace/triggermarker.hpp
pv/views/trace/view.cpp
pv/views/trace/view.hpp
pv/views/trace/viewitem.cpp
pv/views/trace/viewitem.hpp
pv/views/trace/viewitemowner.cpp
pv/views/trace/viewitemowner.hpp
pv/views/trace/viewitempaintparams.cpp
pv/views/trace/viewitempaintparams.hpp
pv/views/trace/viewport.cpp
pv/views/trace/viewport.hpp
pv/views/trace/viewwidget.cpp
pv/views/trace/viewwidget.hpp
pv/views/viewbase.cpp
pv/views/viewbase.hpp
pv/widgets/colorbutton.cpp [new file with mode: 0644]
pv/widgets/colorbutton.hpp [new file with mode: 0644]
pv/widgets/colorpopup.cpp [new file with mode: 0644]
pv/widgets/colorpopup.hpp [new file with mode: 0644]
pv/widgets/colourbutton.cpp [deleted file]
pv/widgets/colourbutton.hpp [deleted file]
pv/widgets/colourpopup.cpp [deleted file]
pv/widgets/colourpopup.hpp [deleted file]
pv/widgets/decodermenu.cpp
pv/widgets/devicetoolbutton.cpp
pv/widgets/popup.hpp
pv/widgets/sweeptimingwidget.cpp
pv/widgets/sweeptimingwidget.hpp
pv/widgets/wellarray.hpp
signalhandler.hpp
test/CMakeLists.txt
test/data/segment.cpp
test/view/ruler.cpp
themes/darkstyle/darkstyle.qss [new file with mode: 0644]
themes/darkstyle/icon_branch_closed.png [new file with mode: 0644]
themes/darkstyle/icon_branch_end.png [new file with mode: 0644]
themes/darkstyle/icon_branch_more.png [new file with mode: 0644]
themes/darkstyle/icon_branch_open.png [new file with mode: 0644]
themes/darkstyle/icon_checkbox_checked.png [new file with mode: 0644]
themes/darkstyle/icon_checkbox_checked_disabled.png [new file with mode: 0644]
themes/darkstyle/icon_checkbox_checked_pressed.png [new file with mode: 0644]
themes/darkstyle/icon_checkbox_indeterminate.png [new file with mode: 0644]
themes/darkstyle/icon_checkbox_indeterminate_disabled.png [new file with mode: 0644]
themes/darkstyle/icon_checkbox_indeterminate_pressed.png [new file with mode: 0644]
themes/darkstyle/icon_checkbox_unchecked.png [new file with mode: 0644]
themes/darkstyle/icon_checkbox_unchecked_disabled.png [new file with mode: 0644]
themes/darkstyle/icon_checkbox_unchecked_pressed.png [new file with mode: 0644]
themes/darkstyle/icon_close.png [new file with mode: 0644]
themes/darkstyle/icon_radiobutton_checked.png [new file with mode: 0644]
themes/darkstyle/icon_radiobutton_checked_disabled.png [new file with mode: 0644]
themes/darkstyle/icon_radiobutton_checked_pressed.png [new file with mode: 0644]
themes/darkstyle/icon_radiobutton_unchecked.png [new file with mode: 0644]
themes/darkstyle/icon_radiobutton_unchecked_disabled.png [new file with mode: 0644]
themes/darkstyle/icon_radiobutton_unchecked_pressed.png [new file with mode: 0644]
themes/darkstyle/icon_restore.png [new file with mode: 0644]
themes/darkstyle/icon_undock.png [new file with mode: 0644]
themes/darkstyle/icon_vline.png [new file with mode: 0644]
themes/qdarkstyle/rc/Hmovetoolbar.png [new file with mode: 0644]
themes/qdarkstyle/rc/Hsepartoolbar.png [new file with mode: 0644]
themes/qdarkstyle/rc/Vmovetoolbar.png [new file with mode: 0644]
themes/qdarkstyle/rc/Vsepartoolbar.png [new file with mode: 0644]
themes/qdarkstyle/rc/branch_closed-on.png [new file with mode: 0644]
themes/qdarkstyle/rc/branch_closed.png [new file with mode: 0644]
themes/qdarkstyle/rc/branch_open-on.png [new file with mode: 0644]
themes/qdarkstyle/rc/branch_open.png [new file with mode: 0644]
themes/qdarkstyle/rc/checkbox_checked.png [new file with mode: 0644]
themes/qdarkstyle/rc/checkbox_checked_disabled.png [new file with mode: 0644]
themes/qdarkstyle/rc/checkbox_checked_focus.png [new file with mode: 0644]
themes/qdarkstyle/rc/checkbox_indeterminate.png [new file with mode: 0644]
themes/qdarkstyle/rc/checkbox_indeterminate_disabled.png [new file with mode: 0644]
themes/qdarkstyle/rc/checkbox_indeterminate_focus.png [new file with mode: 0644]
themes/qdarkstyle/rc/checkbox_unchecked.png [new file with mode: 0644]
themes/qdarkstyle/rc/checkbox_unchecked_disabled.png [new file with mode: 0644]
themes/qdarkstyle/rc/checkbox_unchecked_focus.png [new file with mode: 0644]
themes/qdarkstyle/rc/close-hover.png [new file with mode: 0644]
themes/qdarkstyle/rc/close-pressed.png [new file with mode: 0644]
themes/qdarkstyle/rc/close.png [new file with mode: 0644]
themes/qdarkstyle/rc/down_arrow.png [new file with mode: 0644]
themes/qdarkstyle/rc/down_arrow_disabled.png [new file with mode: 0644]
themes/qdarkstyle/rc/left_arrow.png [new file with mode: 0644]
themes/qdarkstyle/rc/left_arrow_disabled.png [new file with mode: 0644]
themes/qdarkstyle/rc/radio_checked.png [new file with mode: 0644]
themes/qdarkstyle/rc/radio_checked_disabled.png [new file with mode: 0644]
themes/qdarkstyle/rc/radio_checked_focus.png [new file with mode: 0644]
themes/qdarkstyle/rc/radio_unchecked.png [new file with mode: 0644]
themes/qdarkstyle/rc/radio_unchecked_disabled.png [new file with mode: 0644]
themes/qdarkstyle/rc/radio_unchecked_focus.png [new file with mode: 0644]
themes/qdarkstyle/rc/right_arrow.png [new file with mode: 0644]
themes/qdarkstyle/rc/right_arrow_disabled.png [new file with mode: 0644]
themes/qdarkstyle/rc/sizegrip.png [new file with mode: 0644]
themes/qdarkstyle/rc/stylesheet-branch-end.png [new file with mode: 0644]
themes/qdarkstyle/rc/stylesheet-branch-more.png [new file with mode: 0644]
themes/qdarkstyle/rc/stylesheet-vline.png [new file with mode: 0644]
themes/qdarkstyle/rc/transparent.png [new file with mode: 0644]
themes/qdarkstyle/rc/undock.png [new file with mode: 0644]
themes/qdarkstyle/rc/up_arrow.png [new file with mode: 0644]
themes/qdarkstyle/rc/up_arrow_disabled.png [new file with mode: 0644]
themes/qdarkstyle/style.qss [new file with mode: 0644]

index ea272ee6147c921755ec9a57ff4986c91f0ef36d..59d7aa2fc423b4511548bddc9616dcade48ce93f 100644 (file)
@@ -50,3 +50,5 @@ android/src/org/kde/
 # QtCreator cruft
 /CMakeLists.txt.user*
 *.cbp
+
+doxy
index af39c89e19f8b9f9f5e251426d3b10bb8ba9d01b..b74792de7a1d33b3267867f291ea04d3bf0fbce7 100644 (file)
@@ -24,16 +24,27 @@ include(GNUInstallDirs)
 
 project(pulseview)
 
+# Let AUTOMOC and AUTOUIC process GENERATED files.
+if(POLICY CMP0071)
+       cmake_policy(SET CMP0071 NEW)
+endif()
+
+# Only interpret if() arguments as variables or keywords when unquoted.
+if(POLICY CMP0054)
+       cmake_policy(SET CMP0054 NEW)
+endif()
+
 list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMake")
 
 #===============================================================================
 #= User Options
 #-------------------------------------------------------------------------------
 
-option(DISABLE_WERROR "Build without -Werror" FALSE)
+option(DISABLE_WERROR "Build without -Werror" TRUE)
 option(ENABLE_SIGNALS "Build with UNIX signals" TRUE)
+option(ENABLE_STACKTRACE "Enable stack trace when crashing" FALSE)
 option(ENABLE_DECODE "Build with libsigrokdecode" TRUE)
-option(ENABLE_TESTS "Enable unit tests" TRUE)
+option(ENABLE_TESTS "Enable unit tests" FALSE)
 option(STATIC_PKGDEPS_LIBS "Statically link to (pkg-config) libraries" FALSE)
 
 if(WIN32)
@@ -51,6 +62,12 @@ if(NOT CMAKE_BUILD_TYPE)
        FORCE)
 endif()
 
+#===============================================================================
+#= Documentation
+#-------------------------------------------------------------------------------
+
+add_subdirectory(manual)
+
 #===============================================================================
 #= Dependencies
 #-------------------------------------------------------------------------------
@@ -58,10 +75,11 @@ endif()
 list(APPEND PKGDEPS glib-2.0>=2.28.0)
 list(APPEND PKGDEPS glibmm-2.4>=2.28.0)
 
-list(APPEND PKGDEPS libsigrokcxx>=0.5.0)
+set(LIBSR_CXX_BINDING "libsigrokcxx>=0.5.1")
+list(APPEND PKGDEPS "${LIBSR_CXX_BINDING}")
 
 if(ENABLE_DECODE)
-       list(APPEND PKGDEPS libsigrokdecode>=0.5.0)
+       list(APPEND PKGDEPS libsigrokdecode>=0.5.2)
 endif()
 
 if(ANDROID)
@@ -69,6 +87,10 @@ if(ANDROID)
 endif()
 
 find_package(PkgConfig)
+pkg_check_modules(LIBSRCXX QUIET ${LIBSR_CXX_BINDING})
+if(NOT LIBSRCXX_FOUND OR NOT LIBSRCXX_VERSION)
+       message(FATAL_ERROR "libsigrok C++ bindings missing, check libsigrok's 'configure' output (missing dependencies?)")
+endif()
 pkg_check_modules(PKGDEPS REQUIRED ${PKGDEPS})
 
 set(CMAKE_AUTOMOC TRUE)
@@ -78,7 +100,10 @@ find_package(Qt5 COMPONENTS Core Gui Widgets Svg REQUIRED)
 if(WIN32)
        # MXE workaround: Use pkg-config to find Qt5 libs.
        # https://github.com/mxe/mxe/issues/1642
-       pkg_check_modules(QT5ALL REQUIRED Qt5Widgets Qt5Gui Qt5Svg)
+       # Not required (and doesn't work) on MSYS2.
+       if(NOT DEFINED ENV{MSYSTEM})
+               pkg_check_modules(QT5ALL REQUIRED Qt5Widgets Qt5Gui Qt5Svg)
+       endif()
 endif()
 
 set(QT_LIBRARIES Qt5::Gui Qt5::Widgets Qt5::Svg)
@@ -87,7 +112,12 @@ set(BOOSTCOMPS filesystem serialization system)
 if(ENABLE_TESTS)
        list(APPEND BOOSTCOMPS unit_test_framework)
 endif()
-find_package(Boost 1.55 COMPONENTS ${BOOSTCOMPS} REQUIRED)
+
+if(ENABLE_STACKTRACE)
+       find_package(Boost 1.65.1 COMPONENTS ${BOOSTCOMPS} REQUIRED)
+else()
+       find_package(Boost 1.55 COMPONENTS ${BOOSTCOMPS} REQUIRED)
+endif()
 
 # Find the platform's thread library (needed for C++11 threads).
 # This will set ${CMAKE_THREAD_LIBS_INIT} to the correct, OS-specific value.
@@ -153,7 +183,7 @@ memaccess_check_unaligned_le(HAVE_UNALIGNED_LITTLE_ENDIAN_ACCESS)
 #-------------------------------------------------------------------------------
 
 set(PV_TITLE PulseView)
-set(PV_VERSION_STRING "0.4.0")
+set(PV_VERSION_STRING "0.5.0")
 
 set(PV_GLIBMM_VERSION ${PKGDEPS_glibmm-2.4_VERSION})
 
@@ -167,6 +197,12 @@ if(NOT PV_TAG_VERSION_STRING)
                string(SUBSTRING "${PV_HASH}" 0 7 PV_SHORTHASH)
                set(PV_VERSION_STRING "${PV_VERSION_STRING}-git-${PV_SHORTHASH}")
        endif()
+
+       # Non-tagged releases use the unstable manual
+       set(PV_MANUAL_VERSION "unstable")
+else()
+       # Tagged releases use a fixed manual version
+       set(PV_MANUAL_VERSION ${PV_VERSION_STRING})
 endif()
 
 if(PV_VERSION_STRING MATCHES "^([0-9]+)\\.([0-9]+)\\.([0-9]+)(-[-0-9a-z]*)?$")
@@ -176,7 +212,7 @@ if(PV_VERSION_STRING MATCHES "^([0-9]+)\\.([0-9]+)\\.([0-9]+)(-[-0-9a-z]*)?$")
        set(PV_VERSION_SUFFIX ${CMAKE_MATCH_4})
 endif()
 
-message("-- ${PV_TITLE} version: ${PV_VERSION_STRING}")
+message(STATUS "${PV_TITLE} version: ${PV_VERSION_STRING}")
 
 configure_file (
        ${PROJECT_SOURCE_DIR}/config.h.in
@@ -192,6 +228,7 @@ set(pulseview_SOURCES
        pv/application.cpp
        pv/devicemanager.cpp
        pv/globalsettings.cpp
+       pv/logging.cpp
        pv/mainwindow.cpp
        pv/session.cpp
        pv/storesession.cpp
@@ -234,7 +271,6 @@ set(pulseview_SOURCES
        pv/views/trace/rowitem.cpp
        pv/views/trace/ruler.cpp
        pv/views/trace/signal.cpp
-       pv/views/trace/signalscalehandle.cpp
        pv/views/trace/timeitem.cpp
        pv/views/trace/timemarker.cpp
        pv/views/trace/trace.cpp
@@ -251,8 +287,8 @@ set(pulseview_SOURCES
        pv/views/trace/viewwidget.cpp
        pv/views/viewbase.cpp
        pv/views/trace/standardbar.cpp
-       pv/widgets/colourbutton.cpp
-       pv/widgets/colourpopup.cpp
+       pv/widgets/colorbutton.cpp
+       pv/widgets/colorpopup.cpp
        pv/widgets/devicetoolbutton.cpp
        pv/widgets/exportmenu.cpp
        pv/widgets/importmenu.cpp
@@ -265,6 +301,7 @@ set(pulseview_SOURCES
 
 # This list includes only QObject derived class headers.
 set(pulseview_HEADERS
+       pv/logging.hpp
        pv/globalsettings.hpp
        pv/mainwindow.hpp
        pv/session.hpp
@@ -297,7 +334,6 @@ set(pulseview_HEADERS
        pv/views/trace/rowitem.hpp
        pv/views/trace/ruler.hpp
        pv/views/trace/signal.hpp
-       pv/views/trace/signalscalehandle.hpp
        pv/views/trace/timeitem.hpp
        pv/views/trace/timemarker.hpp
        pv/views/trace/trace.hpp
@@ -310,8 +346,8 @@ set(pulseview_HEADERS
        pv/views/trace/viewwidget.hpp
        pv/views/viewbase.hpp
        pv/views/trace/standardbar.hpp
-       pv/widgets/colourbutton.hpp
-       pv/widgets/colourpopup.hpp
+       pv/widgets/colorbutton.hpp
+       pv/widgets/colorpopup.hpp
        pv/widgets/devicetoolbutton.hpp
        pv/widgets/exportmenu.hpp
        pv/widgets/importmenu.hpp
@@ -334,7 +370,7 @@ endif()
 if(ENABLE_DECODE)
        list(APPEND pulseview_SOURCES
                pv/binding/decoder.cpp
-               pv/data/decoderstack.cpp
+               pv/data/decodesignal.cpp
                pv/data/decode/annotation.cpp
                pv/data/decode/decoder.cpp
                pv/data/decode/row.cpp
@@ -345,7 +381,7 @@ if(ENABLE_DECODE)
        )
 
        list(APPEND pulseview_HEADERS
-               pv/data/decoderstack.hpp
+               pv/data/decodesignal.hpp
                pv/views/trace/decodetrace.hpp
                pv/widgets/decodergroupbox.hpp
                pv/widgets/decodermenu.hpp
@@ -390,6 +426,10 @@ if(ENABLE_SIGNALS)
        add_definitions(-DENABLE_SIGNALS)
 endif()
 
+if(ENABLE_STACKTRACE)
+       add_definitions(-DENABLE_STACKTRACE)
+endif()
+
 #===============================================================================
 #= Global Include Directories
 #-------------------------------------------------------------------------------
@@ -430,12 +470,20 @@ endif()
 if(WIN32)
        # On Windows we need to statically link the libqsvg imageformat
        # plugin (and the QtSvg component) for SVG graphics/icons to work.
-       # We also need QWindowsIntegrationPlugin, Qt5PlatformSupport, and all
-       # Qt libs and their dependencies.
+       # We also need QWindowsIntegrationPlugin, Qt5PlatformSupport (only for
+       # Qt < 5.8.0), and all Qt libs and their dependencies.
        add_definitions(-DQT_STATICPLUGIN)
        list(APPEND PULSEVIEW_LINK_LIBS Qt5::QSvgPlugin)
        list(APPEND PULSEVIEW_LINK_LIBS Qt5::QWindowsIntegrationPlugin)
-       list(APPEND PULSEVIEW_LINK_LIBS -lQt5PlatformSupport ${QT5ALL_LDFLAGS})
+       if(Qt5Gui_VERSION VERSION_LESS 5.8.0)
+               list(APPEND PULSEVIEW_LINK_LIBS -lQt5PlatformSupport)
+       endif()
+       list(APPEND PULSEVIEW_LINK_LIBS ${QT5ALL_LDFLAGS})
+endif()
+
+if(ENABLE_STACKTRACE)
+       # Needed to resolve dladdr.
+       list(APPEND PULSEVIEW_LINK_LIBS "-ldl")
 endif()
 
 if(ANDROID)
@@ -450,7 +498,7 @@ endif()
 
 target_link_libraries(${PROJECT_NAME} ${PULSEVIEW_LINK_LIBS})
 
-if(WIN32)
+if(WIN32 AND NOT ${CMAKE_BUILD_TYPE} STREQUAL "Debug")
        # Pass -mwindows so that no "DOS box" opens when PulseView is started.
        set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-mwindows")
 endif()
diff --git a/HACKING b/HACKING
index d539681c38026150e448da04f490c4e64fca3790..32c10dddbb280bc47d92971ca465b6fd75d3b3ad 100644 (file)
--- a/HACKING
+++ b/HACKING
@@ -5,8 +5,9 @@ HACKING
 Coding style
 ------------
 
-This project is programmed using the Linux kernel coding style, see
-http://lxr.linux.no/linux/Documentation/CodingStyle for details.
+This project is programmed using the Linux kernel coding style:
+
+  https://www.kernel.org/doc/html/latest/process/coding-style.html
 
 Please use the same style for any code contributions, thanks!
 
@@ -17,15 +18,16 @@ OK (in order to meet Qt/C++ related guidelines, for example).
 Contributions
 -------------
 
- - Patches should be sent to the development mailinglist at
+ - In order to contribute you should ideally clone the git repository and
+   let us know (preferably via IRC, or via the mailing list) from where to
+   pull/review your changes. You can use github.com, or any other public git
+   hosting site.
+
+ - Alternatively, patches can be sent to the development mailinglist at
    sigrok-devel@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
 ------------
diff --git a/INSTALL b/INSTALL
index 3f883e9731f45a80ca629abcb7fd8f6497e1349c..edb7940d522c9126e1edcbf8579e1d54fd2762f7 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -22,9 +22,12 @@ Requirements
     - libboost-filesystem
     - libboost-serialization
     - libboost-test (optional, only needed to run the unit tests)
- - libsigrokcxx >= 0.5.0 (libsigrok C++ bindings)
- - libsigrokdecode >= 0.5.0
+    - libboost-stacktrace (optional, only needed for debugging)
+ - libsigrokcxx >= 0.5.1 (libsigrok C++ bindings)
+ - libsigrokdecode >= 0.5.2
  - libsigrokandroidutils >= 0.1.0 (optional, only needed on Android)
+ - asciidoctor (optional, only needed to build the HTML manual)
+ - asciidoctor-pdf (optional, only needed to build the PDF manual)
 
 
 Building and installing
@@ -56,3 +59,21 @@ In order to build a source package begin with an unconfigured source tree.
  $ cmake ..
  $ make package_source
 
+
+Generating the manual
+---------------------
+
+To generate the HTML manual, you only need Asciidoctor. If you also want
+to generate the PDF manual, you need to install asciidoctor-pdf as well
+and make it available for execution:
+
+ $ gem install --pre asciidoctor-pdf
+ $ export PATH=~/.gem/ruby/2.3.0/bin:$PATH
+
+Then, to build the PulseView manual, run:
+
+ $ make manual
+
+Note: The stylesheet used is a lightly modified version of "Read The Docs"
+from the Asciidoctor stylesheet factory:
+https://asciidoctor.org/docs/user-manual/#stylesheet-factory
diff --git a/README b/README
index 613ecc5c2faba3be33fe45f831586aaa474eaeb2..8608b3bf7314024ff15b5c0f0e13eca510bde5b9 100644 (file)
--- a/README
+++ b/README
@@ -44,11 +44,26 @@ is to be interpreted as
  Copyright (C) 2010,2011,2012,2013 Contributor Name
 
 
-Icons authors and licenses
---------------------------
+Resource authors and licenses
+-----------------------------
 
 icons/information.svg: Bobarino
   https://en.wikipedia.org/wiki/File:Information.svg
+  License:
+    GFDL 1.2 or later / CC-BY-SA 3.0
+    https://en.wikipedia.org/wiki/File:Information.svg#Licensing
+
+QDarkStyleSheet: Colin Duquesnoy
+  https://github.com/ColinDuquesnoy/QDarkStyleSheet
+  License:
+    CC-BY 4.0
+    https://github.com/ColinDuquesnoy/QDarkStyleSheet/blob/master/LICENSE.md
+
+DarkStyle: Juergen Skrotzky
+  https://github.com/Jorgen-VikingGod/Qt-Frameless-Window-DarkStyle
+  License:
+    MIT license
+    https://github.com/Jorgen-VikingGod/Qt-Frameless-Window-DarkStyle#licence
 
 
 Mailing list
index 2c6674cda51cdcd6a398198525c301a24acf3462..c9953a6e2894e21e924e99d8ffa4fbefa2b8efb7 100644 (file)
 
 #include <android/log.h>
 
-#include <stdint.h>
+#include <cstdint>
 #include <libsigrok/libsigrok.h>
 
 #include "android/loghandler.hpp"
 
 namespace pv {
 
+static sr_log_callback prev_sr_log_cb;
+static void *prev_sr_log_cb_data;
+
+#ifdef ENABLE_DECODE
+static srd_log_callback prev_srd_log_cb;
+static void *prev_srd_log_cb_data;
+#endif
+
 int AndroidLogHandler::sr_callback(void *cb_data, int loglevel, const char *format, va_list args)
 {
        static const int prio[] = {
@@ -40,11 +48,18 @@ int AndroidLogHandler::sr_callback(void *cb_data, int loglevel, const char *form
                [SR_LOG_DBG] = ANDROID_LOG_DEBUG,
                [SR_LOG_SPEW] = ANDROID_LOG_VERBOSE,
        };
+       va_list args2;
        int ret;
 
        /* This specific log callback doesn't need the void pointer data. */
        (void)cb_data;
 
+       /* Call the previously registered log callback (library's default). */
+       va_copy(args2, args);
+       if (prev_sr_log_cb)
+               prev_sr_log_cb(prev_sr_log_cb_data, loglevel, format, args2);
+       va_end(args2);
+
        /* Only output messages of at least the selected loglevel(s). */
        if (loglevel > sr_log_loglevel_get())
                return SR_OK;
@@ -70,11 +85,18 @@ int AndroidLogHandler::srd_callback(void *cb_data, int loglevel, const char *for
                [SRD_LOG_DBG] = ANDROID_LOG_DEBUG,
                [SRD_LOG_SPEW] = ANDROID_LOG_VERBOSE,
        };
+       va_list args2;
        int ret;
 
        /* This specific log callback doesn't need the void pointer data. */
        (void)cb_data;
 
+       /* Call the previously registered log callback (library's default). */
+       va_copy(args2, args);
+       if (prev_srd_log_cb)
+               prev_srd_log_cb(prev_srd_log_cb_data, loglevel, format, args2);
+       va_end(args2);
+
        /* Only output messages of at least the selected loglevel(s). */
        if (loglevel > srd_log_loglevel_get())
                return SRD_OK;
@@ -94,8 +116,10 @@ int AndroidLogHandler::srd_callback(void *cb_data, int loglevel, const char *for
 
 void AndroidLogHandler::install_callbacks()
 {
+       sr_log_callback_get(&prev_sr_log_cb, &prev_sr_log_cb_data);
        sr_log_callback_set(sr_callback, nullptr);
 #ifdef ENABLE_DECODE
+       srd_log_callback_get(&prev_srd_log_cb, &prev_srd_log_cb_data);
        srd_log_callback_set(srd_callback, nullptr);
 #endif
 }
index 7bdfbf309c1e1600136cd8d4079911b033a6c3c0..f76276e27e3eeebc183cdc4f43327cb9c09e40f3 100644 (file)
@@ -7,4 +7,4 @@ Comment=Control your logic analyzer, oscilloscope, or MSO
 Exec=pulseview
 Icon=pulseview
 Type=Application
-MimeType=application/vnd.sigrok.session
+MimeType=application/vnd.sigrok.session;
index 13a45fe18435746f1f46c1062dd611a2ecb6b2cd..fdcdf49ebf667e406192de01869e04d65c30d885 100644 (file)
@@ -81,7 +81,7 @@ RequestExecutionLevel admin
 !define SHCNF_IDLIST 0
 
 
-# --- Functions ---------------------------------------------------------------
+# --- Functions/Macros --------------------------------------------------------
 
 Function register_sr_files
        ${registerExtension} "$INSTDIR\pulseview.exe" ".sr" "sigrok session file"
@@ -90,6 +90,12 @@ Function register_sr_files
        System::Call 'Shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_IDLIST}, i 0, i 0)'
 FunctionEnd
 
+# Inspired by http://nsis.sourceforge.net/Create_Internet_Shorcuts_during_installation
+!Macro "CreateURL" "URLFile" "URLSite" "URLDesc"
+       WriteINIStr "$INSTDIR\${URLFile}.URL" "InternetShortcut" "URL" "${URLSite}"
+       CreateShortCut "$SMPROGRAMS\sigrok\PulseView\${URLFile}.lnk" "$INSTDIR\${URLFile}.url" "" \
+               "$INSTDIR\pulseview.exe" 0 "SW_SHOWNORMAL" "" "${URLDesc}"
+!MacroEnd
 
 # --- MUI pages ---------------------------------------------------------------
 
@@ -177,6 +183,12 @@ Section "PulseView (required)" Section1
                0 SW_SHOWNORMAL \
                "" "Open-source, portable sigrok GUI"
 
+       # Create a shortcut for the PulseView application running in debug mode.
+       CreateShortCut "$SMPROGRAMS\sigrok\PulseView\PulseView (Debug).lnk" \
+               "$INSTDIR\pulseview.exe" "-l 5" "$INSTDIR\pulseview.exe" \
+               0 SW_SHOWNORMAL \
+               "" "Open-source, portable sigrok GUI (debug log level)"
+
        # Create a shortcut for the uninstaller.
        CreateShortCut "$SMPROGRAMS\sigrok\PulseView\Uninstall PulseView.lnk" \
                "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0 \
@@ -192,6 +204,10 @@ Section "PulseView (required)" Section1
                "$INSTDIR\zadig_xp.exe" "" "$INSTDIR\zadig_xp.exe" 0 \
                SW_SHOWNORMAL "" "Zadig (PulseView, Win XP)"
 
+       # Create shortcuts to the HTML and PDF manuals, respectively.
+       !InsertMacro "CreateURL" "PulseView HTML manual" "https://sigrok.org/doc/pulseview/@PV_MANUAL_VERSION@/manual.html" "PulseView HTML manual"
+       !InsertMacro "CreateURL" "PulseView PDF manual" "https://sigrok.org/doc/pulseview/@PV_MANUAL_VERSION@/manual.pdf" "PulseView PDF manual"
+
        # Create registry keys for "Add/remove programs" in the control panel.
        WriteRegStr HKLM "${REGSTR}" "DisplayName" "PulseView"
        WriteRegStr HKLM "${REGSTR}" "UninstallString" \
@@ -267,6 +283,10 @@ Section "Uninstall"
        # Delete the example *.sr files.
        RMDir /r "$INSTDIR\examples\*"
 
+       # Delete the URL files for the manual.
+       Delete "$INSTDIR\PulseView HTML manual.url"
+       Delete "$INSTDIR\PulseView PDF manual.url"
+
        # Delete the install directory and its sub-directories.
        RMDir "$INSTDIR\share"
        RMDir "$INSTDIR\examples"
@@ -274,11 +294,16 @@ Section "Uninstall"
 
        # Delete the links from the start menu.
        Delete "$SMPROGRAMS\sigrok\PulseView\PulseView.lnk"
+       Delete "$SMPROGRAMS\sigrok\PulseView\PulseView (Debug).lnk"
        Delete "$SMPROGRAMS\sigrok\PulseView\Uninstall PulseView.lnk"
        Delete "$SMPROGRAMS\sigrok\PulseView\Zadig (PulseView).lnk"
        Delete "$SMPROGRAMS\sigrok\PulseView\Zadig (PulseView, Win XP).lnk"
        Delete "$SMPROGRAMS\sigrok\PulseView\Examples (PulseView).lnk"
 
+       # Delete the links to the manual.
+       Delete "$SMPROGRAMS\sigrok\PulseView\PulseView HTML manual.lnk"
+       Delete "$SMPROGRAMS\sigrok\PulseView\PulseView PDF manual.lnk"
+
        # Delete the sub-directory in the start menu.
        RMDir "$SMPROGRAMS\sigrok\PulseView"
        RMDir "$SMPROGRAMS\sigrok"
index ad1cf59556974e7c8ae7b6bfb27e0276e4f2809f..8289eaa85f858becb08069c3b67a4e742105402c 100644 (file)
@@ -1,4 +1,4 @@
-.TH PULSEVIEW 1 "June 6, 2017"
+.TH PULSEVIEW 1 "March 30, 2018"
 .SH "NAME"
 PulseView \- Qt-based LA/scope/MSO GUI for sigrok
 .SH "SYNOPSIS"
@@ -41,6 +41,20 @@ Show a help text and exit.
 .B "\-V, \-\-version"
 Show version information and exit.
 .TP
+.BR "\-d, \-\-driver " <drivername>
+Specify the capture device to connect to. If the
+.B \-\-driver
+option is not supplied, PulseView attempts to re-connect to the
+most recently used device, or auto-detect available devices.
+.TP
+.BR "\-D, \-\-dont\-scan "
+Usually PulseView automatically scans all drivers to find suitable
+devices during program startup. This option disables the auto-scan.
+Users can either specify the
+.B \-\-driver
+option to pick a device at startup, or interactively scan for devices
+after PulseView has finished starting up.
+.TP
 .BR "\-i, \-\-input\-file " <filename>
 Load input from a file. If the
 .B \-\-input\-format
diff --git a/icons/help-browser.png b/icons/help-browser.png
new file mode 100644 (file)
index 0000000..593ecd5
Binary files /dev/null and b/icons/help-browser.png differ
diff --git a/icons/media-playback-pause.png b/icons/media-playback-pause.png
new file mode 100644 (file)
index 0000000..ee40fc2
Binary files /dev/null and b/icons/media-playback-pause.png differ
diff --git a/icons/media-playback-start.png b/icons/media-playback-start.png
new file mode 100644 (file)
index 0000000..10102d8
Binary files /dev/null and b/icons/media-playback-start.png differ
diff --git a/icons/settings-general.png b/icons/settings-general.png
new file mode 100644 (file)
index 0000000..4decc89
Binary files /dev/null and b/icons/settings-general.png differ
diff --git a/icons/view-displaymode-last_complete_segment.svg b/icons/view-displaymode-last_complete_segment.svg
new file mode 100644 (file)
index 0000000..125369e
--- /dev/null
@@ -0,0 +1,521 @@
+<?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"
+   width="35.433071"
+   height="35.433071"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.5 r10040"
+   sodipodi:docname="view-displaymode-last_complete_segment.svg">
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="15.465778"
+     inkscape:cx="7.8560557"
+     inkscape:cy="17.231594"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="mm"
+     inkscape:window-width="1369"
+     inkscape:window-height="743"
+     inkscape:window-x="174"
+     inkscape:window-y="153"
+     inkscape:window-maximized="0" />
+  <defs
+     id="defs4" />
+  <metadata
+     id="metadata7">
+    <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
+     inkscape:groupmode="layer"
+     id="layer2"
+     inkscape:label="Layer"
+     style="display:none">
+    <g
+       transform="translate(0,-1016.9291)"
+       style="display:inline"
+       id="g7555-6">
+      <path
+         inkscape:connector-curvature="0"
+         id="path3768-2-3-5-9"
+         d="m 0,1044.2466 35.26169,0"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3768-2-6"
+         d="m 0,1020.9238 35.26169,0"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3768-1-9"
+         d="m 0,1029.0753 35.26169,0"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-29"
+         d="m 3.8604174,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-0-6"
+         d="m 7.7208349,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-5-8"
+         d="m 11.581253,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-9-14"
+         d="m 15.441669,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-7-90"
+         d="m 19.302086,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-3-32"
+         d="m 23.162504,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-1-3"
+         d="m 27.022922,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-90-6"
+         d="m 30.883338,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-2-4"
+         d="m 34.743757,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-49"
+         d="m 0,1017.11 0,15.8294"
+         style="fill:none;stroke:#000000;stroke-width:0.3772082;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3768-9"
+         d="m 0,1024.9996 35.26169,0"
+         style="fill:none;stroke:#000000;stroke-width:0.3772082;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         id="path4190-3"
+         title="sin(x)"
+         d="m 0.17138129,1025.0901 c 0.18430284,-0.3999 0.36860568,-0.8 0.55290852,-1.1919 0.18430286,-0.3921 0.36860569,-0.7762 0.55290859,-1.1447 0.1843028,-0.3686 0.3686057,-0.7216 0.5529085,-1.0521 0.1843028,-0.3303 0.3686057,-0.6383 0.5529085,-0.9175 0.1843029,-0.2794 0.3686057,-0.5299 0.5529085,-0.7468 0.1843029,-0.2172 0.3686057,-0.4004 0.5529086,-0.5466 0.1843028,-0.1461 0.3686056,-0.2549 0.5529085,-0.3244 0.1843028,-0.069 0.3686057,-0.1 0.5529085,-0.09 0.1843029,0.011 0.3686057,0.06 0.5529085,0.1489 0.1843029,0.09 0.3686057,0.217 0.5529086,0.3815 0.1843028,0.1645 0.3686056,0.3655 0.5529085,0.5989 0.1843028,0.2335 0.3686057,0.4995 0.5529085,0.7927 0.1843028,0.2931 0.3686057,0.6135 0.5529085,0.9549 0.1843029,0.3412 0.3686057,0.7034 0.5529086,1.0792 0.1843028,0.3758 0.3686056,0.7653 0.5529085,1.1608 0.1843028,0.3956 0.3686057,0.7969 0.5529085,1.1964 0.1843028,0.3994 0.3686057,0.7969 0.5529085,1.1845 0.1843029,0.3876 0.3686059,0.7653 0.5529088,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208 0.184302,-0.031 0.368605,-0.019 0.552908,0.031 0.184303,0.05 0.368606,0.1392 0.552909,0.2666 0.184303,0.1274 0.368605,0.2927 0.552908,0.4926 0.184303,0.2001 0.368606,0.4347 0.552909,0.6993 0.184303,0.2646 0.368605,0.5594 0.552908,0.8781 0.184303,0.3189 0.368606,0.6618 0.552909,1.0222 0.184303,0.3603 0.368605,0.738 0.552908,1.1256 0.184303,0.3876 0.368606,0.785 0.552909,1.1845 0.184303,0.3994 0.368606,0.8009 0.552908,1.1963 0.184303,0.3955 0.368606,0.7851 0.552909,1.161 0.184303,0.3758 0.368606,0.7379 0.552908,1.0792 0.184303,0.3411 0.368606,0.6616 0.552909,0.9548 0.184303,0.2932 0.368606,0.5592 0.552908,0.7927 0.184303,0.2334 0.368606,0.4343 0.552909,0.5988 0.184303,0.1645 0.368606,0.2926 0.552909,0.3816 0.184302,0.089 0.368605,0.1389 0.552908,0.1489 0.184303,0.011 0.368606,-0.02 0.552909,-0.09 0.184302,-0.069 0.368605,-0.1784 0.552908,-0.3246 0.184303,-0.1461 0.368606,-0.3295 0.552909,-0.5464 0.184302,-0.217 0.368605,-0.4676 0.552908,-0.7469 0.184303,-0.2792 0.368606,-0.5872 0.552909,-0.9177 0.184302,-0.3303 0.368605,-0.6834 0.552908,-1.0519 0.184303,-0.3686 0.368606,-0.7526 0.552909,-1.1446 0.184303,-0.392 0.368605,-0.7921 0.552908,-1.1919"
+         style="fill:none;stroke:#2e0dff;stroke-width:0.30176657;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+         inkscape:connector-curvature="0" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3768-2-3-7"
+         d="m 0,1040.0689 35.26169,0"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3768-1-5-1"
+         d="m 0,1048.373 35.26169,0"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-38-0"
+         d="m 3.860417,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-0-5-8"
+         d="m 7.720835,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-5-0-7"
+         d="m 11.581253,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-9-1-2"
+         d="m 15.441669,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-7-9-8"
+         d="m 19.302086,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-3-3-8"
+         d="m 23.162504,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-1-4-9"
+         d="m 27.022922,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-90-5-2"
+         d="m 30.883338,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-2-0-4"
+         d="m 34.743757,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-6-0"
+         d="m 0,1036.1837 0,16.1257"
+         style="fill:none;stroke:#000000;stroke-width:0.38072109;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3768-4-0"
+         d="m 0,1052.2209 35.26169,0"
+         style="fill:none;stroke:#000000;stroke-width:0.38072109;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         inkscape:connector-curvature="0"
+         style="fill:none;stroke:#ff970d;stroke-width:0.30000001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+         d="m 0.25968796,1051.8993 c 0.18450185,0 0.3690037,0 0.55350556,0 0.18450185,0 0.36900368,0 0.55350558,0 0.1845018,0 0.3690037,0 0.5535055,0 0.1845019,0 0.3690037,0 0.5535056,0 0.1845018,0 0.3690037,0 0.5535055,0 0.1845019,0 0.3690037,0 0.5535056,0 0.1845018,-10e-5 0.3690037,-10e-5 0.5535055,-3e-4 0.1845019,-10e-5 0.3690037,-3e-4 0.5535056,-7e-4 0.1845018,-4e-4 0.3690037,-10e-4 0.5535055,0 0.1845019,0 0.3690038,0 0.5535056,-0.01 0.1845019,0 0.3690037,-0.01 0.5535056,-0.011 0.1845018,-0.01 0.3690037,-0.012 0.5535055,-0.022 0.1845019,-0.01 0.3690037,-0.023 0.5535056,-0.041 0.1845018,-0.018 0.3690037,-0.041 0.5535055,-0.072 0.1845019,-0.031 0.3690037,-0.069 0.5535056,-0.1174 0.1845018,-0.049 0.3690037,-0.1081 0.5535055,-0.1816 0.1845019,-0.073 0.3690037,-0.1613 0.5535056,-0.267 0.1845018,-0.1057 0.3690036,-0.2294 0.5535056,-0.3745 0.184502,-0.1451 0.369003,-0.3119 0.553505,-0.5028 0.184502,-0.1909 0.369004,-0.4062 0.553506,-0.6472 0.184502,-0.241 0.369003,-0.5079 0.553505,-0.8001 0.184502,-0.2922 0.369004,-0.6099 0.553506,-0.9503 0.184502,-0.3405 0.369004,-0.7038 0.553505,-1.0848 0.184502,-0.3811 0.369004,-0.7798 0.553506,-1.1888 0.184502,-0.409 0.369004,-0.8281 0.553506,-1.2479 0.184501,-0.4198 0.369003,-0.8402 0.553505,-1.2503 0.184502,-0.41 0.369004,-0.8095 0.553506,-1.1872 0.184502,-0.3777 0.369003,-0.7333 0.553505,-1.0561 0.184502,-0.3227 0.369004,-0.6123 0.553506,-0.8596 0.184502,-0.2473 0.369003,-0.452 0.553505,-0.6075 0.184502,-0.1555 0.369004,-0.2614 0.553506,-0.3145 0.184502,-0.053 0.369004,-0.053 0.553505,0 0.184502,0.053 0.369004,0.1591 0.553506,0.3145 0.184502,0.1554 0.369004,0.3603 0.553506,0.6075 0.184501,0.2472 0.369003,0.537 0.553505,0.8596 0.184502,0.3227 0.369004,0.6784 0.553506,1.0561 0.184502,0.3776 0.369003,0.7772 0.553505,1.1872 0.184502,0.4101 0.369004,0.8305 0.553506,1.2503 0.184502,0.4198 0.369003,0.8389 0.553505,1.2479 0.184502,0.409 0.369004,0.8077 0.553506,1.1888 0.184502,0.381 0.369004,0.7443 0.553505,1.0848 0.184502,0.3405 0.369004,0.6581 0.553506,0.9503 0.184502,0.2923 0.369004,0.5591 0.553506,0.8001 0.184501,0.241 0.369003,0.4563 0.553505,0.6472 0.184502,0.191 0.369004,0.3576 0.553506,0.5028 0.184502,0.1452 0.369003,0.2688 0.553505,0.3745 0.184502,0.1057 0.369004,0.1934 0.553506,0.267 0.184502,0.074 0.369003,0.1328 0.553505,0.1816 0.184502,0.049 0.369004,0.087 0.553506,0.1174 0.184502,0.031 0.369004,0.054 0.553505,0.072 0.184502,0.018 0.369004,0.031 0.553506,0.041 0.184502,0.01 0.369004,0.017 0.553506,0.022 0.184501,0.01 0.369003,0.01 0.553505,0.011 0.184502,0 0.369004,0 0.553506,0.01 0.184502,10e-4 0.369003,0 0.553505,0 0.184502,4e-4 0.369004,6e-4 0.553506,7e-4 0.184502,2e-4 0.369003,2e-4 0.553505,3e-4 0.184502,0 0.369004,0 0.553506,0 0.184502,0 0.369004,0 0.553505,0 0.184502,0 0.369004,0 0.553506,0 0.184502,0 0.369004,0 0.553506,0 0.184501,0 0.369003,0 0.553505,0 0.184502,0 0.369004,0 0.553506,0"
+         title="pow(sin(x), 10)"
+         id="path7506-5" />
+    </g>
+  </g>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1016.9291)"
+     style="display:inline">
+    <rect
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+       id="rect4735"
+       width="21.984022"
+       height="15.841428"
+       x="-0.12931778"
+       y="1017.1877" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m -0.38664415,1020.9238 22.23492015,0"
+       id="path3768-2"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m -0.25776149,1029.0753 22.04169149,0"
+       id="path3768-1"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 3.8604174,1017.11 0,15.8294"
+       id="path3755-4"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 7.7208349,1017.11 0,15.8294"
+       id="path3755-4-0"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 11.581253,1017.11 0,15.8294"
+       id="path3755-4-5"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 15.441669,1017.11 0,15.8294"
+       id="path3755-4-9"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 19.302086,1017.11 0,15.8294"
+       id="path3755-4-7"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m -0.25689803,1024.9996 22.01207903,0"
+       id="path3768"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 0.17138129,1025.0901 c 0.18430284,-0.3999 0.36860568,-0.8 0.55290852,-1.1919 0.18430286,-0.3921 0.36860569,-0.7762 0.55290859,-1.1447 0.1843028,-0.3686 0.3686057,-0.7216 0.5529085,-1.0521 0.1843028,-0.3303 0.3686057,-0.6383 0.5529085,-0.9175 0.1843029,-0.2794 0.3686057,-0.5299 0.5529085,-0.7468 0.1843029,-0.2172 0.3686057,-0.4004 0.5529086,-0.5466 0.1843028,-0.1461 0.3686056,-0.2549 0.5529085,-0.3244 0.1843028,-0.069 0.3686057,-0.1 0.5529085,-0.09 0.1843029,0.011 0.3686057,0.06 0.5529085,0.1489 0.1843029,0.09 0.3686057,0.217 0.5529086,0.3815 0.1843028,0.1645 0.3686056,0.3655 0.5529085,0.5989 0.1843028,0.2335 0.3686057,0.4995 0.5529085,0.7927 0.1843028,0.2931 0.3686057,0.6135 0.5529085,0.9549 0.1843029,0.3412 0.3686057,0.7034 0.5529086,1.0792 0.1843028,0.3758 0.3686056,0.7653 0.5529085,1.1608 0.1843028,0.3956 0.3686057,0.7969 0.5529085,1.1964 0.1843028,0.3994 0.3686057,0.7969 0.5529085,1.1845 0.1843029,0.3876 0.3686059,0.7653 0.5529088,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208"
+       title="sin(x)"
+       id="path4190"
+       sodipodi:nodetypes="ccccccccccscccsccsccccscccccccccccccsccc" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       id="rect3905"
+       width="22.075964"
+       height="15.974718"
+       x="-0.19596301"
+       y="1017.121" />
+    <rect
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+       id="rect4735-9"
+       width="21.984022"
+       height="15.841428"
+       x="3.2648256"
+       y="1021.9644" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 3.0074993,1025.7005 22.2349197,0"
+       id="path3768-2-9"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 3.1363819,1033.852 22.0416911,0"
+       id="path3768-1-4"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 7.2545608,1021.8867 0,15.8294"
+       id="path3755-4-2"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 11.114978,1021.8867 0,15.8294"
+       id="path3755-4-0-2"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 14.975396,1021.8867 0,15.8294"
+       id="path3755-4-5-3"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 18.835812,1021.8867 0,15.8294"
+       id="path3755-4-9-8"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 22.696229,1021.8867 0,15.8294"
+       id="path3755-4-7-9"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 3.1372454,1029.7763 22.0120786,0"
+       id="path3768-7"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 3.5655247,1029.8668 c 0.1843028,-0.3999 0.3686057,-0.8 0.5529085,-1.1919 0.1843029,-0.3921 0.3686057,-0.7762 0.5529086,-1.1447 0.1843028,-0.3686 0.3686057,-0.7216 0.5529085,-1.0521 0.1843028,-0.3303 0.3686057,-0.6383 0.5529085,-0.9175 0.1843029,-0.2794 0.3686057,-0.5299 0.5529085,-0.7468 0.1843029,-0.2172 0.3686057,-0.4004 0.5529086,-0.5466 0.1843028,-0.1461 0.3686056,-0.2549 0.5529085,-0.3244 0.1843028,-0.069 0.3686057,-0.1 0.5529085,-0.09 0.1843029,0.011 0.3686057,0.06 0.5529085,0.1489 0.1843029,0.09 0.3686057,0.217 0.5529086,0.3815 0.1843028,0.1645 0.3686056,0.3655 0.5529085,0.5989 0.1843028,0.2335 0.3686055,0.4995 0.5529085,0.7927 0.184303,0.2931 0.368606,0.6135 0.552909,0.9549 0.184302,0.3412 0.368605,0.7034 0.552908,1.0792 0.184303,0.3758 0.368606,0.7653 0.552909,1.1608 0.184302,0.3956 0.368605,0.7969 0.552908,1.1964 0.184303,0.3994 0.368606,0.7969 0.552909,1.1845 0.184303,0.3876 0.368606,0.7653 0.552908,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208"
+       title="sin(x)"
+       id="path4190-0"
+       sodipodi:nodetypes="ccccccccccscccsccsccccscccccccccccccsccc" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       id="rect3905-8"
+       width="22.075964"
+       height="15.974718"
+       x="3.1981804"
+       y="1021.8977" />
+    <rect
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+       id="rect4735-2"
+       width="21.984022"
+       height="15.841428"
+       x="6.5999274"
+       y="1026.7411" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 6.3426012,1030.4772 22.2349198,0"
+       id="path3768-2-3"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 6.4714839,1038.6287 22.0416911,0"
+       id="path3768-1-3"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 10.589663,1026.6634 0,15.8294"
+       id="path3755-4-1"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 14.45008,1026.6634 0,15.8294"
+       id="path3755-4-0-3"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 18.310498,1026.6634 0,15.8294"
+       id="path3755-4-5-1"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 22.170914,1026.6634 0,15.8294"
+       id="path3755-4-9-88"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 26.031331,1026.6634 0,15.8294"
+       id="path3755-4-7-2"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 6.4723474,1034.553 22.0120786,0"
+       id="path3768-98"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 6.9006267,1034.6435 c 0.1843028,-0.3999 0.3686057,-0.8 0.5529085,-1.1919 0.1843029,-0.3921 0.3686057,-0.7762 0.5529086,-1.1447 0.1843028,-0.3686 0.3686057,-0.7216 0.5529085,-1.0521 0.1843028,-0.3303 0.3686057,-0.6383 0.5529085,-0.9175 0.1843029,-0.2794 0.3686057,-0.5299 0.5529085,-0.7468 0.1843029,-0.2172 0.3686057,-0.4004 0.5529087,-0.5466 0.184303,-0.1461 0.368605,-0.2549 0.552908,-0.3244 0.184303,-0.069 0.368606,-0.1 0.552909,-0.09 0.184303,0.011 0.368606,0.06 0.552908,0.1489 0.184303,0.09 0.368606,0.217 0.552909,0.3815 0.184303,0.1645 0.368606,0.3655 0.552908,0.5989 0.184303,0.2335 0.368606,0.4995 0.552909,0.7927 0.184303,0.2931 0.368606,0.6135 0.552909,0.9549 0.184302,0.3412 0.368605,0.7034 0.552908,1.0792 0.184303,0.3758 0.368606,0.7653 0.552909,1.1608 0.184302,0.3956 0.368605,0.7969 0.552908,1.1964 0.184303,0.3994 0.368606,0.7969 0.552909,1.1845 0.184303,0.3876 0.368605,0.7653 0.552908,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208"
+       title="sin(x)"
+       id="path4190-8"
+       sodipodi:nodetypes="ccccccccccscccsccsccccscccccccccccccsccc" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       id="rect3905-7"
+       width="22.075964"
+       height="15.974718"
+       x="6.5332823"
+       y="1026.6744" />
+    <rect
+       style="fill:#f6e879;fill-opacity:1;fill-rule:evenodd;stroke:none"
+       id="rect4735-98"
+       width="21.984022"
+       height="15.841428"
+       x="9.935029"
+       y="1031.5178" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 9.6777028,1035.2539 22.2349202,0"
+       id="path3768-2-4"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 9.8065855,1043.4054 22.0416915,0"
+       id="path3768-1-6"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 13.924764,1031.4401 0,15.8294"
+       id="path3755-4-54"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 17.785182,1031.4401 0,15.8294"
+       id="path3755-4-0-5"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 21.6456,1031.4401 0,15.8294"
+       id="path3755-4-5-9"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 25.506016,1031.4401 0,15.8294"
+       id="path3755-4-9-87"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 29.366433,1031.4401 0,15.8294"
+       id="path3755-4-7-4"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 9.807449,1039.3297 22.012079,0"
+       id="path3768-6"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 10.235728,1039.4202 c 0.184303,-0.3999 0.368606,-0.8 0.552909,-1.1919 0.184303,-0.3921 0.368605,-0.7762 0.552908,-1.1447 0.184303,-0.3686 0.368606,-0.7216 0.552909,-1.0521 0.184303,-0.3303 0.368606,-0.6383 0.552908,-0.9175 0.184303,-0.2794 0.368606,-0.5299 0.552909,-0.7468 0.184303,-0.2172 0.368606,-0.4004 0.552908,-0.5466 0.184303,-0.1461 0.368606,-0.2549 0.552909,-0.3244 0.184303,-0.069 0.368606,-0.1 0.552909,-0.09 0.184302,0.011 0.368605,0.06 0.552908,0.1489 0.184303,0.09 0.368606,0.217 0.552909,0.3815 0.184302,0.1645 0.368605,0.3655 0.552908,0.5989 0.184303,0.2335 0.368606,0.4995 0.552909,0.7927 0.184302,0.2931 0.368605,0.6135 0.552908,0.9549 0.184303,0.3412 0.368606,0.7034 0.552909,1.0792 0.184302,0.3758 0.368605,0.7653 0.552908,1.1608 0.184303,0.3956 0.368606,0.7969 0.552909,1.1964 0.184302,0.3994 0.368605,0.7969 0.552908,1.1845 0.184303,0.3876 0.368606,0.7653 0.552909,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208"
+       title="sin(x)"
+       id="path4190-84"
+       sodipodi:nodetypes="ccccccccccscccsccsccccscccccccccccccsccc" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:0.29496232000000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       id="rect3905-86"
+       width="22.075964"
+       height="15.974718"
+       x="9.8683844"
+       y="1031.451" />
+    <rect
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+       id="rect4735-23"
+       width="21.984022"
+       height="15.841428"
+       x="13.270129"
+       y="1036.2946" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 13.012803,1040.0306 22.23492,0"
+       id="path3768-2-5"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 13.141686,1048.1821 22.041691,0"
+       id="path3768-1-5"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 17.259864,1036.2168 0,15.8294"
+       id="path3755-4-6"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 21.120282,1036.2168 0,15.8294"
+       id="path3755-4-0-35"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 24.9807,1036.2168 0,15.8294"
+       id="path3755-4-5-82"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 28.841116,1036.2168 0,15.8294"
+       id="path3755-4-9-5"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 32.701533,1036.2168 0,15.8294"
+       id="path3755-4-7-7"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 13.142549,1044.1064 22.012079,0"
+       id="path3768-25"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 13.570828,1044.1969 c 0.184303,-0.3999 0.368606,-0.8 0.552909,-1.1919 0.184303,-0.3921 0.368605,-0.7762 0.552908,-1.1447 0.184303,-0.3686 0.368606,-0.7216 0.552909,-1.0521 0.184303,-0.3303 0.368606,-0.6383 0.552908,-0.9175 0.184303,-0.2794 0.368606,-0.5299 0.552909,-0.7468 0.184303,-0.2172 0.368606,-0.4004 0.552908,-0.5466 0.184303,-0.1461 0.368606,-0.2549 0.552909,-0.3244 0.184303,-0.069 0.368606,-0.1 0.552909,-0.09 0.184302,0.011 0.368605,0.06 0.552908,0.1489 0.184303,0.09 0.368606,0.217 0.552909,0.3815 0.184302,0.1645 0.368605,0.3655 0.552908,0.5989 0.184303,0.2335 0.368606,0.4995 0.552909,0.7927 0.184302,0.2931 0.368605,0.6135 0.552908,0.9549 0.184303,0.3412 0.368606,0.7034 0.552909,1.0792 0.184303,0.3758 0.368605,0.7653 0.552908,1.1608 0.184303,0.3956 0.368606,0.7969 0.552909,1.1964 0.184303,0.3994 0.368605,0.7969 0.552908,1.1845 0.184303,0.3876 0.368606,0.7653 0.552909,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022"
+       title="sin(x)"
+       id="path4190-80"
+       sodipodi:nodetypes="ccccccccccscccsccscc" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       id="rect3905-9"
+       width="22.075964"
+       height="15.974718"
+       x="13.203484"
+       y="1036.2278" />
+  </g>
+</svg>
diff --git a/icons/view-displaymode-last_segment.svg b/icons/view-displaymode-last_segment.svg
new file mode 100644 (file)
index 0000000..4c4bf53
--- /dev/null
@@ -0,0 +1,521 @@
+<?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"
+   width="35.433071"
+   height="35.433071"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.5 r10040"
+   sodipodi:docname="view-displaymode-last_segment.svg">
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="15.465778"
+     inkscape:cx="7.8560557"
+     inkscape:cy="17.231594"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="mm"
+     inkscape:window-width="1369"
+     inkscape:window-height="743"
+     inkscape:window-x="46"
+     inkscape:window-y="65"
+     inkscape:window-maximized="0" />
+  <defs
+     id="defs4" />
+  <metadata
+     id="metadata7">
+    <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
+     inkscape:groupmode="layer"
+     id="layer2"
+     inkscape:label="Layer"
+     style="display:none">
+    <g
+       transform="translate(0,-1016.9291)"
+       style="display:inline"
+       id="g7555-6">
+      <path
+         inkscape:connector-curvature="0"
+         id="path3768-2-3-5-9"
+         d="m 0,1044.2466 35.26169,0"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3768-2-6"
+         d="m 0,1020.9238 35.26169,0"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3768-1-9"
+         d="m 0,1029.0753 35.26169,0"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-29"
+         d="m 3.8604174,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-0-6"
+         d="m 7.7208349,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-5-8"
+         d="m 11.581253,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-9-14"
+         d="m 15.441669,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-7-90"
+         d="m 19.302086,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-3-32"
+         d="m 23.162504,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-1-3"
+         d="m 27.022922,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-90-6"
+         d="m 30.883338,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-2-4"
+         d="m 34.743757,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-49"
+         d="m 0,1017.11 0,15.8294"
+         style="fill:none;stroke:#000000;stroke-width:0.3772082;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3768-9"
+         d="m 0,1024.9996 35.26169,0"
+         style="fill:none;stroke:#000000;stroke-width:0.3772082;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         id="path4190-3"
+         title="sin(x)"
+         d="m 0.17138129,1025.0901 c 0.18430284,-0.3999 0.36860568,-0.8 0.55290852,-1.1919 0.18430286,-0.3921 0.36860569,-0.7762 0.55290859,-1.1447 0.1843028,-0.3686 0.3686057,-0.7216 0.5529085,-1.0521 0.1843028,-0.3303 0.3686057,-0.6383 0.5529085,-0.9175 0.1843029,-0.2794 0.3686057,-0.5299 0.5529085,-0.7468 0.1843029,-0.2172 0.3686057,-0.4004 0.5529086,-0.5466 0.1843028,-0.1461 0.3686056,-0.2549 0.5529085,-0.3244 0.1843028,-0.069 0.3686057,-0.1 0.5529085,-0.09 0.1843029,0.011 0.3686057,0.06 0.5529085,0.1489 0.1843029,0.09 0.3686057,0.217 0.5529086,0.3815 0.1843028,0.1645 0.3686056,0.3655 0.5529085,0.5989 0.1843028,0.2335 0.3686057,0.4995 0.5529085,0.7927 0.1843028,0.2931 0.3686057,0.6135 0.5529085,0.9549 0.1843029,0.3412 0.3686057,0.7034 0.5529086,1.0792 0.1843028,0.3758 0.3686056,0.7653 0.5529085,1.1608 0.1843028,0.3956 0.3686057,0.7969 0.5529085,1.1964 0.1843028,0.3994 0.3686057,0.7969 0.5529085,1.1845 0.1843029,0.3876 0.3686059,0.7653 0.5529088,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208 0.184302,-0.031 0.368605,-0.019 0.552908,0.031 0.184303,0.05 0.368606,0.1392 0.552909,0.2666 0.184303,0.1274 0.368605,0.2927 0.552908,0.4926 0.184303,0.2001 0.368606,0.4347 0.552909,0.6993 0.184303,0.2646 0.368605,0.5594 0.552908,0.8781 0.184303,0.3189 0.368606,0.6618 0.552909,1.0222 0.184303,0.3603 0.368605,0.738 0.552908,1.1256 0.184303,0.3876 0.368606,0.785 0.552909,1.1845 0.184303,0.3994 0.368606,0.8009 0.552908,1.1963 0.184303,0.3955 0.368606,0.7851 0.552909,1.161 0.184303,0.3758 0.368606,0.7379 0.552908,1.0792 0.184303,0.3411 0.368606,0.6616 0.552909,0.9548 0.184303,0.2932 0.368606,0.5592 0.552908,0.7927 0.184303,0.2334 0.368606,0.4343 0.552909,0.5988 0.184303,0.1645 0.368606,0.2926 0.552909,0.3816 0.184302,0.089 0.368605,0.1389 0.552908,0.1489 0.184303,0.011 0.368606,-0.02 0.552909,-0.09 0.184302,-0.069 0.368605,-0.1784 0.552908,-0.3246 0.184303,-0.1461 0.368606,-0.3295 0.552909,-0.5464 0.184302,-0.217 0.368605,-0.4676 0.552908,-0.7469 0.184303,-0.2792 0.368606,-0.5872 0.552909,-0.9177 0.184302,-0.3303 0.368605,-0.6834 0.552908,-1.0519 0.184303,-0.3686 0.368606,-0.7526 0.552909,-1.1446 0.184303,-0.392 0.368605,-0.7921 0.552908,-1.1919"
+         style="fill:none;stroke:#2e0dff;stroke-width:0.30176657;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+         inkscape:connector-curvature="0" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3768-2-3-7"
+         d="m 0,1040.0689 35.26169,0"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3768-1-5-1"
+         d="m 0,1048.373 35.26169,0"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-38-0"
+         d="m 3.860417,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-0-5-8"
+         d="m 7.720835,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-5-0-7"
+         d="m 11.581253,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-9-1-2"
+         d="m 15.441669,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-7-9-8"
+         d="m 19.302086,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-3-3-8"
+         d="m 23.162504,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-1-4-9"
+         d="m 27.022922,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-90-5-2"
+         d="m 30.883338,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-2-0-4"
+         d="m 34.743757,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-6-0"
+         d="m 0,1036.1837 0,16.1257"
+         style="fill:none;stroke:#000000;stroke-width:0.38072109;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3768-4-0"
+         d="m 0,1052.2209 35.26169,0"
+         style="fill:none;stroke:#000000;stroke-width:0.38072109;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         inkscape:connector-curvature="0"
+         style="fill:none;stroke:#ff970d;stroke-width:0.30000001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+         d="m 0.25968796,1051.8993 c 0.18450185,0 0.3690037,0 0.55350556,0 0.18450185,0 0.36900368,0 0.55350558,0 0.1845018,0 0.3690037,0 0.5535055,0 0.1845019,0 0.3690037,0 0.5535056,0 0.1845018,0 0.3690037,0 0.5535055,0 0.1845019,0 0.3690037,0 0.5535056,0 0.1845018,-10e-5 0.3690037,-10e-5 0.5535055,-3e-4 0.1845019,-10e-5 0.3690037,-3e-4 0.5535056,-7e-4 0.1845018,-4e-4 0.3690037,-10e-4 0.5535055,0 0.1845019,0 0.3690038,0 0.5535056,-0.01 0.1845019,0 0.3690037,-0.01 0.5535056,-0.011 0.1845018,-0.01 0.3690037,-0.012 0.5535055,-0.022 0.1845019,-0.01 0.3690037,-0.023 0.5535056,-0.041 0.1845018,-0.018 0.3690037,-0.041 0.5535055,-0.072 0.1845019,-0.031 0.3690037,-0.069 0.5535056,-0.1174 0.1845018,-0.049 0.3690037,-0.1081 0.5535055,-0.1816 0.1845019,-0.073 0.3690037,-0.1613 0.5535056,-0.267 0.1845018,-0.1057 0.3690036,-0.2294 0.5535056,-0.3745 0.184502,-0.1451 0.369003,-0.3119 0.553505,-0.5028 0.184502,-0.1909 0.369004,-0.4062 0.553506,-0.6472 0.184502,-0.241 0.369003,-0.5079 0.553505,-0.8001 0.184502,-0.2922 0.369004,-0.6099 0.553506,-0.9503 0.184502,-0.3405 0.369004,-0.7038 0.553505,-1.0848 0.184502,-0.3811 0.369004,-0.7798 0.553506,-1.1888 0.184502,-0.409 0.369004,-0.8281 0.553506,-1.2479 0.184501,-0.4198 0.369003,-0.8402 0.553505,-1.2503 0.184502,-0.41 0.369004,-0.8095 0.553506,-1.1872 0.184502,-0.3777 0.369003,-0.7333 0.553505,-1.0561 0.184502,-0.3227 0.369004,-0.6123 0.553506,-0.8596 0.184502,-0.2473 0.369003,-0.452 0.553505,-0.6075 0.184502,-0.1555 0.369004,-0.2614 0.553506,-0.3145 0.184502,-0.053 0.369004,-0.053 0.553505,0 0.184502,0.053 0.369004,0.1591 0.553506,0.3145 0.184502,0.1554 0.369004,0.3603 0.553506,0.6075 0.184501,0.2472 0.369003,0.537 0.553505,0.8596 0.184502,0.3227 0.369004,0.6784 0.553506,1.0561 0.184502,0.3776 0.369003,0.7772 0.553505,1.1872 0.184502,0.4101 0.369004,0.8305 0.553506,1.2503 0.184502,0.4198 0.369003,0.8389 0.553505,1.2479 0.184502,0.409 0.369004,0.8077 0.553506,1.1888 0.184502,0.381 0.369004,0.7443 0.553505,1.0848 0.184502,0.3405 0.369004,0.6581 0.553506,0.9503 0.184502,0.2923 0.369004,0.5591 0.553506,0.8001 0.184501,0.241 0.369003,0.4563 0.553505,0.6472 0.184502,0.191 0.369004,0.3576 0.553506,0.5028 0.184502,0.1452 0.369003,0.2688 0.553505,0.3745 0.184502,0.1057 0.369004,0.1934 0.553506,0.267 0.184502,0.074 0.369003,0.1328 0.553505,0.1816 0.184502,0.049 0.369004,0.087 0.553506,0.1174 0.184502,0.031 0.369004,0.054 0.553505,0.072 0.184502,0.018 0.369004,0.031 0.553506,0.041 0.184502,0.01 0.369004,0.017 0.553506,0.022 0.184501,0.01 0.369003,0.01 0.553505,0.011 0.184502,0 0.369004,0 0.553506,0.01 0.184502,10e-4 0.369003,0 0.553505,0 0.184502,4e-4 0.369004,6e-4 0.553506,7e-4 0.184502,2e-4 0.369003,2e-4 0.553505,3e-4 0.184502,0 0.369004,0 0.553506,0 0.184502,0 0.369004,0 0.553505,0 0.184502,0 0.369004,0 0.553506,0 0.184502,0 0.369004,0 0.553506,0 0.184501,0 0.369003,0 0.553505,0 0.184502,0 0.369004,0 0.553506,0"
+         title="pow(sin(x), 10)"
+         id="path7506-5" />
+    </g>
+  </g>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1016.9291)"
+     style="display:inline">
+    <rect
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+       id="rect4735"
+       width="21.984022"
+       height="15.841428"
+       x="-0.12931778"
+       y="1017.1877" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m -0.38664415,1020.9238 22.23492015,0"
+       id="path3768-2"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m -0.25776149,1029.0753 22.04169149,0"
+       id="path3768-1"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 3.8604174,1017.11 0,15.8294"
+       id="path3755-4"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 7.7208349,1017.11 0,15.8294"
+       id="path3755-4-0"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 11.581253,1017.11 0,15.8294"
+       id="path3755-4-5"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 15.441669,1017.11 0,15.8294"
+       id="path3755-4-9"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 19.302086,1017.11 0,15.8294"
+       id="path3755-4-7"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m -0.25689803,1024.9996 22.01207903,0"
+       id="path3768"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 0.17138129,1025.0901 c 0.18430284,-0.3999 0.36860568,-0.8 0.55290852,-1.1919 0.18430286,-0.3921 0.36860569,-0.7762 0.55290859,-1.1447 0.1843028,-0.3686 0.3686057,-0.7216 0.5529085,-1.0521 0.1843028,-0.3303 0.3686057,-0.6383 0.5529085,-0.9175 0.1843029,-0.2794 0.3686057,-0.5299 0.5529085,-0.7468 0.1843029,-0.2172 0.3686057,-0.4004 0.5529086,-0.5466 0.1843028,-0.1461 0.3686056,-0.2549 0.5529085,-0.3244 0.1843028,-0.069 0.3686057,-0.1 0.5529085,-0.09 0.1843029,0.011 0.3686057,0.06 0.5529085,0.1489 0.1843029,0.09 0.3686057,0.217 0.5529086,0.3815 0.1843028,0.1645 0.3686056,0.3655 0.5529085,0.5989 0.1843028,0.2335 0.3686057,0.4995 0.5529085,0.7927 0.1843028,0.2931 0.3686057,0.6135 0.5529085,0.9549 0.1843029,0.3412 0.3686057,0.7034 0.5529086,1.0792 0.1843028,0.3758 0.3686056,0.7653 0.5529085,1.1608 0.1843028,0.3956 0.3686057,0.7969 0.5529085,1.1964 0.1843028,0.3994 0.3686057,0.7969 0.5529085,1.1845 0.1843029,0.3876 0.3686059,0.7653 0.5529088,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208"
+       title="sin(x)"
+       id="path4190"
+       sodipodi:nodetypes="ccccccccccscccsccsccccscccccccccccccsccc" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       id="rect3905"
+       width="22.075964"
+       height="15.974718"
+       x="-0.19596301"
+       y="1017.121" />
+    <rect
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+       id="rect4735-9"
+       width="21.984022"
+       height="15.841428"
+       x="3.2648256"
+       y="1021.9644" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 3.0074993,1025.7005 22.2349197,0"
+       id="path3768-2-9"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 3.1363819,1033.852 22.0416911,0"
+       id="path3768-1-4"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 7.2545608,1021.8867 0,15.8294"
+       id="path3755-4-2"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 11.114978,1021.8867 0,15.8294"
+       id="path3755-4-0-2"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 14.975396,1021.8867 0,15.8294"
+       id="path3755-4-5-3"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 18.835812,1021.8867 0,15.8294"
+       id="path3755-4-9-8"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 22.696229,1021.8867 0,15.8294"
+       id="path3755-4-7-9"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 3.1372454,1029.7763 22.0120786,0"
+       id="path3768-7"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 3.5655247,1029.8668 c 0.1843028,-0.3999 0.3686057,-0.8 0.5529085,-1.1919 0.1843029,-0.3921 0.3686057,-0.7762 0.5529086,-1.1447 0.1843028,-0.3686 0.3686057,-0.7216 0.5529085,-1.0521 0.1843028,-0.3303 0.3686057,-0.6383 0.5529085,-0.9175 0.1843029,-0.2794 0.3686057,-0.5299 0.5529085,-0.7468 0.1843029,-0.2172 0.3686057,-0.4004 0.5529086,-0.5466 0.1843028,-0.1461 0.3686056,-0.2549 0.5529085,-0.3244 0.1843028,-0.069 0.3686057,-0.1 0.5529085,-0.09 0.1843029,0.011 0.3686057,0.06 0.5529085,0.1489 0.1843029,0.09 0.3686057,0.217 0.5529086,0.3815 0.1843028,0.1645 0.3686056,0.3655 0.5529085,0.5989 0.1843028,0.2335 0.3686055,0.4995 0.5529085,0.7927 0.184303,0.2931 0.368606,0.6135 0.552909,0.9549 0.184302,0.3412 0.368605,0.7034 0.552908,1.0792 0.184303,0.3758 0.368606,0.7653 0.552909,1.1608 0.184302,0.3956 0.368605,0.7969 0.552908,1.1964 0.184303,0.3994 0.368606,0.7969 0.552909,1.1845 0.184303,0.3876 0.368606,0.7653 0.552908,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208"
+       title="sin(x)"
+       id="path4190-0"
+       sodipodi:nodetypes="ccccccccccscccsccsccccscccccccccccccsccc" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       id="rect3905-8"
+       width="22.075964"
+       height="15.974718"
+       x="3.1981804"
+       y="1021.8977" />
+    <rect
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+       id="rect4735-2"
+       width="21.984022"
+       height="15.841428"
+       x="6.5999274"
+       y="1026.7411" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 6.3426012,1030.4772 22.2349198,0"
+       id="path3768-2-3"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 6.4714839,1038.6287 22.0416911,0"
+       id="path3768-1-3"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 10.589663,1026.6634 0,15.8294"
+       id="path3755-4-1"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 14.45008,1026.6634 0,15.8294"
+       id="path3755-4-0-3"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 18.310498,1026.6634 0,15.8294"
+       id="path3755-4-5-1"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 22.170914,1026.6634 0,15.8294"
+       id="path3755-4-9-88"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 26.031331,1026.6634 0,15.8294"
+       id="path3755-4-7-2"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 6.4723474,1034.553 22.0120786,0"
+       id="path3768-98"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 6.9006267,1034.6435 c 0.1843028,-0.3999 0.3686057,-0.8 0.5529085,-1.1919 0.1843029,-0.3921 0.3686057,-0.7762 0.5529086,-1.1447 0.1843028,-0.3686 0.3686057,-0.7216 0.5529085,-1.0521 0.1843028,-0.3303 0.3686057,-0.6383 0.5529085,-0.9175 0.1843029,-0.2794 0.3686057,-0.5299 0.5529085,-0.7468 0.1843029,-0.2172 0.3686057,-0.4004 0.5529087,-0.5466 0.184303,-0.1461 0.368605,-0.2549 0.552908,-0.3244 0.184303,-0.069 0.368606,-0.1 0.552909,-0.09 0.184303,0.011 0.368606,0.06 0.552908,0.1489 0.184303,0.09 0.368606,0.217 0.552909,0.3815 0.184303,0.1645 0.368606,0.3655 0.552908,0.5989 0.184303,0.2335 0.368606,0.4995 0.552909,0.7927 0.184303,0.2931 0.368606,0.6135 0.552909,0.9549 0.184302,0.3412 0.368605,0.7034 0.552908,1.0792 0.184303,0.3758 0.368606,0.7653 0.552909,1.1608 0.184302,0.3956 0.368605,0.7969 0.552908,1.1964 0.184303,0.3994 0.368606,0.7969 0.552909,1.1845 0.184303,0.3876 0.368605,0.7653 0.552908,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208"
+       title="sin(x)"
+       id="path4190-8"
+       sodipodi:nodetypes="ccccccccccscccsccsccccscccccccccccccsccc" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       id="rect3905-7"
+       width="22.075964"
+       height="15.974718"
+       x="6.5332823"
+       y="1026.6744" />
+    <rect
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+       id="rect4735-98"
+       width="21.984022"
+       height="15.841428"
+       x="9.935029"
+       y="1031.5178" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 9.6777028,1035.2539 22.2349202,0"
+       id="path3768-2-4"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 9.8065855,1043.4054 22.0416915,0"
+       id="path3768-1-6"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 13.924764,1031.4401 0,15.8294"
+       id="path3755-4-54"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 17.785182,1031.4401 0,15.8294"
+       id="path3755-4-0-5"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 21.6456,1031.4401 0,15.8294"
+       id="path3755-4-5-9"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 25.506016,1031.4401 0,15.8294"
+       id="path3755-4-9-87"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 29.366433,1031.4401 0,15.8294"
+       id="path3755-4-7-4"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 9.807449,1039.3297 22.012079,0"
+       id="path3768-6"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 10.235728,1039.4202 c 0.184303,-0.3999 0.368606,-0.8 0.552909,-1.1919 0.184303,-0.3921 0.368605,-0.7762 0.552908,-1.1447 0.184303,-0.3686 0.368606,-0.7216 0.552909,-1.0521 0.184303,-0.3303 0.368606,-0.6383 0.552908,-0.9175 0.184303,-0.2794 0.368606,-0.5299 0.552909,-0.7468 0.184303,-0.2172 0.368606,-0.4004 0.552908,-0.5466 0.184303,-0.1461 0.368606,-0.2549 0.552909,-0.3244 0.184303,-0.069 0.368606,-0.1 0.552909,-0.09 0.184302,0.011 0.368605,0.06 0.552908,0.1489 0.184303,0.09 0.368606,0.217 0.552909,0.3815 0.184302,0.1645 0.368605,0.3655 0.552908,0.5989 0.184303,0.2335 0.368606,0.4995 0.552909,0.7927 0.184302,0.2931 0.368605,0.6135 0.552908,0.9549 0.184303,0.3412 0.368606,0.7034 0.552909,1.0792 0.184302,0.3758 0.368605,0.7653 0.552908,1.1608 0.184303,0.3956 0.368606,0.7969 0.552909,1.1964 0.184302,0.3994 0.368605,0.7969 0.552908,1.1845 0.184303,0.3876 0.368606,0.7653 0.552909,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208"
+       title="sin(x)"
+       id="path4190-84"
+       sodipodi:nodetypes="ccccccccccscccsccsccccscccccccccccccsccc" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:0.29496232000000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       id="rect3905-86"
+       width="22.075964"
+       height="15.974718"
+       x="9.8683844"
+       y="1031.451" />
+    <rect
+       style="fill:#f6e879;fill-opacity:1;fill-rule:evenodd;stroke:none"
+       id="rect4735-23"
+       width="21.984022"
+       height="15.841428"
+       x="13.270129"
+       y="1036.2946" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 13.012803,1040.0306 22.23492,0"
+       id="path3768-2-5"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 13.141686,1048.1821 22.041691,0"
+       id="path3768-1-5"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 17.259864,1036.2168 0,15.8294"
+       id="path3755-4-6"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 21.120282,1036.2168 0,15.8294"
+       id="path3755-4-0-35"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 24.9807,1036.2168 0,15.8294"
+       id="path3755-4-5-82"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 28.841116,1036.2168 0,15.8294"
+       id="path3755-4-9-5"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 32.701533,1036.2168 0,15.8294"
+       id="path3755-4-7-7"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 13.142549,1044.1064 22.012079,0"
+       id="path3768-25"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 13.570828,1044.1969 c 0.184303,-0.3999 0.368606,-0.8 0.552909,-1.1919 0.184303,-0.3921 0.368605,-0.7762 0.552908,-1.1447 0.184303,-0.3686 0.368606,-0.7216 0.552909,-1.0521 0.184303,-0.3303 0.368606,-0.6383 0.552908,-0.9175 0.184303,-0.2794 0.368606,-0.5299 0.552909,-0.7468 0.184303,-0.2172 0.368606,-0.4004 0.552908,-0.5466 0.184303,-0.1461 0.368606,-0.2549 0.552909,-0.3244 0.184303,-0.069 0.368606,-0.1 0.552909,-0.09 0.184302,0.011 0.368605,0.06 0.552908,0.1489 0.184303,0.09 0.368606,0.217 0.552909,0.3815 0.184302,0.1645 0.368605,0.3655 0.552908,0.5989 0.184303,0.2335 0.368606,0.4995 0.552909,0.7927 0.184302,0.2931 0.368605,0.6135 0.552908,0.9549 0.184303,0.3412 0.368606,0.7034 0.552909,1.0792 0.184303,0.3758 0.368605,0.7653 0.552908,1.1608 0.184303,0.3956 0.368606,0.7969 0.552909,1.1964 0.184303,0.3994 0.368605,0.7969 0.552908,1.1845 0.184303,0.3876 0.368606,0.7653 0.552909,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022"
+       title="sin(x)"
+       id="path4190-80"
+       sodipodi:nodetypes="ccccccccccscccsccscc" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       id="rect3905-9"
+       width="22.075964"
+       height="15.974718"
+       x="13.203484"
+       y="1036.2278" />
+  </g>
+</svg>
diff --git a/icons/view-displaymode-single_segment.svg b/icons/view-displaymode-single_segment.svg
new file mode 100644 (file)
index 0000000..ed8d3ac
--- /dev/null
@@ -0,0 +1,530 @@
+<?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"
+   width="35.433071"
+   height="35.433071"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.5 r10040"
+   sodipodi:docname="view-displaymode-single_segment.svg">
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="15.465778"
+     inkscape:cx="17.716536"
+     inkscape:cy="17.716536"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="mm"
+     inkscape:window-width="1369"
+     inkscape:window-height="743"
+     inkscape:window-x="242"
+     inkscape:window-y="213"
+     inkscape:window-maximized="0" />
+  <defs
+     id="defs4">
+    <filter
+       inkscape:collect="always"
+       id="filter8271">
+      <feGaussianBlur
+         inkscape:collect="always"
+         stdDeviation="0.17615273"
+         id="feGaussianBlur8273" />
+    </filter>
+  </defs>
+  <metadata
+     id="metadata7">
+    <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
+     inkscape:groupmode="layer"
+     id="layer2"
+     inkscape:label="Layer"
+     style="display:none">
+    <g
+       transform="translate(0,-1016.9291)"
+       style="display:inline"
+       id="g7555-6">
+      <path
+         inkscape:connector-curvature="0"
+         id="path3768-2-3-5-9"
+         d="m 0,1044.2466 35.26169,0"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3768-2-6"
+         d="m 0,1020.9238 35.26169,0"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3768-1-9"
+         d="m 0,1029.0753 35.26169,0"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-29"
+         d="m 3.8604174,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-0-6"
+         d="m 7.7208349,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-5-8"
+         d="m 11.581253,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-9-14"
+         d="m 15.441669,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-7-90"
+         d="m 19.302086,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-3-32"
+         d="m 23.162504,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-1-3"
+         d="m 27.022922,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-90-6"
+         d="m 30.883338,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-2-4"
+         d="m 34.743757,1017.11 0,15.8294"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-49"
+         d="m 0,1017.11 0,15.8294"
+         style="fill:none;stroke:#000000;stroke-width:0.3772082;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3768-9"
+         d="m 0,1024.9996 35.26169,0"
+         style="fill:none;stroke:#000000;stroke-width:0.3772082;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         id="path4190-3"
+         title="sin(x)"
+         d="m 0.17138129,1025.0901 c 0.18430284,-0.3999 0.36860568,-0.8 0.55290852,-1.1919 0.18430286,-0.3921 0.36860569,-0.7762 0.55290859,-1.1447 0.1843028,-0.3686 0.3686057,-0.7216 0.5529085,-1.0521 0.1843028,-0.3303 0.3686057,-0.6383 0.5529085,-0.9175 0.1843029,-0.2794 0.3686057,-0.5299 0.5529085,-0.7468 0.1843029,-0.2172 0.3686057,-0.4004 0.5529086,-0.5466 0.1843028,-0.1461 0.3686056,-0.2549 0.5529085,-0.3244 0.1843028,-0.069 0.3686057,-0.1 0.5529085,-0.09 0.1843029,0.011 0.3686057,0.06 0.5529085,0.1489 0.1843029,0.09 0.3686057,0.217 0.5529086,0.3815 0.1843028,0.1645 0.3686056,0.3655 0.5529085,0.5989 0.1843028,0.2335 0.3686057,0.4995 0.5529085,0.7927 0.1843028,0.2931 0.3686057,0.6135 0.5529085,0.9549 0.1843029,0.3412 0.3686057,0.7034 0.5529086,1.0792 0.1843028,0.3758 0.3686056,0.7653 0.5529085,1.1608 0.1843028,0.3956 0.3686057,0.7969 0.5529085,1.1964 0.1843028,0.3994 0.3686057,0.7969 0.5529085,1.1845 0.1843029,0.3876 0.3686059,0.7653 0.5529088,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208 0.184302,-0.031 0.368605,-0.019 0.552908,0.031 0.184303,0.05 0.368606,0.1392 0.552909,0.2666 0.184303,0.1274 0.368605,0.2927 0.552908,0.4926 0.184303,0.2001 0.368606,0.4347 0.552909,0.6993 0.184303,0.2646 0.368605,0.5594 0.552908,0.8781 0.184303,0.3189 0.368606,0.6618 0.552909,1.0222 0.184303,0.3603 0.368605,0.738 0.552908,1.1256 0.184303,0.3876 0.368606,0.785 0.552909,1.1845 0.184303,0.3994 0.368606,0.8009 0.552908,1.1963 0.184303,0.3955 0.368606,0.7851 0.552909,1.161 0.184303,0.3758 0.368606,0.7379 0.552908,1.0792 0.184303,0.3411 0.368606,0.6616 0.552909,0.9548 0.184303,0.2932 0.368606,0.5592 0.552908,0.7927 0.184303,0.2334 0.368606,0.4343 0.552909,0.5988 0.184303,0.1645 0.368606,0.2926 0.552909,0.3816 0.184302,0.089 0.368605,0.1389 0.552908,0.1489 0.184303,0.011 0.368606,-0.02 0.552909,-0.09 0.184302,-0.069 0.368605,-0.1784 0.552908,-0.3246 0.184303,-0.1461 0.368606,-0.3295 0.552909,-0.5464 0.184302,-0.217 0.368605,-0.4676 0.552908,-0.7469 0.184303,-0.2792 0.368606,-0.5872 0.552909,-0.9177 0.184302,-0.3303 0.368605,-0.6834 0.552908,-1.0519 0.184303,-0.3686 0.368606,-0.7526 0.552909,-1.1446 0.184303,-0.392 0.368605,-0.7921 0.552908,-1.1919"
+         style="fill:none;stroke:#2e0dff;stroke-width:0.30176657;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+         inkscape:connector-curvature="0" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3768-2-3-7"
+         d="m 0,1040.0689 35.26169,0"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3768-1-5-1"
+         d="m 0,1048.373 35.26169,0"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-38-0"
+         d="m 3.860417,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-0-5-8"
+         d="m 7.720835,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-5-0-7"
+         d="m 11.581253,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-9-1-2"
+         d="m 15.441669,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-7-9-8"
+         d="m 19.302086,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-3-3-8"
+         d="m 23.162504,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-1-4-9"
+         d="m 27.022922,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-90-5-2"
+         d="m 30.883338,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-4-2-0-4"
+         d="m 34.743757,1036.1837 0,16.1257"
+         style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3755-6-0"
+         d="m 0,1036.1837 0,16.1257"
+         style="fill:none;stroke:#000000;stroke-width:0.38072109;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3768-4-0"
+         d="m 0,1052.2209 35.26169,0"
+         style="fill:none;stroke:#000000;stroke-width:0.38072109;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         inkscape:connector-curvature="0"
+         style="fill:none;stroke:#ff970d;stroke-width:0.30000001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+         d="m 0.25968796,1051.8993 c 0.18450185,0 0.3690037,0 0.55350556,0 0.18450185,0 0.36900368,0 0.55350558,0 0.1845018,0 0.3690037,0 0.5535055,0 0.1845019,0 0.3690037,0 0.5535056,0 0.1845018,0 0.3690037,0 0.5535055,0 0.1845019,0 0.3690037,0 0.5535056,0 0.1845018,-10e-5 0.3690037,-10e-5 0.5535055,-3e-4 0.1845019,-10e-5 0.3690037,-3e-4 0.5535056,-7e-4 0.1845018,-4e-4 0.3690037,-10e-4 0.5535055,0 0.1845019,0 0.3690038,0 0.5535056,-0.01 0.1845019,0 0.3690037,-0.01 0.5535056,-0.011 0.1845018,-0.01 0.3690037,-0.012 0.5535055,-0.022 0.1845019,-0.01 0.3690037,-0.023 0.5535056,-0.041 0.1845018,-0.018 0.3690037,-0.041 0.5535055,-0.072 0.1845019,-0.031 0.3690037,-0.069 0.5535056,-0.1174 0.1845018,-0.049 0.3690037,-0.1081 0.5535055,-0.1816 0.1845019,-0.073 0.3690037,-0.1613 0.5535056,-0.267 0.1845018,-0.1057 0.3690036,-0.2294 0.5535056,-0.3745 0.184502,-0.1451 0.369003,-0.3119 0.553505,-0.5028 0.184502,-0.1909 0.369004,-0.4062 0.553506,-0.6472 0.184502,-0.241 0.369003,-0.5079 0.553505,-0.8001 0.184502,-0.2922 0.369004,-0.6099 0.553506,-0.9503 0.184502,-0.3405 0.369004,-0.7038 0.553505,-1.0848 0.184502,-0.3811 0.369004,-0.7798 0.553506,-1.1888 0.184502,-0.409 0.369004,-0.8281 0.553506,-1.2479 0.184501,-0.4198 0.369003,-0.8402 0.553505,-1.2503 0.184502,-0.41 0.369004,-0.8095 0.553506,-1.1872 0.184502,-0.3777 0.369003,-0.7333 0.553505,-1.0561 0.184502,-0.3227 0.369004,-0.6123 0.553506,-0.8596 0.184502,-0.2473 0.369003,-0.452 0.553505,-0.6075 0.184502,-0.1555 0.369004,-0.2614 0.553506,-0.3145 0.184502,-0.053 0.369004,-0.053 0.553505,0 0.184502,0.053 0.369004,0.1591 0.553506,0.3145 0.184502,0.1554 0.369004,0.3603 0.553506,0.6075 0.184501,0.2472 0.369003,0.537 0.553505,0.8596 0.184502,0.3227 0.369004,0.6784 0.553506,1.0561 0.184502,0.3776 0.369003,0.7772 0.553505,1.1872 0.184502,0.4101 0.369004,0.8305 0.553506,1.2503 0.184502,0.4198 0.369003,0.8389 0.553505,1.2479 0.184502,0.409 0.369004,0.8077 0.553506,1.1888 0.184502,0.381 0.369004,0.7443 0.553505,1.0848 0.184502,0.3405 0.369004,0.6581 0.553506,0.9503 0.184502,0.2923 0.369004,0.5591 0.553506,0.8001 0.184501,0.241 0.369003,0.4563 0.553505,0.6472 0.184502,0.191 0.369004,0.3576 0.553506,0.5028 0.184502,0.1452 0.369003,0.2688 0.553505,0.3745 0.184502,0.1057 0.369004,0.1934 0.553506,0.267 0.184502,0.074 0.369003,0.1328 0.553505,0.1816 0.184502,0.049 0.369004,0.087 0.553506,0.1174 0.184502,0.031 0.369004,0.054 0.553505,0.072 0.184502,0.018 0.369004,0.031 0.553506,0.041 0.184502,0.01 0.369004,0.017 0.553506,0.022 0.184501,0.01 0.369003,0.01 0.553505,0.011 0.184502,0 0.369004,0 0.553506,0.01 0.184502,10e-4 0.369003,0 0.553505,0 0.184502,4e-4 0.369004,6e-4 0.553506,7e-4 0.184502,2e-4 0.369003,2e-4 0.553505,3e-4 0.184502,0 0.369004,0 0.553506,0 0.184502,0 0.369004,0 0.553505,0 0.184502,0 0.369004,0 0.553506,0 0.184502,0 0.369004,0 0.553506,0 0.184501,0 0.369003,0 0.553505,0 0.184502,0 0.369004,0 0.553506,0"
+         title="pow(sin(x), 10)"
+         id="path7506-5" />
+    </g>
+  </g>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1016.9291)"
+     style="display:inline">
+    <rect
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+       id="rect4735"
+       width="21.984022"
+       height="15.841428"
+       x="-0.12931778"
+       y="1017.1877" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m -0.38664415,1020.9238 22.23492015,0"
+       id="path3768-2"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m -0.25776149,1029.0753 22.04169149,0"
+       id="path3768-1"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 3.8604174,1017.11 0,15.8294"
+       id="path3755-4"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 7.7208349,1017.11 0,15.8294"
+       id="path3755-4-0"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 11.581253,1017.11 0,15.8294"
+       id="path3755-4-5"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 15.441669,1017.11 0,15.8294"
+       id="path3755-4-9"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 19.302086,1017.11 0,15.8294"
+       id="path3755-4-7"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m -0.25689803,1024.9996 22.01207903,0"
+       id="path3768"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 0.17138129,1025.0901 c 0.18430284,-0.3999 0.36860568,-0.8 0.55290852,-1.1919 0.18430286,-0.3921 0.36860569,-0.7762 0.55290859,-1.1447 0.1843028,-0.3686 0.3686057,-0.7216 0.5529085,-1.0521 0.1843028,-0.3303 0.3686057,-0.6383 0.5529085,-0.9175 0.1843029,-0.2794 0.3686057,-0.5299 0.5529085,-0.7468 0.1843029,-0.2172 0.3686057,-0.4004 0.5529086,-0.5466 0.1843028,-0.1461 0.3686056,-0.2549 0.5529085,-0.3244 0.1843028,-0.069 0.3686057,-0.1 0.5529085,-0.09 0.1843029,0.011 0.3686057,0.06 0.5529085,0.1489 0.1843029,0.09 0.3686057,0.217 0.5529086,0.3815 0.1843028,0.1645 0.3686056,0.3655 0.5529085,0.5989 0.1843028,0.2335 0.3686057,0.4995 0.5529085,0.7927 0.1843028,0.2931 0.3686057,0.6135 0.5529085,0.9549 0.1843029,0.3412 0.3686057,0.7034 0.5529086,1.0792 0.1843028,0.3758 0.3686056,0.7653 0.5529085,1.1608 0.1843028,0.3956 0.3686057,0.7969 0.5529085,1.1964 0.1843028,0.3994 0.3686057,0.7969 0.5529085,1.1845 0.1843029,0.3876 0.3686059,0.7653 0.5529088,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208"
+       title="sin(x)"
+       id="path4190"
+       sodipodi:nodetypes="ccccccccccscccsccsccccscccccccccccccsccc" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       id="rect3905"
+       width="22.075964"
+       height="15.974718"
+       x="-0.19596301"
+       y="1017.121" />
+    <rect
+       style="fill:#f6e879;fill-opacity:1;fill-rule:evenodd;stroke:none"
+       id="rect4735-9"
+       width="21.984022"
+       height="15.841428"
+       x="3.2648256"
+       y="1021.9644" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 3.0074993,1025.7005 22.2349197,0"
+       id="path3768-2-9"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 3.1363819,1033.852 22.0416911,0"
+       id="path3768-1-4"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 7.2545608,1021.8867 0,15.8294"
+       id="path3755-4-2"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 11.114978,1021.8867 0,15.8294"
+       id="path3755-4-0-2"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 14.975396,1021.8867 0,15.8294"
+       id="path3755-4-5-3"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 18.835812,1021.8867 0,15.8294"
+       id="path3755-4-9-8"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 22.696229,1021.8867 0,15.8294"
+       id="path3755-4-7-9"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 3.1372454,1029.7763 22.0120786,0"
+       id="path3768-7"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 3.5655247,1029.8668 c 0.1843028,-0.3999 0.3686057,-0.8 0.5529085,-1.1919 0.1843029,-0.3921 0.3686057,-0.7762 0.5529086,-1.1447 0.1843028,-0.3686 0.3686057,-0.7216 0.5529085,-1.0521 0.1843028,-0.3303 0.3686057,-0.6383 0.5529085,-0.9175 0.1843029,-0.2794 0.3686057,-0.5299 0.5529085,-0.7468 0.1843029,-0.2172 0.3686057,-0.4004 0.5529086,-0.5466 0.1843028,-0.1461 0.3686056,-0.2549 0.5529085,-0.3244 0.1843028,-0.069 0.3686057,-0.1 0.5529085,-0.09 0.1843029,0.011 0.3686057,0.06 0.5529085,0.1489 0.1843029,0.09 0.3686057,0.217 0.5529086,0.3815 0.1843028,0.1645 0.3686056,0.3655 0.5529085,0.5989 0.1843028,0.2335 0.3686055,0.4995 0.5529085,0.7927 0.184303,0.2931 0.368606,0.6135 0.552909,0.9549 0.184302,0.3412 0.368605,0.7034 0.552908,1.0792 0.184303,0.3758 0.368606,0.7653 0.552909,1.1608 0.184302,0.3956 0.368605,0.7969 0.552908,1.1964 0.184303,0.3994 0.368606,0.7969 0.552909,1.1845 0.184303,0.3876 0.368606,0.7653 0.552908,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208"
+       title="sin(x)"
+       id="path4190-0"
+       sodipodi:nodetypes="ccccccccccscccsccsccccscccccccccccccsccc" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       id="rect3905-8"
+       width="22.075964"
+       height="15.974718"
+       x="3.1981804"
+       y="1021.8977" />
+    <rect
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+       id="rect4735-2"
+       width="21.984022"
+       height="15.841428"
+       x="6.5999274"
+       y="1026.7411" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 6.3426012,1030.4772 22.2349198,0"
+       id="path3768-2-3"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 6.4714839,1038.6287 22.0416911,0"
+       id="path3768-1-3"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 10.589663,1026.6634 0,15.8294"
+       id="path3755-4-1"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 14.45008,1026.6634 0,15.8294"
+       id="path3755-4-0-3"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 18.310498,1026.6634 0,15.8294"
+       id="path3755-4-5-1"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 22.170914,1026.6634 0,15.8294"
+       id="path3755-4-9-88"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 26.031331,1026.6634 0,15.8294"
+       id="path3755-4-7-2"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 6.4723474,1034.553 22.0120786,0"
+       id="path3768-98"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 6.9006267,1034.6435 c 0.1843028,-0.3999 0.3686057,-0.8 0.5529085,-1.1919 0.1843029,-0.3921 0.3686057,-0.7762 0.5529086,-1.1447 0.1843028,-0.3686 0.3686057,-0.7216 0.5529085,-1.0521 0.1843028,-0.3303 0.3686057,-0.6383 0.5529085,-0.9175 0.1843029,-0.2794 0.3686057,-0.5299 0.5529085,-0.7468 0.1843029,-0.2172 0.3686057,-0.4004 0.5529087,-0.5466 0.184303,-0.1461 0.368605,-0.2549 0.552908,-0.3244 0.184303,-0.069 0.368606,-0.1 0.552909,-0.09 0.184303,0.011 0.368606,0.06 0.552908,0.1489 0.184303,0.09 0.368606,0.217 0.552909,0.3815 0.184303,0.1645 0.368606,0.3655 0.552908,0.5989 0.184303,0.2335 0.368606,0.4995 0.552909,0.7927 0.184303,0.2931 0.368606,0.6135 0.552909,0.9549 0.184302,0.3412 0.368605,0.7034 0.552908,1.0792 0.184303,0.3758 0.368606,0.7653 0.552909,1.1608 0.184302,0.3956 0.368605,0.7969 0.552908,1.1964 0.184303,0.3994 0.368606,0.7969 0.552909,1.1845 0.184303,0.3876 0.368605,0.7653 0.552908,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208"
+       title="sin(x)"
+       id="path4190-8"
+       sodipodi:nodetypes="ccccccccccscccsccsccccscccccccccccccsccc" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       id="rect3905-7"
+       width="22.075964"
+       height="15.974718"
+       x="6.5332823"
+       y="1026.6744" />
+    <rect
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+       id="rect4735-98"
+       width="21.984022"
+       height="15.841428"
+       x="9.935029"
+       y="1031.5178" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 9.6777028,1035.2539 22.2349202,0"
+       id="path3768-2-4"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 9.8065855,1043.4054 22.0416915,0"
+       id="path3768-1-6"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 13.924764,1031.4401 0,15.8294"
+       id="path3755-4-54"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 17.785182,1031.4401 0,15.8294"
+       id="path3755-4-0-5"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 21.6456,1031.4401 0,15.8294"
+       id="path3755-4-5-9"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 25.506016,1031.4401 0,15.8294"
+       id="path3755-4-9-87"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 29.366433,1031.4401 0,15.8294"
+       id="path3755-4-7-4"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 9.807449,1039.3297 22.012079,0"
+       id="path3768-6"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 10.235728,1039.4202 c 0.184303,-0.3999 0.368606,-0.8 0.552909,-1.1919 0.184303,-0.3921 0.368605,-0.7762 0.552908,-1.1447 0.184303,-0.3686 0.368606,-0.7216 0.552909,-1.0521 0.184303,-0.3303 0.368606,-0.6383 0.552908,-0.9175 0.184303,-0.2794 0.368606,-0.5299 0.552909,-0.7468 0.184303,-0.2172 0.368606,-0.4004 0.552908,-0.5466 0.184303,-0.1461 0.368606,-0.2549 0.552909,-0.3244 0.184303,-0.069 0.368606,-0.1 0.552909,-0.09 0.184302,0.011 0.368605,0.06 0.552908,0.1489 0.184303,0.09 0.368606,0.217 0.552909,0.3815 0.184302,0.1645 0.368605,0.3655 0.552908,0.5989 0.184303,0.2335 0.368606,0.4995 0.552909,0.7927 0.184302,0.2931 0.368605,0.6135 0.552908,0.9549 0.184303,0.3412 0.368606,0.7034 0.552909,1.0792 0.184302,0.3758 0.368605,0.7653 0.552908,1.1608 0.184303,0.3956 0.368606,0.7969 0.552909,1.1964 0.184302,0.3994 0.368605,0.7969 0.552908,1.1845 0.184303,0.3876 0.368606,0.7653 0.552909,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208"
+       title="sin(x)"
+       id="path4190-84"
+       sodipodi:nodetypes="ccccccccccscccsccsccccscccccccccccccsccc" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       id="rect3905-86"
+       width="22.075964"
+       height="15.974718"
+       x="9.8683844"
+       y="1031.451" />
+    <rect
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+       id="rect4735-23"
+       width="21.984022"
+       height="15.841428"
+       x="13.270129"
+       y="1036.2946" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 13.012803,1040.0306 22.23492,0"
+       id="path3768-2-5"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 13.141686,1048.1821 22.041691,0"
+       id="path3768-1-5"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 17.259864,1036.2168 0,15.8294"
+       id="path3755-4-6"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 21.120282,1036.2168 0,15.8294"
+       id="path3755-4-0-35"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 24.9807,1036.2168 0,15.8294"
+       id="path3755-4-5-82"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 28.841116,1036.2168 0,15.8294"
+       id="path3755-4-9-5"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 32.701533,1036.2168 0,15.8294"
+       id="path3755-4-7-7"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 13.142549,1044.1064 22.012079,0"
+       id="path3768-25"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 13.570828,1044.1969 c 0.184303,-0.3999 0.368606,-0.8 0.552909,-1.1919 0.184303,-0.3921 0.368605,-0.7762 0.552908,-1.1447 0.184303,-0.3686 0.368606,-0.7216 0.552909,-1.0521 0.184303,-0.3303 0.368606,-0.6383 0.552908,-0.9175 0.184303,-0.2794 0.368606,-0.5299 0.552909,-0.7468 0.184303,-0.2172 0.368606,-0.4004 0.552908,-0.5466 0.184303,-0.1461 0.368606,-0.2549 0.552909,-0.3244 0.184303,-0.069 0.368606,-0.1 0.552909,-0.09 0.184302,0.011 0.368605,0.06 0.552908,0.1489 0.184303,0.09 0.368606,0.217 0.552909,0.3815 0.184302,0.1645 0.368605,0.3655 0.552908,0.5989 0.184303,0.2335 0.368606,0.4995 0.552909,0.7927 0.184302,0.2931 0.368605,0.6135 0.552908,0.9549 0.184303,0.3412 0.368606,0.7034 0.552909,1.0792 0.184303,0.3758 0.368605,0.7653 0.552908,1.1608 0.184303,0.3956 0.368606,0.7969 0.552909,1.1964 0.184303,0.3994 0.368605,0.7969 0.552908,1.1845 0.184303,0.3876 0.368606,0.7653 0.552909,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022"
+       title="sin(x)"
+       id="path4190-80"
+       sodipodi:nodetypes="ccccccccccscccsccscc" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       id="rect3905-9"
+       width="22.075964"
+       height="15.974718"
+       x="13.203484"
+       y="1036.2278" />
+  </g>
+</svg>
diff --git a/icons/zoom-original.png b/icons/zoom-original.png
deleted file mode 100644 (file)
index 8ae3043..0000000
Binary files a/icons/zoom-original.png and /dev/null differ
index ce7db5fadcc2a18a05d567a0ac284cfe7065595f..a77969ea7f72313e6c42b71cf0cd09980c53e0da 100644 (file)
--- a/main.cpp
+++ b/main.cpp
 #endif
 
 #include <cstdint>
-#include <libsigrokcxx/libsigrokcxx.hpp>
-
+#include <fstream>
 #include <getopt.h>
+#include <vector>
+
+#include <libsigrokcxx/libsigrokcxx.hpp>
 
+#include <QCheckBox>
 #include <QDebug>
+#include <QFile>
+#include <QFileInfo>
+#include <QMessageBox>
 #include <QSettings>
+#include <QTextStream>
+
+#include "config.h"
 
 #ifdef ENABLE_SIGNALS
 #include "signalhandler.hpp"
 #endif
 
+#ifdef ENABLE_STACKTRACE
+#include <signal.h>
+#include <boost/stacktrace.hpp>
+#include <QStandardPaths>
+#endif
+
 #include "pv/application.hpp"
 #include "pv/devicemanager.hpp"
+#include "pv/globalsettings.hpp"
+#include "pv/logging.hpp"
 #include "pv/mainwindow.hpp"
+#include "pv/session.hpp"
+#include "pv/util.hpp"
+
 #ifdef ANDROID
 #include <libsigrokandroidutils/libsigrokandroidutils.h>
 #include "android/assetreader.hpp"
 #include "android/loghandler.hpp"
 #endif
 
-#include "config.h"
-
 #ifdef _WIN32
 #include <QtPlugin>
 Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
@@ -51,9 +69,76 @@ Q_IMPORT_PLUGIN(QSvgPlugin)
 #endif
 
 using std::exception;
+using std::ifstream;
+using std::ofstream;
 using std::shared_ptr;
 using std::string;
 
+#if ENABLE_STACKTRACE
+QString stacktrace_filename;
+
+void signal_handler(int signum)
+{
+       ::signal(signum, SIG_DFL);
+       boost::stacktrace::safe_dump_to(stacktrace_filename.toLocal8Bit().data());
+       ::raise(SIGABRT);
+}
+
+void process_stacktrace(QString temp_path)
+{
+       const QString stacktrace_outfile = temp_path + "/pv_stacktrace.txt";
+
+       ifstream ifs(stacktrace_filename.toLocal8Bit().data());
+       ofstream ofs(stacktrace_outfile.toLocal8Bit().data(),
+               ofstream::out | ofstream::trunc);
+
+       boost::stacktrace::stacktrace st =
+               boost::stacktrace::stacktrace::from_dump(ifs);
+       ofs << st;
+
+       ofs.close();
+       ifs.close();
+
+       QFile f(stacktrace_outfile);
+       f.open(QFile::ReadOnly | QFile::Text);
+       QTextStream fs(&f);
+       QString stacktrace = fs.readAll();
+       stacktrace = stacktrace.trimmed().replace('\n', "<br />");
+
+       qDebug() << QObject::tr("Stack trace of previous crash:");
+       qDebug() << "---------------------------------------------------------";
+       // Note: qDebug() prints quotation marks for QString output, so we feed it char*
+       qDebug() << stacktrace.toLocal8Bit().data();
+       qDebug() << "---------------------------------------------------------";
+
+       f.close();
+
+       // Remove stack trace so we don't process it again the next time we run
+       QFile::remove(stacktrace_filename.toLocal8Bit().data());
+
+       // Show notification dialog if permitted
+       pv::GlobalSettings settings;
+       if (settings.value(pv::GlobalSettings::Key_Log_NotifyOfStacktrace).toBool()) {
+               QCheckBox *cb = new QCheckBox(QObject::tr("Don't show this message again"));
+
+               QMessageBox msgbox;
+               msgbox.setText(QObject::tr("When %1 last crashed, it created a stack trace.\n" \
+                       "A human-readable form has been saved to disk and was written to " \
+                       "the log. You may access it from the settings dialog.").arg(PV_TITLE));
+               msgbox.setIcon(QMessageBox::Icon::Information);
+               msgbox.addButton(QMessageBox::Ok);
+               msgbox.setCheckBox(cb);
+
+               QObject::connect(cb, &QCheckBox::stateChanged, [](int state){
+                       pv::GlobalSettings settings;
+                       settings.setValue(pv::GlobalSettings::Key_Log_NotifyOfStacktrace,
+                               !state); });
+
+               msgbox.exec();
+       }
+}
+#endif
+
 void usage()
 {
        fprintf(stdout,
@@ -66,6 +151,8 @@ void usage()
                "Application Options:\n"
                "  -V, --version                   Show release version\n"
                "  -l, --loglevel                  Set libsigrok/libsigrokdecode loglevel\n"
+               "  -d, --driver                    Specify the device driver to use\n"
+               "  -D, --dont-scan                 Don't auto-scan for devices, use -d spec only\n"
                "  -i, --input-file                Load input from file\n"
                "  -I, --input-format              Input format\n"
                "  -c, --clean                     Don't restore previous sessions on startup\n"
@@ -76,8 +163,11 @@ int main(int argc, char *argv[])
 {
        int ret = 0;
        shared_ptr<sigrok::Context> context;
-       string open_file, open_file_format;
+       string open_file_format, driver;
+       vector<string> open_files;
        bool restore_sessions = true;
+       bool do_scan = true;
+       bool show_version = false;
 
        Application a(argc, argv);
 
@@ -93,14 +183,17 @@ int main(int argc, char *argv[])
                        {"help", no_argument, nullptr, 'h'},
                        {"version", no_argument, nullptr, 'V'},
                        {"loglevel", required_argument, nullptr, 'l'},
+                       {"driver", required_argument, nullptr, 'd'},
+                       {"dont-scan", no_argument, nullptr, 'D'},
                        {"input-file", required_argument, nullptr, 'i'},
                        {"input-format", required_argument, nullptr, 'I'},
                        {"clean", no_argument, nullptr, 'c'},
+                       {"log-to-stdout", no_argument, nullptr, 's'},
                        {nullptr, 0, nullptr, 0}
                };
 
                const int c = getopt_long(argc, argv,
-                       "l:Vhc?i:I:", long_options, nullptr);
+                       "h?VDcl:d:i:I:", long_options, nullptr);
                if (c == -1)
                        break;
 
@@ -111,13 +204,16 @@ int main(int argc, char *argv[])
                        return 0;
 
                case 'V':
-                       // Print version info
-                       fprintf(stdout, "%s %s\n", PV_TITLE, PV_VERSION_STRING);
-                       return 0;
+                       show_version = true;
+                       break;
 
                case 'l':
                {
                        const int loglevel = atoi(optarg);
+                       if (loglevel < 0 || loglevel > 5) {
+                               qDebug() << "ERROR: invalid log level spec.";
+                               break;
+                       }
                        context->set_log_level(sigrok::LogLevel::get(loglevel));
 
 #ifdef ENABLE_DECODE
@@ -132,8 +228,16 @@ int main(int argc, char *argv[])
                        break;
                }
 
+               case 'd':
+                       driver = optarg;
+                       break;
+
+               case 'D':
+                       do_scan = false;
+                       break;
+
                case 'i':
-                       open_file = optarg;
+                       open_files.emplace_back(optarg);
                        break;
 
                case 'I':
@@ -145,17 +249,40 @@ int main(int argc, char *argv[])
                        break;
                }
        }
+       argc -= optind;
+       argv += optind;
 
-       if (argc - optind > 1) {
-               fprintf(stderr, "Only one file can be opened.\n");
-               return 1;
-       }
+       for (int i = 0; i < argc; i++)
+               open_files.emplace_back(argv[i]);
+
+       qRegisterMetaType<pv::util::Timestamp>("util::Timestamp");
+       qRegisterMetaType<uint64_t>("uint64_t");
+
+       // Prepare the global settings since logging needs them early on
+       pv::GlobalSettings settings;
+       settings.save_internal_defaults();
+       settings.set_defaults_where_needed();
+       settings.apply_theme();
 
-       if (argc - optind == 1)
-               open_file = argv[argc - 1];
+       pv::logging.init();
 
        // Initialise libsigrok
        context = sigrok::Context::create();
+       pv::Session::sr_context = context;
+
+#if ENABLE_STACKTRACE
+       QString temp_path = QStandardPaths::standardLocations(
+               QStandardPaths::TempLocation).at(0);
+       stacktrace_filename = temp_path + "/pv_stacktrace.dmp";
+       qDebug() << "Stack trace file is" << stacktrace_filename;
+
+       ::signal(SIGSEGV, &signal_handler);
+       ::signal(SIGABRT, &signal_handler);
+
+       if (QFileInfo::exists(stacktrace_filename))
+               process_stacktrace(temp_path);
+#endif
+
 #ifdef ANDROID
        context->set_resource_reader(&asset_reader);
 #endif
@@ -172,10 +299,17 @@ int main(int argc, char *argv[])
                srd_decoder_load_all();
 #endif
 
+#ifndef ENABLE_STACKTRACE
                try {
-                       // Create the device manager, initialise the drivers
-                       pv::DeviceManager device_manager(context);
+#endif
+
+               // Create the device manager, initialise the drivers
+               pv::DeviceManager device_manager(context, driver, do_scan);
 
+               a.collect_version_info(context);
+               if (show_version) {
+                       a.print_version_info();
+               } else {
                        // Initialise the main window
                        pv::MainWindow w(device_manager);
                        w.show();
@@ -183,33 +317,32 @@ int main(int argc, char *argv[])
                        if (restore_sessions)
                                w.restore_sessions();
 
-                       if (!open_file.empty())
-                               w.add_session_with_file(open_file, open_file_format);
-                       else
+                       if (open_files.empty())
                                w.add_default_session();
+                       else
+                               for (string& open_file : open_files)
+                                       w.add_session_with_file(open_file, open_file_format);
 
 #ifdef ENABLE_SIGNALS
                        if (SignalHandler::prepare_signals()) {
-                               SignalHandler *const handler =
-                                       new SignalHandler(&w);
-                               QObject::connect(handler,
-                                       SIGNAL(int_received()),
+                               SignalHandler *const handler = new SignalHandler(&w);
+                               QObject::connect(handler, SIGNAL(int_received()),
                                        &w, SLOT(close()));
-                               QObject::connect(handler,
-                                       SIGNAL(term_received()),
+                               QObject::connect(handler, SIGNAL(term_received()),
                                        &w, SLOT(close()));
-                       } else {
-                               qWarning() <<
-                                       "Could not prepare signal handler.";
-                       }
+                       } else
+                               qWarning() << "Could not prepare signal handler.";
 #endif
 
                        // Run the application
                        ret = a.exec();
+               }
 
-               } catch (exception e) {
-                       qDebug() << e.what();
+#ifndef ENABLE_STACKTRACE
+               } catch (exception& e) {
+                       qDebug() << "Exception:" << e.what();
                }
+#endif
 
 #ifdef ENABLE_DECODE
                // Destroy libsigrokdecode
diff --git a/manual/CMakeLists.txt b/manual/CMakeLists.txt
new file mode 100644 (file)
index 0000000..c425f36
--- /dev/null
@@ -0,0 +1,87 @@
+##
+## This file is part of the PulseView project.
+##
+## Copyright (C) 2018 Gerhard Sittig <gerhard.sittig@gmx.net>
+##
+## 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.12)
+
+# External dependencies, required and optional tools.
+find_program(ASCIIDOCTOR_EXECUTABLE NAMES asciidoctor)
+find_program(ASCIIDOCTOR_PDF_EXECUTABLE NAMES asciidoctor-pdf)
+
+# Tunables.
+set(STYLES_DIR "asciidoctor-stylesheet-factory/stylesheets")
+set(STYLE_SHEET "readthedocs.css")
+
+# Input files.
+set(MANUAL_SRC "${CMAKE_CURRENT_SOURCE_DIR}/manual.txt")
+
+# Output files, conversion results.
+set(MANUAL_OUT_HTML "${CMAKE_CURRENT_BINARY_DIR}/manual.html")
+set(MANUAL_OUT_PDF "${CMAKE_CURRENT_BINARY_DIR}/manual.pdf")
+
+# Manual related make(1) targets.
+add_custom_target(manual-html
+       COMMAND ${ASCIIDOCTOR_EXECUTABLE}
+               -a stylesheet=${STYLE_SHEET}
+               -a stylesdir=${CMAKE_CURRENT_SOURCE_DIR}/${STYLES_DIR}
+               -a toc=left
+               --destination-dir=${CMAKE_CURRENT_BINARY_DIR}
+               ${MANUAL_SRC}
+       BYPRODUCTS ${MANUAL_OUT_HTML}
+       DEPENDS ${MANUAL_SRC}
+       WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+       COMMENT "Generating manual, HTML output"
+)
+if (ASCIIDOCTOR_PDF_EXECUTABLE)
+       add_custom_target(manual-pdf
+               COMMAND ${ASCIIDOCTOR_PDF_EXECUTABLE}
+                       -a stylesheet=${STYLE_SHEET}
+                       -a stylesdir=${CMAKE_CURRENT_SOURCE_DIR}/${STYLES_DIR}
+                       --destination-dir=${CMAKE_CURRENT_BINARY_DIR}
+                       ${MANUAL_SRC}
+               BYPRODUCTS ${MANUAL_OUT_PDF}
+               DEPENDS ${MANUAL_SRC}
+               WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+               COMMENT "Generating manual, HTML output"
+       )
+else ()
+       add_custom_target(manual-pdf
+               COMMAND ${CMAKE_COMMAND} -E echo
+                       "asciidoctor-pdf executable is missing, NOT generating PDF output"
+               DEPENDS ${MANUAL_SRC}
+               WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+       )
+endif ()
+add_custom_target(manual)
+add_dependencies(manual manual-html manual-pdf)
+
+set(MANUAL_INST_SUBDIR "share/doc/pulseview")
+install(
+       FILES ${MANUAL_OUT_HTML} ${MANUAL_OUT_PDF}
+       DESTINATION ${MANUAL_INST_SUBDIR}
+       PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
+       OPTIONAL
+)
+if (ASCIIDOCTOR_EXECUTABLE)
+       install(
+               DIRECTORY images
+               DESTINATION ${MANUAL_INST_SUBDIR}
+               FILE_PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
+               PATTERN "*.xcf" EXCLUDE
+       )
+endif ()
diff --git a/manual/acquisition.txt b/manual/acquisition.txt
new file mode 100644 (file)
index 0000000..685c9cd
--- /dev/null
@@ -0,0 +1,134 @@
+== Data Acquisition
+
+Working with PulseView follows a pattern:
+
+image::pv_nodevice.png[]
+<1> Open a new session
+<2> Select the device you want to work with:
+<3> Click "Run" to acquire signal data (waiting for a trigger first if you set one)
+
+When you start PulseView and no sessions are restored from the last time you used it, it will
+come up with a session that has the demo device selected. That way, you can get to know the
+program even when you don't have any hardware to use it with.
+
+=== Device Selection
+
+The device selector offers two methods to choose the device to use. If you click on the small
+arrow on the side, you see a list of devices PulseView has recognized. If the device you want
+to use it listed, you can just select it here to use it.
+
+image::device_selector_dropdown.png[]
+
+If it's not listed, you'll need to scan for it first. Since most serial port and Ethernet
+devices can't be auto-detected, this is usually required for those.
+To do so, either choose the "Connect to Device" option from the list or click on the button
+itself. You will see the following dialog:
+
+image::device_selector_scan.png[]
+
+First, you'll need to pick a driver that you want to use. In order to do this, you'll need
+to know which driver is used to talk to the device. If you're unsure, you can either try the
+driver which you think may fit best or you can check the wiki. For every supported device there's
+a wiki page, showing you which driver is used.
+
+Once the driver has been chosen, you need to select the interface. Please be aware that USB
+is only usable for devices that directly communicate over USB. Devices that use USB to emulate
+a serial port (like the OpenBench Logic Sniffer) will have their serial port listed in the
+serial port drop-down.
+
+In case your device connects via Ethernet, you must supply the IP address and port. You are
+also given the option to choose between raw TCP access and using the VXI protocol. VXI is an
+industry standard which is mainly used in professional equipment and the device will most
+likely let you know that it supports VXI. If your device however is more of a hobbyist grade
+device, it's more likely that using raw TCP will be the correct choice.
+
+After you selected the appropriate options, clicking the scan button will make PulseView try
+to connect to the device with the given settings. If successful, any device(s) found will be
+shown in the list box.
+
+[NOTE]
+When a session uses a USB device and you close Pulseview, a session with that same device
+is re-opened when you start Pulseview again. Currently, this is however not the case for non-USB
+devices, such as ones that connect via serial port or Ethernet.
+
+[NOTE]
+To avoid having to manually enter the device configuration for a serial port or Ethernet
+device every time you want to use it and then having to scan for it, you can also use the
+command line parameter -d to have PulseView scan for it on startup.
+
+You may then change the device configuration and/or start the data acquisition by clicking
+the "Run" button on the top left.
+
+When you run the acquisition, you'll notice that the newly captured data goes off-screen.
+This is to improve performance and let PulseView acquire the data without bogging down your
+CPU too much. If you find this inconvenient because you'd like to see what kind of data is
+coming in, you have three options:
+
+* Enable "always perform zoom-to-fit" temporarily (see chapter "Data Analysis")
+* Enable "constantly perform zoom-to-fit during acquisition" in the options
+* Enable "always keep newest samples at the right edge during capture" in the options
+
+Which method suits you best is up for you to decide.
+
+=== Device Configuration
+
+In PulseView, the device configuration is done using these buttons:
+
+image::pv_device_config.png[]
+<1> Device-specific settings
+<2> Channel-specific settings
+<3> Number of samples to capture
+<4> Sample rate at which to capture the samples
+<5> Per-channel trigger setting (see below)
+
+The values offered for those four elements depend on your device. Which settings you should choose
+depends on several factors: the needs of your measurement, the device you use to capture the data
+and the capabilities of your computer.
+
+The sample rate you choose must at least be twice that of the highest frequency you want to
+capture - ideally 3 to 5 times as much so that you have some margin. That way, a jittering signal
+won't ruin your measurements.
+
+[NOTE]
+If you're using a device with a Cypress FX2 (most 8 channel / 24 MHz logic analyzers do) then you should
+be aware that the 24 MHz sampling rate (12 MHz for 16 channels) can only be sustained under perfect
+conditions. Usually, those devices are shipped with low-quality USB cables, impairing USB transfers as
+USB traffic increases. Therefore, you can try a different USB cable if you're facing issues at higher
+sample rates. If they persist, it's worth trying a different USB port as well.
+
+=== Triggers
+
+The signal labels on the left side of the view (D0, D1 and so on in the picture above) allow you to
+configure certain aspects of these signals. If the device supports it then the trigger that will be
+used for this signal will be among them.
+
+As of now, the trigger system is awaiting extension for advanced and complex trigger types, meaning
+that the only triggers available to you are:
+
+* Trigger when the signal has a "low" level
+* Trigger when the signal has a "high" level
+* Trigger when the signal switches from "low" to "high" level (rising edge)
+* Trigger when the signal switches from "hig" to "low" level (falling edge)
+* Trigger when the signal changes level in any way (any edge)
+
+Once you choose a trigger, the icon for the type you chose becomes visible on the right side of the
+trace view.
+
+When you click "Run" with a trigger configured, PulseView will wait for the device to trigger and
+send data before it can show anything. There is currently no frame limit, so if the device driver
+supports it, PulseView will continue arming the trigger and collecting data until you either click
+"Stop" or it runs out of memory.
+
+=== Channel Groups
+
+Some devices share certain settings between a group of channels, which is why PulseView may show
+the channels your device offers in groups. You can see which channels are grouped by looking at the
+dark gray bar on the left. If there is none, no channels are grouped.
+
+Currently, the grouping is only done for your convenience and there's no direct functional impact.
+This means that you're free to ungroup and group channels as you please. To do so, right-click
+on the dark gray bar and select "Ungroup".
+If you want to create a new group, select the signals you want to group by holding down CTRL
+and clicking on the signal labels. Once you have selected the ones you want to be grouped,
+right-click on one of the labels you selected and choose "Group".
+
diff --git a/manual/analysis.txt b/manual/analysis.txt
new file mode 100644 (file)
index 0000000..7aa487b
--- /dev/null
@@ -0,0 +1,84 @@
+== Data Analysis
+
+Once you have acquired some measurement data, it's time to have a look and see what
+insights you can gain from it. Usually, the first step is to look at the data as a
+whole, achieved by clicking the _Zoom to Fit_ button:
+
+image::pv_analysis.png[]
+
+<1> Zoom-to-Fit button
+<2> Zoom in/zoom out buttons
+<3> Cursors
+<4> Time scale (used to set up and show markers, see below)
+
+If you have located an area of interest (maybe with the help of decoders, more about
+that later), you can zoom in on it using the _zoom in_/_zoom out_ buttons, using the
+scroll wheel of your mouse or the pinch/expand gestures on your touch panel.
+
+[NOTE]
+When a data capture is ongoing, the Zoom-to-Fit button stays active if you click it,
+meaning that PulseView automatically fits all data to the views until either the
+capture is finished or the Zoom-to-Fit button is clicked again.
+If you want this feature but don't want to always have to click the button, you
+can enable the "Always Zoom-to-Fit" option in the settings.
+
+=== Cursors and Markers
+
+Just looking at the signal data however is usually not sufficient. A lot of times,
+you'll want to make sure that timings are honored and the bit times are like what
+you'd expect. To do so, you'll want to use cursors and markers.
+
+In the picture above, you can enable the cursor by clicking on the cursor button.
+You can move both of its boundaries around by clicking on the blue flags in the
+time scale area. The area between the two boundary lines shows the time distance
+and its inverse (i.e. the frequency). If you can't see it, just zoom in until it
+shows. You can also move both boundaries at the same time by dragging the label
+where this information is shown.
+
+image::pv_cursors_markers.png[]
+
+<1> Cursors button, showing enabled state
+<2> Cursor
+<3> Marker
+
+Markers are movable indicators that you can create wherever you like on the
+time scale - just double-click on it and it'll create one for you where your
+mouse cursor is at the time, or use the context menu when right-clicking on
+the ruler or a signal trace.
+You can click on its label and you'll have the option to change its name, or
+drag it to reposition it.
+
+[NOTE]
+For timing comparison purposes, you can also enable a vertical marker line that
+follows your mouse cursor: _Settings_ -> _Views_ -> _Highlight mouse cursor_
+
+[NOTE]
+There is also a special kind of marker that appears for each time the data
+acquisition device has triggered. It cannot be moved and appears as a vertical
+dashed line.
+
+=== Special-Purpose Decoders
+
+There are some decoders available that analyze the data instead of decoding it.
+You can make use of them to examine various properties of the signals that are
+of interest to you.
+
+Their names are:
+
+* Counter - counts pulses and/or groups of pulses (i.e. words)
+* Guess bitrate - guesses the bitrate when using a serial protocol
+* Jitter - determines the jitter (variance) of a signal
+* Timing - shows the time passing between the chosen signal edges
+
+=== Other Features
+
+Trace Views also allow you to maximize the viewing area by minimizing the area
+occupied by the label area on the left. To do this, simply position the mouse
+cursor at the right edge of the label area (or left edge of the viewing area).
+Your mouse cursor will change shape and you now can drag the border.
+
+This way, you can give signals long, expressive names without clogging up the
+view area.
+
+Also, you can create multiple views by clicking on the "New View" button on
+the very left of the toolbar. Those can be rearranged as you wish.
diff --git a/manual/asciidoctor-stylesheet-factory/LICENSE b/manual/asciidoctor-stylesheet-factory/LICENSE
new file mode 100644 (file)
index 0000000..5f4d18e
--- /dev/null
@@ -0,0 +1,45 @@
+Asciidoctor styles
+------------------
+
+Copyright (c) 2013 Dan Allen
+
+MIT License
+
+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.
+
+
+Other licensed work
+-------------------
+
+- Foundation 4 by Zurb, on which the themes are built, is licensed under the
+  Apache License, v2.0:
+
+  http://apache.org/licenses/LICENSE-2.0
+  http://foundation.zurb.com
+
+- The riak theme is derived from the Riak documentation theme by Basho,
+  licensed under the Creative Commons Attribution 3.0 Unported License:
+
+  http://creativecommons.org/licenses/by/3.0/us
+  http://docs.basho.org
+
+- The iconic theme is inspired by O'Reilly typography and Atlas manual.
+
+  http://oreilly.com
diff --git a/manual/asciidoctor-stylesheet-factory/stylesheets/readthedocs.css b/manual/asciidoctor-stylesheet-factory/stylesheets/readthedocs.css
new file mode 100644 (file)
index 0000000..7adc8d1
--- /dev/null
@@ -0,0 +1,727 @@
+/*! normalize.css v2.1.2 | MIT License | git.io/normalize */
+/* ========================================================================== HTML5 display definitions ========================================================================== */
+/** Correct `block` display not defined in IE 8/9. */
+article, aside, details, figcaption, figure, footer, header, hgroup, main, nav, section, summary { display: block; }
+
+/** Correct `inline-block` display not defined in IE 8/9. */
+audio, canvas, video { display: inline-block; }
+
+/** Prevent modern browsers from displaying `audio` without controls. Remove excess height in iOS 5 devices. */
+audio:not([controls]) { display: none; height: 0; }
+
+/** Address `[hidden]` styling not present in IE 8/9. Hide the `template` element in IE, Safari, and Firefox < 22. */
+[hidden], template { display: none; }
+
+script { display: none !important; }
+
+/* ========================================================================== Base ========================================================================== */
+/** 1. Set default font family to sans-serif. 2. Prevent iOS text size adjust after orientation change, without disabling user zoom. */
+html { font-family: sans-serif; /* 1 */ -ms-text-size-adjust: 100%; /* 2 */ -webkit-text-size-adjust: 100%; /* 2 */ }
+
+/** Remove default margin. */
+body { margin: 0; }
+
+/* ========================================================================== Links ========================================================================== */
+/** Remove the gray background color from active links in IE 10. */
+a { background: transparent; }
+
+/** Address `outline` inconsistency between Chrome and other browsers. */
+a:focus { outline: thin dotted; }
+
+/** Improve readability when focused and also mouse hovered in all browsers. */
+a:active, a:hover { outline: 0; }
+
+/* ========================================================================== Typography ========================================================================== */
+/** Address variable `h1` font-size and margin within `section` and `article` contexts in Firefox 4+, Safari 5, and Chrome. */
+h1 { font-size: 2em; margin: 0.67em 0; }
+
+/** Address styling not present in IE 8/9, Safari 5, and Chrome. */
+abbr[title] { border-bottom: 1px dotted; }
+
+/** Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. */
+b, strong { font-weight: bold; }
+
+/** Address styling not present in Safari 5 and Chrome. */
+dfn { font-style: italic; }
+
+/** Address differences between Firefox and other browsers. */
+hr { -moz-box-sizing: content-box; box-sizing: content-box; height: 0; }
+
+/** Address styling not present in IE 8/9. */
+mark { background: #ff0; color: #000; }
+
+/** Correct font family set oddly in Safari 5 and Chrome. */
+code, kbd, pre, samp { font-family: monospace, serif; font-size: 1em; }
+
+/** Improve readability of pre-formatted text in all browsers. */
+pre { white-space: pre-wrap; }
+
+/** Set consistent quote types. */
+q { quotes: "\201C" "\201D" "\2018" "\2019"; }
+
+/** Address inconsistent and variable font size in all browsers. */
+small { font-size: 80%; }
+
+/** Prevent `sub` and `sup` affecting `line-height` in all browsers. */
+sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
+
+sup { top: -0.5em; }
+
+sub { bottom: -0.25em; }
+
+/* ========================================================================== Embedded content ========================================================================== */
+/** Remove border when inside `a` element in IE 8/9. */
+img { border: 0; }
+
+/** Correct overflow displayed oddly in IE 9. */
+svg:not(:root) { overflow: hidden; }
+
+/* ========================================================================== Figures ========================================================================== */
+/** Address margin not present in IE 8/9 and Safari 5. */
+figure { margin: 0; }
+
+/* ========================================================================== Forms ========================================================================== */
+/** Define consistent border, margin, and padding. */
+fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em; }
+
+/** 1. Correct `color` not being inherited in IE 8/9. 2. Remove padding so people aren't caught out if they zero out fieldsets. */
+legend { border: 0; /* 1 */ padding: 0; /* 2 */ }
+
+/** 1. Correct font family not being inherited in all browsers. 2. Correct font size not being inherited in all browsers. 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. */
+button, input, select, textarea { font-family: inherit; /* 1 */ font-size: 100%; /* 2 */ margin: 0; /* 3 */ }
+
+/** Address Firefox 4+ setting `line-height` on `input` using `!important` in the UA stylesheet. */
+button, input { line-height: normal; }
+
+/** Address inconsistent `text-transform` inheritance for `button` and `select`. All other form control elements do not inherit `text-transform` values. Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+. Correct `select` style inheritance in Firefox 4+ and Opera. */
+button, select { text-transform: none; }
+
+/** 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` and `video` controls. 2. Correct inability to style clickable `input` types in iOS. 3. Improve usability and consistency of cursor style between image-type `input` and others. */
+button, html input[type="button"], input[type="reset"], input[type="submit"] { -webkit-appearance: button; /* 2 */ cursor: pointer; /* 3 */ }
+
+/** Re-set default cursor for disabled elements. */
+button[disabled], html input[disabled] { cursor: default; }
+
+/** 1. Address box sizing set to `content-box` in IE 8/9. 2. Remove excess padding in IE 8/9. */
+input[type="checkbox"], input[type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ }
+
+/** 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome (include `-moz` to future-proof). */
+input[type="search"] { -webkit-appearance: textfield; /* 1 */ -moz-box-sizing: content-box; -webkit-box-sizing: content-box; /* 2 */ box-sizing: content-box; }
+
+/** Remove inner padding and search cancel button in Safari 5 and Chrome on OS X. */
+input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; }
+
+/** Remove inner padding and border in Firefox 4+. */
+button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; }
+
+/** 1. Remove default vertical scrollbar in IE 8/9. 2. Improve readability and alignment in all browsers. */
+textarea { overflow: auto; /* 1 */ vertical-align: top; /* 2 */ }
+
+/* ========================================================================== Tables ========================================================================== */
+/** Remove most spacing between table cells. */
+table { border-collapse: collapse; border-spacing: 0; }
+
+meta.foundation-mq-small { font-family: "only screen and (min-width: 768px)"; width: 768px; }
+
+meta.foundation-mq-medium { font-family: "only screen and (min-width:1280px)"; width: 1280px; }
+
+meta.foundation-mq-large { font-family: "only screen and (min-width:1440px)"; width: 1440px; }
+
+*, *:before, *:after { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; }
+
+html, body { font-size: 100%; }
+
+body { background: #fff; color: #222; padding: 0; margin: 0; font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif; font-weight: normal; font-style: normal; line-height: 1; position: relative; cursor: auto; }
+
+a:hover { cursor: pointer; }
+
+img, object, embed { max-width: 100%; height: auto; }
+
+object, embed { height: 100%; }
+
+img { -ms-interpolation-mode: bicubic; }
+
+#map_canvas img, #map_canvas embed, #map_canvas object, .map_canvas img, .map_canvas embed, .map_canvas object { max-width: none !important; }
+
+.left { float: left !important; }
+
+.right { float: right !important; }
+
+.text-left { text-align: left !important; }
+
+.text-right { text-align: right !important; }
+
+.text-center { text-align: center !important; }
+
+.text-justify { text-align: justify !important; }
+
+.hide { display: none; }
+
+.antialiased { -webkit-font-smoothing: antialiased; }
+
+img { display: inline-block; vertical-align: middle; }
+
+textarea { height: auto; min-height: 50px; }
+
+select { width: 100%; }
+
+object, svg { display: inline-block; vertical-align: middle; }
+
+.center { margin-left: auto; margin-right: auto; }
+
+.stretch { width: 100%; }
+
+p.lead { font-size: 1.21875em; line-height: 1.6; }
+
+.subheader, .admonitionblock td.content > .title, .audioblock > .title, .exampleblock > .title, .imageblock > .title, .listingblock > .title, .literalblock > .title, .stemblock > .title, .openblock > .title, .paragraph > .title, .quoteblock > .title, table.tableblock > .title, .verseblock > .title, .videoblock > .title, .dlist > .title, .olist > .title, .ulist > .title, .qlist > .title, .hdlist > .title { line-height: 1.4; color: #6c818f; font-weight: 300; margin-top: 0.2em; margin-bottom: 0.5em; }
+
+/* Typography resets */
+div, dl, dt, dd, ul, ol, li, h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6, pre, form, p, blockquote, th, td { margin: 0; padding: 0; direction: ltr; }
+
+/* Default Link Styles */
+a { color: #444; text-decoration: underline; line-height: inherit; }
+a:hover, a:focus { color: #111; }
+a img { border: none; }
+
+/* Default paragraph styles */
+p { font-family: inherit; font-weight: normal; font-size: 1em; line-height: 1.5; margin-bottom: 1.25em; text-rendering: optimizeLegibility; }
+p aside { font-size: 0.875em; line-height: 1.35; font-style: italic; }
+
+/* Default header styles */
+h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6 { font-family: ff-meta-web-pro-1, ff-meta-web-pro-2, Arial, "Helvetica Neue", sans-serif; font-weight: bold; font-style: normal; color: #465158; text-rendering: optimizeLegibility; margin-top: 1em; margin-bottom: 0.5em; line-height: 1.2125em; }
+h1 small, h2 small, h3 small, #toctitle small, .sidebarblock > .content > .title small, h4 small, h5 small, h6 small { font-size: 60%; color: #909ea7; line-height: 0; }
+
+h1 { font-size: 2.125em; }
+
+h2 { font-size: 1.6875em; }
+
+h3, #toctitle, .sidebarblock > .content > .title { font-size: 1.375em; }
+
+h4 { font-size: 1.125em; }
+
+h5 { font-size: 1.125em; }
+
+h6 { font-size: 1em; }
+
+hr { border: solid #ddd; border-width: 1px 0 0; clear: both; margin: 1.25em 0 1.1875em; height: 0; }
+
+/* Helpful Typography Defaults */
+em, i { font-style: italic; line-height: inherit; }
+
+strong, b { font-weight: bold; line-height: inherit; }
+
+small { font-size: 60%; line-height: inherit; }
+
+code { font-family: "Consolas", "Deja Vu Sans Mono", "Bitstream Vera Sans Mono", monospace; font-weight: normal; color: #444; }
+
+/* Lists */
+ul, ol, dl { font-size: 1em; line-height: 1.5; margin-bottom: 1.25em; list-style-position: outside; font-family: inherit; }
+
+ul, ol { margin-left: 0; }
+ul.no-bullet, ol.no-bullet { margin-left: 0; }
+
+/* Unordered Lists */
+ul li ul, ul li ol { margin-left: 1.25em; margin-bottom: 0; font-size: 1em; /* Override nested font-size change */ }
+ul.square li ul, ul.circle li ul, ul.disc li ul { list-style: inherit; }
+ul.square { list-style-type: square; }
+ul.circle { list-style-type: circle; }
+ul.disc { list-style-type: disc; }
+ul.no-bullet { list-style: none; }
+
+/* Ordered Lists */
+ol li ul, ol li ol { margin-left: 1.25em; margin-bottom: 0; }
+
+/* Definition Lists */
+dl dt { margin-bottom: 0.3em; font-weight: bold; }
+dl dd { margin-bottom: 0.75em; }
+
+/* Abbreviations */
+abbr, acronym { text-transform: uppercase; font-size: 90%; color: #000; border-bottom: 1px dotted #ddd; cursor: help; }
+
+abbr { text-transform: none; }
+
+/* Blockquotes */
+blockquote { margin: 0 0 1.25em; padding: 0.5625em 1.25em 0 1.1875em; border-left: 1px solid #ddd; }
+blockquote cite { display: block; font-size: 0.8125em; color: #748590; }
+blockquote cite:before { content: "\2014 \0020"; }
+blockquote cite a, blockquote cite a:visited { color: #748590; }
+
+blockquote, blockquote p { line-height: 1.5; color: #909ea7; }
+
+/* Microformats */
+.vcard { display: inline-block; margin: 0 0 1.25em 0; border: 1px solid #ddd; padding: 0.625em 0.75em; }
+.vcard li { margin: 0; display: block; }
+.vcard .fn { font-weight: bold; font-size: 0.9375em; }
+
+.vevent .summary { font-weight: bold; }
+.vevent abbr { cursor: auto; text-decoration: none; font-weight: bold; border: none; padding: 0 0.0625em; }
+
+@media only screen and (min-width: 768px) { h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6 { line-height: 1.4; }
+  h1 { font-size: 2.75em; }
+  h2 { font-size: 2.3125em; }
+  h3, #toctitle, .sidebarblock > .content > .title { font-size: 1.6875em; }
+  h4 { font-size: 1.4375em; } }
+/* Tables */
+table { background: #fff; margin-bottom: 1.25em; border: solid 0 #ddd; }
+table thead, table tfoot { background: none; font-weight: bold; }
+table thead tr th, table thead tr td, table tfoot tr th, table tfoot tr td { padding: 1px 8px 1px 5px; font-size: 1em; color: #222; text-align: left; }
+table tr th, table tr td { padding: 1px 8px 1px 5px; font-size: 1em; color: #222; }
+table tr.even, table tr.alt, table tr:nth-of-type(even) { background: none; }
+table thead tr th, table tfoot tr th, table tbody tr td, table tr td, table tfoot tr td { display: table-cell; line-height: 1.5; }
+
+body { -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; tab-size: 4; }
+
+h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6 { line-height: 1.4; }
+
+.clearfix:before, .clearfix:after, .float-group:before, .float-group:after { content: " "; display: table; }
+.clearfix:after, .float-group:after { clear: both; }
+
+*:not(pre) > code { font-size: 0.95em; font-style: normal !important; letter-spacing: 0; padding: 0; background-color: #f2f2f2; -webkit-border-radius: 6px; border-radius: 6px; line-height: inherit; word-wrap: break-word; }
+*:not(pre) > code.nobreak { word-wrap: normal; }
+*:not(pre) > code.nowrap { white-space: nowrap; }
+
+pre, pre > code { line-height: 1.2; color: inherit; font-family: "Consolas", "Deja Vu Sans Mono", "Bitstream Vera Sans Mono", monospace; font-weight: normal; }
+
+em em { font-style: normal; }
+
+strong strong { font-weight: normal; }
+
+.keyseq { color: #333333; }
+
+kbd { font-family: "Consolas", "Deja Vu Sans Mono", "Bitstream Vera Sans Mono", monospace; display: inline-block; color: #000; font-size: 0.65em; line-height: 1.45; background-color: #f7f7f7; border: 1px solid #ccc; -webkit-border-radius: 3px; border-radius: 3px; -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 0.1em white inset; -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 0.1em white inset; box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 0.1em white inset; margin: 0 0.15em; padding: 0.2em 0.5em; vertical-align: middle; position: relative; top: -0.1em; white-space: nowrap; }
+
+.keyseq kbd:first-child { margin-left: 0; }
+
+.keyseq kbd:last-child { margin-right: 0; }
+
+.menuseq, .menuref { color: #000; }
+
+.menuseq b:not(.caret), .menuref { font-weight: inherit; }
+
+.menuseq { word-spacing: -0.02em; }
+.menuseq b.caret { font-size: 1.25em; line-height: 0.8; }
+.menuseq i.caret { font-weight: bold; text-align: center; width: 0.45em; }
+
+b.button:before, b.button:after { position: relative; top: -1px; font-weight: normal; }
+
+b.button:before { content: "["; padding: 0 3px 0 2px; }
+
+b.button:after { content: "]"; padding: 0 2px 0 3px; }
+
+p a > code:hover { color: #373737; }
+
+#header, #content, #footnotes, #footer { width: 100%; margin-left: auto; margin-right: auto; margin-top: 0; margin-bottom: 0; max-width: 62.5em; *zoom: 1; position: relative; padding-left: 0.9375em; padding-right: 0.9375em; }
+#header:before, #header:after, #content:before, #content:after, #footnotes:before, #footnotes:after, #footer:before, #footer:after { content: " "; display: table; }
+#header:after, #content:after, #footnotes:after, #footer:after { clear: both; }
+
+#content { margin-top: 1.25em; }
+
+#content:before { content: none; }
+
+#header > h1:first-child { color: #111; margin-top: 2.25rem; margin-bottom: 0; }
+#header > h1:first-child + #toc { margin-top: 8px; border-top: 1px solid #ddd; }
+#header > h1:only-child, body.toc2 #header > h1:nth-last-child(2) { border-bottom: 1px solid #ddd; padding-bottom: 8px; }
+#header .details { border-bottom: 1px solid #ddd; line-height: 1.45; padding-top: 0.25em; padding-bottom: 0.25em; padding-left: 0.25em; color: #748590; display: -ms-flexbox; display: -webkit-flex; display: flex; -ms-flex-flow: row wrap; -webkit-flex-flow: row wrap; flex-flow: row wrap; }
+#header .details span:first-child { margin-left: -0.125em; }
+#header .details span.email a { color: #909ea7; }
+#header .details br { display: none; }
+#header .details br + span:before { content: "\00a0\2013\00a0"; }
+#header .details br + span.author:before { content: "\00a0\22c5\00a0"; color: #909ea7; }
+#header .details br + span#revremark:before { content: "\00a0|\00a0"; }
+#header #revnumber { text-transform: capitalize; }
+#header #revnumber:after { content: "\00a0"; }
+
+#content > h1:first-child:not([class]) { color: #111; border-bottom: 1px solid #ddd; padding-bottom: 8px; margin-top: 0; padding-top: 1rem; margin-bottom: 1.25rem; }
+
+#toc { border-bottom: 1px solid #ddd; padding-bottom: 0.5em; }
+#toc > ul { margin-left: 0.125em; }
+#toc ul.sectlevel0 > li > a { font-style: italic; }
+#toc ul.sectlevel0 ul.sectlevel1 { margin: 0.5em 0; }
+#toc ul { font-family: ff-meta-web-pro-1, ff-meta-web-pro-2, Arial, "Helvetica Neue", sans-serif; list-style-type: none; }
+#toc li { line-height: 1.3334; margin-top: 0.3334em; }
+#toc a { text-decoration: none; }
+#toc a:active { text-decoration: underline; }
+
+#toctitle { color: #6c818f; font-size: 1.2em; }
+
+@media only screen and (min-width: 768px) { #toctitle { font-size: 1.375em; }
+  body.toc2 { padding-left: 15em; padding-right: 0; }
+  #toc.toc2 { margin-top: 0 !important; background-color: #f2f2f2; position: fixed; width: 15em; left: 0; top: 0; border-right: 1px solid #ddd; border-top-width: 0 !important; border-bottom-width: 0 !important; z-index: 1000; padding: 1.25em 1em; height: 100%; overflow: auto; }
+  #toc.toc2 #toctitle { margin-top: 0; margin-bottom: 0.8rem; font-size: 1.2em; }
+  #toc.toc2 > ul { font-size: 0.9em; margin-bottom: 0; }
+  #toc.toc2 ul ul { margin-left: 0; padding-left: 1em; }
+  #toc.toc2 ul.sectlevel0 ul.sectlevel1 { padding-left: 0; margin-top: 0.5em; margin-bottom: 0.5em; }
+  body.toc2.toc-right { padding-left: 0; padding-right: 15em; }
+  body.toc2.toc-right #toc.toc2 { border-right-width: 0; border-left: 1px solid #ddd; left: auto; right: 0; } }
+@media only screen and (min-width: 1280px) { body.toc2 { padding-left: 20em; padding-right: 0; }
+  #toc.toc2 { width: 20em; }
+  #toc.toc2 #toctitle { font-size: 1.375em; }
+  #toc.toc2 > ul { font-size: 0.95em; }
+  #toc.toc2 ul ul { padding-left: 1.25em; }
+  body.toc2.toc-right { padding-left: 0; padding-right: 20em; } }
+#content #toc { border-style: solid; border-width: 1px; border-color: #d9d9d9; margin-bottom: 1.25em; padding: 1.25em; background: #f2f2f2; -webkit-border-radius: 6px; border-radius: 6px; }
+#content #toc > :first-child { margin-top: 0; }
+#content #toc > :last-child { margin-bottom: 0; }
+
+#footer { max-width: 100%; background-color: #000; padding: 1.25em; }
+
+#footer-text { color: white; line-height: 1.35; }
+
+#content { margin-bottom: 0.625em; }
+
+.sect1 { padding-bottom: 0.625em; }
+
+@media only screen and (min-width: 768px) { #content { margin-bottom: 1.25em; }
+  .sect1 { padding-bottom: 1.25em; } }
+.sect1:last-child { padding-bottom: 0; }
+
+.sect1 + .sect1 { border-top: 1px solid #ddd; }
+
+#content h1 > a.anchor, h2 > a.anchor, h3 > a.anchor, #toctitle > a.anchor, .sidebarblock > .content > .title > a.anchor, h4 > a.anchor, h5 > a.anchor, h6 > a.anchor { position: absolute; z-index: 1001; width: 1.5ex; margin-left: -1.5ex; display: block; text-decoration: none !important; visibility: hidden; text-align: center; font-weight: normal; }
+#content h1 > a.anchor:before, h2 > a.anchor:before, h3 > a.anchor:before, #toctitle > a.anchor:before, .sidebarblock > .content > .title > a.anchor:before, h4 > a.anchor:before, h5 > a.anchor:before, h6 > a.anchor:before { content: "\00A7"; font-size: 0.85em; display: block; padding-top: 0.1em; }
+#content h1:hover > a.anchor, #content h1 > a.anchor:hover, h2:hover > a.anchor, h2 > a.anchor:hover, h3:hover > a.anchor, #toctitle:hover > a.anchor, .sidebarblock > .content > .title:hover > a.anchor, h3 > a.anchor:hover, #toctitle > a.anchor:hover, .sidebarblock > .content > .title > a.anchor:hover, h4:hover > a.anchor, h4 > a.anchor:hover, h5:hover > a.anchor, h5 > a.anchor:hover, h6:hover > a.anchor, h6 > a.anchor:hover { visibility: visible; }
+#content h1 > a.link, h2 > a.link, h3 > a.link, #toctitle > a.link, .sidebarblock > .content > .title > a.link, h4 > a.link, h5 > a.link, h6 > a.link { color: #465158; text-decoration: none; }
+#content h1 > a.link:hover, h2 > a.link:hover, h3 > a.link:hover, #toctitle > a.link:hover, .sidebarblock > .content > .title > a.link:hover, h4 > a.link:hover, h5 > a.link:hover, h6 > a.link:hover { color: #3b444a; }
+
+.audioblock, .imageblock, .literalblock, .listingblock, .stemblock, .videoblock { margin-bottom: 1.25em; }
+
+.admonitionblock td.content > .title, .audioblock > .title, .exampleblock > .title, .imageblock > .title, .listingblock > .title, .literalblock > .title, .stemblock > .title, .openblock > .title, .paragraph > .title, .quoteblock > .title, table.tableblock > .title, .verseblock > .title, .videoblock > .title, .dlist > .title, .olist > .title, .ulist > .title, .qlist > .title, .hdlist > .title { text-rendering: optimizeLegibility; text-align: left; }
+
+table.tableblock.fit-content > caption.title { white-space: nowrap; width: 0; }
+
+.paragraph.lead > p, #preamble > .sectionbody > [class="paragraph"]:first-of-type p { font-size: 1.21875em; line-height: 1.6; color: #111; }
+
+table.tableblock #preamble > .sectionbody > [class="paragraph"]:first-of-type p { font-size: inherit; }
+
+.admonitionblock > table { border-collapse: separate; border: 0; background: none; width: 100%; }
+.admonitionblock > table td.icon { text-align: center; width: 80px; }
+.admonitionblock > table td.icon img { max-width: none; }
+.admonitionblock > table td.icon .title { font-weight: bold; font-family: ff-meta-web-pro-1, ff-meta-web-pro-2, Arial, "Helvetica Neue", sans-serif; text-transform: uppercase; }
+.admonitionblock > table td.content { padding-left: 1.125em; padding-right: 1.25em; border-left: 1px solid #ddd; color: #748590; }
+.admonitionblock > table td.content > :last-child > :last-child { margin-bottom: 0; }
+
+.exampleblock > .content { border-style: solid; border-width: 1px; border-color: #e6e6e6; margin-bottom: 1.25em; padding: 1.25em; background: #fff; -webkit-border-radius: 6px; border-radius: 6px; }
+.exampleblock > .content > :first-child { margin-top: 0; }
+.exampleblock > .content > :last-child { margin-bottom: 0; }
+
+.sidebarblock { border-style: solid; border-width: 1px; border-color: #d9d9d9; margin-bottom: 1.25em; padding: 1.25em; background: #f2f2f2; -webkit-border-radius: 6px; border-radius: 6px; }
+.sidebarblock > :first-child { margin-top: 0; }
+.sidebarblock > :last-child { margin-bottom: 0; }
+.sidebarblock > .content > .title { color: #6c818f; margin-top: 0; }
+
+.exampleblock > .content > :last-child > :last-child, .exampleblock > .content .olist > ol > li:last-child > :last-child, .exampleblock > .content .ulist > ul > li:last-child > :last-child, .exampleblock > .content .qlist > ol > li:last-child > :last-child, .sidebarblock > .content > :last-child > :last-child, .sidebarblock > .content .olist > ol > li:last-child > :last-child, .sidebarblock > .content .ulist > ul > li:last-child > :last-child, .sidebarblock > .content .qlist > ol > li:last-child > :last-child { margin-bottom: 0; }
+
+.literalblock pre, .listingblock pre:not(.highlight), .listingblock pre[class="highlight"], .listingblock pre[class^="highlight "], .listingblock pre.CodeRay, .listingblock pre.prettyprint { background: #eee; }
+.sidebarblock .literalblock pre, .sidebarblock .listingblock pre:not(.highlight), .sidebarblock .listingblock pre[class="highlight"], .sidebarblock .listingblock pre[class^="highlight "], .sidebarblock .listingblock pre.CodeRay, .sidebarblock .listingblock pre.prettyprint { background: #f2f1f1; }
+
+.literalblock pre, .literalblock pre[class], .listingblock pre, .listingblock pre[class] { border: 1px solid #ccc; -webkit-border-radius: 6px; border-radius: 6px; word-wrap: break-word; padding: 0.5em; font-size: 0.8125em; }
+.literalblock pre.nowrap, .literalblock pre[class].nowrap, .listingblock pre.nowrap, .listingblock pre[class].nowrap { overflow-x: auto; white-space: pre; word-wrap: normal; }
+@media only screen and (min-width: 768px) { .literalblock pre, .literalblock pre[class], .listingblock pre, .listingblock pre[class] { font-size: 0.90625em; } }
+@media only screen and (min-width: 1280px) { .literalblock pre, .literalblock pre[class], .listingblock pre, .listingblock pre[class] { font-size: 1em; } }
+
+.literalblock.output pre { color: #eee; background-color: inherit; }
+
+.listingblock pre.highlightjs { padding: 0; }
+.listingblock pre.highlightjs > code { padding: 0.5em; -webkit-border-radius: 6px; border-radius: 6px; }
+
+.listingblock > .content { position: relative; }
+
+.listingblock code[data-lang]:before { display: none; content: attr(data-lang); position: absolute; font-size: 0.75em; top: 0.425rem; right: 0.5rem; line-height: 1; text-transform: uppercase; color: #999; }
+
+.listingblock:hover code[data-lang]:before { display: block; }
+
+.listingblock.terminal pre .command:before { content: attr(data-prompt); padding-right: 0.5em; color: #999; }
+
+.listingblock.terminal pre .command:not([data-prompt]):before { content: "$"; }
+
+table.pyhltable { border-collapse: separate; border: 0; margin-bottom: 0; background: none; }
+
+table.pyhltable td { vertical-align: top; padding-top: 0; padding-bottom: 0; line-height: 1.2; }
+
+table.pyhltable td.code { padding-left: .75em; padding-right: 0; }
+
+pre.pygments .lineno, table.pyhltable td:not(.code) { color: #999; padding-left: 0; padding-right: .5em; border-right: 1px solid #ddd; }
+
+pre.pygments .lineno { display: inline-block; margin-right: .25em; }
+
+table.pyhltable .linenodiv { background: none !important; padding-right: 0 !important; }
+
+.quoteblock { margin: 0 1em 1.25em 1.5em; display: table; }
+.quoteblock > .title { margin-left: -1.5em; margin-bottom: 0.75em; }
+.quoteblock blockquote, .quoteblock blockquote p { color: #909ea7; font-size: 1.15rem; line-height: 1.75; word-spacing: 0.1em; letter-spacing: 0; font-style: italic; text-align: justify; }
+.quoteblock blockquote { margin: 0; padding: 0; border: 0; }
+.quoteblock blockquote:before { content: "\201c"; float: left; font-size: 2.75em; font-weight: bold; line-height: 0.6em; margin-left: -0.6em; color: #6c818f; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); }
+.quoteblock blockquote > .paragraph:last-child p { margin-bottom: 0; }
+.quoteblock .attribution { margin-top: 0.5em; margin-right: 0.5ex; text-align: right; }
+.quoteblock .quoteblock { margin-left: 0; margin-right: 0; padding: 0.5em 0; border-left: 3px solid #748590; }
+.quoteblock .quoteblock blockquote { padding: 0 0 0 0.75em; }
+.quoteblock .quoteblock blockquote:before { display: none; }
+
+.verseblock { margin: 0 1em 1.25em 1em; }
+.verseblock pre { font-family: "Open Sans", "DejaVu Sans", sans; font-size: 1.15rem; color: #909ea7; font-weight: 300; text-rendering: optimizeLegibility; }
+.verseblock pre strong { font-weight: 400; }
+.verseblock .attribution { margin-top: 1.25rem; margin-left: 0.5ex; }
+
+.quoteblock .attribution, .verseblock .attribution { font-size: 0.8125em; line-height: 1.45; font-style: italic; }
+.quoteblock .attribution br, .verseblock .attribution br { display: none; }
+.quoteblock .attribution cite, .verseblock .attribution cite { display: block; letter-spacing: -0.025em; color: #748590; }
+
+.quoteblock.abstract { margin: 0 1em 1.25em 1em; display: block; }
+.quoteblock.abstract > .title { margin: 0 0 0.375em 0; font-size: 1.15em; text-align: center; }
+.quoteblock.abstract blockquote, .quoteblock.abstract blockquote p { word-spacing: 0; line-height: 1.6; }
+.quoteblock.abstract blockquote:before, .quoteblock.abstract p:before { display: none; }
+
+table.tableblock { max-width: 100%; border-collapse: separate; }
+
+p.tableblock:last-child { margin-bottom: 0; }
+
+td.tableblock > .content { margin-bottom: -1.25em; }
+
+table.tableblock, th.tableblock, td.tableblock { border: 0 solid #ddd; }
+
+table.grid-all > thead > tr > .tableblock, table.grid-all > tbody > tr > .tableblock { border-width: 0 0 0 0; }
+
+table.grid-all > tfoot > tr > .tableblock { border-width: 0 0 0 0; }
+
+table.grid-cols > * > tr > .tableblock { border-width: 0 0 0 0; }
+
+table.grid-rows > thead > tr > .tableblock, table.grid-rows > tbody > tr > .tableblock { border-width: 0 0 0 0; }
+
+table.grid-rows > tfoot > tr > .tableblock { border-width: 0 0 0 0; }
+
+table.grid-all > * > tr > .tableblock:last-child, table.grid-cols > * > tr > .tableblock:last-child { border-right-width: 0; }
+
+table.grid-all > tbody > tr:last-child > .tableblock, table.grid-all > thead:last-child > tr > .tableblock, table.grid-rows > tbody > tr:last-child > .tableblock, table.grid-rows > thead:last-child > tr > .tableblock { border-bottom-width: 0; }
+
+table.frame-all { border-width: 0; }
+
+table.frame-sides { border-width: 0 0; }
+
+table.frame-topbot, table.frame-ends { border-width: 0 0; }
+
+table.stripes-all tr, table.stripes-odd tr:nth-of-type(odd) { background: none; }
+
+table.stripes-none tr, table.stripes-odd tr:nth-of-type(even) { background: none; }
+
+th.halign-left, td.halign-left { text-align: left; }
+
+th.halign-right, td.halign-right { text-align: right; }
+
+th.halign-center, td.halign-center { text-align: center; }
+
+th.valign-top, td.valign-top { vertical-align: top; }
+
+th.valign-bottom, td.valign-bottom { vertical-align: bottom; }
+
+th.valign-middle, td.valign-middle { vertical-align: middle; }
+
+table thead th, table tfoot th { font-weight: bold; }
+
+tbody tr th { display: table-cell; line-height: 1.5; background: none; }
+
+tbody tr th, tbody tr th p, tfoot tr th, tfoot tr th p { color: #222; font-weight: bold; }
+
+p.tableblock > code:only-child { background: none; padding: 0; }
+
+p.tableblock { font-size: 1em; }
+
+td > div.verse { white-space: pre; }
+
+ol { margin-left: 0.25em; }
+
+ul li ol { margin-left: 0; }
+
+dl dd { margin-left: 1.125em; }
+
+dl dd:last-child, dl dd:last-child > :last-child { margin-bottom: 0; }
+
+ol > li p, ul > li p, ul dd, ol dd, .olist .olist, .ulist .ulist, .ulist .olist, .olist .ulist { margin-bottom: 0.625em; }
+
+ul.checklist, ul.none, ol.none, ul.no-bullet, ol.no-bullet, ol.unnumbered, ul.unstyled, ol.unstyled { list-style-type: none; }
+
+ul.no-bullet, ol.no-bullet, ol.unnumbered { margin-left: 0.625em; }
+
+ul.unstyled, ol.unstyled { margin-left: 0; }
+
+ul.checklist { margin-left: 0.625em; }
+
+ul.checklist li > p:first-child > .fa-square-o:first-child, ul.checklist li > p:first-child > .fa-check-square-o:first-child { width: 1.25em; font-size: 0.8em; position: relative; bottom: 0.125em; }
+
+ul.checklist li > p:first-child > input[type="checkbox"]:first-child { margin-right: 0.25em; }
+
+ul.inline { display: -ms-flexbox; display: -webkit-box; display: flex; -ms-flex-flow: row wrap; -webkit-flex-flow: row wrap; flex-flow: row wrap; list-style: none; margin: 0 0 0.625em -1.25em; }
+
+ul.inline > li { margin-left: 1.25em; }
+
+.unstyled dl dt { font-weight: normal; font-style: normal; }
+
+ol.arabic { list-style-type: decimal; }
+
+ol.decimal { list-style-type: decimal-leading-zero; }
+
+ol.loweralpha { list-style-type: lower-alpha; }
+
+ol.upperalpha { list-style-type: upper-alpha; }
+
+ol.lowerroman { list-style-type: lower-roman; }
+
+ol.upperroman { list-style-type: upper-roman; }
+
+ol.lowergreek { list-style-type: lower-greek; }
+
+.hdlist > table, .colist > table { border: 0; background: none; }
+.hdlist > table > tbody > tr, .colist > table > tbody > tr { background: none; }
+
+td.hdlist1, td.hdlist2 { vertical-align: top; padding: 0 0.625em; }
+
+td.hdlist1 { font-weight: bold; padding-bottom: 1.25em; }
+
+.literalblock + .colist, .listingblock + .colist { margin-top: -0.5em; }
+
+.colist td:not([class]):first-child { padding: 0.4em 0.75em 0 0.75em; line-height: 1; vertical-align: top; }
+.colist td:not([class]):first-child img { max-width: none; }
+.colist td:not([class]):last-child { padding: 0.25em 0; }
+
+.thumb, .th { line-height: 0; display: inline-block; border: solid 4px #fff; -webkit-box-shadow: 0 0 0 1px #ddd; box-shadow: 0 0 0 1px #ddd; }
+
+.imageblock.left { margin: 0.25em 0.625em 1.25em 0; }
+.imageblock.right { margin: 0.25em 0 1.25em 0.625em; }
+.imageblock > .title { margin-bottom: 0; }
+.imageblock.thumb, .imageblock.th { border-width: 6px; }
+.imageblock.thumb > .title, .imageblock.th > .title { padding: 0 0.125em; }
+
+.image.left, .image.right { margin-top: 0.25em; margin-bottom: 0.25em; display: inline-block; line-height: 0; }
+.image.left { margin-right: 0.625em; }
+.image.right { margin-left: 0.625em; }
+
+a.image { text-decoration: none; display: inline-block; }
+a.image object { pointer-events: none; }
+
+sup.footnote, sup.footnoteref { font-size: 0.875em; position: static; vertical-align: super; }
+sup.footnote a, sup.footnoteref a { text-decoration: none; }
+sup.footnote a:active, sup.footnoteref a:active { text-decoration: underline; }
+
+#footnotes { padding-top: 0.75em; padding-bottom: 0.75em; margin-bottom: 0.625em; }
+#footnotes hr { width: 20%; min-width: 6.25em; margin: -0.25em 0 0.75em 0; border-width: 1px 0 0 0; }
+#footnotes .footnote { padding: 0 0.375em 0 0.225em; line-height: 1.3334; font-size: 0.875em; margin-left: 1.2em; margin-bottom: 0.2em; }
+#footnotes .footnote a:first-of-type { font-weight: bold; text-decoration: none; margin-left: -1.05em; }
+#footnotes .footnote:last-of-type { margin-bottom: 0; }
+#content #footnotes { margin-top: -0.625em; margin-bottom: 0; padding: 0.75em 0; }
+
+.gist .file-data > table { border: 0; background: #fff; width: 100%; margin-bottom: 0; }
+.gist .file-data > table td.line-data { width: 99%; }
+
+div.unbreakable { page-break-inside: avoid; }
+
+.big { font-size: larger; }
+
+.small { font-size: smaller; }
+
+.underline { text-decoration: underline; }
+
+.overline { text-decoration: overline; }
+
+.line-through { text-decoration: line-through; }
+
+.aqua { color: #00bfbf; }
+
+.aqua-background { background-color: #00fafa; }
+
+.black { color: black; }
+
+.black-background { background-color: black; }
+
+.blue { color: #0000bf; }
+
+.blue-background { background-color: #0000fa; }
+
+.fuchsia { color: #bf00bf; }
+
+.fuchsia-background { background-color: #fa00fa; }
+
+.gray { color: #606060; }
+
+.gray-background { background-color: #7d7d7d; }
+
+.green { color: #006000; }
+
+.green-background { background-color: #007d00; }
+
+.lime { color: #00bf00; }
+
+.lime-background { background-color: #00fa00; }
+
+.maroon { color: #600000; }
+
+.maroon-background { background-color: #7d0000; }
+
+.navy { color: #000060; }
+
+.navy-background { background-color: #00007d; }
+
+.olive { color: #606000; }
+
+.olive-background { background-color: #7d7d00; }
+
+.purple { color: #600060; }
+
+.purple-background { background-color: #7d007d; }
+
+.red { color: #bf0000; }
+
+.red-background { background-color: #fa0000; }
+
+.silver { color: #909090; }
+
+.silver-background { background-color: #bcbcbc; }
+
+.teal { color: #006060; }
+
+.teal-background { background-color: #007d7d; }
+
+.white { color: #bfbfbf; }
+
+.white-background { background-color: #fafafa; }
+
+.yellow { color: #bfbf00; }
+
+.yellow-background { background-color: #fafa00; }
+
+span.icon > .fa { cursor: default; }
+a span.icon > .fa { cursor: inherit; }
+
+.admonitionblock td.icon [class^="fa icon-"] { font-size: 2.5em; text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); cursor: default; }
+.admonitionblock td.icon .icon-note:before { content: "\f05a"; color: #333333; }
+.admonitionblock td.icon .icon-tip:before { content: "\f0eb"; text-shadow: 1px 1px 2px rgba(155, 155, 0, 0.8); color: #111; }
+.admonitionblock td.icon .icon-warning:before { content: "\f071"; color: #bf6900; }
+.admonitionblock td.icon .icon-caution:before { content: "\f06d"; color: #bf3400; }
+.admonitionblock td.icon .icon-important:before { content: "\f06a"; color: #bf0000; }
+
+.conum[data-value] { display: inline-block; color: #fff !important; background-color: #000; -webkit-border-radius: 100px; border-radius: 100px; text-align: center; font-size: 0.75em; width: 1.67em; height: 1.67em; line-height: 1.67em; font-family: "Open Sans", "DejaVu Sans", sans-serif; font-style: normal; font-weight: bold; }
+.conum[data-value] * { color: #fff !important; }
+.conum[data-value] + b { display: none; }
+.conum[data-value]:after { content: attr(data-value); }
+pre .conum[data-value] { position: relative; top: -0.125em; }
+
+b.conum * { color: inherit !important; }
+
+.conum:not([data-value]):empty { display: none; }
+
+h4 { color: #6c818f; }
+
+.literalblock > .content > pre, .listingblock > .content > pre { -webkit-border-radius: 6px; border-radius: 6px; margin-left: 2em; margin-right: 2em; }
+
+.admonitionblock { margin-left: 2em; margin-right: 2em; }
+.admonitionblock > table { border: 1px solid #607f90; border-top-width: 1.5em; background-color: #f0f9ff; border-collapse: separate; -webkit-border-radius: 0; border-radius: 0; }
+.admonitionblock > table td.icon { padding-top: .5em; padding-bottom: .5em; }
+.admonitionblock > table td.content { padding: .5em 1em; color: #000; font-size: .9em; border-left: none; }
+
+.sidebarblock { background-color: #e8ecef; border-color: #ccc; }
+.sidebarblock > .content > .title { color: #444; }
+
+table.tableblock.grid-all { border-collapse: collapse; -webkit-border-radius: 0; border-radius: 0; }
+table.tableblock.grid-all th.tableblock, table.tableblock.grid-all td.tableblock { border-bottom: 1px solid #aaa; }
+
+#footer { background-color: #465158; padding: 2em; }
+
+#footer-text { color: #eee; font-size: 0.8em; text-align: center; }
diff --git a/manual/cli.txt b/manual/cli.txt
new file mode 100644 (file)
index 0000000..001b6e9
--- /dev/null
@@ -0,0 +1,42 @@
+== Command-line Interface
+
+Even though PulseView has a graphical user interface, there is also a command-line interface that
+you can make use of. It is meant to provide functions for convenience and debug purposes, not to
+replace the user interface itself.
+
+Running 
+
+       pulseview -h
+
+gives you a list of these functions.
+
+Since PulseView can't automatically scan for devices connected to a COM port (ttySx on Linux) or
+Ethernet, you can tell it to look for a specific device using the -d or --driver parameter. Its
+usage is the same as for sigrok-cli. For example:
+
+       pulseview -d lecroy-xstream:conn=vxi/192.168.178.20/111
+
+Also, just as with sigrok-cli, you can specify -i / --input-file and -I / --input-format to open
+a file on startup. Without -I, it is assumed that the file is in the native sigrok format (.sr).
+You can also specify more than one file but they are all expected to be in the same format then.
+Example:
+
+       pulseview -i data.csv -I csv:samplerate=3000000
+
+The remaining parameters are mostly for debug purposes:
+
+       -V / --version          Shows the release version
+       -l / --loglevel         Sets the libsigrok/libsigrokdecode log level (max is 5)
+       -D / --dont-scan        Don't auto-scan for devices
+       -c / --clean            Don't restore previous sessions on startup
+
+Of these, -D / --dont-scan can be useful when PulseView gets stuck during the startup device scan.
+No such scan will be performed then, allowing the program to start up but you'll have to scan for
+your acquisition device(s) manually before you can use them.
+
+Another potentially useful option is -c / --clean, which can be used when PulseView doesn't start
+up and you don't know what could cause this.
+
+Thus, the combination of both parameters can be seen as some kind of "safe mode" for PulseView:
+
+       pulseview -c -D
diff --git a/manual/decoders.txt b/manual/decoders.txt
new file mode 100644 (file)
index 0000000..d2ca15a
--- /dev/null
@@ -0,0 +1,140 @@
+== Decoders
+
+Protocol decoders are one of the key elements of PulseView's functionality. They take
+input data that you acquired and process it in a way that results in a (hopefully) much
+easier to understand representation of that same data.
+
+In its simplest form, a protocol decoder (PD) converts a group of 1-bit signals into a
+stream of n-bit events. This is exactly what the parallel PD does: it takes for example
+8 logic channels and treats them like an 8-bit parallel bus, emitting annotations that
+show the current state of the bus at any point in time.
+
+=== Basic Operation
+
+Another one of the protocol decoders available to you is the I²C decoder. It takes the
+two I²C signals SCL and SDA (serial clock / serial data) and shows you the details of
+the I²C communication without the need to evaluate the signal bit by bit yourself.
+
+As an example, let's have look at one of the sample .sr files we keep around for
+validation of the PD code base: https://sigrok.org/gitweb/?p=sigrok-dumps.git;a=blob_plain;f=i2c/rtc_dallas_ds1307/rtc_ds1307_200khz.sr;hb=HEAD[rtc_ds1307_200khz.sr].
+It contains the capture of an I²C master interacting with a https://www.maximintegrated.com/en/products/digital/real-time-clocks/DS1307.html[Dallas DS1307 I²C Real-Time Clock]
+where the master repeatedly sets and queries the time of day. After loading and using
+"zoom-to-fit", it looks similar to this:
+
+image::pv_decoders_1.png[]
+
+Adding the I²C decoder by clicking on âžŠ and selecting I²C from the list adds
+a new decode signal to the view. PulseView tries to match existing signals to the signals
+that the newly added protocol decoder needs to function, which is what happened here -
+SCL and SDA have been automatically assigned and the PD has decoded the communication with
+the default parameters. If you need to change the signal assignment or change the decoding
+parameters, you can click on âž‹ to do so.
+
+When you zoom in, you now see that PulseView has decoded the I²C messages and displays
+these annotations as part of the decode signal (note that we have zoomed in so far that
+PulseView shows you the individual samples):
+
+image::pv_decoders_2.png[]
+
+This is already very useful, and a massive improvement over counting out pulses on an
+oscilloscope screen. However, sigrok allows us to go one step further with the use of
+so-called stacked decoders. 
+
+=== Decoder Stacking
+
+To add a stacked decoder we open the settings of the decode signal, go to the _Stack
+Decoder_ menu âžŠ, and select the DS1307 decoder:
+
+image::pv_decoders_3.png[]
+
+With the stacked decoder added, we can now see that PulseView has decoded the meaning
+of the I²C commands, so that we don't need to bother searching the reference manual.
+In this view, we can see that the I²C packet was a command to read the date and time,
+which was then reported to be 10.03.2013 23:35:30. 
+
+There are all kinds of stacked decoders available, but keep in mind that they're not
+shown in the decoder menu. Stacked decoders require a lower-level decoder first before
+they become stackable. Most of the time, they require either the UART, I²C or SPI decoder.
+
+You can check the https://sigrok.org/wiki/Protocol_decoders[List of Protocol Decoders]
+to see which protocol decoders have been created already.
+
+=== Using Decoders on Analog Signals
+
+If you're capturing data using an oscilloscope or import analog signal data from a file,
+you'll quickly notice that protocol decoders don't give you the option to select analog
+channels as inputs. That is because as of now, decoders only work on logic signals. You
+can however convert analog signals into logic signals by choosing a conversion setting
+from the signal setting popup.
+
+image::pv_conversion_a2l.png[]
+
+Here, A1 has been converted using a threshold (with default settings) and A2 has been
+converted using a Schmitt-Trigger emulation (also with default settings). Additionally,
+the conversion threshold display mode has been set to _Background_ in the _Views_ settings
+dialog. This way, you can tell how PulseView decided to change the logic signal level
+as you can now visually understand where the ranges for high and low are placed.
+
+Aside from the default conversion threshold(s), you can choose from a few common presets
+or enter custom values as well. They take the form "0.0V" and "0.0V/0.0V", respectively.
+
+=== Troubleshooting
+
+In case a protocol decoder doesn't provide the expected result, there are several things
+you can check.
+
+The first check you should perform is whether the time unit in the ruler
+is given as "sa". This is short for "samples" and means that the device didn't provide
+a sample rate and so PulseView has no way of showing a time scale in seconds or
+fractions thereof. While some decoders can run without timing information, or only
+optionally make use of the time scale, others may not be able to interpret the
+input data since timing information is an essential part of the very protocol.
+
+Another issue to remain aware of is that decoders need enough samples per protocol step
+to reliably interpret the information. In typical cases the minimum sample rate should
+be four to five times the rate of the fastest activity in the protocol.
+
+If a protocol decoder runs but shows you annotations that don't seem to make any sense,
+it's worth double-checking the decoder settings. One common source of error is the
+baud rate. For example, the CAN protocol decoder doesn't know what baud rate
+is used on the bus that you captured, so it could be that a different baud rate is used
+than the one you set. Also, if this is still not the reason for the malfunction, it's
+worth checking whether any of the signals have been captured inverted. Again using the
+CAN bus as an example, the decoder will decode the signal just fine if it's inverted but
+it'll show data even when the signal looks "idle".
+
+When a protocol decoder stops execution because of an unmet constraint (required input
+not connected, essential parameter not specified) or a bug in the decoder itself, you
+will be presented a static red message in the protocol decoder's display area.
+In that case, you check the log output in the settings menu. There you'll find the Python
+error description which you can use to either adjust the configuration,
+or debug the decoder (and let us know of the fix) or you can copy that information and
+file a bug report so that we can fix it.
+
+Further helpful knowledge and explanations on logic analyzers can be found in our
+https://sigrok.org/wiki/FAQ#Where_can_I_learn_more_about_logic_analyzers.3F["Learn about logic analyzers" FAQ item].
+
+=== Exporting Annotations
+
+If you want to postprocess annotations that were generated by a protocol decoder, you
+can do so by right-clicking into the area of the decode signal (not on the signal label
+on the left). You are shown several export methods to choose from, with the last one
+being only available if the cursor is enabled.
+
+After you chose a method that suits your needs, you are prompted for a file to export
+the annotations to. The contents of the file very much depend on the option you chose
+but also on the annotation export format string that you can define in the _Decoders_
+menu of the settings dialog. If the default output isn't useful to you, you can
+customize it there.
+
+=== Creating a Protocol Decoder
+
+Protocol decoders are written in Python and can be created using nothing more than a
+text editor. You, too, can write one!
+
+To find out how to go about it, please see our https://sigrok.org/wiki/Protocol_decoder_HOWTO[Protocol Decoder How-To]
+and the https://sigrok.org/wiki/Protocol_decoder_API[Protocol Decoder API Reference].
+
+If you do write one, we'd appreciate if you'd contribute to our project so that everyone
+can benefit from your work.
+
diff --git a/manual/images/_callouts.xcf b/manual/images/_callouts.xcf
new file mode 100644 (file)
index 0000000..c712315
Binary files /dev/null and b/manual/images/_callouts.xcf differ
diff --git a/manual/images/device_selector.png b/manual/images/device_selector.png
new file mode 100644 (file)
index 0000000..2bba96b
Binary files /dev/null and b/manual/images/device_selector.png differ
diff --git a/manual/images/device_selector_dropdown.png b/manual/images/device_selector_dropdown.png
new file mode 100644 (file)
index 0000000..08c7399
Binary files /dev/null and b/manual/images/device_selector_dropdown.png differ
diff --git a/manual/images/device_selector_scan.png b/manual/images/device_selector_scan.png
new file mode 100644 (file)
index 0000000..0cbe54b
Binary files /dev/null and b/manual/images/device_selector_scan.png differ
diff --git a/manual/images/pv_after_startup.png b/manual/images/pv_after_startup.png
new file mode 100644 (file)
index 0000000..22a0fcc
Binary files /dev/null and b/manual/images/pv_after_startup.png differ
diff --git a/manual/images/pv_after_startup.xcf b/manual/images/pv_after_startup.xcf
new file mode 100644 (file)
index 0000000..e81a4a5
Binary files /dev/null and b/manual/images/pv_after_startup.xcf differ
diff --git a/manual/images/pv_analysis.png b/manual/images/pv_analysis.png
new file mode 100644 (file)
index 0000000..215d148
Binary files /dev/null and b/manual/images/pv_analysis.png differ
diff --git a/manual/images/pv_analysis.xcf b/manual/images/pv_analysis.xcf
new file mode 100644 (file)
index 0000000..93b7859
Binary files /dev/null and b/manual/images/pv_analysis.xcf differ
diff --git a/manual/images/pv_conversion_a2l.png b/manual/images/pv_conversion_a2l.png
new file mode 100644 (file)
index 0000000..b804fef
Binary files /dev/null and b/manual/images/pv_conversion_a2l.png differ
diff --git a/manual/images/pv_cursors_markers.png b/manual/images/pv_cursors_markers.png
new file mode 100644 (file)
index 0000000..fc8552c
Binary files /dev/null and b/manual/images/pv_cursors_markers.png differ
diff --git a/manual/images/pv_cursors_markers.xcf b/manual/images/pv_cursors_markers.xcf
new file mode 100644 (file)
index 0000000..3748545
Binary files /dev/null and b/manual/images/pv_cursors_markers.xcf differ
diff --git a/manual/images/pv_decoders_1.png b/manual/images/pv_decoders_1.png
new file mode 100644 (file)
index 0000000..ac33def
Binary files /dev/null and b/manual/images/pv_decoders_1.png differ
diff --git a/manual/images/pv_decoders_1.xcf b/manual/images/pv_decoders_1.xcf
new file mode 100644 (file)
index 0000000..19fc127
Binary files /dev/null and b/manual/images/pv_decoders_1.xcf differ
diff --git a/manual/images/pv_decoders_2.png b/manual/images/pv_decoders_2.png
new file mode 100644 (file)
index 0000000..4c85672
Binary files /dev/null and b/manual/images/pv_decoders_2.png differ
diff --git a/manual/images/pv_decoders_3.png b/manual/images/pv_decoders_3.png
new file mode 100644 (file)
index 0000000..153cfdd
Binary files /dev/null and b/manual/images/pv_decoders_3.png differ
diff --git a/manual/images/pv_decoders_3.xcf b/manual/images/pv_decoders_3.xcf
new file mode 100644 (file)
index 0000000..f570f12
Binary files /dev/null and b/manual/images/pv_decoders_3.xcf differ
diff --git a/manual/images/pv_decoders_4.png b/manual/images/pv_decoders_4.png
new file mode 100644 (file)
index 0000000..bb8d863
Binary files /dev/null and b/manual/images/pv_decoders_4.png differ
diff --git a/manual/images/pv_device_config.png b/manual/images/pv_device_config.png
new file mode 100644 (file)
index 0000000..ee7694b
Binary files /dev/null and b/manual/images/pv_device_config.png differ
diff --git a/manual/images/pv_device_config.xcf b/manual/images/pv_device_config.xcf
new file mode 100644 (file)
index 0000000..0e7252d
Binary files /dev/null and b/manual/images/pv_device_config.xcf differ
diff --git a/manual/images/pv_import.png b/manual/images/pv_import.png
new file mode 100644 (file)
index 0000000..645e319
Binary files /dev/null and b/manual/images/pv_import.png differ
diff --git a/manual/images/pv_nodevice.png b/manual/images/pv_nodevice.png
new file mode 100644 (file)
index 0000000..a5db293
Binary files /dev/null and b/manual/images/pv_nodevice.png differ
diff --git a/manual/images/top_bar.png b/manual/images/top_bar.png
new file mode 100644 (file)
index 0000000..5aeae70
Binary files /dev/null and b/manual/images/top_bar.png differ
diff --git a/manual/import_export.txt b/manual/import_export.txt
new file mode 100644 (file)
index 0000000..2e5c8b2
--- /dev/null
@@ -0,0 +1,34 @@
+== Data Import/Export
+
+In order to facilitate versatile use of the sigrok suite, libsigrok allows users to import
+and export data from files in various formats - some of them as generic as possible, others
+very specific. For a list and details, make sure to check https://sigrok.org/wiki/Input_output_formats[the wiki].
+
+=== Import
+
+The first step to importing data from a file is to know what format the data in the file is
+encoded in. There are common, not-so-common and outright exotic ways to represent data and sigrok
+tries to suit as many needs as it can. To see which formats your version of PulseView supports,
+just click on the small arrow next to the _Open_ button:
+
+image::pv_import.png[]
+
+After choosing the format that you want to use, PulseView will ask for the file name to open.
+Once you picked the file, you may be asked to specify the details of the format, if the input
+module requires them.
+
+For example, the VCD import will ask you for these:
+
+* Compress idle periods: Compress idle periods longer than the specified value (default 0)
+* Downsampling factor: Downsample, i.e. divide the samplerate by the specified factor (default 1)
+* Number of logic channels: The number of (logic) channels in the data (default 0)
+* Skip samples until timestamp: Skip samples until the specified timestamp; < 0: Skip until first timestamp listed; 0: Don't skip (default -1)
+
+The detailed description of each item can also be seen when clicking on the help icon on the right
+or hovering your mouse over it. A click on _OK_ then loads the data from the selected file and you
+can work with it.
+
+=== Export
+
+Export works just the same as the import: clicking on the small arrow next to the _Save_ button
+brings up the export menu. Simply choose the format you want to use and proceed.
diff --git a/manual/installation.txt b/manual/installation.txt
new file mode 100644 (file)
index 0000000..047333e
--- /dev/null
@@ -0,0 +1,100 @@
+[[installation,Installation]]
+== Installation
+
+PulseView can be run on Linux, Windows, Mac OS X or Android. For some platforms, we provide binary
+packages, for others we provide installers and for others we provide AppImage containers that
+you can run without the need to install anything. Check the https://sigrok.org/wiki/Downloads[sigrok download page]
+to see which option is available for your platform.
+
+=== Linux
+
+On Linux, the usual way to install PulseView is to install the packages provided by your distro's
+package manager. However, sometimes only outdated packages are made available to you. In that case,
+you have two options:
+
+. https://sigrok.org/wiki/Downloads[Download] and use the AppImage which contains all required files and needs no installation:
++
+--
+[listing, subs="normal"]
+chmod u+x PulseView-NIGHTLY-x86_64.AppImage
+./PulseView-NIGHTLY-x86_64.AppImage
+
+Please be aware, however, that the AppImages are built every night, so they always contain
+the latest development changes. While we do try to keep the code base in a working state, it is sometimes
+unavoidable to introduce bugs that show up in the nightly builds. If you encounter something that is
+odd to you, please download and install the latest nightly and check if the issue still exists. If it
+does, feel free to https://sigrok.org/bugzilla/[file a bug].
+
+No system files are changed, so if you decide that you no longer want to use PulseView, simply
+delete the AppImage. If you also want the stored settings gone, delete ~/.config/sigrok as well.
+--
+
+. Uninstall any sigrok packages from your package manager and build PulseView from source:
++
+--
+[listing, subs="normal"]
+_[install dependencies https://sigrok.org/wiki/Linux#Building[as listed on the wiki]]_
+mkdir ~/sr
+cd ~/sr
+wget 'https://sigrok.org/gitweb/?p=sigrok-util.git;a=blob_plain;f=cross-compile/linux/sigrok-cross-linux' -O sigrok-cross-linux
+chmod u+x sigrok-cross-linux
+./sigrok-cross-linux
+export LD_LIBRARY_PATH=~/sr/lib
+~/sr/bin/pulseview
+
+No system files are changed, so if you decide that you no longer want to use PulseView, simply
+delete the ~/sr directory. If you also want the stored settings gone, delete ~/.config/sigrok
+as well.
+--
+
+[WARNING]
+--
+If you don't install the PulseView distro packages (as is the case when using the AppImage or building
+from source), PulseView will not be able to access USB and serial port devices unless it's run as root.
+Since programs shouldn't be run as root unless absolutely necessary, we provide udev configuration files
+that allows PulseView access to those devices without being root.
+
+Here's how you install them:
+[listing, subs="normal"]
+sudo bash
+cd /etc/udev/rules.d/
+wget 'https://sigrok.org/gitweb/?p=libsigrok.git;a=blob_plain;f=contrib/60-libsigrok.rules' -O 60-libsigrok.rules
+wget 'https://sigrok.org/gitweb/?p=libsigrok.git;a=blob_plain;f=contrib/61-libsigrok-plugdev.rules' -O 61-libsigrok-plugdev.rules
+wget 'https://sigrok.org/gitweb/?p=libsigrok.git;a=blob_plain;f=contrib/61-libsigrok-uaccess.rules' -O 61-libsigrok-uaccess.rules
+sudo udevadm control --reload-rules
+--
+
+=== Windows
+
+We offer installers for PulseView that contain everything you need to get started. Simply download
+them from the https://sigrok.org/wiki/Downloads[sigrok download page] and run them as any other Windows
+installer.
+Please be aware, however, that the Windows installers are built every night, so they always contain
+the latest development changes. While we do try to keep the code base in a working state, it is sometimes
+unavoidable to introduce bugs that show up in the nightly builds. If you encounter something that is
+odd to you, please download and install the latest nightly and check if the issue still exists. If it
+does, feel free to https://sigrok.org/bugzilla/[file a bug].
+
+After installation, you will find a program called Zadig in the start menu. By default, certain devices
+recognized by Windows will have drivers installed for them that PulseView cannot use. The purpose of
+Zadig is to let you change the driver Windows uses for a particular device - for most devices you'll need
+to choose WinUSB to use them with PulseView or the original proprietary Windows driver to use it with whatever
+other software you access the device with. More details are available https://sigrok.org/wiki/Windows[in the wiki].
+
+In case your device doesn't show up in PulseView and you can't find it with a scan either (see next
+chapter), check with Zadig whether the correct driver is assigned for the device.
+
+=== Mac OS X
+
+We offer DMG installers for PulseView that contain everything you need to get started. Simply download
+them from the https://sigrok.org/wiki/Downloads[sigrok download page] and run them.
+
+Please be aware, however, that the DMG installers are built every night, so they always contain
+the latest development changes. While we do try to keep the code base in a working state, it is sometimes
+unavoidable to introduce bugs that show up in the nightly builds. If you encounter something that is
+odd to you, please download and install the latest nightly and check if the issue still exists. If it
+does, feel free to https://sigrok.org/bugzilla/[file a bug].
+
+No system files are changed, so if you decide that you no longer want to use PulseView, simply
+delete the DMG file. If you also want the stored settings gone, delete
+~/Library/Preferences/pulseview.plist as well.
diff --git a/manual/license.txt b/manual/license.txt
new file mode 100644 (file)
index 0000000..34ac580
--- /dev/null
@@ -0,0 +1,3 @@
+== License
+
+This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
diff --git a/manual/manual.txt b/manual/manual.txt
new file mode 100644 (file)
index 0000000..c4a3127
--- /dev/null
@@ -0,0 +1,28 @@
+PulseView User Manual
+=====================
+unreleased development snapshot, dated 2018-10-29
+:doctype: book
+:imagesdir: ./images
+:sectnums:
+:toc:
+:toclevels: 2
+:icons: font
+:figure-caption: Image
+
+ifdef::ebook-format[:leveloffset: -1]
+
+include::license.txt[]
+
+include::overview.txt[]
+
+include::installation.txt[]
+
+include::acquisition.txt[]
+
+include::analysis.txt[]
+
+include::decoders.txt[]
+
+include::import_export.txt[]
+
+include::cli.txt[]
diff --git a/manual/overview.txt b/manual/overview.txt
new file mode 100644 (file)
index 0000000..bfd87f8
--- /dev/null
@@ -0,0 +1,40 @@
+[[overview,Overview]]
+== Overview
+
+PulseView is a graphical frontend for the libsigrok and libsigrokdecode libraries, permitting
+access to a wide range of devices and protocol decoders to let you record, analyze, process
+and export analog and logic data. It is part of the sigrok suite, just like the libraries that
+it makes use of.
+
+The sigrok suite needs some kind of hardware to interface to the signals you want to examine.
+We found that most people are currently using logic analyzers based on the http://www.cypress.com/products/ez-usb-fx2lp[Cypress FX2 microcontroller].
+With http://sigrok.org/wiki/Fx2lafw[fx2lafw], sigrok's open source runtime firmware, any device
+containing an FX2 can become a powerful streaming logic analyzer.
+A variety of compatible low cost chinese made logic analyzer products are available for as little
+as $5. These can easily be found by searching for _24MHz Logic Analyzer_. There are also barebone
+Cypress FX2 boards such as the Lcsoft Mini Board, which can usually be found by searching for
+_Cypress FX2 Board_ or similar.
+
+In addition, a good set of https://sigrok.org/wiki/Probe_comparison[quality probe hooks] is recommended.
+
+Aside from FX2-based logic analyzers, sigrok also supports FX2-based oscilloscopes such as the
+https://sigrok.org/wiki/Hantek_6022BE[Hantek 6022BE], non-FX2 devices like the
+https://sigrok.org/wiki/Openbench_Logic_Sniffer[Openbench Logic Sniffer] or devices that make use
+of the SCPI protocol, as all reasonably modern oscilloscopes do (Rigol DS1054z, LeCroy WaveRunner,
+Yokogawa DLM and similar).
+
+Please be aware however, that PulseView currently only supports devices that can either work as an
+https://sigrok.org/wiki/Supported_hardware#Oscilloscopes[oscilloscope],
+a https://sigrok.org/wiki/Supported_hardware#Logic_analyzers[logic analyzer] or
+a https://sigrok.org/wiki/Supported_hardware#Mixed-signal_devices[mixed-signal device]. This
+means that multimeters in particular are currently only usable with either https://sigrok.org/wiki/Sigrok-cli[sigrok-cli]
+or https://sigrok.org/wiki/Sigrok-meter[sigrok-meter].
+
+image::pv_after_startup.png[]
+
+The PulseView user interface is geared towards navigation and analysis of captured waveforms, so
+the most space is by default used up by the main trace view. From here, you can access the most
+often used features.
+
+Before we dive deeper into how to accomplish things, let's make PulseView available on your
+system first.
index 5b6d09836d6cb8ea62e70cb080f9451bf9ce1704..33e36100b075b92783de804a2dd841ce9b1e8f8b 100644 (file)
@@ -1,5 +1,5 @@
 <RCC>
-    <qresource prefix="/" >
+    <qresource prefix="/">
        <file>icons/add-decoder.svg</file>
        <file>icons/application-exit.png</file>
        <file>icons/channels.svg</file>
@@ -9,9 +9,13 @@
        <file>icons/document-new.png</file>
        <file>icons/document-open.png</file>
        <file>icons/document-save-as.png</file>
+       <file>icons/help-browser.png</file>
        <file>icons/information.svg</file>
+       <file>icons/media-playback-pause.png</file>
+       <file>icons/media-playback-start.png</file>
        <file>icons/menu.svg</file>
        <file>icons/preferences-system.png</file>
+       <file>icons/settings-general.png</file>
        <file>icons/settings-views.svg</file>
        <file>icons/pulseview.png</file>
        <file>icons/pulseview.svg</file>
        <file>icons/trigger-marker-rising.svg</file>
        <file>icons/trigger-none.svg</file>
        <file>icons/trigger-rising.svg</file>
+       <file>icons/view-displaymode-last_complete_segment.svg</file>
+       <file>icons/view-displaymode-last_segment.svg</file>
+       <file>icons/view-displaymode-single_segment.svg</file>
        <file>icons/window-new.png</file>
        <file>icons/zoom-fit-best.png</file>
        <file>icons/zoom-in.png</file>
-       <file>icons/zoom-original.png</file>
        <file>icons/zoom-out.png</file>
     </qresource>
+
+    <qresource prefix="/">
+       <!-- QDarkStyleSheet -->
+       <file>themes/qdarkstyle/style.qss</file>
+       <file>themes/qdarkstyle/rc/up_arrow_disabled.png</file>
+       <file>themes/qdarkstyle/rc/Hmovetoolbar.png</file>
+       <file>themes/qdarkstyle/rc/stylesheet-branch-end.png</file>
+       <file>themes/qdarkstyle/rc/branch_closed-on.png</file>
+       <file>themes/qdarkstyle/rc/stylesheet-vline.png</file>
+       <file>themes/qdarkstyle/rc/branch_closed.png</file>
+       <file>themes/qdarkstyle/rc/branch_open-on.png</file>
+       <file>themes/qdarkstyle/rc/transparent.png</file>
+       <file>themes/qdarkstyle/rc/right_arrow_disabled.png</file>
+       <file>themes/qdarkstyle/rc/sizegrip.png</file>
+       <file>themes/qdarkstyle/rc/close.png</file>
+       <file>themes/qdarkstyle/rc/close-hover.png</file>
+       <file>themes/qdarkstyle/rc/close-pressed.png</file>
+       <file>themes/qdarkstyle/rc/down_arrow.png</file>
+       <file>themes/qdarkstyle/rc/Vmovetoolbar.png</file>
+       <file>themes/qdarkstyle/rc/left_arrow.png</file>
+       <file>themes/qdarkstyle/rc/stylesheet-branch-more.png</file>
+       <file>themes/qdarkstyle/rc/up_arrow.png</file>
+       <file>themes/qdarkstyle/rc/right_arrow.png</file>
+       <file>themes/qdarkstyle/rc/left_arrow_disabled.png</file>
+       <file>themes/qdarkstyle/rc/Hsepartoolbar.png</file>
+       <file>themes/qdarkstyle/rc/branch_open.png</file>
+       <file>themes/qdarkstyle/rc/Vsepartoolbar.png</file>
+       <file>themes/qdarkstyle/rc/down_arrow_disabled.png</file>
+       <file>themes/qdarkstyle/rc/undock.png</file>
+       <file>themes/qdarkstyle/rc/checkbox_checked_disabled.png</file>
+       <file>themes/qdarkstyle/rc/checkbox_checked_focus.png</file>
+       <file>themes/qdarkstyle/rc/checkbox_checked.png</file>
+       <file>themes/qdarkstyle/rc/checkbox_indeterminate.png</file>
+       <file>themes/qdarkstyle/rc/checkbox_indeterminate_focus.png</file>
+       <file>themes/qdarkstyle/rc/checkbox_unchecked_disabled.png</file>
+       <file>themes/qdarkstyle/rc/checkbox_unchecked_focus.png</file>
+       <file>themes/qdarkstyle/rc/checkbox_unchecked.png</file>
+       <file>themes/qdarkstyle/rc/radio_checked_disabled.png</file>
+       <file>themes/qdarkstyle/rc/radio_checked_focus.png</file>
+       <file>themes/qdarkstyle/rc/radio_checked.png</file>
+       <file>themes/qdarkstyle/rc/radio_unchecked_disabled.png</file>
+       <file>themes/qdarkstyle/rc/radio_unchecked_focus.png</file>
+       <file>themes/qdarkstyle/rc/radio_unchecked.png</file>
+    </qresource>
+
+    <qresource prefix="/">
+       <!-- DarkStyle -->
+       <file>themes/darkstyle/darkstyle.qss</file>
+       <file>themes/darkstyle/icon_close.png</file>
+       <file>themes/darkstyle/icon_restore.png</file>
+       <file>themes/darkstyle/icon_undock.png</file>
+       <file>themes/darkstyle/icon_branch_closed.png</file>
+       <file>themes/darkstyle/icon_branch_end.png</file>
+       <file>themes/darkstyle/icon_branch_more.png</file>
+       <file>themes/darkstyle/icon_branch_open.png</file>
+       <file>themes/darkstyle/icon_vline.png</file>
+       <file>themes/darkstyle/icon_checkbox_checked.png</file>
+       <file>themes/darkstyle/icon_checkbox_indeterminate.png</file>
+       <file>themes/darkstyle/icon_checkbox_unchecked.png</file>
+       <file>themes/darkstyle/icon_checkbox_checked_pressed.png</file>
+       <file>themes/darkstyle/icon_checkbox_indeterminate_pressed.png</file>
+       <file>themes/darkstyle/icon_checkbox_unchecked_pressed.png</file>
+       <file>themes/darkstyle/icon_checkbox_checked_disabled.png</file>
+       <file>themes/darkstyle/icon_checkbox_indeterminate_disabled.png</file>
+       <file>themes/darkstyle/icon_checkbox_unchecked_disabled.png</file>
+       <file>themes/darkstyle/icon_radiobutton_checked.png</file>
+       <file>themes/darkstyle/icon_radiobutton_unchecked.png</file>
+       <file>themes/darkstyle/icon_radiobutton_checked_pressed.png</file>
+       <file>themes/darkstyle/icon_radiobutton_unchecked_pressed.png</file>
+       <file>themes/darkstyle/icon_radiobutton_checked_disabled.png</file>
+       <file>themes/darkstyle/icon_radiobutton_unchecked_disabled.png</file>
+</qresource>
 </RCC>
index 383b5c23d29737c6c14ebb537b4756a57ee83139..5a6e28a1da996034aa24218df7ef2d66eb425543 100644 (file)
 #include "config.h"
 
 #include <iostream>
+#include <typeinfo>
 
-using std::cerr;
+#include <QDebug>
+
+#include <boost/version.hpp>
+
+#ifdef ENABLE_STACKTRACE
+#include <boost/stacktrace.hpp>
+#endif
+
+#ifdef ENABLE_DECODE
+#include <libsigrokdecode/libsigrokdecode.h>
+#endif
+
+using std::cout;
 using std::endl;
 using std::exception;
+using std::shared_ptr;
+
+#ifdef ENABLE_DECODE
+static gint sort_pds(gconstpointer a, gconstpointer b)
+{
+       const struct srd_decoder *sda, *sdb;
+
+       sda = (const struct srd_decoder *)a;
+       sdb = (const struct srd_decoder *)b;
+       return strcmp(sda->id, sdb->id);
+}
+#endif
 
 Application::Application(int &argc, char* argv[]) :
        QApplication(argc, argv)
@@ -35,13 +60,186 @@ Application::Application(int &argc, char* argv[]) :
        setOrganizationDomain("sigrok.org");
 }
 
+void Application::collect_version_info(shared_ptr<sigrok::Context> context)
+{
+       // Library versions and features
+       version_info_.emplace_back(applicationName(), applicationVersion());
+       version_info_.emplace_back("Qt", qVersion());
+       version_info_.emplace_back("glibmm", PV_GLIBMM_VERSION);
+       version_info_.emplace_back("Boost", BOOST_LIB_VERSION);
+
+       version_info_.emplace_back("libsigrok", QString("%1/%2 (rt: %3/%4)")
+               .arg(SR_PACKAGE_VERSION_STRING, SR_LIB_VERSION_STRING,
+               sr_package_version_string_get(), sr_lib_version_string_get()));
+
+       GSList *l_orig = sr_buildinfo_libs_get();
+       for (GSList *l = l_orig; l; l = l->next) {
+               GSList *m = (GSList *)l->data;
+               const char *lib = (const char *)m->data;
+               const char *version = (const char *)m->next->data;
+               version_info_.emplace_back(QString(" - %1").arg(QString(lib)), QString(version));
+               g_slist_free_full(m, g_free);
+       }
+       g_slist_free(l_orig);
+
+       char *host = sr_buildinfo_host_get();
+       version_info_.emplace_back(" - Host", QString(host));
+       g_free(host);
+
+       char *scpi_backends = sr_buildinfo_scpi_backends_get();
+       version_info_.emplace_back(" - SCPI backends", QString(scpi_backends));
+       g_free(scpi_backends);
+
+#ifdef ENABLE_DECODE
+       struct srd_decoder *dec;
+
+       version_info_.emplace_back("libsigrokdecode", QString("%1/%2 (rt: %3/%4)")
+               .arg(SRD_PACKAGE_VERSION_STRING, SRD_LIB_VERSION_STRING,
+               srd_package_version_string_get(), srd_lib_version_string_get()));
+
+       l_orig = srd_buildinfo_libs_get();
+       for (GSList *l = l_orig; l; l = l->next) {
+               GSList *m = (GSList *)l->data;
+               const char *lib = (const char *)m->data;
+               const char *version = (const char *)m->next->data;
+               version_info_.emplace_back(QString(" - %1").arg(QString(lib)), QString(version));
+               g_slist_free_full(m, g_free);
+       }
+       g_slist_free(l_orig);
+
+       host = srd_buildinfo_host_get();
+       version_info_.emplace_back(" - Host", QString(host));
+       g_free(host);
+#endif
+
+       // Firmware paths
+       l_orig = sr_resourcepaths_get(SR_RESOURCE_FIRMWARE);
+       for (GSList *l = l_orig; l; l = l->next)
+               fw_path_list_.emplace_back((char*)l->data);
+       g_slist_free_full(l_orig, g_free);
+
+       // PD paths
+#ifdef ENABLE_DECODE
+       l_orig = srd_searchpaths_get();
+       for (GSList *l = l_orig; l; l = l->next)
+               pd_path_list_.emplace_back((char*)l->data);
+       g_slist_free_full(l_orig, g_free);
+#endif
+
+       // Device drivers
+       for (auto& entry : context->drivers())
+               driver_list_.emplace_back(QString::fromUtf8(entry.first.c_str()),
+                       QString::fromUtf8(entry.second->long_name().c_str()));
+
+       // Input formats
+       for (auto& entry : context->input_formats())
+               input_format_list_.emplace_back(QString::fromUtf8(entry.first.c_str()),
+                       QString::fromUtf8(entry.second->description().c_str()));
+
+       // Output formats
+       for (auto& entry : context->output_formats())
+               output_format_list_.emplace_back(QString::fromUtf8(entry.first.c_str()),
+                       QString::fromUtf8(entry.second->description().c_str()));
+
+       // Protocol decoders
+#ifdef ENABLE_DECODE
+       GSList *sl = g_slist_copy((GSList *)srd_decoder_list());
+       sl = g_slist_sort(sl, sort_pds);
+       for (const GSList *l = sl; l; l = l->next) {
+               dec = (struct srd_decoder *)l->data;
+               pd_list_.emplace_back(QString::fromUtf8(dec->id),
+                       QString::fromUtf8(dec->longname));
+       }
+       g_slist_free(sl);
+#endif
+}
+
+void Application::print_version_info()
+{
+       cout << PV_TITLE << " " << PV_VERSION_STRING << endl;
+
+       cout << endl << "Libraries and features:" << endl;
+       for (pair<QString, QString>& entry : version_info_)
+               cout << "  " << entry.first.toStdString() << " " << entry.second.toStdString() << endl;
+
+       cout << endl << "Firmware search paths:" << endl;
+       for (QString& entry : fw_path_list_)
+               cout << "  " << entry.toStdString() << endl;
+
+       cout << endl << "Protocol decoder search paths:" << endl;
+       for (QString& entry : pd_path_list_)
+               cout << "  " << entry.toStdString() << endl;
+
+       cout << endl << "Supported hardware drivers:" << endl;
+       for (pair<QString, QString>& entry : driver_list_)
+               cout << "  " << entry.first.leftJustified(21, ' ').toStdString() <<
+               entry.second.toStdString() << endl;
+
+       cout << endl << "Supported input formats:" << endl;
+       for (pair<QString, QString>& entry : input_format_list_)
+               cout << "  " << entry.first.leftJustified(21, ' ').toStdString() <<
+               entry.second.toStdString() << endl;
+
+       cout << endl << "Supported output formats:" << endl;
+       for (pair<QString, QString>& entry : output_format_list_)
+               cout << "  " << entry.first.leftJustified(21, ' ').toStdString() <<
+               entry.second.toStdString() << endl;
+
+#ifdef ENABLE_DECODE
+       cout << endl << "Supported protocol decoders:" << endl;
+       for (pair<QString, QString>& entry : pd_list_)
+               cout << "  " << entry.first.leftJustified(21, ' ').toStdString() <<
+               entry.second.toStdString() << endl;
+#endif
+}
+
+vector< pair<QString, QString> > Application::get_version_info() const
+{
+       return version_info_;
+}
+
+vector<QString> Application::get_fw_path_list() const
+{
+       return fw_path_list_;
+}
+
+vector<QString> Application::get_pd_path_list() const
+{
+       return pd_path_list_;
+}
+
+vector< pair<QString, QString> > Application::get_driver_list() const
+{
+       return driver_list_;
+}
+
+vector< pair<QString, QString> > Application::get_input_format_list() const
+{
+       return input_format_list_;
+}
+
+vector< pair<QString, QString> > Application::get_output_format_list() const
+{
+       return output_format_list_;
+}
+
+vector< pair<QString, QString> > Application::get_pd_list() const
+{
+       return pd_list_;
+}
+
 bool Application::notify(QObject *receiver, QEvent *event)
 {
        try {
                return QApplication::notify(receiver, event);
        } catch (exception& e) {
-               cerr << "Caught exception: " << e.what() << endl;
+               qDebug().nospace() << "Caught exception of type " << \
+                       typeid(e).name() << " (" << e.what() << ")";
+#ifdef ENABLE_STACKTRACE
+               throw e;
+#else
                exit(1);
+#endif
                return false;
        }
 }
index 7059c02a35293f0e741977ea5cf588356734cbd5..c618f80d9c1abeee91a1ee61e54d46d8d3c7b9b3 100644 (file)
 #ifndef PULSEVIEW_PV_APPLICATION_HPP
 #define PULSEVIEW_PV_APPLICATION_HPP
 
+#include <vector>
+
 #include <QApplication>
 
+#include <libsigrokcxx/libsigrokcxx.hpp>
+
+using std::shared_ptr;
+using std::pair;
+using std::vector;
+
 class Application : public QApplication
 {
+       Q_OBJECT
+
 public:
        Application(int &argc, char* argv[]);
+
+       void collect_version_info(shared_ptr<sigrok::Context> context);
+       void print_version_info();
+
+       vector< pair<QString, QString> > get_version_info() const;
+       vector<QString> get_fw_path_list() const;
+       vector<QString> get_pd_path_list() const;
+       vector< pair<QString, QString> > get_driver_list() const;
+       vector< pair<QString, QString> > get_input_format_list() const;
+       vector< pair<QString, QString> > get_output_format_list() const;
+       vector< pair<QString, QString> > get_pd_list() const;
+
 private:
        bool notify(QObject *receiver, QEvent *event);
+
+       vector< pair<QString, QString> > version_info_;
+       vector<QString> fw_path_list_;
+       vector<QString> pd_path_list_;
+       vector< pair<QString, QString> > driver_list_;
+       vector< pair<QString, QString> > input_format_list_;
+       vector< pair<QString, QString> > output_format_list_;
+       vector< pair<QString, QString> > pd_list_;
 };
 
 #endif // PULSEVIEW_PV_APPLICATION_HPP
index ae4f2943bdfe988a9dc6e82af5337525c317c04f..9735e14681edaa6d9f1b6bdd642f76ba8db694d8 100644 (file)
 #include <cassert>
 
 #include <QFormLayout>
+#include <QHBoxLayout>
+#include <QIcon>
 #include <QLabel>
+#include <QPushButton>
 
 #include <pv/prop/property.hpp>
 
@@ -46,28 +49,56 @@ void Binding::commit()
        }
 }
 
-void Binding::add_properties_to_form(QFormLayout *layout,
-       bool auto_commit) const
+void Binding::add_properties_to_form(QFormLayout *layout, bool auto_commit)
 {
        assert(layout);
 
+       help_labels_.clear();
+
        for (shared_ptr<pv::prop::Property> p : properties_) {
                assert(p);
 
-               QWidget *const widget = p->get_widget(layout->parentWidget(),
-                       auto_commit);
+               QWidget *widget;
+               QLabel *help_lbl = nullptr;
+
+               if (p->desc().isEmpty()) {
+                       widget = p->get_widget(layout->parentWidget(), auto_commit);
+               } else {
+                       QPushButton *help_btn = new QPushButton();
+                       help_btn->setFlat(true);
+                       help_btn->setIcon(QIcon(":/icons/help-browser.png"));
+                       help_btn->setToolTip(p->desc());
+                       connect(help_btn, SIGNAL(clicked(bool)),
+                               this, SLOT(on_help_clicked()));
+
+                       QHBoxLayout *layout = new QHBoxLayout();
+                       layout->setContentsMargins(0, 0, 0, 0);
+                       layout->addWidget(p->get_widget(layout->parentWidget(), auto_commit));
+                       layout->addWidget(help_btn, 0, Qt::AlignRight);
+
+                       widget = new QWidget();
+                       widget->setLayout(layout);
+
+                       help_lbl = new QLabel(p->desc());
+                       help_lbl->setVisible(false);
+                       help_lbl->setWordWrap(true);
+                       help_labels_[help_btn] = help_lbl;
+               }
+
                if (p->labeled_widget()) {
                        layout->addRow(widget);
                } else {
                        auto *lbl = new QLabel(p->name());
-                       lbl->setToolTip(p->desc());
+                       lbl->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
                        layout->addRow(lbl, widget);
                }
+
+               if (help_lbl)
+                       layout->addRow(help_lbl);
        }
 }
 
-QWidget* Binding::get_property_form(QWidget *parent,
-       bool auto_commit) const
+QWidget* Binding::get_property_form(QWidget *parent, bool auto_commit)
 {
        QWidget *const form = new QWidget(parent);
        QFormLayout *const layout = new QFormLayout(form);
@@ -76,6 +107,14 @@ QWidget* Binding::get_property_form(QWidget *parent,
        return form;
 }
 
+void Binding::update_property_widgets()
+{
+       for (shared_ptr<pv::prop::Property> p : properties_) {
+               assert(p);
+               p->update_widget();
+       }
+}
+
 QString Binding::print_gvariant(Glib::VariantBase gvar)
 {
        QString s;
@@ -84,13 +123,21 @@ QString Binding::print_gvariant(Glib::VariantBase gvar)
                s = QString::fromStdString("(null)");
        else if (gvar.is_of_type(Glib::VariantType("s")))
                s = QString::fromStdString(
-                       Glib::VariantBase::cast_dynamic<Glib::Variant<string>>(
-                               gvar).get());
+                       Glib::VariantBase::cast_dynamic<Glib::Variant<string>>(gvar).get());
        else
                s = QString::fromStdString(gvar.print());
 
        return s;
 }
 
+void Binding::on_help_clicked()
+{
+       QPushButton *btn = qobject_cast<QPushButton*>(QObject::sender());
+       assert(btn);
+
+       QLabel *lbl = help_labels_.at(btn);
+       lbl->setVisible(!lbl->isVisible());
+}
+
 }  // namespace binding
 }  // namespace pv
index e672ff176f24a5301026e519057b47b0db0b263f..5580a0b05b48765d74fd9272beafe4e38ddc2ca8 100644 (file)
@@ -26,15 +26,19 @@ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
 #include <glibmm.h>
 G_GNUC_END_IGNORE_DEPRECATIONS
 
+#include <map>
 #include <memory>
 #include <vector>
 
+#include <QObject>
 #include <QString>
 
+using std::map;
 using std::shared_ptr;
 using std::vector;
 
 class QFormLayout;
+class QLabel;
 class QWidget;
 
 namespace pv {
@@ -45,23 +49,29 @@ class Property;
 
 namespace binding {
 
-class Binding
+class Binding: public QObject
 {
+       Q_OBJECT
+
 public:
        const vector< shared_ptr<prop::Property> >& properties();
 
        void commit();
 
-       void add_properties_to_form(QFormLayout *layout,
-               bool auto_commit = false) const;
+       void add_properties_to_form(QFormLayout *layout, bool auto_commit = false);
+
+       QWidget* get_property_form(QWidget *parent, bool auto_commit = false);
 
-       QWidget* get_property_form(QWidget *parent,
-               bool auto_commit = false) const;
+       void update_property_widgets();
 
        static QString print_gvariant(Glib::VariantBase gvar);
 
+protected Q_SLOTS:
+       void on_help_clicked();
+
 protected:
        vector< shared_ptr<prop::Property> > properties_;
+       map<QWidget*, QLabel*> help_labels_;
 };
 
 }  // namespace binding
index f2e2e1d579f46eabe7b67104de3a103a6647055a..f51f90774afd0561d130704819528f9fd887a0f1 100644 (file)
 #include <boost/none_t.hpp>
 
 #include <pv/data/decode/decoder.hpp>
-#include <pv/data/decoderstack.hpp>
+#include <pv/data/decodesignal.hpp>
 #include <pv/prop/double.hpp>
 #include <pv/prop/enum.hpp>
 #include <pv/prop/int.hpp>
 #include <pv/prop/string.hpp>
 
 using boost::none;
-using std::make_pair;
 using std::map;
 using std::pair;
 using std::shared_ptr;
@@ -48,9 +47,9 @@ namespace pv {
 namespace binding {
 
 Decoder::Decoder(
-       shared_ptr<pv::data::DecoderStack> decoder_stack,
+       shared_ptr<pv::data::DecodeSignal> decode_signal,
        shared_ptr<data::decode::Decoder> decoder) :
-       decoder_stack_(decoder_stack),
+       decode_signal_(decode_signal),
        decoder_(decoder)
 {
        assert(decoder_);
@@ -97,7 +96,7 @@ shared_ptr<Property> Decoder::bind_enum(
        vector< pair<Glib::VariantBase, QString> > values;
        for (GSList *l = option->values; l; l = l->next) {
                Glib::VariantBase var = Glib::VariantBase((GVariant*)l->data, true);
-               values.push_back(make_pair(var, print_gvariant(var)));
+               values.emplace_back(var, print_gvariant(var));
        }
 
        return shared_ptr<Property>(new Enum(name, desc, values, getter, setter));
@@ -137,8 +136,8 @@ void Decoder::setter(const char *id, Glib::VariantBase value)
        assert(decoder_);
        decoder_->set_option(id, value.gobj());
 
-       assert(decoder_stack_);
-       decoder_stack_->begin_decode();
+       assert(decode_signal_);
+       decode_signal_->begin_decode();
 }
 
 }  // namespace binding
index bf670403dee317f486c5d43ab093eb74932288fa..a93a057f1a371b8571f19b0cd2808aa11e217b9e 100644 (file)
@@ -31,7 +31,7 @@ struct srd_decoder_option;
 namespace pv {
 
 namespace data {
-class DecoderStack;
+class DecodeSignal;
 namespace decode {
 class Decoder;
 }
@@ -42,7 +42,7 @@ namespace binding {
 class Decoder : public Binding
 {
 public:
-       Decoder(shared_ptr<pv::data::DecoderStack> decoder_stack,
+       Decoder(shared_ptr<pv::data::DecodeSignal> decode_signal,
                shared_ptr<pv::data::decode::Decoder> decoder);
 
 private:
@@ -55,7 +55,7 @@ private:
        void setter(const char *id, Glib::VariantBase value);
 
 private:
-       shared_ptr<pv::data::DecoderStack> decoder_stack_;
+       shared_ptr<pv::data::DecodeSignal> decode_signal_;
        shared_ptr<pv::data::decode::Decoder> decoder_;
 };
 
index 20082db2f7a949666151fc01e006d7408f7fb3ab..37c9d43d55248218b4e7e247cd6173fa1696b664 100644 (file)
@@ -32,7 +32,6 @@
 using boost::optional;
 
 using std::function;
-using std::make_pair;
 using std::pair;
 using std::set;
 using std::shared_ptr;
@@ -69,7 +68,7 @@ Device::Device(shared_ptr<sigrok::Configurable> configurable) :
                string name_str;
                try {
                        name_str = key->description();
-               } catch (Error e) {
+               } catch (Error& e) {
                        name_str = key->name();
                }
 
@@ -98,6 +97,8 @@ Device::Device(shared_ptr<sigrok::Configurable> configurable) :
                case SR_CONF_TRIGGER_SLOPE:
                case SR_CONF_COUPLING:
                case SR_CONF_CLOCK_EDGE:
+               case SR_CONF_DATA_SOURCE:
+               case SR_CONF_EXTERNAL_CLOCK_SOURCE:
                        bind_enum(name, "", key, capabilities, get, set);
                        break;
 
@@ -105,6 +106,7 @@ Device::Device(shared_ptr<sigrok::Configurable> configurable) :
                case SR_CONF_EXTERNAL_CLOCK:
                case SR_CONF_RLE:
                case SR_CONF_POWER_OFF:
+               case SR_CONF_AVERAGING:
                        bind_bool(name, "", get, set);
                        break;
 
@@ -127,6 +129,13 @@ Device::Device(shared_ptr<sigrok::Configurable> configurable) :
                                bind_int(name, "", "", pair<int64_t, int64_t>(1, 500), get, set);
                        break;
 
+               case SR_CONF_AVG_SAMPLES:
+                       if (capabilities.count(Capability::LIST))
+                               bind_enum(name, "", key, capabilities, get, set, print_averages);
+                       else
+                               bind_int(name, "", "", pair<int64_t, int64_t>(0, INT32_MAX), get, set);
+                       break;
+
                default:
                        break;
                }
@@ -157,7 +166,7 @@ void Device::bind_enum(const QString &name, const QString &desc,
 
                vector< pair<Glib::VariantBase, QString> > values;
                while ((iter.next_value(gvar)))
-                       values.push_back(make_pair(gvar, printer(gvar)));
+                       values.emplace_back(gvar, printer(gvar));
 
                properties_.push_back(shared_ptr<Property>(new Enum(name, desc, values,
                        getter, setter)));
@@ -206,5 +215,12 @@ QString Device::print_probe_factor(Glib::VariantBase gvar)
        return QString("%1x").arg(factor);
 }
 
+QString Device::print_averages(Glib::VariantBase gvar)
+{
+       uint64_t avg;
+       avg = g_variant_get_uint64(gvar.gobj());
+       return QString("%1").arg(avg);
+}
+
 }  // namespace binding
 }  // namespace pv
index dcef9470cc12e091565871486d1ef3d6a8799053..9f5daf574a250beef9e3b5030e75599f88766fc1 100644 (file)
@@ -20,6 +20,8 @@
 #ifndef PULSEVIEW_PV_BINDING_DEVICE_HPP
 #define PULSEVIEW_PV_BINDING_DEVICE_HPP
 
+#include <functional>
+
 #include <boost/optional.hpp>
 
 #include <QObject>
@@ -40,7 +42,7 @@ namespace pv {
 
 namespace binding {
 
-class Device : public QObject, public Binding
+class Device : public Binding
 {
        Q_OBJECT
 
@@ -66,6 +68,7 @@ private:
        static QString print_vdiv(Glib::VariantBase gvar);
        static QString print_voltage_threshold(Glib::VariantBase gvar);
        static QString print_probe_factor(Glib::VariantBase gvar);
+       static QString print_averages(Glib::VariantBase gvar);
 
 protected:
        shared_ptr<sigrok::Configurable> configurable_;
index 9d2299afaf95fd767a5d7142fb71a606e3d97f0f..f9a061c76e78c7b7e77a33ec1d04250328263b70 100644 (file)
@@ -35,7 +35,6 @@
 
 using boost::none;
 
-using std::make_pair;
 using std::map;
 using std::pair;
 using std::shared_ptr;
@@ -60,12 +59,13 @@ namespace binding {
 InputOutput::InputOutput(
        const map<string, shared_ptr<Option>> &options)
 {
-       for (pair<string, shared_ptr<Option>> o : options) {
+       for (const pair<string, shared_ptr<Option>>& o : options) {
                const shared_ptr<Option> &opt = o.second;
                assert(opt);
 
                const QString name = QString::fromStdString(opt->name());
                const QString desc = QString::fromStdString(opt->description());
+
                const VariantBase def_val = opt->default_value();
                const vector<VariantBase> values = opt->values();
 
@@ -111,7 +111,7 @@ shared_ptr<Property> InputOutput::bind_enum(
 {
        vector< pair<VariantBase, QString> > enum_vals;
        for (VariantBase var : values)
-               enum_vals.push_back(make_pair(var, print_gvariant(var)));
+               enum_vals.emplace_back(var, print_gvariant(var));
        return shared_ptr<Property>(new Enum(name, desc, enum_vals, getter, setter));
 }
 
index 854dae3e68eaaec8d71212219365323abcefbbe8..f8fe473d3681ebc31f0850d052af2d952d8fbae4 100644 (file)
@@ -37,7 +37,7 @@ Analog::Analog() :
 
 void Analog::push_segment(shared_ptr<AnalogSegment> &segment)
 {
-       segments_.push_front(segment);
+       segments_.push_back(segment);
 }
 
 const deque< shared_ptr<AnalogSegment> >& Analog::analog_segments() const
@@ -51,6 +51,11 @@ vector< shared_ptr<Segment> > Analog::segments() const
                segments_.begin(), segments_.end());
 }
 
+uint32_t Analog::get_segment_count() const
+{
+       return (uint32_t)segments_.size();
+}
+
 void Analog::clear()
 {
        segments_.clear();
@@ -58,10 +63,18 @@ void Analog::clear()
        samples_cleared();
 }
 
+double Analog::get_samplerate() const
+{
+       if (segments_.empty())
+               return 1.0;
+
+       return segments_.front()->samplerate();
+}
+
 uint64_t Analog::max_sample_count() const
 {
        uint64_t l = 0;
-       for (const shared_ptr<AnalogSegment> s : segments_) {
+       for (const shared_ptr<AnalogSegment>& s : segments_) {
                assert(s);
                l = max(l, s->get_sample_count());
        }
@@ -74,5 +87,10 @@ void Analog::notify_samples_added(QObject* segment, uint64_t start_sample,
        samples_added(segment, start_sample, end_sample);
 }
 
+void Analog::notify_min_max_changed(float min, float max)
+{
+       min_max_changed(min, max);
+}
+
 } // namespace data
 } // namespace pv
index 30860ce18e4a0c4f98590629fd965c8a0326d3de..c0b9a0134d72d9b5028f75b5d19040a71d81c991 100644 (file)
@@ -49,19 +49,27 @@ public:
 
        vector< shared_ptr<Segment> > segments() const;
 
+       uint32_t get_segment_count() const;
+
        void clear();
 
+       double get_samplerate() const;
+
        uint64_t max_sample_count() const;
 
        void notify_samples_added(QObject* segment, uint64_t start_sample,
                uint64_t end_sample);
 
+       void notify_min_max_changed(float min, float max);
+
 Q_SIGNALS:
        void samples_cleared();
 
        void samples_added(QObject* segment, uint64_t start_sample,
                uint64_t end_sample);
 
+       void min_max_changed(float min, float max);
+
 private:
        deque< shared_ptr<AnalogSegment> > segments_;
 };
index 5e42556ca8c3f01341283f909f2ffc81ed2d394a..342612aae02e15954671d2044f3bdb337682cd09 100644 (file)
@@ -48,8 +48,8 @@ const int AnalogSegment::EnvelopeScaleFactor = 1 << EnvelopeScalePower;
 const float AnalogSegment::LogEnvelopeScaleFactor = logf(EnvelopeScaleFactor);
 const uint64_t AnalogSegment::EnvelopeDataUnit = 64 * 1024;    // bytes
 
-AnalogSegment::AnalogSegment(Analog& owner, uint64_t samplerate) :
-       Segment(samplerate, sizeof(float)),
+AnalogSegment::AnalogSegment(Analog& owner, uint32_t segment_id, uint64_t samplerate) :
+       Segment(segment_id, samplerate, sizeof(float)),
        owner_(owner),
        min_value_(0),
        max_value_(0)
@@ -75,7 +75,7 @@ void AnalogSegment::append_interleaved_samples(const float *data,
        uint64_t prev_sample_count = sample_count_;
 
        // Deinterleave the samples and add them
-       unique_ptr<float> deint_data(new float[sample_count]);
+       unique_ptr<float[]> deint_data(new float[sample_count]);
        float *deint_data_ptr = deint_data.get();
        for (uint32_t i = 0; i < sample_count; i++) {
                *deint_data_ptr = (float)(*data);
@@ -96,18 +96,19 @@ void AnalogSegment::append_interleaved_samples(const float *data,
                        prev_sample_count + 1);
 }
 
-const float* AnalogSegment::get_samples(
-       int64_t start_sample, int64_t end_sample) const
+void AnalogSegment::get_samples(int64_t start_sample, int64_t end_sample,
+       float* dest) const
 {
        assert(start_sample >= 0);
        assert(start_sample < (int64_t)sample_count_);
        assert(end_sample >= 0);
-       assert(end_sample < (int64_t)sample_count_);
+       assert(end_sample <= (int64_t)sample_count_);
        assert(start_sample <= end_sample);
+       assert(dest != nullptr);
 
        lock_guard<recursive_mutex> lock(mutex_);
 
-       return (float*)get_raw_samples(start_sample, (end_sample - start_sample));
+       get_raw_samples(start_sample, (end_sample - start_sample), (uint8_t*)dest);
 }
 
 const pair<float, float> AnalogSegment::get_min_max() const
@@ -115,19 +116,11 @@ const pair<float, float> AnalogSegment::get_min_max() const
        return make_pair(min_value_, max_value_);
 }
 
-SegmentAnalogDataIterator* AnalogSegment::begin_sample_iteration(uint64_t start)
+float* AnalogSegment::get_iterator_value_ptr(SegmentDataIterator* it)
 {
-       return (SegmentAnalogDataIterator*)begin_raw_sample_iteration(start);
-}
-
-void AnalogSegment::continue_sample_iteration(SegmentAnalogDataIterator* it, uint64_t increase)
-{
-       Segment::continue_raw_sample_iteration((SegmentRawDataIterator*)it, increase);
-}
+       assert(it->sample_index <= (sample_count_ - 1));
 
-void AnalogSegment::end_sample_iteration(SegmentAnalogDataIterator* it)
-{
-       Segment::end_raw_sample_iteration((SegmentRawDataIterator*)it);
+       return (float*)(it->chunk + it->chunk_offs);
 }
 
 void AnalogSegment::get_envelope_section(EnvelopeSection &s,
@@ -170,24 +163,25 @@ void AnalogSegment::append_payload_to_envelope_levels()
        Envelope &e0 = envelope_levels_[0];
        uint64_t prev_length;
        EnvelopeSample *dest_ptr;
-       SegmentRawDataIterator* it;
+       SegmentDataIterator* it;
 
        // Expand the data buffer to fit the new samples
        prev_length = e0.length;
        e0.length = sample_count_ / EnvelopeScaleFactor;
 
        // Calculate min/max values in case we have too few samples for an envelope
+       const float old_min_value = min_value_, old_max_value = max_value_;
        if (sample_count_ < EnvelopeScaleFactor) {
-               it = begin_raw_sample_iteration(0);
+               it = begin_sample_iteration(0);
                for (uint64_t i = 0; i < sample_count_; i++) {
-                       const float sample = *((float*)it->value);
+                       const float sample = *get_iterator_value_ptr(it);
                        if (sample < min_value_)
                                min_value_ = sample;
                        if (sample > max_value_)
                                max_value_ = sample;
-                       continue_raw_sample_iteration(it, 1);
+                       continue_sample_iteration(it, 1);
                }
-               end_raw_sample_iteration(it);
+               end_sample_iteration(it);
        }
 
        // Break off if there are no new samples to compute
@@ -202,9 +196,9 @@ void AnalogSegment::append_payload_to_envelope_levels()
        uint64_t start_sample = prev_length * EnvelopeScaleFactor;
        uint64_t end_sample = e0.length * EnvelopeScaleFactor;
 
-       it = begin_raw_sample_iteration(start_sample);
+       it = begin_sample_iteration(start_sample);
        for (uint64_t i = start_sample; i < end_sample; i += EnvelopeScaleFactor) {
-               const float* samples = (float*)it->value;
+               const float* samples = get_iterator_value_ptr(it);
 
                const EnvelopeSample sub_sample = {
                        *min_element(samples, samples + EnvelopeScaleFactor),
@@ -216,10 +210,10 @@ void AnalogSegment::append_payload_to_envelope_levels()
                if (sub_sample.max > max_value_)
                        max_value_ = sub_sample.max;
 
-               continue_raw_sample_iteration(it, EnvelopeScaleFactor);
+               continue_sample_iteration(it, EnvelopeScaleFactor);
                *dest_ptr++ = sub_sample;
        }
-       end_raw_sample_iteration(it);
+       end_sample_iteration(it);
 
        // Compute higher level mipmaps
        for (unsigned int level = 1; level < ScaleStepCount; level++) {
@@ -256,6 +250,10 @@ void AnalogSegment::append_payload_to_envelope_levels()
                        *dest_ptr = sub_sample;
                }
        }
+
+       // Notify if the min or max value changed
+       if ((old_min_value != min_value_) || (old_max_value != max_value_))
+               owner_.min_max_changed(min_value_, max_value_);
 }
 
 } // namespace data
index 8dd6f5f189746ec903157c7563892ad2073f1170..df25f0b74a669109663a08e0df4dbeff3b5a7641 100644 (file)
@@ -38,13 +38,7 @@ namespace data {
 
 class Analog;
 
-typedef struct {
-       uint64_t sample_index, chunk_num, chunk_offs;
-       uint8_t* chunk;
-       float* value;
-} SegmentAnalogDataIterator;
-
-class AnalogSegment : public QObject, public Segment
+class AnalogSegment : public Segment
 {
        Q_OBJECT
 
@@ -79,21 +73,18 @@ private:
        static const uint64_t EnvelopeDataUnit;
 
 public:
-       AnalogSegment(Analog& owner, uint64_t samplerate);
+       AnalogSegment(Analog& owner, uint32_t segment_id, uint64_t samplerate);
 
        virtual ~AnalogSegment();
 
        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_samples(int64_t start_sample, int64_t end_sample, float* dest) const;
 
        const pair<float, float> get_min_max() const;
 
-       SegmentAnalogDataIterator* begin_sample_iteration(uint64_t start);
-       void continue_sample_iteration(SegmentAnalogDataIterator* it, uint64_t increase);
-       void end_sample_iteration(SegmentAnalogDataIterator* it);
+       float* get_iterator_value_ptr(SegmentDataIterator* it);
 
        void get_envelope_section(EnvelopeSection &s,
                uint64_t start, uint64_t end, float min_length) const;
index 7c720430e9d6cf7e6a688fd6c138cad2cd90589b..e983b0df1c3e971ba7942c9ccadb86ed10e09bbb 100644 (file)
@@ -32,16 +32,17 @@ namespace pv {
 namespace data {
 namespace decode {
 
-Annotation::Annotation(const srd_proto_data *const pdata) :
+Annotation::Annotation(const srd_proto_data *const pdata, const Row *row) :
        start_sample_(pdata->start_sample),
-       end_sample_(pdata->end_sample)
+       end_sample_(pdata->end_sample),
+       row_(row)
 {
        assert(pdata);
        const srd_proto_data_annotation *const pda =
                (const srd_proto_data_annotation*)pdata->data;
        assert(pda);
 
-       format_ = pda->ann_class;
+       ann_class_ = (Class)(pda->ann_class);
 
        const char *const *annotations = (char**)pda->ann_text;
        while (*annotations) {
@@ -60,9 +61,9 @@ uint64_t Annotation::end_sample() const
        return end_sample_;
 }
 
-int Annotation::format() const
+Annotation::Class Annotation::ann_class() const
 {
-       return format_;
+       return ann_class_;
 }
 
 const vector<QString>& Annotation::annotations() const
@@ -70,6 +71,16 @@ const vector<QString>& Annotation::annotations() const
        return annotations_;
 }
 
+const Row* Annotation::row() const
+{
+       return row_;
+}
+
+bool Annotation::operator<(const Annotation &other) const
+{
+       return (start_sample_ < other.start_sample_);
+}
+
 } // namespace decode
 } // namespace data
 } // namespace pv
index 2be8e88d989e4cddacaa18d9b86156ca875c4e95..8b91c4f47e066605e307ed0301fbd1871d59436d 100644 (file)
@@ -20,7 +20,8 @@
 #ifndef PULSEVIEW_PV_VIEW_DECODE_ANNOTATION_HPP
 #define PULSEVIEW_PV_VIEW_DECODE_ANNOTATION_HPP
 
-#include <stdint.h>
+#include <cstdint>
+#include <vector>
 
 #include <QString>
 
@@ -32,21 +33,30 @@ namespace pv {
 namespace data {
 namespace decode {
 
+class Row;
+
 class Annotation
 {
 public:
-       Annotation(const srd_proto_data *const pdata);
+       typedef uint32_t Class;
+
+public:
+       Annotation(const srd_proto_data *const pdata, const Row *row);
 
        uint64_t start_sample() const;
        uint64_t end_sample() const;
-       int format() const;
+       Class ann_class() const;
        const vector<QString>& annotations() const;
+       const Row* row() const;
+
+       bool operator<(const Annotation &other) const;
 
 private:
        uint64_t start_sample_;
        uint64_t end_sample_;
-       int format_;
+       Class ann_class_;
        vector<QString> annotations_;
+       const Row *row_;
 };
 
 } // namespace decode
index 841d4fd7b874cdf4ba30227beaf862f361d60627..f86c5d08dc86d9261718ab92e2005b1b30dbd914 100644 (file)
 
 #include <cassert>
 
+#include <QDebug>
+
 #include <libsigrokcxx/libsigrokcxx.hpp>
 #include <libsigrokdecode/libsigrokdecode.h>
 
 #include "decoder.hpp"
 
 #include <pv/data/signalbase.hpp>
+#include <pv/data/decodesignal.hpp>
 
-using std::set;
+using pv::data::DecodeChannel;
 using std::map;
-using std::shared_ptr;
 using std::string;
 
 namespace pv {
@@ -38,7 +40,7 @@ namespace decode {
 Decoder::Decoder(const srd_decoder *const dec) :
        decoder_(dec),
        shown_(true),
-       initial_pins_(nullptr)
+       decoder_inst_(nullptr)
 {
 }
 
@@ -63,30 +65,16 @@ void Decoder::show(bool show)
        shown_ = show;
 }
 
-const map<const srd_channel*, shared_ptr<data::SignalBase> >&
-Decoder::channels() const
+const vector<DecodeChannel*>& Decoder::channels() const
 {
        return channels_;
 }
 
-void Decoder::set_channels(map<const srd_channel*,
-       shared_ptr<data::SignalBase> > channels)
+void Decoder::set_channels(vector<DecodeChannel*> channels)
 {
        channels_ = channels;
 }
 
-void Decoder::set_initial_pins(GArray *initial_pins)
-{
-       if (initial_pins_)
-               g_array_free(initial_pins_, TRUE);
-       initial_pins_ = initial_pins;
-}
-
-GArray *Decoder::initial_pins() const
-{
-       return initial_pins_;
-}
-
 const map<string, GVariant*>& Decoder::options() const
 {
        return options_;
@@ -97,33 +85,39 @@ void Decoder::set_option(const char *id, GVariant *value)
        assert(value);
        g_variant_ref(value);
        options_[id] = value;
+
+       // If we have a decoder instance, apply option value immediately
+       apply_all_options();
 }
 
-bool Decoder::have_required_channels() const
+void Decoder::apply_all_options()
 {
-       for (GSList *l = decoder_->channels; l; l = l->next) {
-               const srd_channel *const pdch = (const srd_channel*)l->data;
-               assert(pdch);
-               if (channels_.find(pdch) == channels_.end())
-                       return false;
+       if (decoder_inst_) {
+               GHashTable *const opt_hash = g_hash_table_new_full(g_str_hash,
+                       g_str_equal, g_free, (GDestroyNotify)g_variant_unref);
+
+               for (const auto& option : options_) {
+                       GVariant *const value = option.second;
+                       g_variant_ref(value);
+                       g_hash_table_replace(opt_hash, (void*)g_strdup(
+                               option.first.c_str()), value);
+               }
+
+               srd_inst_option_set(decoder_inst_, opt_hash);
+               g_hash_table_destroy(opt_hash);
        }
-
-       return true;
 }
 
-set< shared_ptr<pv::data::Logic> > Decoder::get_data()
+bool Decoder::have_required_channels() const
 {
-       set< shared_ptr<pv::data::Logic> > data;
-       for (const auto& channel : channels_) {
-               shared_ptr<data::SignalBase> b(channel.second);
-               assert(b);
-               data.insert(b->logic_data());
-       }
+       for (DecodeChannel *ch : channels_)
+               if (!ch->assigned_signal && !ch->is_optional)
+                       return false;
 
-       return data;
+       return true;
 }
 
-srd_decoder_inst* Decoder::create_decoder_inst(srd_session *session) const
+srd_decoder_inst* Decoder::create_decoder_inst(srd_session *session)
 {
        GHashTable *const opt_hash = g_hash_table_new_full(g_str_hash,
                g_str_equal, g_free, (GDestroyNotify)g_variant_unref);
@@ -135,29 +129,47 @@ srd_decoder_inst* Decoder::create_decoder_inst(srd_session *session) const
                        option.first.c_str()), value);
        }
 
-       srd_decoder_inst *const decoder_inst = srd_inst_new(
-               session, decoder_->id, opt_hash);
+       if (decoder_inst_)
+               qDebug() << "WARNING: previous decoder instance" << decoder_inst_ << "exists";
+
+       decoder_inst_ = srd_inst_new(session, decoder_->id, opt_hash);
        g_hash_table_destroy(opt_hash);
 
-       if (!decoder_inst)
+       if (!decoder_inst_)
                return nullptr;
 
        // Setup the channels
+       GArray *const init_pin_states = g_array_sized_new(false, true,
+               sizeof(uint8_t), channels_.size());
+
+       g_array_set_size(init_pin_states, channels_.size());
+
        GHashTable *const channels = g_hash_table_new_full(g_str_hash,
                g_str_equal, g_free, (GDestroyNotify)g_variant_unref);
 
-       for (const auto& channel : channels_) {
-               shared_ptr<data::SignalBase> b(channel.second);
-               GVariant *const gvar = g_variant_new_int32(b->index());
+       for (DecodeChannel *ch : channels_) {
+               if (!ch->assigned_signal)
+                       continue;
+
+               init_pin_states->data[ch->id] = ch->initial_pin_state;
+
+               GVariant *const gvar = g_variant_new_int32(ch->bit_id);  // bit_id = bit position
                g_variant_ref_sink(gvar);
-               g_hash_table_insert(channels, channel.first->id, gvar);
+               // key is channel name (pdch->id), value is bit position in each sample (gvar)
+               g_hash_table_insert(channels, ch->pdch_->id, gvar);
        }
 
-       srd_inst_channel_set_all(decoder_inst, channels);
+       srd_inst_channel_set_all(decoder_inst_, channels);
 
-       srd_inst_initial_pins_set_all(decoder_inst, initial_pins_);
+       srd_inst_initial_pins_set_all(decoder_inst_, init_pin_states);
+       g_array_free(init_pin_states, true);
 
-       return decoder_inst;
+       return decoder_inst_;
+}
+
+void Decoder::invalidate_decoder_inst()
+{
+       decoder_inst_ = nullptr;
 }
 
 }  // namespace decode
index eff2367fb3604cf4c76e7e9ca7c9887cf0e741c4..55742c6070e5a020c00042b2c8894e744b13986e 100644 (file)
 #include <map>
 #include <memory>
 #include <set>
+#include <vector>
 
 #include <glib.h>
 
 using std::map;
-using std::set;
-using std::shared_ptr;
 using std::string;
+using std::vector;
 
 struct srd_decoder;
 struct srd_decoder_inst;
@@ -40,6 +40,7 @@ namespace pv {
 
 namespace data {
 
+struct DecodeChannel;
 class Logic;
 class SignalBase;
 
@@ -57,33 +58,28 @@ public:
        bool shown() const;
        void show(bool show = true);
 
-       const map<const srd_channel*,
-               shared_ptr<data::SignalBase> >& channels() const;
-       void set_channels(map<const srd_channel*,
-               shared_ptr<data::SignalBase> > channels);
-
-       void set_initial_pins(GArray *initial_pins);
-
-       GArray *initial_pins() const;
+       const vector<data::DecodeChannel*>& channels() const;
+       void set_channels(vector<data::DecodeChannel*> channels);
 
        const map<string, GVariant*>& options() const;
 
        void set_option(const char *id, GVariant *value);
 
-       bool have_required_channels() const;
+       void apply_all_options();
 
-       srd_decoder_inst* create_decoder_inst(srd_session *session) const;
+       bool have_required_channels() const;
 
-       set< shared_ptr<pv::data::Logic> > get_data();
+       srd_decoder_inst* create_decoder_inst(srd_session *session);
+       void invalidate_decoder_inst();
 
 private:
        const srd_decoder *const decoder_;
 
        bool shown_;
 
-       map<const srd_channel*, shared_ptr<pv::data::SignalBase> > channels_;
-       GArray *initial_pins_;
+       vector<data::DecodeChannel*> channels_;
        map<string, GVariant*> options_;
+       srd_decoder_inst *decoder_inst_;
 };
 
 } // namespace decode
index 1266b8436e1e09934b92418b62e5a6ebc42e7728..8195f3e2d00f023c0cdfb91f4b0464c8065d5eab 100644 (file)
@@ -31,7 +31,8 @@ Row::Row() :
 {
 }
 
-Row::Row(const srd_decoder *decoder, const srd_decoder_annotation_row *row) :
+Row::Row(int index, const srd_decoder *decoder, const srd_decoder_annotation_row *row) :
+       index_(index),
        decoder_(decoder),
        row_(row)
 {
@@ -60,6 +61,18 @@ const QString Row::title() const
        return QString();
 }
 
+const QString Row::class_name() const
+{
+       if (row_ && row_->desc)
+               return QString::fromUtf8(row_->desc);
+       return QString();
+}
+
+int Row::index() const
+{
+       return index_;
+}
+
 bool Row::operator<(const Row &other) const
 {
        return (decoder_ < other.decoder_) ||
index 5ddd10d3318fb2b0c9e0d399fa8b83915164d45f..34bb2373e844babd768335d4d8a335a227696b0c 100644 (file)
@@ -36,17 +36,20 @@ class Row
 public:
        Row();
 
-       Row(const srd_decoder *decoder,
+       Row(int index, const srd_decoder *decoder,
                const srd_decoder_annotation_row *row = nullptr);
 
        const srd_decoder* decoder() const;
        const srd_decoder_annotation_row* row() const;
 
        const QString title() const;
+       const QString class_name() const;
+       int index() const;
 
        bool operator<(const Row &other) const;
 
 private:
+       int index_;
        const srd_decoder *decoder_;
        const srd_decoder_annotation_row *row_;
 };
index 02859b2790a5b10f7979653ccc1ff41f8000e84c..2a26169eb6cc0f14adb5a107726c40ee891838b6 100644 (file)
@@ -42,9 +42,9 @@ void RowData::get_annotation_subset(
                        dest.push_back(annotation);
 }
 
-void RowData::push_annotation(const Annotation &a)
+void RowData::emplace_annotation(srd_proto_data *pdata, const Row *row)
 {
-       annotations_.push_back(a);
+       annotations_.emplace_back(pdata, row);
 }
 
 }  // namespace decode
index 3cb69b3ef1b5ad84ad95581c961c9a4f9694850f..0589ec894eb444fad4f2d0d759c98a1eabc13fc7 100644 (file)
@@ -22,6 +22,8 @@
 
 #include <vector>
 
+#include <libsigrokdecode/libsigrokdecode.h>
+
 #include "annotation.hpp"
 
 using std::vector;
@@ -30,6 +32,8 @@ namespace pv {
 namespace data {
 namespace decode {
 
+class Row;
+
 class RowData
 {
 public:
@@ -39,13 +43,15 @@ public:
        uint64_t get_max_sample() const;
 
        /**
-        * Extracts sorted annotations between two period into a vector.
+        * Extracts annotations between the given sample range into a vector.
+        * Note: The annotations are unsorted and only annotations that fully
+        * fit into the sample range are considered.
         */
        void get_annotation_subset(
                vector<pv::data::decode::Annotation> &dest,
                uint64_t start_sample, uint64_t end_sample) const;
 
-       void push_annotation(const Annotation &a);
+       void emplace_annotation(srd_proto_data *pdata, const Row *row);
 
 private:
        vector<Annotation> annotations_;
diff --git a/pv/data/decoderstack.cpp b/pv/data/decoderstack.cpp
deleted file mode 100644 (file)
index 1e1d600..0000000
+++ /dev/null
@@ -1,460 +0,0 @@
-/*
- * This file is part of the PulseView project.
- *
- * Copyright (C) 2012 Joel Holdsworth <joel@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, see <http://www.gnu.org/licenses/>.
- */
-
-#include <libsigrokdecode/libsigrokdecode.h>
-
-#include <stdexcept>
-
-#include <QDebug>
-
-#include "decoderstack.hpp"
-
-#include <pv/data/decode/annotation.hpp>
-#include <pv/data/decode/decoder.hpp>
-#include <pv/data/logic.hpp>
-#include <pv/data/logicsegment.hpp>
-#include <pv/session.hpp>
-#include <pv/views/trace/logicsignal.hpp>
-
-using std::lock_guard;
-using std::mutex;
-using std::unique_lock;
-using std::deque;
-using std::make_pair;
-using std::max;
-using std::min;
-using std::list;
-using std::shared_ptr;
-using std::make_shared;
-using std::vector;
-
-using boost::optional;
-
-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 = 10 * 1024 * 1024;
-const unsigned int DecoderStack::DecodeNotifyPeriod = 1024;
-
-mutex DecoderStack::global_srd_mutex_;
-
-DecoderStack::DecoderStack(pv::Session &session,
-       const srd_decoder *const dec) :
-       session_(session),
-       start_time_(0),
-       samplerate_(0),
-       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(make_shared<decode::Decoder>(dec));
-}
-
-DecoderStack::~DecoderStack()
-{
-       if (decode_thread_.joinable()) {
-               interrupt_ = true;
-               input_cond_.notify_one();
-               decode_thread_.join();
-       }
-}
-
-const list< shared_ptr<decode::Decoder> >& DecoderStack::stack() const
-{
-       return stack_;
-}
-
-void DecoderStack::push(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
-       auto iter = stack_.begin();
-       for (int i = 0; i < index; i++, iter++)
-               assert(iter != stack_.end());
-
-       // Delete the element
-       stack_.erase(iter);
-}
-
-double DecoderStack::samplerate() const
-{
-       return samplerate_;
-}
-
-const pv::util::Timestamp& DecoderStack::start_time() const
-{
-       return start_time_;
-}
-
-int64_t DecoderStack::samples_decoded() const
-{
-       lock_guard<mutex> decode_lock(output_mutex_);
-       return samples_decoded_;
-}
-
-vector<Row> DecoderStack::get_visible_rows() const
-{
-       lock_guard<mutex> lock(output_mutex_);
-
-       vector<Row> rows;
-
-       for (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.emplace_back(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.emplace_back(decc, ann_row);
-               }
-       }
-
-       return rows;
-}
-
-void DecoderStack::get_annotation_subset(
-       vector<pv::data::decode::Annotation> &dest,
-       const Row &row, uint64_t start_sample,
-       uint64_t end_sample) const
-{
-       lock_guard<mutex> lock(output_mutex_);
-
-       const auto 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()
-{
-       if (decode_thread_.joinable()) {
-               interrupt_ = true;
-               input_cond_.notify_one();
-               decode_thread_.join();
-       }
-
-       clear();
-
-       // Check that all decoders have the required channels
-       for (const shared_ptr<decode::Decoder> &dec : stack_)
-               if (!dec->have_required_channels()) {
-                       error_message_ = tr("One or more required channels "
-                               "have not been specified");
-                       return;
-               }
-
-       // Add classes
-       for (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
-       // logic signals have the same data/segment
-       pv::data::SignalBase *signalbase;
-       pv::data::Logic *data = nullptr;
-
-       for (const shared_ptr<decode::Decoder> &dec : stack_)
-               if (dec && !dec->channels().empty() &&
-                       ((signalbase = (*dec->channels().begin()).second.get())) &&
-                       ((data = signalbase->logic_data().get())))
-                       break;
-
-       if (!data)
-               return;
-
-       // Check we have a segment of data
-       const deque< shared_ptr<pv::data::LogicSegment> > &segments =
-               data->logic_segments();
-       if (segments.empty())
-               return;
-       segment_ = segments.front();
-
-       // Get the samplerate and start time
-       start_time_ = segment_->start_time();
-       samplerate_ = segment_->samplerate();
-       if (samplerate_ == 0.0)
-               samplerate_ = 1.0;
-
-       interrupt_ = false;
-       decode_thread_ = std::thread(&DecoderStack::decode_proc, this);
-}
-
-uint64_t DecoderStack::max_sample_count() const
-{
-       uint64_t max_sample_count = 0;
-
-       for (const auto& row : rows_)
-               max_sample_count = max(max_sample_count,
-                       row.second.get_max_sample());
-
-       return max_sample_count;
-}
-
-optional<int64_t> DecoderStack::wait_for_data() const
-{
-       unique_lock<mutex> input_lock(input_mutex_);
-
-       // Do wait if we decoded all samples but we're still capturing
-       // Do not wait if we're done capturing
-       while (!interrupt_ && !frame_complete_ &&
-               (samples_decoded_ >= sample_count_) &&
-               (session_.get_capture_state() != Session::Stopped)) {
-
-               input_cond_.wait(input_lock);
-       }
-
-       // Return value is valid if we're not aborting the decode,
-       return boost::make_optional(!interrupt_ &&
-               // and there's more work to do...
-               (samples_decoded_ < sample_count_ || !frame_complete_) &&
-               // and if the end of the data hasn't been reached yet
-               (!((samples_decoded_ >= sample_count_) && (session_.get_capture_state() == Session::Stopped))),
-               sample_count_);
-}
-
-void DecoderStack::decode_data(
-       const int64_t abs_start_samplenum, const int64_t sample_count, const unsigned int unit_size,
-       srd_session *const session)
-{
-       const unsigned int chunk_sample_count =
-               DecodeChunkLength / segment_->unit_size();
-
-       for (int64_t i = abs_start_samplenum; !interrupt_ && i < sample_count;
-                       i += chunk_sample_count) {
-
-               const int64_t chunk_end = min(
-                       i + chunk_sample_count, sample_count);
-               const uint8_t* chunk = segment_->get_samples(i, chunk_end);
-
-               if (srd_session_send(session, i, chunk_end, chunk,
-                               (chunk_end - i) * unit_size, unit_size) != SRD_OK) {
-                       error_message_ = tr("Decoder reported an error");
-                       delete[] chunk;
-                       break;
-               }
-               delete[] chunk;
-
-               {
-                       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 = nullptr;
-
-       assert(segment_);
-
-       // Prevent any other decode threads from accessing libsigrokdecode
-       lock_guard<mutex> srd_lock(global_srd_mutex_);
-
-       // Create the session
-       srd_session_new(&session);
-       assert(session);
-
-       // Create the decoders
-       const unsigned int unit_size = segment_->unit_size();
-
-       for (const shared_ptr<decode::Decoder> &dec : stack_) {
-               srd_decoder_inst *const di = dec->create_decoder_inst(session);
-
-               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_ = segment_->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);
-
-       int64_t abs_start_samplenum = 0;
-       do {
-               decode_data(abs_start_samplenum, *sample_count, unit_size, session);
-               abs_start_samplenum = *sample_count;
-       } 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);
-
-       auto row_iter = d->rows_.end();
-
-       // Try looking up the sub-row of this class
-       const auto 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(false);
-               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 (segment_)
-                       sample_count_ = segment_->get_sample_count();
-       }
-       input_cond_.notify_one();
-}
-
-void DecoderStack::on_frame_ended()
-{
-       {
-               unique_lock<mutex> lock(input_mutex_);
-               if (segment_)
-                       frame_complete_ = true;
-       }
-       input_cond_.notify_one();
-}
-
-} // namespace data
-} // namespace pv
diff --git a/pv/data/decoderstack.hpp b/pv/data/decoderstack.hpp
deleted file mode 100644 (file)
index 5216006..0000000
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * This file is part of the PulseView project.
- *
- * Copyright (C) 2012 Joel Holdsworth <joel@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, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef PULSEVIEW_PV_DATA_DECODERSTACK_HPP
-#define PULSEVIEW_PV_DATA_DECODERSTACK_HPP
-
-#include "signaldata.hpp"
-
-#include <atomic>
-#include <condition_variable>
-#include <list>
-#include <map>
-#include <memory>
-#include <thread>
-
-#include <boost/optional.hpp>
-
-#include <QObject>
-#include <QString>
-
-#include <pv/data/decode/row.hpp>
-#include <pv/data/decode/rowdata.hpp>
-#include <pv/util.hpp>
-
-using std::atomic;
-using std::condition_variable;
-using std::list;
-using std::map;
-using std::mutex;
-using std::pair;
-using std::shared_ptr;
-using std::vector;
-
-struct srd_decoder;
-struct srd_decoder_annotation_row;
-struct srd_channel;
-struct srd_proto_data;
-struct srd_session;
-
-namespace DecoderStackTest {
-struct TwoDecoderStack;
-}
-
-namespace pv {
-
-class Session;
-
-namespace view {
-class LogicSignal;
-}
-
-namespace data {
-
-class LogicSegment;
-
-namespace decode {
-class Annotation;
-class Decoder;
-}
-
-class Logic;
-
-class DecoderStack : public QObject
-{
-       Q_OBJECT
-
-private:
-       static const double DecodeMargin;
-       static const double DecodeThreshold;
-       static const int64_t DecodeChunkLength;
-       static const unsigned int DecodeNotifyPeriod;
-
-public:
-       DecoderStack(pv::Session &session, const srd_decoder *const dec);
-
-       virtual ~DecoderStack();
-
-       const list< shared_ptr<decode::Decoder> >& stack() const;
-       void push(shared_ptr<decode::Decoder> decoder);
-       void remove(int index);
-
-       double samplerate() const;
-
-       const pv::util::Timestamp& start_time() const;
-
-       int64_t samples_decoded() const;
-
-       vector<decode::Row> get_visible_rows() const;
-
-       /**
-        * Extracts sorted annotations between two period into a vector.
-        */
-       void get_annotation_subset(
-               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 max_sample_count() const;
-
-       void begin_decode();
-
-private:
-       boost::optional<int64_t> wait_for_data() const;
-
-       void decode_data(const int64_t abs_start_samplenum, 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 Q_SLOTS:
-       void on_new_frame();
-
-       void on_data_received();
-
-       void on_frame_ended();
-
-Q_SIGNALS:
-       void new_decode_data();
-
-private:
-       pv::Session &session_;
-
-       pv::util::Timestamp start_time_;
-       double samplerate_;
-
-       /**
-        * This mutex prevents more than one thread from accessing
-        * libsigrokdecode concurrently.
-        * @todo A proper solution should be implemented to allow multiple
-        * decode operations in parallel.
-        */
-       static mutex global_srd_mutex_;
-
-       list< shared_ptr<decode::Decoder> > stack_;
-
-       shared_ptr<pv::data::LogicSegment> segment_;
-
-       mutable mutex input_mutex_;
-       mutable condition_variable input_cond_;
-       int64_t sample_count_;
-       bool frame_complete_;
-
-       mutable mutex output_mutex_;
-       int64_t samples_decoded_;
-
-       map<const decode::Row, decode::RowData> rows_;
-
-       map<pair<const srd_decoder*, int>, decode::Row> class_rows_;
-
-       QString error_message_;
-
-       std::thread decode_thread_;
-       atomic<bool> interrupt_;
-
-       friend struct DecoderStackTest::TwoDecoderStack;
-};
-
-} // namespace data
-} // namespace pv
-
-#endif // PULSEVIEW_PV_DATA_DECODERSTACK_HPP
diff --git a/pv/data/decodesignal.cpp b/pv/data/decodesignal.cpp
new file mode 100644 (file)
index 0000000..68b17a6
--- /dev/null
@@ -0,0 +1,1317 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2017 Soeren Apel <soeren@apelpie.net>
+ *
+ * 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 <forward_list>
+#include <limits>
+
+#include <QDebug>
+
+#include "logic.hpp"
+#include "logicsegment.hpp"
+#include "decodesignal.hpp"
+#include "signaldata.hpp"
+
+#include <pv/binding/decoder.hpp>
+#include <pv/data/decode/decoder.hpp>
+#include <pv/data/decode/row.hpp>
+#include <pv/globalsettings.hpp>
+#include <pv/session.hpp>
+
+using std::forward_list;
+using std::lock_guard;
+using std::make_pair;
+using std::make_shared;
+using std::min;
+using std::out_of_range;
+using std::shared_ptr;
+using std::unique_lock;
+using pv::data::decode::Annotation;
+using pv::data::decode::Decoder;
+using pv::data::decode::Row;
+
+namespace pv {
+namespace data {
+
+const double DecodeSignal::DecodeMargin = 1.0;
+const double DecodeSignal::DecodeThreshold = 0.2;
+const int64_t DecodeSignal::DecodeChunkLength = 256 * 1024;
+
+
+DecodeSignal::DecodeSignal(pv::Session &session) :
+       SignalBase(nullptr, SignalBase::DecodeChannel),
+       session_(session),
+       srd_session_(nullptr),
+       logic_mux_data_invalid_(false),
+       stack_config_changed_(true),
+       current_segment_id_(0)
+{
+       connect(&session_, SIGNAL(capture_state_changed(int)),
+               this, SLOT(on_capture_state_changed(int)));
+}
+
+DecodeSignal::~DecodeSignal()
+{
+       reset_decode(true);
+}
+
+const vector< shared_ptr<Decoder> >& DecodeSignal::decoder_stack() const
+{
+       return stack_;
+}
+
+void DecodeSignal::stack_decoder(const srd_decoder *decoder)
+{
+       assert(decoder);
+       const shared_ptr<Decoder> dec = make_shared<decode::Decoder>(decoder);
+
+       stack_.push_back(dec);
+
+       // Set name if this decoder is the first in the list
+       if (stack_.size() == 1)
+               set_name(QString::fromUtf8(decoder->name));
+
+       // Include the newly created decode channels in the channel lists
+       update_channel_list();
+
+       stack_config_changed_ = true;
+       auto_assign_signals(dec);
+       commit_decoder_channels();
+       begin_decode();
+}
+
+void DecodeSignal::remove_decoder(int index)
+{
+       assert(index >= 0);
+       assert(index < (int)stack_.size());
+
+       // Find the decoder in the stack
+       auto iter = stack_.begin();
+       for (int i = 0; i < index; i++, iter++)
+               assert(iter != stack_.end());
+
+       // Delete the element
+       stack_.erase(iter);
+
+       // Update channels and decoded data
+       stack_config_changed_ = true;
+       update_channel_list();
+       begin_decode();
+}
+
+bool DecodeSignal::toggle_decoder_visibility(int index)
+{
+       auto iter = stack_.cbegin();
+       for (int i = 0; i < index; i++, iter++)
+               assert(iter != stack_.end());
+
+       shared_ptr<Decoder> dec = *iter;
+
+       // Toggle decoder visibility
+       bool state = false;
+       if (dec) {
+               state = !dec->shown();
+               dec->show(state);
+       }
+
+       return state;
+}
+
+void DecodeSignal::reset_decode(bool shutting_down)
+{
+       if (stack_config_changed_ || shutting_down)
+               stop_srd_session();
+       else
+               terminate_srd_session();
+
+       if (decode_thread_.joinable()) {
+               decode_interrupt_ = true;
+               decode_input_cond_.notify_one();
+               decode_thread_.join();
+       }
+
+       if (logic_mux_thread_.joinable()) {
+               logic_mux_interrupt_ = true;
+               logic_mux_cond_.notify_one();
+               logic_mux_thread_.join();
+       }
+
+       resume_decode();  // Make sure the decode thread isn't blocked by pausing
+
+       class_rows_.clear();
+       current_segment_id_ = 0;
+       segments_.clear();
+
+       logic_mux_data_.reset();
+       logic_mux_data_invalid_ = true;
+
+       if (!error_message_.isEmpty()) {
+               error_message_ = QString();
+               // TODO Emulate noquote()
+               qDebug().nospace() << name() << ": Error cleared";
+       }
+
+       decode_reset();
+}
+
+void DecodeSignal::begin_decode()
+{
+       if (decode_thread_.joinable()) {
+               decode_interrupt_ = true;
+               decode_input_cond_.notify_one();
+               decode_thread_.join();
+       }
+
+       if (logic_mux_thread_.joinable()) {
+               logic_mux_interrupt_ = true;
+               logic_mux_cond_.notify_one();
+               logic_mux_thread_.join();
+       }
+
+       reset_decode();
+
+       if (stack_.size() == 0) {
+               set_error_message(tr("No decoders"));
+               return;
+       }
+
+       assert(channels_.size() > 0);
+
+       if (get_assigned_signal_count() == 0) {
+               set_error_message(tr("There are no channels assigned to this decoder"));
+               return;
+       }
+
+       // Make sure that all assigned channels still provide logic data
+       // (can happen when a converted signal was assigned but the
+       // conversion removed in the meanwhile)
+       for (data::DecodeChannel& ch : channels_)
+               if (ch.assigned_signal && !(ch.assigned_signal->logic_data() != nullptr))
+                       ch.assigned_signal = nullptr;
+
+       // Check that all decoders have the required channels
+       for (const shared_ptr<decode::Decoder>& dec : stack_)
+               if (!dec->have_required_channels()) {
+                       set_error_message(tr("One or more required channels "
+                               "have not been specified"));
+                       return;
+               }
+
+       // Map out all the annotation classes
+       int row_index = 0;
+       for (const shared_ptr<decode::Decoder>& dec : stack_) {
+               assert(dec);
+               const srd_decoder *const decc = dec->decoder();
+               assert(dec->decoder());
+
+               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(row_index++, decc, ann_row);
+
+                       for (const GSList *ll = ann_row->ann_classes;
+                               ll; ll = ll->next)
+                               class_rows_[make_pair(decc,
+                                       GPOINTER_TO_INT(ll->data))] = row;
+               }
+       }
+
+       // Free the logic data and its segment(s) if it needs to be updated
+       if (logic_mux_data_invalid_)
+               logic_mux_data_.reset();
+
+       if (!logic_mux_data_) {
+               const uint32_t ch_count = get_assigned_signal_count();
+               logic_mux_unit_size_ = (ch_count + 7) / 8;
+               logic_mux_data_ = make_shared<Logic>(ch_count);
+       }
+
+       // Receive notifications when new sample data is available
+       connect_input_notifiers();
+
+       if (get_input_segment_count() == 0) {
+               set_error_message(tr("No input data"));
+               return;
+       }
+
+       // Make sure the logic output data is complete and up-to-date
+       logic_mux_interrupt_ = false;
+       logic_mux_thread_ = std::thread(&DecodeSignal::logic_mux_proc, this);
+
+       // Decode the muxed logic data
+       decode_interrupt_ = false;
+       decode_thread_ = std::thread(&DecodeSignal::decode_proc, this);
+}
+
+void DecodeSignal::pause_decode()
+{
+       decode_paused_ = true;
+}
+
+void DecodeSignal::resume_decode()
+{
+       // Manual unlocking is done before notifying, to avoid waking up the
+       // waiting thread only to block again (see notify_one for details)
+       decode_pause_mutex_.unlock();
+       decode_pause_cond_.notify_one();
+       decode_paused_ = false;
+}
+
+bool DecodeSignal::is_paused() const
+{
+       return decode_paused_;
+}
+
+QString DecodeSignal::error_message() const
+{
+       lock_guard<mutex> lock(output_mutex_);
+       return error_message_;
+}
+
+const vector<data::DecodeChannel> DecodeSignal::get_channels() const
+{
+       return channels_;
+}
+
+void DecodeSignal::auto_assign_signals(const shared_ptr<Decoder> dec)
+{
+       bool new_assignment = false;
+
+       // Try to auto-select channels that don't have signals assigned yet
+       for (data::DecodeChannel& ch : channels_) {
+               // If a decoder is given, auto-assign only its channels
+               if (dec && (ch.decoder_ != dec))
+                       continue;
+
+               if (ch.assigned_signal)
+                       continue;
+
+               QString ch_name = ch.name.toLower();
+               ch_name = ch_name.replace(QRegExp("[-_.]"), " ");
+
+               shared_ptr<data::SignalBase> match;
+               for (const shared_ptr<data::SignalBase>& s : session_.signalbases()) {
+                       if (!s->enabled())
+                               continue;
+
+                       QString s_name = s->name().toLower();
+                       s_name = s_name.replace(QRegExp("[-_.]"), " ");
+
+                       if (s->logic_data() &&
+                               ((ch_name.contains(s_name)) || (s_name.contains(ch_name)))) {
+                               if (!match)
+                                       match = s;
+                               else {
+                                       // Only replace an existing match if it matches more characters
+                                       int old_unmatched = ch_name.length() - match->name().length();
+                                       int new_unmatched = ch_name.length() - s->name().length();
+                                       if (abs(new_unmatched) < abs(old_unmatched))
+                                               match = s;
+                               }
+                       }
+               }
+
+               if (match) {
+                       ch.assigned_signal = match.get();
+                       new_assignment = true;
+               }
+       }
+
+       if (new_assignment) {
+               logic_mux_data_invalid_ = true;
+               stack_config_changed_ = true;
+               commit_decoder_channels();
+               channels_updated();
+       }
+}
+
+void DecodeSignal::assign_signal(const uint16_t channel_id, const SignalBase *signal)
+{
+       for (data::DecodeChannel& ch : channels_)
+               if (ch.id == channel_id) {
+                       ch.assigned_signal = signal;
+                       logic_mux_data_invalid_ = true;
+               }
+
+       stack_config_changed_ = true;
+       commit_decoder_channels();
+       channels_updated();
+       begin_decode();
+}
+
+int DecodeSignal::get_assigned_signal_count() const
+{
+       // Count all channels that have a signal assigned to them
+       return count_if(channels_.begin(), channels_.end(),
+               [](data::DecodeChannel ch) { return ch.assigned_signal; });
+}
+
+void DecodeSignal::set_initial_pin_state(const uint16_t channel_id, const int init_state)
+{
+       for (data::DecodeChannel& ch : channels_)
+               if (ch.id == channel_id)
+                       ch.initial_pin_state = init_state;
+
+       stack_config_changed_ = true;
+       channels_updated();
+       begin_decode();
+}
+
+double DecodeSignal::samplerate() const
+{
+       double result = 0;
+
+       // TODO For now, we simply return the first samplerate that we have
+       if (segments_.size() > 0)
+               result = segments_.front().samplerate;
+
+       return result;
+}
+
+const pv::util::Timestamp DecodeSignal::start_time() const
+{
+       pv::util::Timestamp result;
+
+       // TODO For now, we simply return the first start time that we have
+       if (segments_.size() > 0)
+               result = segments_.front().start_time;
+
+       return result;
+}
+
+int64_t DecodeSignal::get_working_sample_count(uint32_t segment_id) const
+{
+       // The working sample count is the highest sample number for
+       // which all used signals have data available, so go through all
+       // channels and use the lowest overall sample count of the segment
+
+       int64_t count = std::numeric_limits<int64_t>::max();
+       bool no_signals_assigned = true;
+
+       for (const data::DecodeChannel& ch : channels_)
+               if (ch.assigned_signal) {
+                       no_signals_assigned = false;
+
+                       const shared_ptr<Logic> logic_data = ch.assigned_signal->logic_data();
+                       if (!logic_data || logic_data->logic_segments().empty())
+                               return 0;
+
+                       try {
+                               const shared_ptr<LogicSegment> segment = logic_data->logic_segments().at(segment_id);
+                               count = min(count, (int64_t)segment->get_sample_count());
+                       } catch (out_of_range&) {
+                               return 0;
+                       }
+               }
+
+       return (no_signals_assigned ? 0 : count);
+}
+
+int64_t DecodeSignal::get_decoded_sample_count(uint32_t segment_id,
+       bool include_processing) const
+{
+       lock_guard<mutex> decode_lock(output_mutex_);
+
+       int64_t result = 0;
+
+       try {
+               const DecodeSegment *segment = &(segments_.at(segment_id));
+               if (include_processing)
+                       result = segment->samples_decoded_incl;
+               else
+                       result = segment->samples_decoded_excl;
+       } catch (out_of_range&) {
+               // Do nothing
+       }
+
+       return result;
+}
+
+vector<Row> DecodeSignal::visible_rows() const
+{
+       lock_guard<mutex> lock(output_mutex_);
+
+       vector<Row> rows;
+
+       for (const shared_ptr<decode::Decoder>& dec : stack_) {
+               assert(dec);
+               if (!dec->shown())
+                       continue;
+
+               const srd_decoder *const decc = dec->decoder();
+               assert(dec->decoder());
+
+               int row_index = 0;
+               // Add a row for the decoder if it doesn't have a row list
+               if (!decc->annotation_rows)
+                       rows.emplace_back(row_index++, 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.emplace_back(row_index++, decc, ann_row);
+               }
+       }
+
+       return rows;
+}
+
+void DecodeSignal::get_annotation_subset(
+       vector<pv::data::decode::Annotation> &dest,
+       const decode::Row &row, uint32_t segment_id, uint64_t start_sample,
+       uint64_t end_sample) const
+{
+       lock_guard<mutex> lock(output_mutex_);
+
+       try {
+               const DecodeSegment *segment = &(segments_.at(segment_id));
+               const map<const decode::Row, decode::RowData> *rows =
+                       &(segment->annotation_rows);
+
+               const auto iter = rows->find(row);
+               if (iter != rows->end())
+                       (*iter).second.get_annotation_subset(dest,
+                               start_sample, end_sample);
+       } catch (out_of_range&) {
+               // Do nothing
+       }
+}
+
+void DecodeSignal::get_annotation_subset(
+       vector<pv::data::decode::Annotation> &dest,
+       uint32_t segment_id, uint64_t start_sample, uint64_t end_sample) const
+{
+       // Note: We put all vectors and lists on the heap, not the stack
+
+       const vector<Row> rows = visible_rows();
+
+       // Use forward_lists for faster merging
+       forward_list<Annotation> *all_ann_list = new forward_list<Annotation>();
+
+       for (const Row& row : rows) {
+               vector<Annotation> *ann_vector = new vector<Annotation>();
+               get_annotation_subset(*ann_vector, row, segment_id, start_sample, end_sample);
+
+               forward_list<Annotation> *ann_list =
+                       new forward_list<Annotation>(ann_vector->begin(), ann_vector->end());
+               delete ann_vector;
+
+               all_ann_list->merge(*ann_list);
+               delete ann_list;
+       }
+
+       move(all_ann_list->begin(), all_ann_list->end(), back_inserter(dest));
+       delete all_ann_list;
+}
+
+void DecodeSignal::save_settings(QSettings &settings) const
+{
+       SignalBase::save_settings(settings);
+
+       settings.setValue("decoders", (int)(stack_.size()));
+
+       // Save decoder stack
+       int decoder_idx = 0;
+       for (const shared_ptr<decode::Decoder>& decoder : stack_) {
+               settings.beginGroup("decoder" + QString::number(decoder_idx++));
+
+               settings.setValue("id", decoder->decoder()->id);
+
+               // Save decoder options
+               const map<string, GVariant*>& options = decoder->options();
+
+               settings.setValue("options", (int)options.size());
+
+               // Note: decode::Decoder::options() returns only the options
+               // that differ from the default. See binding::Decoder::getter()
+               int i = 0;
+               for (auto& option : options) {
+                       settings.beginGroup("option" + QString::number(i));
+                       settings.setValue("name", QString::fromStdString(option.first));
+                       GlobalSettings::store_gvariant(settings, option.second);
+                       settings.endGroup();
+                       i++;
+               }
+
+               settings.endGroup();
+       }
+
+       // Save channel mapping
+       settings.setValue("channels", (int)channels_.size());
+
+       for (unsigned int channel_id = 0; channel_id < channels_.size(); channel_id++) {
+               auto channel = find_if(channels_.begin(), channels_.end(),
+                       [&](data::DecodeChannel ch) { return ch.id == channel_id; });
+
+               if (channel == channels_.end()) {
+                       qDebug() << "ERROR: Gap in channel index:" << channel_id;
+                       continue;
+               }
+
+               settings.beginGroup("channel" + QString::number(channel_id));
+
+               settings.setValue("name", channel->name);  // Useful for debugging
+               settings.setValue("initial_pin_state", channel->initial_pin_state);
+
+               if (channel->assigned_signal)
+                       settings.setValue("assigned_signal_name", channel->assigned_signal->name());
+
+               settings.endGroup();
+       }
+}
+
+void DecodeSignal::restore_settings(QSettings &settings)
+{
+       SignalBase::restore_settings(settings);
+
+       // Restore decoder stack
+       GSList *dec_list = g_slist_copy((GSList*)srd_decoder_list());
+
+       int decoders = settings.value("decoders").toInt();
+
+       for (int decoder_idx = 0; decoder_idx < decoders; decoder_idx++) {
+               settings.beginGroup("decoder" + QString::number(decoder_idx));
+
+               QString id = settings.value("id").toString();
+
+               for (GSList *entry = dec_list; entry; entry = entry->next) {
+                       const srd_decoder *dec = (srd_decoder*)entry->data;
+                       if (!dec)
+                               continue;
+
+                       if (QString::fromUtf8(dec->id) == id) {
+                               shared_ptr<decode::Decoder> decoder =
+                                       make_shared<decode::Decoder>(dec);
+
+                               stack_.push_back(decoder);
+
+                               // Restore decoder options that differ from their default
+                               int options = settings.value("options").toInt();
+
+                               for (int i = 0; i < options; i++) {
+                                       settings.beginGroup("option" + QString::number(i));
+                                       QString name = settings.value("name").toString();
+                                       GVariant *value = GlobalSettings::restore_gvariant(settings);
+                                       decoder->set_option(name.toUtf8(), value);
+                                       settings.endGroup();
+                               }
+
+                               // Include the newly created decode channels in the channel lists
+                               update_channel_list();
+                               break;
+                       }
+               }
+
+               settings.endGroup();
+               channels_updated();
+       }
+
+       // Restore channel mapping
+       unsigned int channels = settings.value("channels").toInt();
+
+       const unordered_set< shared_ptr<data::SignalBase> > signalbases =
+               session_.signalbases();
+
+       for (unsigned int channel_id = 0; channel_id < channels; channel_id++) {
+               auto channel = find_if(channels_.begin(), channels_.end(),
+                       [&](data::DecodeChannel ch) { return ch.id == channel_id; });
+
+               if (channel == channels_.end()) {
+                       qDebug() << "ERROR: Non-existant channel index:" << channel_id;
+                       continue;
+               }
+
+               settings.beginGroup("channel" + QString::number(channel_id));
+
+               QString assigned_signal_name = settings.value("assigned_signal_name").toString();
+
+               for (const shared_ptr<data::SignalBase>& signal : signalbases)
+                       if (signal->name() == assigned_signal_name)
+                               channel->assigned_signal = signal.get();
+
+               channel->initial_pin_state = settings.value("initial_pin_state").toInt();
+
+               settings.endGroup();
+       }
+
+       // Update the internal structures
+       stack_config_changed_ = true;
+       update_channel_list();
+       commit_decoder_channels();
+
+       begin_decode();
+}
+
+void DecodeSignal::set_error_message(QString msg)
+{
+       error_message_ = msg;
+       // TODO Emulate noquote()
+       qDebug().nospace() << name() << ": " << msg;
+}
+
+uint32_t DecodeSignal::get_input_segment_count() const
+{
+       uint64_t count = std::numeric_limits<uint64_t>::max();
+       bool no_signals_assigned = true;
+
+       for (const data::DecodeChannel& ch : channels_)
+               if (ch.assigned_signal) {
+                       no_signals_assigned = false;
+
+                       const shared_ptr<Logic> logic_data = ch.assigned_signal->logic_data();
+                       if (!logic_data || logic_data->logic_segments().empty())
+                               return 0;
+
+                       // Find the min value of all segment counts
+                       if ((uint64_t)(logic_data->logic_segments().size()) < count)
+                               count = logic_data->logic_segments().size();
+               }
+
+       return (no_signals_assigned ? 0 : count);
+}
+
+uint32_t DecodeSignal::get_input_samplerate(uint32_t segment_id) const
+{
+       double samplerate = 0;
+
+       for (const data::DecodeChannel& ch : channels_)
+               if (ch.assigned_signal) {
+                       const shared_ptr<Logic> logic_data = ch.assigned_signal->logic_data();
+                       if (!logic_data || logic_data->logic_segments().empty())
+                               continue;
+
+                       try {
+                               const shared_ptr<LogicSegment> segment = logic_data->logic_segments().at(segment_id);
+                               samplerate = segment->samplerate();
+                       } catch (out_of_range&) {
+                               // Do nothing
+                       }
+                       break;
+               }
+
+       return samplerate;
+}
+
+void DecodeSignal::update_channel_list()
+{
+       vector<data::DecodeChannel> prev_channels = channels_;
+       channels_.clear();
+
+       uint16_t id = 0;
+
+       // Copy existing entries, create new as needed
+       for (shared_ptr<Decoder>& decoder : stack_) {
+               const srd_decoder* srd_d = decoder->decoder();
+               const GSList *l;
+
+               // Mandatory channels
+               for (l = srd_d->channels; l; l = l->next) {
+                       const struct srd_channel *const pdch = (struct srd_channel *)l->data;
+                       bool ch_added = false;
+
+                       // Copy but update ID if this channel was in the list before
+                       for (data::DecodeChannel& ch : prev_channels)
+                               if (ch.pdch_ == pdch) {
+                                       ch.id = id++;
+                                       channels_.push_back(ch);
+                                       ch_added = true;
+                                       break;
+                               }
+
+                       if (!ch_added) {
+                               // Create new entry without a mapped signal
+                               data::DecodeChannel ch = {id++, 0, false, nullptr,
+                                       QString::fromUtf8(pdch->name), QString::fromUtf8(pdch->desc),
+                                       SRD_INITIAL_PIN_SAME_AS_SAMPLE0, decoder, pdch};
+                               channels_.push_back(ch);
+                       }
+               }
+
+               // Optional channels
+               for (l = srd_d->opt_channels; l; l = l->next) {
+                       const struct srd_channel *const pdch = (struct srd_channel *)l->data;
+                       bool ch_added = false;
+
+                       // Copy but update ID if this channel was in the list before
+                       for (data::DecodeChannel& ch : prev_channels)
+                               if (ch.pdch_ == pdch) {
+                                       ch.id = id++;
+                                       channels_.push_back(ch);
+                                       ch_added = true;
+                                       break;
+                               }
+
+                       if (!ch_added) {
+                               // Create new entry without a mapped signal
+                               data::DecodeChannel ch = {id++, 0, true, nullptr,
+                                       QString::fromUtf8(pdch->name), QString::fromUtf8(pdch->desc),
+                                       SRD_INITIAL_PIN_SAME_AS_SAMPLE0, decoder, pdch};
+                               channels_.push_back(ch);
+                       }
+               }
+       }
+
+       // Invalidate the logic output data if the channel assignment changed
+       if (prev_channels.size() != channels_.size()) {
+               // The number of channels changed, there's definitely a difference
+               logic_mux_data_invalid_ = true;
+       } else {
+               // Same number but assignment may still differ, so compare all channels
+               for (size_t i = 0; i < channels_.size(); i++) {
+                       const data::DecodeChannel& p_ch = prev_channels[i];
+                       const data::DecodeChannel& ch = channels_[i];
+
+                       if ((p_ch.pdch_ != ch.pdch_) ||
+                               (p_ch.assigned_signal != ch.assigned_signal)) {
+                               logic_mux_data_invalid_ = true;
+                               break;
+                       }
+               }
+
+       }
+
+       channels_updated();
+}
+
+void DecodeSignal::commit_decoder_channels()
+{
+       // Submit channel list to every decoder, containing only the relevant channels
+       for (shared_ptr<decode::Decoder> dec : stack_) {
+               vector<data::DecodeChannel*> channel_list;
+
+               for (data::DecodeChannel& ch : channels_)
+                       if (ch.decoder_ == dec)
+                               channel_list.push_back(&ch);
+
+               dec->set_channels(channel_list);
+       }
+
+       // Channel bit IDs must be in sync with the channel's apperance in channels_
+       int id = 0;
+       for (data::DecodeChannel& ch : channels_)
+               if (ch.assigned_signal)
+                       ch.bit_id = id++;
+}
+
+void DecodeSignal::mux_logic_samples(uint32_t segment_id, const int64_t start, const int64_t end)
+{
+       // Enforce end to be greater than start
+       if (end <= start)
+               return;
+
+       // Fetch the channel segments and their data
+       vector<shared_ptr<LogicSegment> > segments;
+       vector<const uint8_t*> signal_data;
+       vector<uint8_t> signal_in_bytepos;
+       vector<uint8_t> signal_in_bitpos;
+
+       for (data::DecodeChannel& ch : channels_)
+               if (ch.assigned_signal) {
+                       const shared_ptr<Logic> logic_data = ch.assigned_signal->logic_data();
+
+                       shared_ptr<LogicSegment> segment;
+                       try {
+                               segment = logic_data->logic_segments().at(segment_id);
+                       } catch (out_of_range&) {
+                               qDebug() << "Muxer error for" << name() << ":" << ch.assigned_signal->name() \
+                                       << "has no logic segment" << segment_id;
+                               return;
+                       }
+                       segments.push_back(segment);
+
+                       uint8_t* data = new uint8_t[(end - start) * segment->unit_size()];
+                       segment->get_samples(start, end, data);
+                       signal_data.push_back(data);
+
+                       const int bitpos = ch.assigned_signal->logic_bit_index();
+                       signal_in_bytepos.push_back(bitpos / 8);
+                       signal_in_bitpos.push_back(bitpos % 8);
+               }
+
+
+       shared_ptr<LogicSegment> output_segment;
+       try {
+               output_segment = logic_mux_data_->logic_segments().at(segment_id);
+       } catch (out_of_range&) {
+               qDebug() << "Muxer error for" << name() << ": no logic mux segment" \
+                       << segment_id << "in mux_logic_samples(), mux segments size is" \
+                       << logic_mux_data_->logic_segments().size();
+               return;
+       }
+
+       // Perform the muxing of signal data into the output data
+       uint8_t* output = new uint8_t[(end - start) * output_segment->unit_size()];
+       unsigned int signal_count = signal_data.size();
+
+       for (int64_t sample_cnt = 0; !logic_mux_interrupt_ && (sample_cnt < (end - start));
+               sample_cnt++) {
+
+               int bitpos = 0;
+               uint8_t bytepos = 0;
+
+               const int out_sample_pos = sample_cnt * output_segment->unit_size();
+               for (unsigned int i = 0; i < output_segment->unit_size(); i++)
+                       output[out_sample_pos + i] = 0;
+
+               for (unsigned int i = 0; i < signal_count; i++) {
+                       const int in_sample_pos = sample_cnt * segments[i]->unit_size();
+                       const uint8_t in_sample = 1 &
+                               ((signal_data[i][in_sample_pos + signal_in_bytepos[i]]) >> (signal_in_bitpos[i]));
+
+                       const uint8_t out_sample = output[out_sample_pos + bytepos];
+
+                       output[out_sample_pos + bytepos] = out_sample | (in_sample << bitpos);
+
+                       bitpos++;
+                       if (bitpos > 7) {
+                               bitpos = 0;
+                               bytepos++;
+                       }
+               }
+       }
+
+       output_segment->append_payload(output, (end - start) * output_segment->unit_size());
+       delete[] output;
+
+       for (const uint8_t* data : signal_data)
+               delete[] data;
+}
+
+void DecodeSignal::logic_mux_proc()
+{
+       uint32_t segment_id = 0;
+
+       assert(logic_mux_data_);
+
+       // Create initial logic mux segment
+       shared_ptr<LogicSegment> output_segment =
+               make_shared<LogicSegment>(*logic_mux_data_, segment_id,
+                       logic_mux_unit_size_, 0);
+       logic_mux_data_->push_segment(output_segment);
+
+       output_segment->set_samplerate(get_input_samplerate(0));
+
+       do {
+               const uint64_t input_sample_count = get_working_sample_count(segment_id);
+               const uint64_t output_sample_count = output_segment->get_sample_count();
+
+               const uint64_t samples_to_process =
+                       (input_sample_count > output_sample_count) ?
+                       (input_sample_count - output_sample_count) : 0;
+
+               // Process the samples if necessary...
+               if (samples_to_process > 0) {
+                       const uint64_t unit_size = output_segment->unit_size();
+                       const uint64_t chunk_sample_count = DecodeChunkLength / unit_size;
+
+                       uint64_t processed_samples = 0;
+                       do {
+                               const uint64_t start_sample = output_sample_count + processed_samples;
+                               const uint64_t sample_count =
+                                       min(samples_to_process - processed_samples,     chunk_sample_count);
+
+                               mux_logic_samples(segment_id, start_sample, start_sample + sample_count);
+                               processed_samples += sample_count;
+
+                               // ...and process the newly muxed logic data
+                               decode_input_cond_.notify_one();
+                       } while (!logic_mux_interrupt_ && (processed_samples < samples_to_process));
+               }
+
+               if (samples_to_process == 0) {
+                       // TODO Optimize this by caching the input segment count and only
+                       // querying it when the cached value was reached
+                       if (segment_id < get_input_segment_count() - 1) {
+                               // Process next segment
+                               segment_id++;
+
+                               output_segment =
+                                       make_shared<LogicSegment>(*logic_mux_data_, segment_id,
+                                               logic_mux_unit_size_, 0);
+                               logic_mux_data_->push_segment(output_segment);
+
+                               output_segment->set_samplerate(get_input_samplerate(segment_id));
+
+                       } else {
+                               // All segments have been processed
+                               logic_mux_data_invalid_ = false;
+
+                               // Wait for more input
+                               unique_lock<mutex> logic_mux_lock(logic_mux_mutex_);
+                               logic_mux_cond_.wait(logic_mux_lock);
+                       }
+               }
+
+       } while (!logic_mux_interrupt_);
+}
+
+void DecodeSignal::decode_data(
+       const int64_t abs_start_samplenum, const int64_t sample_count,
+       const shared_ptr<LogicSegment> input_segment)
+{
+       const int64_t unit_size = input_segment->unit_size();
+       const int64_t chunk_sample_count = DecodeChunkLength / unit_size;
+
+       for (int64_t i = abs_start_samplenum;
+               error_message_.isEmpty() && !decode_interrupt_ &&
+                       (i < (abs_start_samplenum + sample_count));
+               i += chunk_sample_count) {
+
+               const int64_t chunk_end = min(i + chunk_sample_count,
+                       abs_start_samplenum + sample_count);
+
+               {
+                       lock_guard<mutex> lock(output_mutex_);
+                       // Update the sample count showing the samples including currently processed ones
+                       segments_.at(current_segment_id_).samples_decoded_incl = chunk_end;
+               }
+
+               int64_t data_size = (chunk_end - i) * unit_size;
+               uint8_t* chunk = new uint8_t[data_size];
+               input_segment->get_samples(i, chunk_end, chunk);
+
+               if (srd_session_send(srd_session_, i, chunk_end, chunk,
+                               data_size, unit_size) != SRD_OK)
+                       set_error_message(tr("Decoder reported an error"));
+
+               delete[] chunk;
+
+               {
+                       lock_guard<mutex> lock(output_mutex_);
+                       // Now that all samples are processed, the exclusive sample count catches up
+                       segments_.at(current_segment_id_).samples_decoded_excl = chunk_end;
+               }
+
+               // Notify the frontend that we processed some data and
+               // possibly have new annotations as well
+               new_annotations();
+
+               if (decode_paused_) {
+                       unique_lock<mutex> pause_wait_lock(decode_pause_mutex_);
+                       decode_pause_cond_.wait(pause_wait_lock);
+               }
+       }
+}
+
+void DecodeSignal::decode_proc()
+{
+       current_segment_id_ = 0;
+
+       // If there is no input data available yet, wait until it is or we're interrupted
+       if (logic_mux_data_->logic_segments().size() == 0) {
+               unique_lock<mutex> input_wait_lock(input_mutex_);
+               decode_input_cond_.wait(input_wait_lock);
+       }
+
+       if (decode_interrupt_)
+               return;
+
+       shared_ptr<LogicSegment> input_segment = logic_mux_data_->logic_segments().front();
+       assert(input_segment);
+
+       // Create the initial segment and set its sample rate so that we can pass it to SRD
+       create_decode_segment();
+       segments_.at(current_segment_id_).samplerate = input_segment->samplerate();
+       segments_.at(current_segment_id_).start_time = input_segment->start_time();
+
+       start_srd_session();
+
+       uint64_t sample_count = 0;
+       uint64_t abs_start_samplenum = 0;
+       do {
+               // Keep processing new samples until we exhaust the input data
+               do {
+                       lock_guard<mutex> input_lock(input_mutex_);
+                       sample_count = input_segment->get_sample_count() - abs_start_samplenum;
+
+                       if (sample_count > 0) {
+                               decode_data(abs_start_samplenum, sample_count, input_segment);
+                               abs_start_samplenum += sample_count;
+                       }
+               } while (error_message_.isEmpty() && (sample_count > 0) && !decode_interrupt_);
+
+               if (error_message_.isEmpty() && !decode_interrupt_ && sample_count == 0) {
+                       if (current_segment_id_ < logic_mux_data_->logic_segments().size() - 1) {
+                               // Process next segment
+                               current_segment_id_++;
+
+                               try {
+                                       input_segment = logic_mux_data_->logic_segments().at(current_segment_id_);
+                               } catch (out_of_range&) {
+                                       qDebug() << "Decode error for" << name() << ": no logic mux segment" \
+                                               << current_segment_id_ << "in decode_proc(), mux segments size is" \
+                                               << logic_mux_data_->logic_segments().size();
+                                       return;
+                               }
+                               abs_start_samplenum = 0;
+
+                               // Create the next segment and set its metadata
+                               create_decode_segment();
+                               segments_.at(current_segment_id_).samplerate = input_segment->samplerate();
+                               segments_.at(current_segment_id_).start_time = input_segment->start_time();
+
+                               // Reset decoder state but keep the decoder stack intact
+                               terminate_srd_session();
+                       } else {
+                               // All segments have been processed
+                               decode_finished();
+
+                               // Wait for new input data or an interrupt was requested
+                               unique_lock<mutex> input_wait_lock(input_mutex_);
+                               decode_input_cond_.wait(input_wait_lock);
+                       }
+               }
+       } while (error_message_.isEmpty() && !decode_interrupt_);
+
+       // Potentially reap decoders when the application no longer is
+       // interested in their (pending) results.
+       if (decode_interrupt_)
+               terminate_srd_session();
+}
+
+void DecodeSignal::start_srd_session()
+{
+       // If there were stack changes, the session has been destroyed by now, so if
+       // it hasn't been destroyed, we can just reset and re-use it
+       if (srd_session_) {
+               // When a decoder stack was created before, re-use it
+               // for the next stream of input data, after terminating
+               // potentially still executing operations, and resetting
+               // internal state. Skip the rather expensive (teardown
+               // and) construction of another decoder stack.
+
+               // TODO Reduce redundancy, use a common code path for
+               // the meta/start sequence?
+               terminate_srd_session();
+
+               // Metadata is cleared also, so re-set it
+               uint64_t samplerate = 0;
+               if (segments_.size() > 0)
+                       samplerate = segments_.at(current_segment_id_).samplerate;
+               if (samplerate)
+                       srd_session_metadata_set(srd_session_, SRD_CONF_SAMPLERATE,
+                               g_variant_new_uint64(samplerate));
+               for (const shared_ptr<decode::Decoder>& dec : stack_)
+                       dec->apply_all_options();
+               srd_session_start(srd_session_);
+
+               return;
+       }
+
+       // Create the session
+       srd_session_new(&srd_session_);
+       assert(srd_session_);
+
+       // Create the decoders
+       srd_decoder_inst *prev_di = nullptr;
+       for (const shared_ptr<decode::Decoder>& dec : stack_) {
+               srd_decoder_inst *const di = dec->create_decoder_inst(srd_session_);
+
+               if (!di) {
+                       set_error_message(tr("Failed to create decoder instance"));
+                       srd_session_destroy(srd_session_);
+                       srd_session_ = nullptr;
+                       return;
+               }
+
+               if (prev_di)
+                       srd_inst_stack(srd_session_, prev_di, di);
+
+               prev_di = di;
+       }
+
+       // Start the session
+       if (segments_.size() > 0)
+               srd_session_metadata_set(srd_session_, SRD_CONF_SAMPLERATE,
+                       g_variant_new_uint64(segments_.at(current_segment_id_).samplerate));
+
+       srd_pd_output_callback_add(srd_session_, SRD_OUTPUT_ANN,
+               DecodeSignal::annotation_callback, this);
+
+       srd_session_start(srd_session_);
+
+       // We just recreated the srd session, so all stack changes are applied now
+       stack_config_changed_ = false;
+}
+
+void DecodeSignal::terminate_srd_session()
+{
+       // Call the "terminate and reset" routine for the decoder stack
+       // (if available). This does not harm those stacks which already
+       // have completed their operation, and reduces response time for
+       // those stacks which still are processing data while the
+       // application no longer wants them to.
+       if (srd_session_) {
+               srd_session_terminate_reset(srd_session_);
+
+               // Metadata is cleared also, so re-set it
+               uint64_t samplerate = 0;
+               if (segments_.size() > 0)
+                       samplerate = segments_.at(current_segment_id_).samplerate;
+               if (samplerate)
+                       srd_session_metadata_set(srd_session_, SRD_CONF_SAMPLERATE,
+                               g_variant_new_uint64(samplerate));
+               for (const shared_ptr<decode::Decoder>& dec : stack_)
+                       dec->apply_all_options();
+       }
+}
+
+void DecodeSignal::stop_srd_session()
+{
+       if (srd_session_) {
+               // Destroy the session
+               srd_session_destroy(srd_session_);
+               srd_session_ = nullptr;
+
+               // Mark the decoder instances as non-existant since they were deleted
+               for (const shared_ptr<decode::Decoder>& dec : stack_)
+                       dec->invalidate_decoder_inst();
+       }
+}
+
+void DecodeSignal::connect_input_notifiers()
+{
+       // Disconnect the notification slot from the previous set of signals
+       disconnect(this, SLOT(on_data_cleared()));
+       disconnect(this, SLOT(on_data_received()));
+
+       // Connect the currently used signals to our slot
+       for (data::DecodeChannel& ch : channels_) {
+               if (!ch.assigned_signal)
+                       continue;
+
+               const data::SignalBase *signal = ch.assigned_signal;
+               connect(signal, SIGNAL(samples_cleared()),
+                       this, SLOT(on_data_cleared()));
+               connect(signal, SIGNAL(samples_added(uint64_t, uint64_t, uint64_t)),
+                       this, SLOT(on_data_received()));
+       }
+}
+
+void DecodeSignal::create_decode_segment()
+{
+       // Create annotation segment
+       segments_.emplace_back(DecodeSegment());
+
+       // Add annotation classes
+       for (const shared_ptr<decode::Decoder>& dec : stack_) {
+               assert(dec);
+               const srd_decoder *const decc = dec->decoder();
+               assert(dec->decoder());
+
+               int row_index = 0;
+               // Add a row for the decoder if it doesn't have a row list
+               if (!decc->annotation_rows)
+                       (segments_.back().annotation_rows)[Row(row_index++, 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(row_index++, decc, ann_row);
+
+                       // Add a new empty row data object
+                       (segments_.back().annotation_rows)[row] =
+                               decode::RowData();
+               }
+       }
+}
+
+void DecodeSignal::annotation_callback(srd_proto_data *pdata, void *decode_signal)
+{
+       assert(pdata);
+       assert(decode_signal);
+
+       DecodeSignal *const ds = (DecodeSignal*)decode_signal;
+       assert(ds);
+
+       if (ds->decode_interrupt_)
+               return;
+
+       lock_guard<mutex> lock(ds->output_mutex_);
+
+       // Find the row
+       assert(pdata->pdo);
+       assert(pdata->pdo->di);
+       const srd_decoder *const decc = pdata->pdo->di->decoder;
+       assert(decc);
+
+       const srd_proto_data_annotation *const pda =
+               (const srd_proto_data_annotation*)pdata->data;
+       assert(pda);
+
+       auto row_iter = ds->segments_.at(ds->current_segment_id_).annotation_rows.end();
+
+       // Try looking up the sub-row of this class
+       const auto format = pda->ann_class;
+       const auto r = ds->class_rows_.find(make_pair(decc, format));
+       if (r != ds->class_rows_.end())
+               row_iter = ds->segments_.at(ds->current_segment_id_).annotation_rows.find((*r).second);
+       else {
+               // Failing that, use the decoder as a key
+               row_iter = ds->segments_.at(ds->current_segment_id_).annotation_rows.find(Row(0, decc));
+       }
+
+       if (row_iter == ds->segments_.at(ds->current_segment_id_).annotation_rows.end()) {
+               qDebug() << "Unexpected annotation: decoder = " << decc <<
+                       ", format = " << format;
+               assert(false);
+               return;
+       }
+
+       // Add the annotation
+       (*row_iter).second.emplace_annotation(pdata, &((*row_iter).first));
+}
+
+void DecodeSignal::on_capture_state_changed(int state)
+{
+       // If a new acquisition was started, we need to start decoding from scratch
+       if (state == Session::Running) {
+               logic_mux_data_invalid_ = true;
+               begin_decode();
+       }
+}
+
+void DecodeSignal::on_data_cleared()
+{
+       reset_decode();
+}
+
+void DecodeSignal::on_data_received()
+{
+       // If we detected a lack of input data when trying to start decoding,
+       // we have set an error message. Only try again if we now have data
+       // to work with
+       if ((!error_message_.isEmpty()) && (get_input_segment_count() == 0))
+               return;
+
+       if (!logic_mux_thread_.joinable())
+               begin_decode();
+       else
+               logic_mux_cond_.notify_one();
+}
+
+} // namespace data
+} // namespace pv
diff --git a/pv/data/decodesignal.hpp b/pv/data/decodesignal.hpp
new file mode 100644 (file)
index 0000000..ba9c9b5
--- /dev/null
@@ -0,0 +1,236 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2017 Soeren Apel <soeren@apelpie.net>
+ *
+ * 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_PV_DATA_DECODESIGNAL_HPP
+#define PULSEVIEW_PV_DATA_DECODESIGNAL_HPP
+
+#include <atomic>
+#include <condition_variable>
+#include <unordered_set>
+#include <vector>
+
+#include <QSettings>
+#include <QString>
+
+#include <libsigrokdecode/libsigrokdecode.h>
+
+#include <pv/data/decode/row.hpp>
+#include <pv/data/decode/rowdata.hpp>
+#include <pv/data/signalbase.hpp>
+#include <pv/util.hpp>
+
+using std::atomic;
+using std::condition_variable;
+using std::map;
+using std::mutex;
+using std::pair;
+using std::vector;
+using std::shared_ptr;
+
+namespace pv {
+class Session;
+
+namespace data {
+
+namespace decode {
+class Annotation;
+class Decoder;
+class Row;
+}
+
+class Logic;
+class LogicSegment;
+class SignalBase;
+class SignalData;
+
+struct DecodeChannel
+{
+       uint16_t id;     ///< Global numerical ID for the decode channels in the stack
+       uint16_t bit_id; ///< Tells which bit within a sample represents this channel
+       const bool is_optional;
+       const pv::data::SignalBase *assigned_signal;
+       const QString name, desc;
+       int initial_pin_state;
+       const shared_ptr<pv::data::decode::Decoder> decoder_;
+       const srd_channel *pdch_;
+};
+
+struct DecodeSegment
+{
+       map<const decode::Row, decode::RowData> annotation_rows;
+       pv::util::Timestamp start_time;
+       double samplerate;
+       int64_t samples_decoded_incl, samples_decoded_excl;
+};
+
+class DecodeSignal : public SignalBase
+{
+       Q_OBJECT
+
+private:
+       static const double DecodeMargin;
+       static const double DecodeThreshold;
+       static const int64_t DecodeChunkLength;
+
+public:
+       DecodeSignal(pv::Session &session);
+       virtual ~DecodeSignal();
+
+       bool is_decode_signal() const;
+       const vector< shared_ptr<data::decode::Decoder> >& decoder_stack() const;
+
+       void stack_decoder(const srd_decoder *decoder);
+       void remove_decoder(int index);
+       bool toggle_decoder_visibility(int index);
+
+       void reset_decode(bool shutting_down = false);
+       void begin_decode();
+       void pause_decode();
+       void resume_decode();
+       bool is_paused() const;
+       QString error_message() const;
+
+       const vector<data::DecodeChannel> get_channels() const;
+       void auto_assign_signals(const shared_ptr<pv::data::decode::Decoder> dec);
+       void assign_signal(const uint16_t channel_id, const SignalBase *signal);
+       int get_assigned_signal_count() const;
+
+       void set_initial_pin_state(const uint16_t channel_id, const int init_state);
+
+       double samplerate() const;
+       const pv::util::Timestamp start_time() const;
+
+       /**
+        * Returns the number of samples that can be worked on,
+        * i.e. the number of samples where samples are available
+        * for all connected channels.
+        */
+       int64_t get_working_sample_count(uint32_t segment_id) const;
+
+       /**
+        * Returns the number of processed samples. Newly generated annotations will
+        * have sample numbers greater than this.
+        *
+        * If include_processing is true, this number will include the ones being
+        * currently processed (in case the decoder stack is running). In this case,
+        * newly generated annotations will have sample numbers smaller than this.
+        */
+       int64_t get_decoded_sample_count(uint32_t segment_id,
+               bool include_processing) const;
+
+       vector<decode::Row> visible_rows() const;
+
+       /**
+        * Extracts annotations from a single row into a vector.
+        * Note: The annotations may be unsorted and only annotations that fully
+        * fit into the sample range are considered.
+        */
+       void get_annotation_subset(
+               vector<pv::data::decode::Annotation> &dest,
+               const decode::Row &row, uint32_t segment_id, uint64_t start_sample,
+               uint64_t end_sample) const;
+
+       /**
+        * Extracts annotations from all rows into a vector.
+        * Note: The annotations may be unsorted and only annotations that fully
+        * fit into the sample range are considered.
+        */
+       void get_annotation_subset(
+               vector<pv::data::decode::Annotation> &dest,
+               uint32_t segment_id, uint64_t start_sample, uint64_t end_sample) const;
+
+       virtual void save_settings(QSettings &settings) const;
+
+       virtual void restore_settings(QSettings &settings);
+
+private:
+       void set_error_message(QString msg);
+
+       uint32_t get_input_segment_count() const;
+
+       uint32_t get_input_samplerate(uint32_t segment_id) const;
+
+       void update_channel_list();
+
+       void commit_decoder_channels();
+
+       void mux_logic_samples(uint32_t segment_id, const int64_t start, const int64_t end);
+
+       void logic_mux_proc();
+
+       void decode_data(const int64_t abs_start_samplenum, const int64_t sample_count,
+               const shared_ptr<LogicSegment> input_segment);
+
+       void decode_proc();
+
+       void start_srd_session();
+       void terminate_srd_session();
+       void stop_srd_session();
+
+       void connect_input_notifiers();
+
+       void create_decode_segment();
+
+       static void annotation_callback(srd_proto_data *pdata, void *decode_signal);
+
+Q_SIGNALS:
+       void new_annotations(); // TODO Supply segment for which they belong to
+       void decode_reset();
+       void decode_finished();
+       void channels_updated();
+
+private Q_SLOTS:
+       void on_capture_state_changed(int state);
+       void on_data_cleared();
+       void on_data_received();
+
+private:
+       pv::Session &session_;
+
+       vector<data::DecodeChannel> channels_;
+
+       struct srd_session *srd_session_;
+
+       shared_ptr<Logic> logic_mux_data_;
+       uint32_t logic_mux_unit_size_;
+       bool logic_mux_data_invalid_;
+
+       vector< shared_ptr<decode::Decoder> > stack_;
+       bool stack_config_changed_;
+       map<pair<const srd_decoder*, int>, decode::Row> class_rows_;
+
+       vector<DecodeSegment> segments_;
+       uint32_t current_segment_id_;
+
+       mutable mutex input_mutex_, output_mutex_, decode_pause_mutex_, logic_mux_mutex_;
+       mutable condition_variable decode_input_cond_, decode_pause_cond_,
+               logic_mux_cond_;
+
+       std::thread decode_thread_, logic_mux_thread_;
+       atomic<bool> decode_interrupt_, logic_mux_interrupt_;
+
+       bool decode_paused_;
+
+       QString error_message_;
+};
+
+} // namespace data
+} // namespace pv
+
+#endif // PULSEVIEW_PV_DATA_DECODESIGNAL_HPP
index ac47c29d6ac12d004f44c23ccc80b6678e44e78b..516060a0aec9013c139f8597dc0bf9bfd70de6aa 100644 (file)
@@ -44,7 +44,7 @@ unsigned int Logic::num_channels() const
 
 void Logic::push_segment(shared_ptr<LogicSegment> &segment)
 {
-       segments_.push_front(segment);
+       segments_.push_back(segment);
 }
 
 const deque< shared_ptr<LogicSegment> >& Logic::logic_segments() const
@@ -57,6 +57,11 @@ vector< shared_ptr<Segment> > Logic::segments() const
        return vector< shared_ptr<Segment> >(segments_.begin(), segments_.end());
 }
 
+uint32_t Logic::get_segment_count() const
+{
+       return (uint32_t)segments_.size();
+}
+
 void Logic::clear()
 {
        segments_.clear();
@@ -64,10 +69,18 @@ void Logic::clear()
        samples_cleared();
 }
 
+double Logic::get_samplerate() const
+{
+       if (segments_.empty())
+               return 1.0;
+
+       return segments_.front()->samplerate();
+}
+
 uint64_t Logic::max_sample_count() const
 {
        uint64_t l = 0;
-       for (shared_ptr<LogicSegment> s : segments_) {
+       for (const shared_ptr<LogicSegment>& s : segments_) {
                assert(s);
                l = max(l, s->get_sample_count());
        }
index 1ca5d3ed3af0f407ca34cb54908d4e6dc01e40e0..e0fb7dd14dd05dab2dc93451d999350f0d6dd60f 100644 (file)
@@ -50,8 +50,12 @@ public:
 
        vector< shared_ptr<Segment> > segments() const;
 
+       uint32_t get_segment_count() const;
+
        void clear();
 
+       double get_samplerate() const;
+
        uint64_t max_sample_count() const;
 
        void notify_samples_added(QObject* segment, uint64_t start_sample,
index b648f5d1c302af35c22c7c1dec35eaf08dee923e..4170f6422d521b0a519432a04b680423844eaf73 100644 (file)
  * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
+#include "config.h" // For HAVE_UNALIGNED_LITTLE_ENDIAN_ACCESS
+
 #include <extdef.h>
 
 #include <cassert>
 #include <cmath>
 #include <cstdlib>
 #include <cstring>
+#include <cstdint>
 
 #include "logic.hpp"
 #include "logicsegment.hpp"
@@ -46,11 +49,13 @@ const int LogicSegment::MipMapScaleFactor = 1 << MipMapScalePower;
 const float LogicSegment::LogMipMapScaleFactor = logf(MipMapScaleFactor);
 const uint64_t LogicSegment::MipMapDataUnit = 64 * 1024; // bytes
 
-LogicSegment::LogicSegment(pv::data::Logic& owner, unsigned int unit_size,
-       uint64_t samplerate) :
-       Segment(samplerate, unit_size),
+LogicSegment::LogicSegment(pv::data::Logic& owner, uint32_t segment_id,
+       unsigned int unit_size, uint64_t samplerate) :
+       Segment(segment_id, samplerate, unit_size),
        owner_(owner),
-       last_append_sample_(0)
+       last_append_sample_(0),
+       last_append_accumulator_(0),
+       last_append_extra_(0)
 {
        memset(mip_map_, 0, sizeof(mip_map_));
 }
@@ -62,7 +67,184 @@ LogicSegment::~LogicSegment()
                free(l.data);
 }
 
-uint64_t LogicSegment::unpack_sample(const uint8_t *ptr) const
+template <class T>
+void LogicSegment::downsampleTmain(const T*&in, T &acc, T &prev)
+{
+       // Accumulate one sample at a time
+       for (uint64_t i = 0; i < MipMapScaleFactor; i++) {
+               T sample = *in++;
+               acc |= prev ^ sample;
+               prev = sample;
+       }
+}
+
+template <>
+void LogicSegment::downsampleTmain<uint8_t>(const uint8_t*&in, uint8_t &acc, uint8_t &prev)
+{
+       // Handle 8 bit samples in 32 bit steps
+       uint32_t prev32 = prev | prev << 8 | prev << 16 | prev << 24;
+       uint32_t acc32 = acc;
+       const uint32_t *in32 = (const uint32_t*)in;
+       for (uint64_t i = 0; i < MipMapScaleFactor; i += 4) {
+               uint32_t sample32 = *in32++;
+               acc32 |= prev32 ^ sample32;
+               prev32 = sample32;
+       }
+       // Reduce result back to uint8_t
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+       prev = (prev32 >> 24) & 0xff; // MSB is last
+#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+       prev = prev32 & 0xff; // LSB is last
+#else
+#error Endianness unknown
+#endif
+       acc |= acc32 & 0xff;
+       acc |= (acc32 >> 8) & 0xff;
+       acc |= (acc32 >> 16) & 0xff;
+       acc |= (acc32 >> 24) & 0xff;
+       in = (const uint8_t*)in32;
+}
+
+template <>
+void LogicSegment::downsampleTmain<uint16_t>(const uint16_t*&in, uint16_t &acc, uint16_t &prev)
+{
+       // Handle 16 bit samples in 32 bit steps
+       uint32_t prev32 = prev | prev << 16;
+       uint32_t acc32 = acc;
+       const uint32_t *in32 = (const uint32_t*)in;
+       for (uint64_t i = 0; i < MipMapScaleFactor; i += 2) {
+               uint32_t sample32 = *in32++;
+               acc32 |= prev32 ^ sample32;
+               prev32 = sample32;
+       }
+       // Reduce result back to uint16_t
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+       prev = (prev32 >> 16) & 0xffff; // MSB is last
+#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+       prev = prev32 & 0xffff; // LSB is last
+#else
+#error Endian unknown
+#endif
+       acc |= acc32 & 0xffff;
+       acc |= (acc32 >> 16) & 0xffff;
+       in = (const uint16_t*)in32;
+}
+
+template <class T>
+void LogicSegment::downsampleT(const uint8_t *in_, uint8_t *&out_, uint64_t len)
+{
+       const T *in = (const T*)in_;
+       T *out = (T*)out_;
+       T prev = last_append_sample_;
+       T acc = last_append_accumulator_;
+
+       // Try to complete the previous downsample
+       if (last_append_extra_) {
+               while (last_append_extra_ < MipMapScaleFactor && len > 0) {
+                       T sample = *in++;
+                       acc |= prev ^ sample;
+                       prev = sample;
+                       last_append_extra_++;
+                       len--;
+               }
+               if (!len) {
+                       // Not enough samples available to complete downsample
+                       last_append_sample_ = prev;
+                       last_append_accumulator_ = acc;
+                       return;
+               }
+               // We have a complete downsample
+               *out++ = acc;
+               acc = 0;
+               last_append_extra_ = 0;
+       }
+
+       // Handle complete blocks of MipMapScaleFactor samples
+       while (len >= MipMapScaleFactor) {
+               downsampleTmain<T>(in, acc, prev);
+               len -= MipMapScaleFactor;
+               // Output downsample
+               *out++ = acc;
+               acc = 0;
+       }
+
+       // Process remainder, not enough for a complete sample
+       while (len > 0) {
+               T sample = *in++;
+               acc |= prev ^ sample;
+               prev = sample;
+               last_append_extra_++;
+               len--;
+       }
+
+       // Update context
+       last_append_sample_ = prev;
+       last_append_accumulator_ = acc;
+       out_ = (uint8_t *)out;
+}
+
+void LogicSegment::downsampleGeneric(const uint8_t *in, uint8_t *&out, uint64_t len)
+{
+       // Downsample using the generic unpack_sample()
+       // which can handle any width between 1 and 8 bytes
+       uint64_t prev = last_append_sample_;
+       uint64_t acc = last_append_accumulator_;
+
+       // Try to complete the previous downsample
+       if (last_append_extra_) {
+               while (last_append_extra_ < MipMapScaleFactor && len > 0) {
+                       const uint64_t sample = unpack_sample(in);
+                       in += unit_size_;
+                       acc |= prev ^ sample;
+                       prev = sample;
+                       last_append_extra_++;
+                       len--;
+               }
+               if (!len) {
+                       // Not enough samples available to complete downsample
+                       last_append_sample_ = prev;
+                       last_append_accumulator_ = acc;
+                       return;
+               }
+               // We have a complete downsample
+               pack_sample(out, acc);
+               out += unit_size_;
+               acc = 0;
+               last_append_extra_ = 0;
+       }
+
+       // Handle complete blocks of MipMapScaleFactor samples
+       while (len >= MipMapScaleFactor) {
+               // Accumulate one sample at a time
+               for (uint64_t i = 0; i < MipMapScaleFactor; i++) {
+                       const uint64_t sample = unpack_sample(in);
+                       in += unit_size_;
+                       acc |= prev ^ sample;
+                       prev = sample;
+               }
+               len -= MipMapScaleFactor;
+               // Output downsample
+               pack_sample(out, acc);
+               out += unit_size_;
+               acc = 0;
+       }
+
+       // Process remainder, not enough for a complete sample
+       while (len > 0) {
+               const uint64_t sample = unpack_sample(in);
+               in += unit_size_;
+               acc |= prev ^ sample;
+               prev = sample;
+               last_append_extra_++;
+               len--;
+       }
+
+       // Update context
+       last_append_sample_ = prev;
+       last_append_accumulator_ = acc;
+}
+
+inline uint64_t LogicSegment::unpack_sample(const uint8_t *ptr) const
 {
 #ifdef HAVE_UNALIGNED_LITTLE_ENDIAN_ACCESS
        return *(uint64_t*)ptr;
@@ -100,7 +282,7 @@ uint64_t LogicSegment::unpack_sample(const uint8_t *ptr) const
 #endif
 }
 
-void LogicSegment::pack_sample(uint8_t *ptr, uint64_t value)
+inline void LogicSegment::pack_sample(uint8_t *ptr, uint64_t value)
 {
 #ifdef HAVE_UNALIGNED_LITTLE_ENDIAN_ACCESS
        *(uint64_t*)ptr = value;
@@ -150,8 +332,8 @@ void LogicSegment::append_payload(void *data, uint64_t data_size)
 
        lock_guard<recursive_mutex> lock(mutex_);
 
-       uint64_t prev_sample_count = sample_count_;
-       uint64_t sample_count = data_size / unit_size_;
+       const uint64_t prev_sample_count = sample_count_;
+       const uint64_t sample_count = data_size / unit_size_;
 
        append_samples(data, sample_count);
 
@@ -166,153 +348,31 @@ void LogicSegment::append_payload(void *data, uint64_t data_size)
                        prev_sample_count + 1);
 }
 
-const uint8_t* LogicSegment::get_samples(int64_t start_sample,
-       int64_t end_sample) const
+void LogicSegment::get_samples(int64_t start_sample,
+       int64_t end_sample,     uint8_t* dest) 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);
+       assert(dest != nullptr);
 
        lock_guard<recursive_mutex> lock(mutex_);
 
-       return get_raw_samples(start_sample, (end_sample - start_sample));
-}
-
-SegmentLogicDataIterator* LogicSegment::begin_sample_iteration(uint64_t start)
-{
-       return (SegmentLogicDataIterator*)begin_raw_sample_iteration(start);
-}
-
-void LogicSegment::continue_sample_iteration(SegmentLogicDataIterator* it, uint64_t increase)
-{
-       Segment::continue_raw_sample_iteration((SegmentRawDataIterator*)it, increase);
-}
-
-void LogicSegment::end_sample_iteration(SegmentLogicDataIterator* it)
-{
-       Segment::end_raw_sample_iteration((SegmentRawDataIterator*)it);
-}
-
-void LogicSegment::reallocate_mipmap_level(MipMapLevel &m)
-{
-       lock_guard<recursive_mutex> lock(mutex_);
-
-       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 LogicSegment::append_payload_to_mipmap()
-{
-       MipMapLevel &m0 = mip_map_[0];
-       uint64_t prev_length;
-       uint8_t *dest_ptr;
-       SegmentRawDataIterator* it;
-       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
-       uint64_t start_sample = prev_length * MipMapScaleFactor;
-       uint64_t end_sample = m0.length * MipMapScaleFactor;
-
-       it = begin_raw_sample_iteration(start_sample);
-       for (uint64_t i = start_sample; i < end_sample;) {
-               // Accumulate transitions which have occurred in this sample
-               accumulator = 0;
-               diff_counter = MipMapScaleFactor;
-               while (diff_counter-- > 0) {
-                       const uint64_t sample = unpack_sample(it->value);
-                       accumulator |= last_append_sample_ ^ sample;
-                       last_append_sample_ = sample;
-                       continue_raw_sample_iteration(it, 1);
-                       i++;
-               }
-
-               pack_sample(dest_ptr, accumulator);
-               dest_ptr += unit_size_;
-       }
-       end_raw_sample_iteration(it);
-
-       // 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 be computed
-               if (m.length == prev_length)
-                       break;
-
-               reallocate_mipmap_level(m);
-
-               // Subsample the lower level
-               const uint8_t* 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 LogicSegment::get_unpacked_sample(uint64_t index) const
-{
-       assert(index < sample_count_);
-
-       const uint8_t* data = get_raw_samples(index, 1);
-       uint64_t sample = unpack_sample(data);
-       delete[] data;
-
-       return sample;
+       get_raw_samples(start_sample, (end_sample - start_sample), dest);
 }
 
 void LogicSegment::get_subsampled_edges(
        vector<EdgePair> &edges,
        uint64_t start, uint64_t end,
-       float min_length, int sig_index)
+       float min_length, int sig_index, bool first_change_only)
 {
        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);
@@ -320,6 +380,10 @@ void LogicSegment::get_subsampled_edges(
 
        lock_guard<recursive_mutex> lock(mutex_);
 
+       // Make sure we only process as many samples as we have
+       if (end > get_sample_count())
+               end = get_sample_count();
+
        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);
@@ -327,27 +391,27 @@ void LogicSegment::get_subsampled_edges(
 
        // Store the initial state
        last_sample = (get_unpacked_sample(start) & sig_mask) != 0;
-       edges.emplace_back(index++, last_sample);
+       if (!first_change_only)
+               edges.emplace_back(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.
+               // the minimum level.
                fast_forward = (mip_map_[level].data != nullptr);
 
                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));
+                       const uint64_t final_index = min(end, pow2_ceil(index, MipMapScalePower));
 
                        for (; index < final_index &&
                                        (index & ~((uint64_t)(~0) << MipMapScalePower)) != 0;
                                        index++) {
-                               const bool sample =
-                                       (get_unpacked_sample(index) & sig_mask) != 0;
+
+                               const bool sample = (get_unpacked_sample(index) & sig_mask) != 0;
 
                                // If there was a change we cannot fast forward
                                if (sample != last_sample) {
@@ -359,15 +423,13 @@ void LogicSegment::get_subsampled_edges(
                        // 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;
+                       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_unpacked_sample(index) & sig_mask) != 0;
+                       const bool sample = (get_unpacked_sample(index) & sig_mask) != 0;
                        if (last_sample != sample)
                                fast_forward = false;
                }
@@ -382,32 +444,27 @@ void LogicSegment::get_subsampled_edges(
                        // Slide right and zoom out at the beginnings of mip-map
                        // blocks until we encounter a change
                        while (true) {
-                               const int level_scale_power =
-                                       (level + 1) * MipMapScalePower;
-                               const uint64_t offset =
-                                       index >> level_scale_power;
+                               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))
+                                       (get_subsample(level, offset) & sig_mask))
                                        break;
 
                                if ((offset & ~((uint64_t)(~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)
+                                       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);
+                                       index = pow2_ceil(index + 1, level_scale_power);
                                }
                        }
 
@@ -416,16 +473,13 @@ void LogicSegment::get_subsampled_edges(
                        while (true) {
                                assert(mip_map_[level].data);
 
-                               const int level_scale_power =
-                                       (level + 1) * MipMapScalePower;
-                               const uint64_t offset =
-                                       index >> level_scale_power;
+                               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)) {
+                                               (get_subsample(level, offset) & sig_mask)) {
                                        // Zoom in unless we reached the minimum
                                        // zoom
                                        if (level == min_level)
@@ -435,8 +489,7 @@ void LogicSegment::get_subsampled_edges(
                                } else {
                                        // Slide right to the beginning of the
                                        // next mip map block
-                                       index = pow2_ceil(index + 1,
-                                               level_scale_power);
+                                       index = pow2_ceil(index + 1, level_scale_power);
                                }
                        }
 
@@ -445,8 +498,7 @@ void LogicSegment::get_subsampled_edges(
                        // block
                        if (min_length < MipMapScaleFactor) {
                                for (; index < end; index++) {
-                                       const bool sample = (get_unpacked_sample(index) &
-                                               sig_mask) != 0;
+                                       const bool sample = (get_unpacked_sample(index) & sig_mask) != 0;
                                        if (sample != last_sample)
                                                break;
                                }
@@ -461,19 +513,177 @@ void LogicSegment::get_subsampled_edges(
                        break;
 
                // Store the final state
-               const bool final_sample =
-                       (get_unpacked_sample(final_index - 1) & sig_mask) != 0;
+               const bool final_sample = (get_unpacked_sample(final_index - 1) & sig_mask) != 0;
                edges.emplace_back(index, final_sample);
 
                index = final_index;
                last_sample = final_sample;
+
+               if (first_change_only)
+                       break;
        }
 
        // Add the final state
-       const bool end_sample = get_unpacked_sample(end) & sig_mask;
-       if (last_sample != end_sample)
-               edges.emplace_back(end, end_sample);
-       edges.emplace_back(end + 1, end_sample);
+       if (!first_change_only) {
+               const bool end_sample = get_unpacked_sample(end) & sig_mask;
+               if (last_sample != end_sample)
+                       edges.emplace_back(end, end_sample);
+               edges.emplace_back(end + 1, end_sample);
+       }
+}
+
+void LogicSegment::get_surrounding_edges(vector<EdgePair> &dest,
+       uint64_t origin_sample, float min_length, int sig_index)
+{
+       if (origin_sample >= sample_count_)
+               return;
+
+       // Put the edges vector on the heap, it can become quite big until we can
+       // use a get_subsampled_edges() implementation that searches backwards
+       vector<EdgePair>* edges = new vector<EdgePair>;
+
+       // Get all edges to the left of origin_sample
+       get_subsampled_edges(*edges, 0, origin_sample, min_length, sig_index, false);
+
+       // If we don't specify "first only", the first and last edge are the states
+       // at samples 0 and origin_sample. If only those exist, there are no edges
+       if (edges->size() == 2) {
+               delete edges;
+               return;
+       }
+
+       // Dismiss the entry for origin_sample so that back() gives us the
+       // real last entry
+       edges->pop_back();
+       dest.push_back(edges->back());
+       edges->clear();
+
+       // Get first edge to the right of origin_sample
+       get_subsampled_edges(*edges, origin_sample, sample_count_, min_length, sig_index, true);
+
+       // "first only" is specified, so nothing needs to be dismissed
+       if (edges->size() == 0) {
+               delete edges;
+               return;
+       }
+
+       dest.push_back(edges->front());
+
+       delete edges;
+}
+
+void LogicSegment::reallocate_mipmap_level(MipMapLevel &m)
+{
+       lock_guard<recursive_mutex> lock(mutex_);
+
+       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 LogicSegment::append_payload_to_mipmap()
+{
+       MipMapLevel &m0 = mip_map_[0];
+       uint64_t prev_length;
+       uint8_t *dest_ptr;
+       SegmentDataIterator* it;
+       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 uint64_t start_sample = prev_length * MipMapScaleFactor;
+       const uint64_t end_sample = m0.length * MipMapScaleFactor;
+       uint64_t len_sample = end_sample - start_sample;
+       it = begin_sample_iteration(start_sample);
+       while (len_sample > 0) {
+               // Number of samples available in this chunk
+               uint64_t count = get_iterator_valid_length(it);
+               // Reduce if less than asked for
+               count = std::min(count, len_sample);
+               uint8_t *src_ptr = get_iterator_value(it);
+               // Submit these contiguous samples to downsampling in bulk
+               if (unit_size_ == 1)
+                       downsampleT<uint8_t>(src_ptr, dest_ptr, count);
+               else if (unit_size_ == 2)
+                       downsampleT<uint16_t>(src_ptr, dest_ptr, count);
+               else if (unit_size_ == 4)
+                       downsampleT<uint32_t>(src_ptr, dest_ptr, count);
+               else if (unit_size_ == 8)
+                       downsampleT<uint8_t>(src_ptr, dest_ptr, count);
+               else
+                       downsampleGeneric(src_ptr, dest_ptr, count);
+               len_sample -= count;
+               // Advance iterator, should move to start of next chunk
+               continue_sample_iteration(it, count);
+       }
+       end_sample_iteration(it);
+
+       // 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 be computed
+               if (m.length == prev_length)
+                       break;
+
+               reallocate_mipmap_level(m);
+
+               // Subsample the lower level
+               const uint8_t* 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 LogicSegment::get_unpacked_sample(uint64_t index) const
+{
+       assert(index < sample_count_);
+
+       assert(unit_size_ <= 8);  // 8 * 8 = 64 channels
+       uint8_t data[8];
+
+       get_raw_samples(index, 1, data);
+
+       return unpack_sample(data);
 }
 
 uint64_t LogicSegment::get_subsample(int level, uint64_t offset) const
@@ -486,7 +696,7 @@ uint64_t LogicSegment::get_subsample(int level, uint64_t offset) const
 
 uint64_t LogicSegment::pow2_ceil(uint64_t x, unsigned int power)
 {
-       const uint64_t p = 1 << power;
+       const uint64_t p = UINT64_C(1) << power;
        return (x + p - 1) / p * p;
 }
 
index 3c012a45c3257bcf4c20be27256b3a243ba0cf2b..67959b31ec17e402bca93466249355fd60cc5ea4 100644 (file)
@@ -48,59 +48,38 @@ namespace data {
 
 class Logic;
 
-typedef struct {
-       uint64_t sample_index, chunk_num, chunk_offs;
-       uint8_t* chunk;
-       uint8_t* value;
-} SegmentLogicDataIterator;
-
-class LogicSegment : public QObject, public Segment
+class LogicSegment : public Segment
 {
        Q_OBJECT
 
-private:
-       struct MipMapLevel
-       {
-               uint64_t length;
-               uint64_t data_length;
-               void *data;
-       };
+public:
+       typedef pair<int64_t, bool> EdgePair;
 
-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 pair<int64_t, bool> EdgePair;
+private:
+       struct MipMapLevel
+       {
+               uint64_t length;
+               uint64_t data_length;
+               void *data;
+       };
 
 public:
-       LogicSegment(pv::data::Logic& owner, unsigned int unit_size, uint64_t samplerate);
+       LogicSegment(pv::data::Logic& owner, uint32_t segment_id,
+               unsigned int unit_size, uint64_t samplerate);
 
        virtual ~LogicSegment();
 
        void append_payload(shared_ptr<sigrok::Logic> logic);
        void append_payload(void *data, uint64_t data_size);
 
-       const uint8_t* get_samples(int64_t start_sample, int64_t end_sample) const;
-
-       SegmentLogicDataIterator* begin_sample_iteration(uint64_t start);
-       void continue_sample_iteration(SegmentLogicDataIterator* it, uint64_t increase);
-       void end_sample_iteration(SegmentLogicDataIterator* it);
-
-private:
-       uint64_t unpack_sample(const uint8_t *ptr) const;
-       void pack_sample(uint8_t *ptr, uint64_t value);
+       void get_samples(int64_t start_sample, int64_t end_sample, uint8_t* dest) const;
 
-       void reallocate_mipmap_level(MipMapLevel &m);
-
-       void append_payload_to_mipmap();
-
-       uint64_t get_unpacked_sample(uint64_t index) const;
-
-public:
        /**
         * Parses a logic data segment to generate a list of transitions
         * in a time interval to a given level of detail.
@@ -113,7 +92,24 @@ public:
         */
        void get_subsampled_edges(vector<EdgePair> &edges,
                uint64_t start, uint64_t end,
-               float min_length, int sig_index);
+               float min_length, int sig_index, bool first_change_only = false);
+
+       void get_surrounding_edges(vector<EdgePair> &dest,
+               uint64_t origin_sample, float min_length, int sig_index);
+
+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_unpacked_sample(uint64_t index) const;
+
+       template <class T> void downsampleTmain(const T*&in, T &acc, T &prev);
+       template <class T> void downsampleT(const uint8_t *in, uint8_t *&out, uint64_t len);
+       void downsampleGeneric(const uint8_t *in, uint8_t *&out, uint64_t len);
 
 private:
        uint64_t get_subsample(int level, uint64_t offset) const;
@@ -125,6 +121,8 @@ private:
 
        struct MipMapLevel mip_map_[ScaleStepCount];
        uint64_t last_append_sample_;
+       uint64_t last_append_accumulator_;
+       uint64_t last_append_extra_;
 
        friend struct LogicSegmentTest::Pow2;
        friend struct LogicSegmentTest::Basic;
index 905b79274efeb6ee5ddebd7045348c5da85d9db3..2bac00621a0eb2de8dfd15c65f1cdfa5b9f22e4c 100644 (file)
@@ -24,6 +24,9 @@
 #include <cstdlib>
 #include <cstring>
 
+#include <QDebug>
+
+using std::bad_alloc;
 using std::lock_guard;
 using std::min;
 using std::recursive_mutex;
@@ -33,13 +36,15 @@ namespace data {
 
 const uint64_t Segment::MaxChunkSize = 10 * 1024 * 1024;  /* 10MiB */
 
-Segment::Segment(uint64_t samplerate, unsigned int unit_size) :
+Segment::Segment(uint32_t segment_id, uint64_t samplerate, unsigned int unit_size) :
+       segment_id_(segment_id),
        sample_count_(0),
        start_time_(0),
        samplerate_(samplerate),
        unit_size_(unit_size),
        iterator_count_(0),
-       mem_optimization_requested_(false)
+       mem_optimization_requested_(false),
+       is_complete_(false)
 {
        lock_guard<recursive_mutex> lock(mutex_);
        assert(unit_size_ > 0);
@@ -49,7 +54,7 @@ Segment::Segment(uint64_t samplerate, unsigned int unit_size) :
        chunk_size_ = min(MaxChunkSize, (MaxChunkSize / unit_size_) * unit_size_);
 
        // Create the initial chunk
-       current_chunk_ = new uint8_t[chunk_size_];
+       current_chunk_ = new uint8_t[chunk_size_ + 7];  /* FIXME +7 is workaround for #1284 */
        data_chunks_.push_back(current_chunk_);
        used_samples_ = 0;
        unused_samples_ = chunk_size_ / unit_size_;
@@ -89,6 +94,21 @@ unsigned int Segment::unit_size() const
        return unit_size_;
 }
 
+uint32_t Segment::segment_id() const
+{
+       return segment_id_;
+}
+
+void Segment::set_complete()
+{
+       is_complete_ = true;
+}
+
+bool Segment::is_complete() const
+{
+       return is_complete_;
+}
+
 void Segment::free_unused_memory()
 {
        lock_guard<recursive_mutex> lock(mutex_);
@@ -99,15 +119,17 @@ void Segment::free_unused_memory()
                return;
        }
 
-       // No more data will come in, so re-create the last chunk accordingly
-       uint8_t* resized_chunk = new uint8_t[used_samples_ * unit_size_];
-       memcpy(resized_chunk, current_chunk_, used_samples_ * unit_size_);
+       if (current_chunk_) {
+               // No more data will come in, so re-create the last chunk accordingly
+               uint8_t* resized_chunk = new uint8_t[used_samples_ * unit_size_ + 7];  /* FIXME +7 is workaround for #1284 */
+               memcpy(resized_chunk, current_chunk_, used_samples_ * unit_size_);
 
-       delete[] current_chunk_;
-       current_chunk_ = resized_chunk;
+               delete[] current_chunk_;
+               current_chunk_ = resized_chunk;
 
-       data_chunks_.pop_back();
-       data_chunks_.push_back(resized_chunk);
+               data_chunks_.pop_back();
+               data_chunks_.push_back(resized_chunk);
+       }
 }
 
 void Segment::append_single_sample(void *data)
@@ -122,7 +144,7 @@ void Segment::append_single_sample(void *data)
        unused_samples_--;
 
        if (unused_samples_ == 0) {
-               current_chunk_ = new uint8_t[chunk_size_];
+               current_chunk_ = new uint8_t[chunk_size_ + 7];  /* FIXME +7 is workaround for #1284 */
                data_chunks_.push_back(current_chunk_);
                used_samples_ = 0;
                unused_samples_ = chunk_size_ / unit_size_;
@@ -160,8 +182,26 @@ void Segment::append_samples(void* data, uint64_t samples)
                data_offset += (copy_count * unit_size_);
 
                if (unused_samples_ == 0) {
-                       // If we're out of memory, this will throw std::bad_alloc
-                       current_chunk_ = new uint8_t[chunk_size_];
+                       try {
+                               // If we're out of memory, allocating a chunk will throw
+                               // std::bad_alloc. To give the application some usable memory
+                               // to work with in case chunk allocation fails, we allocate
+                               // extra memory and throw it away if it all succeeded.
+                               // This way, memory allocation will fail early enough to let
+                               // PV remain alive. Otherwise, PV will crash in a random
+                               // memory-allocating part of the application.
+                               current_chunk_ = new uint8_t[chunk_size_ + 7];  /* FIXME +7 is workaround for #1284 */
+
+                               const int dummy_size = 2 * chunk_size_;
+                               auto dummy_chunk = new uint8_t[dummy_size];
+                               memset(dummy_chunk, 0xFF, dummy_size);
+                               delete[] dummy_chunk;
+                       } catch (bad_alloc&) {
+                               delete[] current_chunk_;  // The new may have succeeded
+                               current_chunk_ = nullptr;
+                               throw;
+                       }
+
                        data_chunks_.push_back(current_chunk_);
                        used_samples_ = 0;
                        unused_samples_ = chunk_size_ / unit_size_;
@@ -171,15 +211,16 @@ void Segment::append_samples(void* data, uint64_t samples)
        sample_count_ += samples;
 }
 
-uint8_t* Segment::get_raw_samples(uint64_t start, uint64_t count) const
+void Segment::get_raw_samples(uint64_t start, uint64_t count,
+       uint8_t* dest) const
 {
        assert(start < sample_count_);
        assert(start + count <= sample_count_);
        assert(count > 0);
+       assert(dest != nullptr);
 
        lock_guard<recursive_mutex> lock(mutex_);
 
-       uint8_t* dest = new uint8_t[count * unit_size_];
        uint8_t* dest_ptr = dest;
 
        uint64_t chunk_num = (start * unit_size_) / chunk_size_;
@@ -199,13 +240,11 @@ uint8_t* Segment::get_raw_samples(uint64_t start, uint64_t count) const
                chunk_num++;
                chunk_offs = 0;
        }
-
-       return dest;
 }
 
-SegmentRawDataIterator* Segment::begin_raw_sample_iteration(uint64_t start)
+SegmentDataIterator* Segment::begin_sample_iteration(uint64_t start)
 {
-       SegmentRawDataIterator* it = new SegmentRawDataIterator;
+       SegmentDataIterator* it = new SegmentDataIterator;
 
        assert(start < sample_count_);
 
@@ -215,17 +254,12 @@ SegmentRawDataIterator* Segment::begin_raw_sample_iteration(uint64_t start)
        it->chunk_num = (start * unit_size_) / chunk_size_;
        it->chunk_offs = (start * unit_size_) % chunk_size_;
        it->chunk = data_chunks_[it->chunk_num];
-       it->value = it->chunk + it->chunk_offs;
 
        return it;
 }
 
-void Segment::continue_raw_sample_iteration(SegmentRawDataIterator* it, uint64_t increase)
+void Segment::continue_sample_iteration(SegmentDataIterator* it, uint64_t increase)
 {
-       // Fail gracefully if we are asked to deliver data we don't have
-       if (it->sample_index > sample_count_)
-               return;
-
        it->sample_index += increase;
        it->chunk_offs += (increase * unit_size_);
 
@@ -234,11 +268,9 @@ void Segment::continue_raw_sample_iteration(SegmentRawDataIterator* it, uint64_t
                it->chunk_offs -= chunk_size_;
                it->chunk = data_chunks_[it->chunk_num];
        }
-
-       it->value = it->chunk + it->chunk_offs;
 }
 
-void Segment::end_raw_sample_iteration(SegmentRawDataIterator* it)
+void Segment::end_sample_iteration(SegmentDataIterator* it)
 {
        delete it;
 
@@ -250,5 +282,19 @@ void Segment::end_raw_sample_iteration(SegmentRawDataIterator* it)
        }
 }
 
+uint8_t* Segment::get_iterator_value(SegmentDataIterator* it)
+{
+       assert(it->sample_index <= (sample_count_ - 1));
+
+       return (it->chunk + it->chunk_offs);
+}
+
+uint64_t Segment::get_iterator_valid_length(SegmentDataIterator* it)
+{
+       assert(it->sample_index <= (sample_count_ - 1));
+
+       return ((chunk_size_ - it->chunk_offs) / unit_size_);
+}
+
 } // namespace data
 } // namespace pv
index 7338bd3896f5a055bbe5575b3f06ba514b9cdb90..f5a00c9581530529044cb33d283f52cbb7c1d162 100644 (file)
@@ -27,6 +27,8 @@
 #include <thread>
 #include <vector>
 
+#include <QObject>
+
 using std::recursive_mutex;
 using std::vector;
 
@@ -49,16 +51,17 @@ namespace data {
 typedef struct {
        uint64_t sample_index, chunk_num, chunk_offs;
        uint8_t* chunk;
-       uint8_t* value;
-} SegmentRawDataIterator;
+} SegmentDataIterator;
 
-class Segment
+class Segment : public QObject
 {
+       Q_OBJECT
+
 private:
        static const uint64_t MaxChunkSize;
 
 public:
-       Segment(uint64_t samplerate, unsigned int unit_size);
+       Segment(uint32_t segment_id, uint64_t samplerate, unsigned int unit_size);
 
        virtual ~Segment();
 
@@ -71,17 +74,25 @@ public:
 
        unsigned int unit_size() const;
 
+       uint32_t segment_id() const;
+
+       void set_complete();
+       bool is_complete() const;
+
        void free_unused_memory();
 
 protected:
        void append_single_sample(void *data);
        void append_samples(void *data, uint64_t samples);
-       uint8_t* get_raw_samples(uint64_t start, uint64_t count) const;
+       void get_raw_samples(uint64_t start, uint64_t count, uint8_t *dest) const;
 
-       SegmentRawDataIterator* begin_raw_sample_iteration(uint64_t start);
-       void continue_raw_sample_iteration(SegmentRawDataIterator* it, uint64_t increase);
-       void end_raw_sample_iteration(SegmentRawDataIterator* it);
+       SegmentDataIterator* begin_sample_iteration(uint64_t start);
+       void continue_sample_iteration(SegmentDataIterator* it, uint64_t increase);
+       void end_sample_iteration(SegmentDataIterator* it);
+       uint8_t* get_iterator_value(SegmentDataIterator* it);
+       uint64_t get_iterator_valid_length(SegmentDataIterator* it);
 
+       uint32_t segment_id_;
        mutable recursive_mutex mutex_;
        vector<uint8_t*> data_chunks_;
        uint8_t* current_chunk_;
@@ -93,6 +104,7 @@ protected:
        unsigned int unit_size_;
        int iterator_count_;
        bool mem_optimization_requested_;
+       bool is_complete_;
 
        friend struct SegmentTest::SmallSize8Single;
        friend struct SegmentTest::MediumSize8Single;
index c8a6d556648f1112c524be5ab7db4803cf20dcf0..78633de977706ac5947969f1138fb340562a170a 100644 (file)
 #include "signalbase.hpp"
 #include "signaldata.hpp"
 
+#include <QDebug>
+
 #include <pv/binding/decoder.hpp>
 #include <pv/session.hpp>
 
 using std::dynamic_pointer_cast;
 using std::make_shared;
+using std::out_of_range;
 using std::shared_ptr;
 using std::tie;
+using std::unique_lock;
 
 namespace pv {
 namespace data {
 
-const int SignalBase::ColourBGAlpha = 8 * 256 / 100;
+const int SignalBase::ColorBGAlpha = 8 * 256 / 100;
+const uint64_t SignalBase::ConversionBlockSize = 4096;
+const uint32_t SignalBase::ConversionDelay = 1000;  // 1 second
 
 SignalBase::SignalBase(shared_ptr<sigrok::Channel> channel, ChannelType channel_type) :
        channel_(channel),
        channel_type_(channel_type),
-       conversion_type_(NoConversion)
+       conversion_type_(NoConversion),
+       min_value_(0),
+       max_value_(0)
 {
        if (channel_)
                internal_name_ = QString::fromStdString(channel_->name());
+
+       connect(&delayed_conversion_starter_, SIGNAL(timeout()),
+               this, SLOT(on_delayed_conversion_start()));
+       delayed_conversion_starter_.setSingleShot(true);
+       delayed_conversion_starter_.setInterval(ConversionDelay);
 }
 
 SignalBase::~SignalBase()
 {
-       // Wait for the currently ongoing conversion to finish
-       if (conversion_thread_.joinable())
-               conversion_thread_.join();
+       stop_conversion();
 }
 
 shared_ptr<sigrok::Channel> SignalBase::channel() const
@@ -70,6 +81,14 @@ QString SignalBase::internal_name() const
        return internal_name_;
 }
 
+QString SignalBase::display_name() const
+{
+       if (name() != internal_name_)
+               return name() + " (" + internal_name_ + ")";
+       else
+               return name();
+}
+
 void SignalBase::set_name(QString name)
 {
        if (channel_)
@@ -100,27 +119,35 @@ SignalBase::ChannelType SignalBase::type() const
 
 unsigned int SignalBase::index() const
 {
-       return (channel_) ? channel_->index() : (unsigned int)-1;
+       return (channel_) ? channel_->index() : 0;
+}
+
+unsigned int SignalBase::logic_bit_index() const
+{
+       if (channel_type_ == LogicChannel)
+               return channel_->index();
+       else
+               return 0;
 }
 
-QColor SignalBase::colour() const
+QColor SignalBase::color() const
 {
-       return colour_;
+       return color_;
 }
 
-void SignalBase::set_colour(QColor colour)
+void SignalBase::set_color(QColor color)
 {
-       colour_ = colour;
+       color_ = color;
 
-       bgcolour_ = colour;
-       bgcolour_.setAlpha(ColourBGAlpha);
+       bgcolor_ = color;
+       bgcolor_.setAlpha(ColorBGAlpha);
 
-       colour_changed(colour);
+       color_changed(color);
 }
 
-QColor SignalBase::bgcolour() const
+QColor SignalBase::bgcolor() const
 {
-       return bgcolour_;
+       return bgcolor_;
 }
 
 void SignalBase::set_data(shared_ptr<pv::data::SignalData> data)
@@ -130,6 +157,14 @@ void SignalBase::set_data(shared_ptr<pv::data::SignalData> data)
                        this, SLOT(on_samples_cleared()));
                disconnect(data.get(), SIGNAL(samples_added(QObject*, uint64_t, uint64_t)),
                        this, SLOT(on_samples_added(QObject*, uint64_t, uint64_t)));
+
+               if (channel_type_ == AnalogChannel) {
+                       shared_ptr<Analog> analog = analog_data();
+                       assert(analog);
+
+                       disconnect(analog.get(), SIGNAL(min_max_changed(float, float)),
+                               this, SLOT(on_min_max_changed(float, float)));
+               }
        }
 
        data_ = data;
@@ -139,6 +174,14 @@ void SignalBase::set_data(shared_ptr<pv::data::SignalData> data)
                        this, SLOT(on_samples_cleared()));
                connect(data.get(), SIGNAL(samples_added(QObject*, uint64_t, uint64_t)),
                        this, SLOT(on_samples_added(QObject*, uint64_t, uint64_t)));
+
+               if (channel_type_ == AnalogChannel) {
+                       shared_ptr<Analog> analog = analog_data();
+                       assert(analog);
+
+                       connect(analog.get(), SIGNAL(min_max_changed(float, float)),
+                               this, SLOT(on_min_max_changed(float, float)));
+               }
        }
 }
 
@@ -159,59 +202,247 @@ shared_ptr<data::Logic> SignalBase::logic_data() const
        if (channel_type_ == LogicChannel)
                result = dynamic_pointer_cast<Logic>(data_);
 
-       if (((conversion_type_ == A2LConversionByTreshold) ||
+       if (((conversion_type_ == A2LConversionByThreshold) ||
                (conversion_type_ == A2LConversionBySchmittTrigger)))
                result = dynamic_pointer_cast<Logic>(converted_data_);
 
        return result;
 }
 
+bool SignalBase::segment_is_complete(uint32_t segment_id) const
+{
+       bool result = true;
+
+       if (channel_type_ == AnalogChannel)
+       {
+               shared_ptr<Analog> data = dynamic_pointer_cast<Analog>(data_);
+               auto segments = data->analog_segments();
+               try {
+                       result = segments.at(segment_id)->is_complete();
+               } catch (out_of_range&) {
+                       // Do nothing
+               }
+       }
+
+       if (channel_type_ == LogicChannel)
+       {
+               shared_ptr<Logic> data = dynamic_pointer_cast<Logic>(data_);
+               auto segments = data->logic_segments();
+               try {
+                       result = segments.at(segment_id)->is_complete();
+               } catch (out_of_range&) {
+                       // Do nothing
+               }
+       }
+
+       return result;
+}
+
+bool SignalBase::has_samples() const
+{
+       bool result = false;
+
+       if (channel_type_ == AnalogChannel)
+       {
+               shared_ptr<Analog> data = dynamic_pointer_cast<Analog>(data_);
+               if (data) {
+                       auto segments = data->analog_segments();
+                       if ((segments.size() > 0) && (segments.front()->get_sample_count() > 0))
+                               result = true;
+               }
+       }
+
+       if (channel_type_ == LogicChannel)
+       {
+               shared_ptr<Logic> data = dynamic_pointer_cast<Logic>(data_);
+               if (data) {
+                       auto segments = data->logic_segments();
+                       if ((segments.size() > 0) && (segments.front()->get_sample_count() > 0))
+                               result = true;
+               }
+       }
+
+       return result;
+}
+
+double SignalBase::get_samplerate() const
+{
+       if (channel_type_ == AnalogChannel)
+       {
+               shared_ptr<Analog> data = dynamic_pointer_cast<Analog>(data_);
+               if (data)
+                       return data->get_samplerate();
+       }
+
+       if (channel_type_ == LogicChannel)
+       {
+               shared_ptr<Logic> data = dynamic_pointer_cast<Logic>(data_);
+               if (data)
+                       return data->get_samplerate();
+       }
+
+       // Default samplerate is 1 Hz
+       return 1.0;
+}
+
+SignalBase::ConversionType SignalBase::get_conversion_type() const
+{
+       return conversion_type_;
+}
+
 void SignalBase::set_conversion_type(ConversionType t)
 {
        if (conversion_type_ != NoConversion) {
-               // Wait for the currently ongoing conversion to finish
-               if (conversion_thread_.joinable())
-                       conversion_thread_.join();
+               stop_conversion();
 
                // Discard converted data
                converted_data_.reset();
+               samples_cleared();
        }
 
        conversion_type_ = t;
 
-       if ((channel_type_ == AnalogChannel) &&
-               ((conversion_type_ == A2LConversionByTreshold) ||
-               (conversion_type_ == A2LConversionBySchmittTrigger))) {
+       // Re-create an empty container
+       // so that the signal is recognized as providing logic data
+       // and thus can be assigned to a decoder
+       if (conversion_is_a2l())
+               if (!converted_data_)
+                       converted_data_ = make_shared<Logic>(1);  // Contains only one channel
+
+       start_conversion();
+
+       conversion_type_changed(t);
+}
+
+map<QString, QVariant> SignalBase::get_conversion_options() const
+{
+       return conversion_options_;
+}
+
+bool SignalBase::set_conversion_option(QString key, QVariant value)
+{
+       QVariant old_value;
+
+       auto key_iter = conversion_options_.find(key);
+       if (key_iter != conversion_options_.end())
+               old_value = key_iter->second;
+
+       conversion_options_[key] = value;
 
-               shared_ptr<Analog> analog_data = dynamic_pointer_cast<Analog>(data_);
+       return (value != old_value);
+}
+
+vector<double> SignalBase::get_conversion_thresholds(const ConversionType t,
+       const bool always_custom) const
+{
+       vector<double> result;
+       ConversionType conv_type = t;
+       ConversionPreset preset;
+
+       // Use currently active conversion if no conversion type was supplied
+       if (conv_type == NoConversion)
+               conv_type = conversion_type_;
+
+       if (always_custom)
+               preset = NoPreset;
+       else
+               preset = get_current_conversion_preset();
+
+       if (conv_type == A2LConversionByThreshold) {
+               double thr = 0;
+
+               if (preset == NoPreset) {
+                       auto thr_iter = conversion_options_.find("threshold_value");
+                       if (thr_iter != conversion_options_.end())
+                               thr = (thr_iter->second).toDouble();
+               }
+
+               if (preset == DynamicPreset)
+                       thr = (min_value_ + max_value_) * 0.5;  // middle between min and max
+
+               if ((int)preset == 1) thr = 0.9;
+               if ((int)preset == 2) thr = 1.8;
+               if ((int)preset == 3) thr = 2.5;
+               if ((int)preset == 4) thr = 1.5;
 
-               if (analog_data->analog_segments().size() > 0) {
-                       AnalogSegment *asegment = analog_data->analog_segments().front().get();
+               result.push_back(thr);
+       }
+
+       if (conv_type == A2LConversionBySchmittTrigger) {
+               double thr_lo = 0, thr_hi = 0;
+
+               if (preset == NoPreset) {
+                       auto thr_lo_iter = conversion_options_.find("threshold_value_low");
+                       if (thr_lo_iter != conversion_options_.end())
+                               thr_lo = (thr_lo_iter->second).toDouble();
+
+                       auto thr_hi_iter = conversion_options_.find("threshold_value_high");
+                       if (thr_hi_iter != conversion_options_.end())
+                               thr_hi = (thr_hi_iter->second).toDouble();
+               }
 
-                       // Begin conversion of existing sample data
-                       // TODO Support for multiple segments is missing
-                       on_samples_added(asegment, 0, 0);
+               if (preset == DynamicPreset) {
+                       const double amplitude = max_value_ - min_value_;
+                       const double center = min_value_ + (amplitude / 2);
+                       thr_lo = center - (amplitude * 0.15);  // 15% margin
+                       thr_hi = center + (amplitude * 0.15);  // 15% margin
                }
+
+               if ((int)preset == 1) { thr_lo = 0.3; thr_hi = 1.2; }
+               if ((int)preset == 2) { thr_lo = 0.7; thr_hi = 2.5; }
+               if ((int)preset == 3) { thr_lo = 1.3; thr_hi = 3.7; }
+               if ((int)preset == 4) { thr_lo = 0.8; thr_hi = 2.0; }
+
+               result.push_back(thr_lo);
+               result.push_back(thr_hi);
        }
 
-       conversion_type_changed(t);
+       return result;
 }
 
-#ifdef ENABLE_DECODE
-bool SignalBase::is_decode_signal() const
+vector< pair<QString, int> > SignalBase::get_conversion_presets() const
 {
-       return (decoder_stack_ != nullptr);
+       vector< pair<QString, int> > presets;
+
+       if (conversion_type_ == A2LConversionByThreshold) {
+               // Source: http://www.interfacebus.com/voltage_threshold.html
+               presets.emplace_back(tr("Signal average"), 0);
+               presets.emplace_back(tr("0.9V (for 1.8V CMOS)"), 1);
+               presets.emplace_back(tr("1.8V (for 3.3V CMOS)"), 2);
+               presets.emplace_back(tr("2.5V (for 5.0V CMOS)"), 3);
+               presets.emplace_back(tr("1.5V (for TTL)"), 4);
+       }
+
+       if (conversion_type_ == A2LConversionBySchmittTrigger) {
+               // Source: http://www.interfacebus.com/voltage_threshold.html
+               presets.emplace_back(tr("Signal average +/- 15%"), 0);
+               presets.emplace_back(tr("0.3V/1.2V (for 1.8V CMOS)"), 1);
+               presets.emplace_back(tr("0.7V/2.5V (for 3.3V CMOS)"), 2);
+               presets.emplace_back(tr("1.3V/3.7V (for 5.0V CMOS)"), 3);
+               presets.emplace_back(tr("0.8V/2.0V (for TTL)"), 4);
+       }
+
+       return presets;
 }
 
-shared_ptr<pv::data::DecoderStack> SignalBase::decoder_stack() const
+SignalBase::ConversionPreset SignalBase::get_current_conversion_preset() const
 {
-       return decoder_stack_;
+       auto preset = conversion_options_.find("preset");
+       if (preset != conversion_options_.end())
+               return (ConversionPreset)((preset->second).toInt());
+
+       return DynamicPreset;
 }
 
-void SignalBase::set_decoder_stack(shared_ptr<pv::data::DecoderStack>
-       decoder_stack)
+void SignalBase::set_conversion_preset(ConversionPreset id)
 {
-       decoder_stack_ = decoder_stack;
+       conversion_options_["preset"] = (int)id;
+}
+
+#ifdef ENABLE_DECODE
+bool SignalBase::is_decode_signal() const
+{
+       return (channel_type_ == DecodeChannel);
 }
 #endif
 
@@ -219,134 +450,291 @@ void SignalBase::save_settings(QSettings &settings) const
 {
        settings.setValue("name", name());
        settings.setValue("enabled", enabled());
-       settings.setValue("colour", colour());
+       settings.setValue("color", color().rgba());
        settings.setValue("conversion_type", (int)conversion_type_);
-}
 
-void SignalBase::restore_settings(QSettings &settings)
-{
-       set_name(settings.value("name").toString());
-       set_enabled(settings.value("enabled").toBool());
-       set_colour(settings.value("colour").value<QColor>());
-       set_conversion_type((ConversionType)settings.value("conversion_type").toInt());
+       settings.setValue("conv_options", (int)(conversion_options_.size()));
+       int i = 0;
+       for (auto& kvp : conversion_options_) {
+               settings.setValue(QString("conv_option%1_key").arg(i), kvp.first);
+               settings.setValue(QString("conv_option%1_value").arg(i), kvp.second);
+               i++;
+       }
 }
 
-uint8_t SignalBase::convert_a2l_threshold(float threshold, float value)
+void SignalBase::restore_settings(QSettings &settings)
 {
-       return (value >= threshold) ? 1 : 0;
-}
+       if (settings.contains("name"))
+               set_name(settings.value("name").toString());
 
-uint8_t SignalBase::convert_a2l_schmitt_trigger(float lo_thr, float hi_thr,
-       float value, uint8_t &state)
-{
-       if (value < lo_thr)
-               state = 0;
-       else if (value > hi_thr)
-               state = 1;
+       if (settings.contains("enabled"))
+               set_enabled(settings.value("enabled").toBool());
 
-       return state;
-}
+       if (settings.contains("color")) {
+               QVariant value = settings.value("color");
 
-void SignalBase::conversion_thread_proc(QObject* segment, uint64_t start_sample,
-       uint64_t end_sample)
-{
-       const uint64_t block_size = 4096;
+               // Workaround for Qt QColor serialization bug on OSX
+               if ((QMetaType::Type)(value.type()) == QMetaType::QColor)
+                       set_color(value.value<QColor>());
+               else
+                       set_color(QColor::fromRgba(value.value<uint32_t>()));
 
-       // TODO Support for multiple segments is missing
+               // A color with an alpha value of 0 makes the signal marker invisible
+               if (color() == QColor(0, 0, 0, 0))
+                       set_color(Qt::gray);
+       }
 
-       if ((channel_type_ == AnalogChannel) &&
-               ((conversion_type_ == A2LConversionByTreshold) ||
-               (conversion_type_ == A2LConversionBySchmittTrigger))) {
+       if (settings.contains("conversion_type"))
+               set_conversion_type((ConversionType)settings.value("conversion_type").toInt());
 
-               AnalogSegment *asegment = qobject_cast<AnalogSegment*>(segment);
+       int conv_options = 0;
+       if (settings.contains("conv_options"))
+               conv_options = settings.value("conv_options").toInt();
 
-               // Create the logic data container if needed
-               shared_ptr<Logic> logic_data;
-               if (!converted_data_) {
-                       logic_data = make_shared<Logic>(1);  // Contains only one channel
-                       converted_data_ = logic_data;
-               } else
-                        logic_data = dynamic_pointer_cast<Logic>(converted_data_);
+       if (conv_options)
+               for (int i = 0; i < conv_options; i++) {
+                       const QString key_id = QString("conv_option%1_key").arg(i);
+                       const QString value_id = QString("conv_option%1_value").arg(i);
 
-               // Create the initial logic data segment if needed
-               if (logic_data->segments().size() == 0) {
-                       shared_ptr<LogicSegment> lsegment =
-                               make_shared<LogicSegment>(*logic_data.get(), 1, asegment->samplerate());
-                       logic_data->push_segment(lsegment);
+                       if (settings.contains(key_id) && settings.contains(value_id))
+                               conversion_options_[settings.value(key_id).toString()] =
+                                       settings.value(value_id);
                }
+}
 
-               LogicSegment *lsegment = dynamic_cast<LogicSegment*>(logic_data->segments().front().get());
+bool SignalBase::conversion_is_a2l() const
+{
+       return ((channel_type_ == AnalogChannel) &&
+               ((conversion_type_ == A2LConversionByThreshold) ||
+               (conversion_type_ == A2LConversionBySchmittTrigger)));
+}
 
-               // start_sample=end_sample=0 means we need to figure out the unprocessed range
-               if ((start_sample == 0) && (end_sample == 0)) {
-                       start_sample = lsegment->get_sample_count();
-                       end_sample = asegment->get_sample_count();
-               }
+void SignalBase::convert_single_segment_range(AnalogSegment *asegment,
+       LogicSegment *lsegment, uint64_t start_sample, uint64_t end_sample)
+{
+       if (end_sample > start_sample) {
+               tie(min_value_, max_value_) = asegment->get_min_max();
+
+               // Create sigrok::Analog instance
+               float *asamples = new float[ConversionBlockSize];
+               uint8_t *lsamples = new uint8_t[ConversionBlockSize];
 
-               if (start_sample == end_sample)
-                       return;  // Nothing to do
+               vector<shared_ptr<sigrok::Channel> > channels;
+               channels.push_back(channel_);
 
-               float min_v, max_v;
-               tie(min_v, max_v) = asegment->get_min_max();
+               vector<const sigrok::QuantityFlag*> mq_flags;
+               const sigrok::Quantity * const mq = sigrok::Quantity::VOLTAGE;
+               const sigrok::Unit * const unit = sigrok::Unit::VOLT;
 
-               vector<uint8_t> lsamples;
-               lsamples.reserve(block_size);
+               shared_ptr<sigrok::Packet> packet =
+                       Session::sr_context->create_analog_packet(channels,
+                       asamples, ConversionBlockSize, mq, unit, mq_flags);
 
+               shared_ptr<sigrok::Analog> analog =
+                       dynamic_pointer_cast<sigrok::Analog>(packet->payload());
+
+               // Convert
                uint64_t i = start_sample;
 
-               if (conversion_type_ == A2LConversionByTreshold) {
-                       const float threshold = (min_v + max_v) * 0.5;  // middle between min and max
+               if (conversion_type_ == A2LConversionByThreshold) {
+                       const double threshold = get_conversion_thresholds()[0];
 
                        // Convert as many sample blocks as we can
-                       while ((end_sample - i) > block_size) {
-                               const float* asamples = asegment->get_samples(i, i + block_size);
-                               for (uint32_t j = 0; j < block_size; j++)
-                                       lsamples.push_back(convert_a2l_threshold(threshold, asamples[j]));
-                               lsegment->append_payload(lsamples.data(), lsamples.size());
-                               i += block_size;
-                               lsamples.clear();
-                               delete[] asamples;
+                       while ((end_sample - i) > ConversionBlockSize) {
+                               asegment->get_samples(i, i + ConversionBlockSize, asamples);
+
+                               shared_ptr<sigrok::Logic> logic =
+                                       analog->get_logic_via_threshold(threshold, lsamples);
+
+                               lsegment->append_payload(logic->data_pointer(), logic->data_length());
+                               samples_added(lsegment->segment_id(), i, i + ConversionBlockSize);
+                               i += ConversionBlockSize;
                        }
 
-                       // Convert remaining samples
-                       const float* asamples = asegment->get_samples(i, end_sample);
-                       for (uint32_t j = 0; j < (end_sample - i); j++)
-                               lsamples.push_back(convert_a2l_threshold(threshold, asamples[j]));
-                       lsegment->append_payload(lsamples.data(), lsamples.size());
-                       delete[] asamples;
+                       // Re-create sigrok::Analog and convert remaining samples
+                       packet = Session::sr_context->create_analog_packet(channels,
+                               asamples, end_sample - i, mq, unit, mq_flags);
 
-                       samples_added(lsegment, start_sample, end_sample);
+                       analog = dynamic_pointer_cast<sigrok::Analog>(packet->payload());
+
+                       asegment->get_samples(i, end_sample, asamples);
+                       shared_ptr<sigrok::Logic> logic =
+                               analog->get_logic_via_threshold(threshold, lsamples);
+                       lsegment->append_payload(logic->data_pointer(), logic->data_length());
+                       samples_added(lsegment->segment_id(), i, end_sample);
                }
 
                if (conversion_type_ == A2LConversionBySchmittTrigger) {
-                       const float amplitude = max_v - min_v;
-                       const float lo_thr = min_v + (amplitude * 0.1);  // 10% above min
-                       const float hi_thr = max_v - (amplitude * 0.1);  // 10% below max
+                       const vector<double> thresholds = get_conversion_thresholds();
+                       const double lo_thr = thresholds[0];
+                       const double hi_thr = thresholds[1];
+
                        uint8_t state = 0;  // TODO Use value of logic sample n-1 instead of 0
 
                        // Convert as many sample blocks as we can
-                       while ((end_sample - i) > block_size) {
-                               const float* asamples = asegment->get_samples(i, i + block_size);
-                               for (uint32_t j = 0; j < block_size; j++)
-                                       lsamples.push_back(convert_a2l_schmitt_trigger(lo_thr, hi_thr, asamples[j], state));
-                               lsegment->append_payload(lsamples.data(), lsamples.size());
-                               i += block_size;
-                               lsamples.clear();
-                               delete[] asamples;
+                       while ((end_sample - i) > ConversionBlockSize) {
+                               asegment->get_samples(i, i + ConversionBlockSize, asamples);
+
+                               shared_ptr<sigrok::Logic> logic =
+                                       analog->get_logic_via_schmitt_trigger(lo_thr, hi_thr,
+                                               &state, lsamples);
+
+                               lsegment->append_payload(logic->data_pointer(), logic->data_length());
+                               samples_added(lsegment->segment_id(), i, i + ConversionBlockSize);
+                               i += ConversionBlockSize;
                        }
 
-                       // Convert remaining samples
-                       const float* asamples = asegment->get_samples(i, end_sample);
-                       for (uint32_t j = 0; j < (end_sample - i); j++)
-                               lsamples.push_back(convert_a2l_schmitt_trigger(lo_thr, hi_thr, asamples[j], state));
-                       lsegment->append_payload(lsamples.data(), lsamples.size());
-                       delete[] asamples;
+                       // Re-create sigrok::Analog and convert remaining samples
+                       packet = Session::sr_context->create_analog_packet(channels,
+                               asamples, end_sample - i, mq, unit, mq_flags);
+
+                       analog = dynamic_pointer_cast<sigrok::Analog>(packet->payload());
 
-                       samples_added(lsegment, start_sample, end_sample);
+                       asegment->get_samples(i, end_sample, asamples);
+                       shared_ptr<sigrok::Logic> logic =
+                               analog->get_logic_via_schmitt_trigger(lo_thr, hi_thr,
+                                       &state, lsamples);
+                       lsegment->append_payload(logic->data_pointer(), logic->data_length());
+                       samples_added(lsegment->segment_id(), i, end_sample);
                }
+
+               // If acquisition is ongoing, start-/endsample may have changed
+               end_sample = asegment->get_sample_count();
+
+               delete[] lsamples;
+               delete[] asamples;
        }
 }
 
+void SignalBase::convert_single_segment(AnalogSegment *asegment, LogicSegment *lsegment)
+{
+       uint64_t start_sample, end_sample, old_end_sample;
+       start_sample = end_sample = 0;
+       bool complete_state, old_complete_state;
+
+       start_sample = lsegment->get_sample_count();
+       end_sample = asegment->get_sample_count();
+       complete_state = asegment->is_complete();
+
+       // Don't do anything if the segment is still being filled and the sample count is too small
+       if ((!complete_state) && (end_sample - start_sample < ConversionBlockSize))
+               return;
+
+       do {
+               convert_single_segment_range(asegment, lsegment, start_sample, end_sample);
+
+               old_end_sample = end_sample;
+               old_complete_state = complete_state;
+
+               start_sample = lsegment->get_sample_count();
+               end_sample = asegment->get_sample_count();
+               complete_state = asegment->is_complete();
+
+               // If the segment has been incomplete when we were called and has been
+               // completed in the meanwhile, we convert the remaining samples as well.
+               // Also, if a sufficient number of samples was added in the meanwhile,
+               // we do another round of sample conversion.
+       } while ((complete_state != old_complete_state) ||
+               (end_sample - old_end_sample >= ConversionBlockSize));
+}
+
+void SignalBase::conversion_thread_proc()
+{
+       shared_ptr<Analog> analog_data;
+
+       if (conversion_is_a2l()) {
+               analog_data = dynamic_pointer_cast<Analog>(data_);
+
+               if (analog_data->analog_segments().size() == 0) {
+                       unique_lock<mutex> input_lock(conversion_input_mutex_);
+                       conversion_input_cond_.wait(input_lock);
+               }
+
+       } else
+               // Currently, we only handle A2L conversions
+               return;
+
+       // If we had to wait for input data, we may have been notified to terminate
+       if (conversion_interrupt_)
+               return;
+
+       uint32_t segment_id = 0;
+
+       AnalogSegment *asegment = analog_data->analog_segments().front().get();
+       assert(asegment);
+
+       const shared_ptr<Logic> logic_data = dynamic_pointer_cast<Logic>(converted_data_);
+       assert(logic_data);
+
+       // Create the initial logic data segment if needed
+       if (logic_data->logic_segments().size() == 0) {
+               shared_ptr<LogicSegment> new_segment =
+                       make_shared<LogicSegment>(*logic_data.get(), 0, 1, asegment->samplerate());
+               logic_data->push_segment(new_segment);
+       }
+
+       LogicSegment *lsegment = logic_data->logic_segments().front().get();
+       assert(lsegment);
+
+       do {
+               convert_single_segment(asegment, lsegment);
+
+               // Only advance to next segment if the current input segment is complete
+               if (asegment->is_complete() &&
+                       analog_data->analog_segments().size() > logic_data->logic_segments().size()) {
+                       // There are more segments to process
+                       segment_id++;
+
+                       try {
+                               asegment = analog_data->analog_segments().at(segment_id).get();
+                       } catch (out_of_range&) {
+                               qDebug() << "Conversion error for" << name() << ": no analog segment" \
+                                       << segment_id << ", segments size is" << analog_data->analog_segments().size();
+                               return;
+                       }
+
+                       shared_ptr<LogicSegment> new_segment = make_shared<LogicSegment>(
+                               *logic_data.get(), segment_id, 1, asegment->samplerate());
+                       logic_data->push_segment(new_segment);
+
+                       lsegment = logic_data->logic_segments().back().get();
+               } else {
+                       // No more samples/segments to process, wait for data or interrupt
+                       if (!conversion_interrupt_) {
+                               unique_lock<mutex> input_lock(conversion_input_mutex_);
+                               conversion_input_cond_.wait(input_lock);
+                       }
+               }
+       } while (!conversion_interrupt_);
+}
+
+void SignalBase::start_conversion(bool delayed_start)
+{
+       if (delayed_start) {
+               delayed_conversion_starter_.start();
+               return;
+       }
+
+       stop_conversion();
+
+       if (converted_data_)
+               converted_data_->clear();
+       samples_cleared();
+
+       conversion_interrupt_ = false;
+       conversion_thread_ = std::thread(
+               &SignalBase::conversion_thread_proc, this);
+}
+
+void SignalBase::stop_conversion()
+{
+       // Stop conversion so we can restart it from the beginning
+       conversion_interrupt_ = true;
+       conversion_input_cond_.notify_one();
+       if (conversion_thread_.joinable())
+               conversion_thread_.join();
+}
+
 void SignalBase::on_samples_cleared()
 {
        if (converted_data_)
@@ -359,44 +747,43 @@ void SignalBase::on_samples_added(QObject* segment, uint64_t start_sample,
        uint64_t end_sample)
 {
        if (conversion_type_ != NoConversion) {
-
-               // Wait for the currently ongoing conversion to finish
-               if (conversion_thread_.joinable())
-                       conversion_thread_.join();
-
-               conversion_thread_ = std::thread(
-                       &SignalBase::conversion_thread_proc, this,
-                       segment, start_sample, end_sample);
+               if (conversion_thread_.joinable()) {
+                       // Notify the conversion thread since it's running
+                       conversion_input_cond_.notify_one();
+               } else {
+                       // Start the conversion thread unless the delay timer is running
+                       if (!delayed_conversion_starter_.isActive())
+                               start_conversion();
+               }
        }
 
-       samples_added(segment, start_sample, end_sample);
+       data::Segment* s = qobject_cast<data::Segment*>(segment);
+       samples_added(s->segment_id(), start_sample, end_sample);
 }
 
-void SignalBase::on_capture_state_changed(int state)
+void SignalBase::on_min_max_changed(float min, float max)
 {
-       return;
-       if (state == Session::Stopped) {
-               // Make sure that all data is converted
-
-               if ((channel_type_ == AnalogChannel) &&
-                       ((conversion_type_ == A2LConversionByTreshold) ||
-                       (conversion_type_ == A2LConversionBySchmittTrigger))) {
+       // Restart conversion if one is enabled and uses a calculated threshold
+       if ((conversion_type_ != NoConversion) &&
+               (get_current_conversion_preset() == DynamicPreset))
+               start_conversion(true);
 
-                       shared_ptr<Analog> analog_data = dynamic_pointer_cast<Analog>(data_);
-
-                       if (analog_data->analog_segments().size() > 0) {
-                               // TODO Support for multiple segments is missing
-                               AnalogSegment *asegment = analog_data->analog_segments().front().get();
-
-                               if (conversion_thread_.joinable())
-                                       conversion_thread_.join();
+       min_max_changed(min, max);
+}
 
-                               conversion_thread_ = std::thread(
-                                       &SignalBase::conversion_thread_proc, this, asegment, 0, 0);
-                       }
-               }
+void SignalBase::on_capture_state_changed(int state)
+{
+       if (state == Session::Running) {
+               // Restart conversion if one is enabled
+               if (conversion_type_ != NoConversion)
+                       start_conversion();
        }
 }
 
+void SignalBase::on_delayed_conversion_start()
+{
+       start_conversion();
+}
+
 } // namespace data
 } // namespace pv
index e6726c5aec160d124b538e07ea04696ab721f629..c3e0d3d64ef56d02b289451c7cb3d54ca89c44f9 100644 (file)
 #ifndef PULSEVIEW_PV_DATA_SIGNALBASE_HPP
 #define PULSEVIEW_PV_DATA_SIGNALBASE_HPP
 
+#include <atomic>
+#include <condition_variable>
 #include <thread>
+#include <vector>
 
 #include <QColor>
 #include <QObject>
 #include <QSettings>
 #include <QString>
+#include <QTimer>
+#include <QVariant>
 
 #include <libsigrokcxx/libsigrokcxx.hpp>
 
+using std::atomic;
+using std::condition_variable;
+using std::map;
+using std::mutex;
+using std::pair;
 using std::shared_ptr;
+using std::vector;
 
 namespace sigrok {
 class Channel;
@@ -40,8 +51,10 @@ namespace pv {
 namespace data {
 
 class Analog;
+class AnalogSegment;
 class DecoderStack;
 class Logic;
+class LogicSegment;
 class SignalData;
 
 class SignalBase : public QObject
@@ -50,21 +63,32 @@ class SignalBase : public QObject
 
 public:
        enum ChannelType {
-               AnalogChannel = 1,
-               LogicChannel,
-               DecodeChannel,
-               A2LChannel,  // Analog converted to logic, joint representation
-               MathChannel
+               AnalogChannel = 1, ///< Analog data
+               LogicChannel,  ///< Logic data
+               DecodeChannel, ///< Protocol Decoder channel using libsigrokdecode
+               MathChannel    ///< Virtual channel generated by math operations
        };
 
        enum ConversionType {
                NoConversion = 0,
-               A2LConversionByTreshold = 1,
+               A2LConversionByThreshold = 1,
                A2LConversionBySchmittTrigger = 2
        };
 
+       /**
+        * Conversion presets range from -1 to n, where 1..n are dependent on
+        * the conversion these presets apply to. -1 and 0 have fixed meanings,
+        * however.
+        */
+       enum ConversionPreset {
+               NoPreset = -1,     ///< Conversion uses custom values
+               DynamicPreset = 0  ///< Conversion uses calculated values
+       };
+
 private:
-       static const int ColourBGAlpha;
+       static const int ColorBGAlpha;
+       static const uint64_t ConversionBlockSize;
+       static const uint32_t ConversionDelay;
 
 public:
        SignalBase(shared_ptr<sigrok::Channel> channel, ChannelType channel_type);
@@ -93,10 +117,19 @@ public:
        ChannelType type() const;
 
        /**
-        * Gets the index number of this channel.
+        * Gets the index number of this channel, i.e. a unique ID assigned by
+        * the device driver.
         */
        unsigned int index() const;
 
+       /**
+        * Returns which bit of a given sample for this signal represents the
+        * signal itself. This is relevant for compound signals like logic,
+        * rather meaningless for everything else but provided in case there
+        * is a conversion active that provides a digital signal using bit #0.
+        */
+       unsigned int logic_bit_index() const;
+
        /**
         * Gets the name of this signal.
         */
@@ -107,25 +140,31 @@ public:
         */
        QString internal_name() const;
 
+       /**
+        * Produces a string for this signal that can be used for display,
+        * i.e. it contains one or both of the signal/internal names.
+        */
+       QString display_name() const;
+
        /**
         * Sets the name of the signal.
         */
        virtual void set_name(QString name);
 
        /**
-        * Get the colour of the signal.
+        * Get the color of the signal.
         */
-       QColor colour() const;
+       QColor color() const;
 
        /**
-        * Set the colour of the signal.
+        * Set the color of the signal.
         */
-       void set_colour(QColor colour);
+       void set_color(QColor color);
 
        /**
-        * Get the background colour of the signal.
+        * Get the background color of the signal.
         */
-       QColor bgcolour() const;
+       QColor bgcolor() const;
 
        /**
         * Sets the internal data object.
@@ -142,68 +181,169 @@ public:
         */
        shared_ptr<pv::data::Logic> logic_data() const;
 
+       /**
+        * Determines whether a given segment is complete (i.e. end-of-frame has
+        * been seen). It only considers the original data, not the converted data.
+        */
+       bool segment_is_complete(uint32_t segment_id) const;
+
+       /**
+        * Determines whether this signal has any sample data at all.
+        */
+       bool has_samples() const;
+
+       /**
+        * Returns the sample rate for this signal.
+        */
+       double get_samplerate() const;
+
+       /**
+        * Queries the kind of conversion performed on this channel.
+        */
+       ConversionType get_conversion_type() const;
+
        /**
         * Changes the kind of conversion performed on this channel.
+        *
+        * Restarts the conversion.
         */
        void set_conversion_type(ConversionType t);
 
-#ifdef ENABLE_DECODE
-       bool is_decode_signal() const;
+       /**
+        * Returns all currently known conversion options
+        */
+       map<QString, QVariant> get_conversion_options() const;
 
-       shared_ptr<pv::data::DecoderStack> decoder_stack() const;
+       /**
+        * Sets the value of a particular conversion option
+        * Note: it is not checked whether the option is valid for the
+        * currently conversion. If it's not, it will be silently ignored.
+        *
+        * Does not restart the conversion.
+        *
+        * @return true if the value is different from before, false otherwise
+        */
+       bool set_conversion_option(QString key, QVariant value);
+
+       /**
+        * Returns the threshold(s) used for conversions, if applicable.
+        * The resulting thresholds are given for the chosen conversion, so you
+        * can query thresholds also for conversions which aren't currently active.
+        *
+        * If you want the thresholds for the currently active conversion,
+        * call it either with NoConversion or no parameter.
+        *
+        * @param t the type of conversion to obtain the thresholds for, leave
+        *          empty or use NoConversion if you want to query the currently
+        *          used conversion
+        *
+        * @param always_custom ignore the currently selected preset and always
+        *        return the custom values for this conversion, using 0 if those
+        *        aren't set
+        *
+        * @return a list of threshold(s) used by the chosen conversion
+        */
+       vector<double> get_conversion_thresholds(
+               const ConversionType t = NoConversion, const bool always_custom=false) const;
+
+       /**
+        * Provides all conversion presets available for the currently active
+        * conversion.
+        *
+        * @return a list of description/ID pairs for each preset
+        */
+       vector<pair<QString, int> > get_conversion_presets() const;
+
+       /**
+        * Determines the ID of the currently used conversion preset, which is only
+        * valid for the currently available conversion presets. It is therefore
+        * suggested to call @ref get_conversion_presets right before calling this.
+        *
+        * @return the ID of the currently used conversion preset. -1 if no preset
+        *         is used. In that case, a user setting is used instead.
+        */
+       ConversionPreset get_current_conversion_preset() const;
+
+       /**
+        * Sets the conversion preset to be used.
+        *
+        * Does not restart the conversion.
+        *
+        * @param id the id of the preset to use
+        */
+       void set_conversion_preset(ConversionPreset id);
 
-       void set_decoder_stack(shared_ptr<pv::data::DecoderStack> decoder_stack);
+#ifdef ENABLE_DECODE
+       bool is_decode_signal() const;
 #endif
 
-       void save_settings(QSettings &settings) const;
+       virtual void save_settings(QSettings &settings) const;
+
+       virtual void restore_settings(QSettings &settings);
 
-       void restore_settings(QSettings &settings);
+       void start_conversion(bool delayed_start=false);
 
 private:
+       bool conversion_is_a2l() const;
+
        uint8_t convert_a2l_threshold(float threshold, float value);
        uint8_t convert_a2l_schmitt_trigger(float lo_thr, float hi_thr,
                float value, uint8_t &state);
 
-       void conversion_thread_proc(QObject* segment, uint64_t start_sample,
-               uint64_t end_sample);
+       void convert_single_segment_range(AnalogSegment *asegment,
+               LogicSegment *lsegment, uint64_t start_sample, uint64_t end_sample);
+       void convert_single_segment(pv::data::AnalogSegment *asegment,
+               pv::data::LogicSegment *lsegment);
+       void conversion_thread_proc();
+
+       void stop_conversion();
 
 Q_SIGNALS:
        void enabled_changed(const bool &value);
 
        void name_changed(const QString &name);
 
-       void colour_changed(const QColor &colour);
+       void color_changed(const QColor &color);
 
        void conversion_type_changed(const ConversionType t);
 
        void samples_cleared();
 
-       void samples_added(QObject* segment, uint64_t start_sample,
+       void samples_added(uint64_t segment_id, uint64_t start_sample,
                uint64_t end_sample);
 
+       void min_max_changed(float min, float max);
+
 private Q_SLOTS:
        void on_samples_cleared();
 
        void on_samples_added(QObject* segment, uint64_t start_sample,
                uint64_t end_sample);
 
+       void on_min_max_changed(float min, float max);
+
        void on_capture_state_changed(int state);
 
-private:
+       void on_delayed_conversion_start();
+
+protected:
        shared_ptr<sigrok::Channel> channel_;
        ChannelType channel_type_;
        shared_ptr<pv::data::SignalData> data_;
        shared_ptr<pv::data::SignalData> converted_data_;
-       int conversion_type_;
+       ConversionType conversion_type_;
+       map<QString, QVariant> conversion_options_;
 
-#ifdef ENABLE_DECODE
-       shared_ptr<pv::data::DecoderStack> decoder_stack_;
-#endif
+       float min_value_, max_value_;
 
        std::thread conversion_thread_;
+       atomic<bool> conversion_interrupt_;
+       mutex conversion_input_mutex_;
+       condition_variable conversion_input_cond_;
+       QTimer delayed_conversion_starter_;
 
        QString internal_name_, name_;
-       QColor colour_, bgcolour_;
+       QColor color_, bgcolor_;
 };
 
 } // namespace data
index a90204f06f23bd379abd78278bb6bcf0bbc41455..5168fee8e4b58fb6c75dec7afe26364a4709d47a 100644 (file)
@@ -45,9 +45,13 @@ public:
 public:
        virtual vector< shared_ptr<Segment> > segments() const = 0;
 
+       virtual uint32_t get_segment_count() const = 0;
+
        virtual void clear() = 0;
 
        virtual uint64_t max_sample_count() const = 0;
+
+       virtual double get_samplerate() const = 0;
 };
 
 } // namespace data
index 540f88150e4cf117be1ca1fa12762e8b734f3943..5090b480e3d49adc41bc4e861102ce9a6e6bbb8b 100644 (file)
 #include <libsigrokcxx/libsigrokcxx.hpp>
 
 #include <QApplication>
+#include <QDebug>
 #include <QObject>
 #include <QProgressDialog>
 
 #include <boost/filesystem.hpp>
 
 #include <pv/devices/hardwaredevice.hpp>
+#include <pv/util.hpp>
 
 using std::bind;
 using std::list;
@@ -45,6 +47,7 @@ using std::placeholders::_2;
 using std::shared_ptr;
 using std::string;
 using std::unique_ptr;
+using std::vector;
 
 using Glib::VariantBase;
 
@@ -54,40 +57,96 @@ using sigrok::Driver;
 
 namespace pv {
 
-DeviceManager::DeviceManager(shared_ptr<Context> context) :
+DeviceManager::DeviceManager(shared_ptr<Context> context,
+       std::string driver, bool do_scan) :
        context_(context)
 {
        unique_ptr<QProgressDialog> progress(new QProgressDialog("",
-               QObject::tr("Cancel"), 0, context->drivers().size()));
+               QObject::tr("Cancel"), 0, context->drivers().size() + 1));
        progress->setWindowModality(Qt::WindowModal);
        progress->setMinimumDuration(1);  // To show the dialog immediately
 
        int entry_num = 1;
 
-       for (auto entry : context->drivers()) {
-               progress->setLabelText(QObject::tr("Scanning for %1...")
-                       .arg(QString::fromStdString(entry.first)));
+       /*
+        * Check the presence of an optional user spec for device scans.
+        * Determine the driver name and options (in generic format) when
+        * applicable.
+        */
+       std::string user_name;
+       vector<std::string> user_opts;
+       if (!driver.empty()) {
+               user_opts = pv::util::split_string(driver, ":");
+               user_name = user_opts.front();
+               user_opts.erase(user_opts.begin());
+       }
 
-               /**
-                * 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.
-                */
-               const auto keys = (entry.second)->config_keys();
+       /*
+        * Scan for devices. No specific options apply here, this is
+        * best effort auto detection.
+        */
+       for (auto& entry : context->drivers()) {
+               if (!do_scan)
+                       break;
 
-               bool supported_device = keys.count(ConfigKey::LOGIC_ANALYZER) |
-                       keys.count(ConfigKey::OSCILLOSCOPE);
+               // Skip drivers we won't scan anyway
+               if (!driver_supported(entry.second))
+                       continue;
 
-               if (supported_device)
-                       driver_scan(entry.second, map<const ConfigKey *, VariantBase>());
+               progress->setLabelText(QObject::tr("Scanning for %1...")
+                       .arg(QString::fromStdString(entry.first)));
+
+               if (entry.first == user_name)
+                       continue;
+               driver_scan(entry.second, map<const ConfigKey *, VariantBase>());
 
                progress->setValue(entry_num++);
                QApplication::processEvents();
                if (progress->wasCanceled())
                        break;
        }
+
+       /*
+        * Optionally run another scan with potentially more specific
+        * options when requested by the user. This is motivated by
+        * several different uses: It can find devices that are not
+        * covered by the above auto detection (UART, TCP). It can
+        * prefer one out of multiple found devices, and have this
+        * device pre-selected for new sessions upon user's request.
+        */
+       user_spec_device_.reset();
+       if (!driver.empty()) {
+               shared_ptr<sigrok::Driver> scan_drv;
+               map<const ConfigKey *, VariantBase> scan_opts;
+
+               /*
+                * Lookup the device driver name.
+                */
+               map<string, shared_ptr<Driver>> drivers = context->drivers();
+               auto entry = drivers.find(user_name);
+               scan_drv = (entry != drivers.end()) ? entry->second : nullptr;
+
+               /*
+                * Convert generic string representation of options
+                * to the driver specific data types.
+                */
+               if (scan_drv && !user_opts.empty()) {
+                       auto drv_opts = scan_drv->scan_options();
+                       scan_opts = drive_scan_options(user_opts, drv_opts);
+               }
+
+               /*
+                * Run another scan for the specified driver, passing
+                * user provided scan options this time.
+                */
+               list< shared_ptr<devices::HardwareDevice> > found;
+               if (scan_drv) {
+                       found = driver_scan(scan_drv, scan_opts);
+                       if (!found.empty())
+                               user_spec_device_ = found.front();
+               }
+       }
+       progress->setValue(entry_num++);
 }
 
 const shared_ptr<sigrok::Context>& DeviceManager::context() const
@@ -106,6 +165,78 @@ DeviceManager::devices() const
        return devices_;
 }
 
+/**
+ * Get the device that was detected with user provided scan options.
+ */
+shared_ptr<devices::HardwareDevice>
+DeviceManager::user_spec_device() const
+{
+       return user_spec_device_;
+}
+
+/**
+ * Convert generic options to data types that are specific to Driver::scan().
+ *
+ * @param[in] user_spec Vector of tokenized words, string format.
+ * @param[in] driver_opts Driver's scan options, result of Driver::scan_options().
+ *
+ * @return Map of options suitable for Driver::scan().
+ */
+map<const ConfigKey *, Glib::VariantBase>
+DeviceManager::drive_scan_options(vector<string> user_spec,
+       set<const ConfigKey *> driver_opts)
+{
+       map<const ConfigKey *, Glib::VariantBase> result;
+
+       for (auto& entry : user_spec) {
+               /*
+                * Split key=value specs. Accept entries without separator
+                * (for simplified boolean specifications).
+                */
+               string key, val;
+               size_t pos = entry.find("=");
+               if (pos == std::string::npos) {
+                       key = entry;
+                       val = "";
+               } else {
+                       key = entry.substr(0, pos);
+                       val = entry.substr(pos + 1);
+               }
+
+               /*
+                * Skip user specifications that are not a member of the
+                * driver's set of supported options. Have the text format
+                * input spec converted to the required driver specific type.
+                */
+               const ConfigKey *cfg;
+               try {
+                       cfg = ConfigKey::get_by_identifier(key);
+                       if (!cfg)
+                               continue;
+                       if (driver_opts.find(cfg) == driver_opts.end())
+                               continue;
+               } catch (...) {
+                       continue;
+               }
+               result[cfg] = cfg->parse_string(val);
+       }
+
+       return result;
+}
+
+bool DeviceManager::driver_supported(shared_ptr<Driver> driver) const
+{
+       /*
+        * 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 (DMMs, sensors, etc).
+        */
+       const auto keys = driver->config_keys();
+
+       return keys.count(ConfigKey::LOGIC_ANALYZER) | keys.count(ConfigKey::OSCILLOSCOPE);
+}
+
 list< shared_ptr<devices::HardwareDevice> >
 DeviceManager::driver_scan(
        shared_ptr<Driver> driver, map<const ConfigKey *, VariantBase> drvopts)
@@ -114,27 +245,36 @@ DeviceManager::driver_scan(
 
        assert(driver);
 
+       if (!driver_supported(driver))
+               return driver_devices;
+
        // Remove any device instances from this driver from the device
        // list. They will not be valid after the scan.
        devices_.remove_if([&](shared_ptr<devices::HardwareDevice> device) {
                return device->hardware_device()->driver() == driver; });
 
-       // Do the scan
-       auto devices = driver->scan(drvopts);
-
-       // Add the scanned devices to the main list, set display names and sort.
-       for (shared_ptr<sigrok::HardwareDevice> device : devices) {
-               const shared_ptr<devices::HardwareDevice> d(
-                       new devices::HardwareDevice(context_, device));
-               driver_devices.push_back(d);
+       try {
+               // Do the scan
+               auto devices = driver->scan(drvopts);
+
+               // Add the scanned devices to the main list, set display names and sort.
+               for (shared_ptr<sigrok::HardwareDevice>& device : devices) {
+                       const shared_ptr<devices::HardwareDevice> d(
+                               new devices::HardwareDevice(context_, device));
+                       driver_devices.push_back(d);
+               }
+
+               devices_.insert(devices_.end(), driver_devices.begin(),
+                       driver_devices.end());
+               devices_.sort(bind(&DeviceManager::compare_devices, this, _1, _2));
+               driver_devices.sort(bind(
+                       &DeviceManager::compare_devices, this, _1, _2));
+
+       } catch (const sigrok::Error &e) {
+               qWarning() << QApplication::tr("Error when scanning device driver '%1': %2").
+                       arg(QString::fromStdString(driver->name()), e.what());
        }
 
-       devices_.insert(devices_.end(), driver_devices.begin(),
-               driver_devices.end());
-       devices_.sort(bind(&DeviceManager::compare_devices, this, _1, _2));
-       driver_devices.sort(bind(
-               &DeviceManager::compare_devices, this, _1, _2));
-
        return driver_devices;
 }
 
index 43d93a7a88f5778d0bed24bcead6ec1f2e9dc85e..9b8ef3d120a3a4c68f97a9e359156270516ed4eb 100644 (file)
 #include <list>
 #include <map>
 #include <memory>
+#include <set>
 #include <string>
+#include <vector>
 
 using std::list;
 using std::map;
+using std::set;
 using std::shared_ptr;
 using std::string;
+using std::vector;
 
 namespace Glib {
 class VariantBase;
@@ -40,6 +44,8 @@ class Context;
 class Driver;
 }
 
+using sigrok::ConfigKey;
+
 namespace pv {
 
 namespace devices {
@@ -52,7 +58,8 @@ class Session;
 class DeviceManager
 {
 public:
-       DeviceManager(shared_ptr<sigrok::Context> context);
+       DeviceManager(shared_ptr<sigrok::Context> context,
+               std::string driver, bool do_scan);
 
        ~DeviceManager() = default;
 
@@ -61,6 +68,9 @@ public:
        shared_ptr<sigrok::Context> context();
 
        const list< shared_ptr<devices::HardwareDevice> >& devices() const;
+       shared_ptr<devices::HardwareDevice> user_spec_device() const;
+
+       bool driver_supported(shared_ptr<sigrok::Driver> driver) const;
 
        list< shared_ptr<devices::HardwareDevice> > driver_scan(
                shared_ptr<sigrok::Driver> driver,
@@ -76,9 +86,14 @@ private:
        bool compare_devices(shared_ptr<devices::Device> a,
                shared_ptr<devices::Device> b);
 
+       static map<const ConfigKey *, Glib::VariantBase>
+       drive_scan_options(vector<string> user_spec,
+               set<const ConfigKey *> driver_opts);
+
 protected:
        shared_ptr<sigrok::Context> context_;
        list< shared_ptr<devices::HardwareDevice> > devices_;
+       shared_ptr<devices::HardwareDevice> user_spec_device_;
 };
 
 } // namespace pv
index 855a1d82fca65019952b1baf46f21aef60c859de..4ce055965167bc842d766269ff259f8cc45454fa 100644 (file)
  */
 
 #include <cassert>
+#include <type_traits>
+
+#include <QApplication>
+#include <QDebug>
+#include <QString>
 
 #include <libsigrokcxx/libsigrokcxx.hpp>
 
 #include "device.hpp"
 
+using std::is_same;
 using std::shared_ptr;
 
 using sigrok::ConfigKey;
@@ -50,9 +56,7 @@ shared_ptr<sigrok::Device> Device::device() const
        return device_;
 }
 
-template
-uint64_t Device::read_config(const sigrok::ConfigKey*,
-       const uint64_t);
+template uint64_t Device::read_config(const sigrok::ConfigKey*, const uint64_t);
 
 template<typename T>
 T Device::read_config(const ConfigKey *key, const T default_value)
@@ -62,11 +66,33 @@ T Device::read_config(const ConfigKey *key, const T default_value)
        if (!device_)
                return default_value;
 
-       if (!device_->config_check(key, Capability::GET))
+       if (!device_->config_check(key, Capability::GET)) {
+               qWarning() << QApplication::tr("Querying config key %1 is not allowed")
+                       .arg(QString::fromStdString(key->identifier()));
                return default_value;
-
-       return VariantBase::cast_dynamic<Glib::Variant<guint64>>(
-               device_->config_get(ConfigKey::SAMPLERATE)).get();
+       }
+
+       VariantBase value;
+       try {
+               value = device_->config_get(key);
+       } catch (const sigrok::Error &e) {
+               qWarning() << QApplication::tr("Querying config key %1 resulted in %2")
+                       .arg(QString::fromStdString(key->identifier()), e.what());
+               return default_value;
+       }
+
+       if (is_same<T, uint32_t>::value)
+               return VariantBase::cast_dynamic<Glib::Variant<guint32>>(value).get();
+       if (is_same<T, int32_t>::value)
+               return VariantBase::cast_dynamic<Glib::Variant<gint32>>(value).get();
+       if (is_same<T, uint64_t>::value)
+               return VariantBase::cast_dynamic<Glib::Variant<guint64>>(value).get();
+       if (is_same<T, int64_t>::value)
+               return VariantBase::cast_dynamic<Glib::Variant<gint64>>(value).get();
+
+       qWarning() << QApplication::tr("Unknown type supplied when attempting to query %1")
+               .arg(QString::fromStdString(key->identifier()));
+       return default_value;
 }
 
 void Device::start()
index d8e9ae9a01eb2b6879ddae931a9da2c04d39d788..23234caa38244134a8f522e594560a2a69344f3a 100644 (file)
@@ -36,7 +36,7 @@ protected:
 
 public:
        /**
-        * Builds the full name. It only contains all the fields.
+        * Builds the full name. It contains all the fields.
         */
        string full_name() const;
 
@@ -46,7 +46,7 @@ public:
        string display_name(const DeviceManager&) const;
 
 protected:
-       const string file_name_;
+       string file_name_;
 };
 
 } // namespace devices
index ff52f817327d6d0d5790c91675e764d1d6091212..a3d84d6fadf175973fab3a2a420f190838948fc2 100644 (file)
@@ -54,8 +54,15 @@ HardwareDevice::~HardwareDevice()
 
 string HardwareDevice::full_name() const
 {
-       vector<string> parts = {device_->vendor(), device_->model(),
-               device_->version(), device_->serial_number()};
+       vector<string> parts = {};
+       if (device_->vendor().length() > 0)
+               parts.push_back(device_->vendor());
+       if (device_->model().length() > 0)
+               parts.push_back(device_->model());
+       if (device_->version().length() > 0)
+               parts.push_back(device_->version());
+       if (device_->serial_number().length() > 0)
+               parts.push_back("[S/N: " + device_->serial_number() + "]");
        if (device_->connection_id().length() > 0)
                parts.push_back("(" + device_->connection_id() + ")");
        return join(parts, " ");
@@ -84,11 +91,17 @@ string HardwareDevice::display_name(
                                dev->device_ != device_;
                });
 
-       vector<string> parts = {device_->vendor(), device_->model()};
+       vector<string> parts = {};
+       if (device_->vendor().length() > 0)
+               parts.push_back(device_->vendor());
+       if (device_->model().length() > 0)
+               parts.push_back(device_->model());
 
        if (multiple_dev) {
-               parts.push_back(device_->version());
-               parts.push_back(device_->serial_number());
+               if (device_->version().length() > 0)
+                       parts.push_back(device_->version());
+               if (device_->serial_number().length() > 0)
+                       parts.push_back("[S/N: " + device_->serial_number() + "]");
 
                if ((device_->serial_number().length() == 0) &&
                        (device_->connection_id().length() > 0))
index 58c60d700bbd25375222b20f09c7f35a41097e3a..94fb9c82cb6d4df2e2a47d261ff8220bb471ad3e 100644 (file)
 
 #include <cassert>
 #include <fstream>
+#include <vector>
 
+#include <QDebug>
 #include <QString>
 
+#include <pv/globalsettings.hpp>
+
 #include "inputfile.hpp"
 
+using sigrok::InputFormat;
+
 using std::map;
+using std::out_of_range;
+using std::pair;
 using std::shared_ptr;
 using std::streamsize;
 using std::string;
 using std::ifstream;
 using std::ios;
+using std::vector;
 
 namespace pv {
 namespace devices {
 
-const streamsize InputFile::BufferSize = 16384;
+// Use a 4MB chunk size for reading a file into memory. Larger values don't
+// seem to provide any substancial performance improvements, but can cause
+// UI lag and a visually "stuttering" display of the data currently loading.
+const streamsize InputFile::BufferSize = (4 * 1024 * 1024);
 
 InputFile::InputFile(const shared_ptr<sigrok::Context> &context,
        const string &file_name,
@@ -48,6 +60,56 @@ InputFile::InputFile(const shared_ptr<sigrok::Context> &context,
 {
 }
 
+InputFile::InputFile(const shared_ptr<sigrok::Context> &context,
+       QSettings &settings):
+       File(""),
+       context_(context),
+       interrupt_(false)
+{
+       file_name_ = settings.value("filename").toString().toStdString();
+
+       QString format_name = settings.value("format").toString();
+
+       // Find matching format
+       const map<string, shared_ptr<InputFormat> > formats = context->input_formats();
+
+       try {
+               format_ = formats.at(format_name.toStdString());
+
+               // Restore all saved options
+               int options = settings.value("options").toInt();
+
+               for (int i = 0; i < options; i++) {
+                       settings.beginGroup("option" + QString::number(i));
+                       QString name = settings.value("name").toString();
+                       options_[name.toStdString()] = GlobalSettings::restore_variantbase(settings);
+                       settings.endGroup();
+               }
+
+       } catch (out_of_range&) {
+               qWarning() << "Could not find input format" << format_name <<
+                       "needed to restore session input file";
+       }
+}
+
+void InputFile::save_meta_to_settings(QSettings &settings)
+{
+       settings.setValue("filename", QString::fromStdString(file_name_));
+
+       settings.setValue("format", QString::fromStdString(format_->name()));
+
+       settings.setValue("options", (int)options_.size());
+
+       int i = 0;
+       for (const pair<string, Glib::VariantBase>& option : options_) {
+               settings.beginGroup("option" + QString::number(i));
+               settings.setValue("name", QString::fromStdString(option.first));
+               GlobalSettings::store_variantbase(settings, option.second);
+               settings.endGroup();
+               i++;
+       }
+}
+
 void InputFile::open()
 {
        if (session_)
@@ -55,6 +117,9 @@ void InputFile::open()
        else
                session_ = context_->create_session();
 
+       if (!format_)
+               return;
+
        input_ = format_->create_input(options_);
 
        if (!input_)
@@ -64,18 +129,20 @@ void InputFile::open()
        // we can't open the device without sending some data first
        f = new ifstream(file_name_, ios::binary);
 
-       char buffer[BufferSize];
-       f->read(buffer, BufferSize);
+       vector<char> buffer(BufferSize);
+
+       f->read(buffer.data(), BufferSize);
        const streamsize size = f->gcount();
+
        if (size == 0)
-               return;
+               throw QString("Failed to read file");
 
-       input_->send(buffer, size);
+       input_->send(buffer.data(), size);
 
        try {
                device_ = input_->device();
-       } catch (sigrok::Error) {
-               return;
+       } catch (sigrok::Error& e) {
+               throw e;
        }
 
        session_->add_device(device_);
@@ -93,7 +160,8 @@ void InputFile::start()
 
 void InputFile::run()
 {
-       char buffer[BufferSize];
+       if (!input_)
+               return;
 
        if (!f) {
                // Previous call to run() processed the entire file already
@@ -101,14 +169,16 @@ void InputFile::run()
                input_->reset();
        }
 
+       vector<char> buffer(BufferSize);
+
        interrupt_ = false;
        while (!interrupt_ && !f->eof()) {
-               f->read(buffer, BufferSize);
+               f->read(buffer.data(), BufferSize);
                const streamsize size = f->gcount();
                if (size == 0)
                        break;
 
-               input_->send(buffer, size);
+               input_->send(buffer.data(), size);
 
                if (size != BufferSize)
                        break;
index 41698e6d6be45638b7d00f24cb139deb9f2c6f6a..e08832f608d6247e9febce369f32c3b8aea843e5 100644 (file)
@@ -26,6 +26,8 @@
 
 #include "file.hpp"
 
+#include <QSettings>
+
 using std::atomic;
 using std::ifstream;
 using std::map;
@@ -47,6 +49,15 @@ public:
                shared_ptr<sigrok::InputFormat> format,
                const map<string, Glib::VariantBase> &options);
 
+       /**
+        * Constructor that loads a file using the metadata saved by
+        * save_meta_to_settings() before.
+        */
+       InputFile(const shared_ptr<sigrok::Context> &context,
+               QSettings &settings);
+
+       void save_meta_to_settings(QSettings &settings);
+
        void open();
 
        void close();
@@ -59,8 +70,8 @@ public:
 
 private:
        const shared_ptr<sigrok::Context> context_;
-       const shared_ptr<sigrok::InputFormat> format_;
-       const map<string, Glib::VariantBase> options_;
+       shared_ptr<sigrok::InputFormat> format_;
+       map<string, Glib::VariantBase> options_;
        shared_ptr<sigrok::Input> input_;
 
        ifstream *f;
index 911956af73fab4b856e8c09a1b57421439e3cc12..084b1142c198c2b24b82ae3da4a00590f3d042c2 100644 (file)
@@ -95,13 +95,19 @@ Connect::Connect(QWidget *parent, pv::DeviceManager &device_manager) :
        tcp_port_->setRange(1, 65535);
        tcp_port_->setValue(5555);
        tcp_config_layout->addWidget(tcp_port_);
-       tcp_use_vxi_ = new QCheckBox();
-       tcp_use_vxi_->setText(tr("Use VXI"));
+
        tcp_config_layout->addSpacing(30);
-       tcp_config_layout->addWidget(tcp_use_vxi_);
+       tcp_config_layout->addWidget(new QLabel(tr("Protocol:")));
+       tcp_protocol_ = new QComboBox();
+       tcp_protocol_->addItem("Raw TCP", QVariant("tcp-raw/%1/%2"));
+       tcp_protocol_->addItem("VXI", QVariant("vxi/%1/%2"));
+       tcp_config_layout->addWidget(tcp_protocol_);
        tcp_config_layout->setContentsMargins(0, 0, 0, 0);
        tcp_config_->setEnabled(false);
 
+       // Let the device list occupy only the minimum space needed
+       device_list_.setMaximumHeight(device_list_.minimumSizeHint().height());
+
        QVBoxLayout *vbox_if = new QVBoxLayout;
        vbox_if->addWidget(radiobtn_usb);
        vbox_if->addWidget(radiobtn_serial);
@@ -148,7 +154,7 @@ shared_ptr<HardwareDevice> Connect::get_selected_device() const
 
 void Connect::populate_drivers()
 {
-       for (auto entry : device_manager_.context()->drivers()) {
+       for (auto& entry : device_manager_.context()->drivers()) {
                auto name = entry.first;
                auto driver = entry.second;
                /**
@@ -173,7 +179,7 @@ void Connect::populate_drivers()
 void Connect::populate_serials(shared_ptr<Driver> driver)
 {
        serial_devices_.clear();
-       for (auto serial : device_manager_.context()->serials(driver))
+       for (auto& serial : device_manager_.context()->serials(driver))
                serial_devices_.addItem(QString("%1 (%2)").arg(
                        serial.first.c_str(), serial.second.c_str()),
                        QString::fromStdString(serial.first));
@@ -215,7 +221,7 @@ void Connect::scan_pressed()
                const int index = serial_devices_.currentIndex();
                if (index >= 0 && index < serial_devices_.count() &&
                    serial_devices_.currentText() == serial_devices_.itemText(index))
-                       serial = serial_devices_.itemData(index).value<QString>();
+                       serial = serial_devices_.itemData(index).toString();
                else
                        serial = serial_devices_.currentText();
                drvopts[ConfigKey::CONN] = Variant<ustring>::create(
@@ -226,11 +232,10 @@ void Connect::scan_pressed()
                QString host = tcp_host_->text();
                QString port = tcp_port_->text();
                if (!host.isEmpty()) {
-                       QString conn;
-                       if (tcp_use_vxi_->isChecked())
-                               conn = QString("vxi/%1/%2").arg(host, port);
-                       else
-                               conn = QString("tcp-raw/%1/%2").arg(host, port);
+                       QString conn =
+                               tcp_protocol_->itemData(tcp_protocol_->currentIndex()).toString();
+
+                       conn = conn.arg(host, port);
 
                        drvopts[ConfigKey::CONN] = Variant<ustring>::create(
                                conn.toUtf8().constData());
@@ -240,16 +245,13 @@ void Connect::scan_pressed()
        const list< shared_ptr<HardwareDevice> > devices =
                device_manager_.driver_scan(driver, drvopts);
 
-       for (shared_ptr<HardwareDevice> device : devices) {
+       for (const shared_ptr<HardwareDevice>& device : devices) {
                assert(device);
 
-               QString text = QString::fromStdString(
-                       device->display_name(device_manager_));
-               text += QString(" with %1 channels").arg(
-                       device->device()->channels().size());
+               QString text = QString::fromStdString(device->display_name(device_manager_));
+               text += QString(" with %1 channels").arg(device->device()->channels().size());
 
-               QListWidgetItem *const item = new QListWidgetItem(text,
-                       &device_list_);
+               QListWidgetItem *const item = new QListWidgetItem(text, &device_list_);
                item->setData(Qt::UserRole, qVariantFromValue(device));
                device_list_.addItem(item);
        }
index d9cf0a53cfc689b3142b64ae4d078b2a3b0ed7fc..914c58783ee3693b87ed6b7c2a3232c0eb119a62 100644 (file)
@@ -94,7 +94,7 @@ private:
        QWidget *tcp_config_;
        QLineEdit *tcp_host_;
        QSpinBox *tcp_port_;
-       QCheckBox *tcp_use_vxi_;
+       QComboBox *tcp_protocol_;
 
        QPushButton scan_button_;
        QListWidget device_list_;
index b74264ac2cbd89301a83ea25dc4a58d92e932a64..2bbab122ffb3731159f2385f8c6457150ee0d1a2 100644 (file)
 #include "config.h"
 
 #include <glib.h>
-#include <boost/version.hpp>
 
 #include <QApplication>
+#include <QComboBox>
 #include <QDialogButtonBox>
+#include <QFileDialog>
 #include <QFormLayout>
 #include <QGroupBox>
 #include <QHBoxLayout>
 #include <QLabel>
+#include <QMainWindow>
+#include <QMessageBox>
+#include <QPushButton>
+#include <QScrollBar>
+#include <QSpinBox>
 #include <QString>
+#include <QStyleFactory>
 #include <QTextBrowser>
 #include <QTextDocument>
+#include <QTextStream>
 #include <QVBoxLayout>
 
 #include "settings.hpp"
 
+#include "pv/application.hpp"
 #include "pv/devicemanager.hpp"
 #include "pv/globalsettings.hpp"
+#include "pv/logging.hpp"
+#include "pv/widgets/colorbutton.hpp"
 
 #include <libsigrokcxx/libsigrokcxx.hpp>
 
 #include <libsigrokdecode/libsigrokdecode.h>
 #endif
 
-using std::shared_ptr;
+using pv::widgets::ColorButton;
 
 namespace pv {
 namespace dialogs {
 
+/**
+ * Special version of a QListView that has the width of the first column as minimum size.
+ *
+ * @note Inspired by https://github.com/qt-creator/qt-creator/blob/master/src/plugins/coreplugin/dialogs/settingsdialog.cpp
+ */
+class PageListWidget: public QListWidget
+{
+public:
+       PageListWidget() :
+               QListWidget()
+       {
+               setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding);
+       }
+
+       QSize sizeHint() const final
+       {
+               int width = sizeHintForColumn(0) + frameWidth() * 2 + 5;
+               if (verticalScrollBar()->isVisible())
+                       width += verticalScrollBar()->width();
+               return QSize(width, 100);
+       }
+};
+
 Settings::Settings(DeviceManager &device_manager, QWidget *parent) :
        QDialog(parent, nullptr),
        device_manager_(device_manager)
 {
-       const int icon_size = 64;
-
        resize(600, 400);
 
-       page_list = new QListWidget;
-       page_list->setViewMode(QListView::IconMode);
-       page_list->setIconSize(QSize(icon_size, icon_size));
+       // Create log view
+       log_view_ = create_log_view();
+
+       // Create pages
+       page_list = new PageListWidget();
+       page_list->setViewMode(QListView::ListMode);
        page_list->setMovement(QListView::Static);
-       page_list->setMaximumWidth(icon_size + (icon_size / 2));
-       page_list->setSpacing(12);
+       page_list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
 
        pages = new QStackedWidget;
        create_pages();
        page_list->setCurrentIndex(page_list->model()->index(0, 0));
 
+       // Create the rest of the dialog
        QHBoxLayout *tab_layout = new QHBoxLayout;
        tab_layout->addWidget(page_list);
        tab_layout->addWidget(pages, Qt::AlignLeft);
@@ -91,13 +126,22 @@ Settings::Settings(DeviceManager &device_manager, QWidget *parent) :
 
 void Settings::create_pages()
 {
+       // General page
+       pages->addWidget(get_general_settings_form(pages));
+
+       QListWidgetItem *generalButton = new QListWidgetItem(page_list);
+       generalButton->setIcon(QIcon(":/icons/settings-general.png"));
+       generalButton->setText(tr("General"));
+       generalButton->setTextAlignment(Qt::AlignVCenter);
+       generalButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+
        // View page
        pages->addWidget(get_view_settings_form(pages));
 
        QListWidgetItem *viewButton = new QListWidgetItem(page_list);
        viewButton->setIcon(QIcon(":/icons/settings-views.svg"));
        viewButton->setText(tr("Views"));
-       viewButton->setTextAlignment(Qt::AlignHCenter);
+       viewButton->setTextAlignment(Qt::AlignVCenter);
        viewButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
 
 #ifdef ENABLE_DECODE
@@ -107,7 +151,7 @@ void Settings::create_pages()
        QListWidgetItem *decoderButton = new QListWidgetItem(page_list);
        decoderButton->setIcon(QIcon(":/icons/add-decoder.svg"));
        decoderButton->setText(tr("Decoders"));
-       decoderButton->setTextAlignment(Qt::AlignHCenter);
+       decoderButton->setTextAlignment(Qt::AlignVCenter);
        decoderButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
 #endif
 
@@ -117,8 +161,17 @@ void Settings::create_pages()
        QListWidgetItem *aboutButton = new QListWidgetItem(page_list);
        aboutButton->setIcon(QIcon(":/icons/information.svg"));
        aboutButton->setText(tr("About"));
-       aboutButton->setTextAlignment(Qt::AlignHCenter);
+       aboutButton->setTextAlignment(Qt::AlignVCenter);
        aboutButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+
+       // Logging page
+       pages->addWidget(get_logging_page(pages));
+
+       QListWidgetItem *loggingButton = new QListWidgetItem(page_list);
+       loggingButton->setIcon(QIcon(":/icons/information.svg"));
+       loggingButton->setText(tr("Logging"));
+       loggingButton->setTextAlignment(Qt::AlignVCenter);
+       loggingButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
 }
 
 QCheckBox *Settings::create_checkbox(const QString& key, const char* slot) const
@@ -131,8 +184,77 @@ QCheckBox *Settings::create_checkbox(const QString& key, const char* slot) const
        return cb;
 }
 
+QPlainTextEdit *Settings::create_log_view() const
+{
+       GlobalSettings settings;
+
+       QPlainTextEdit *log_view = new QPlainTextEdit();
+
+       log_view->setReadOnly(true);
+       log_view->setWordWrapMode(QTextOption::NoWrap);
+       log_view->setCenterOnScroll(true);
+
+       log_view->appendHtml(logging.get_log());
+       connect(&logging, SIGNAL(logged_text(QString)),
+               log_view, SLOT(appendHtml(QString)));
+
+       return log_view;
+}
+
+QWidget *Settings::get_general_settings_form(QWidget *parent) const
+{
+       GlobalSettings settings;
+
+       QWidget *form = new QWidget(parent);
+       QVBoxLayout *form_layout = new QVBoxLayout(form);
+
+       // General settings
+       QGroupBox *general_group = new QGroupBox(tr("General"));
+       form_layout->addWidget(general_group);
+
+       QFormLayout *general_layout = new QFormLayout();
+       general_group->setLayout(general_layout);
+
+       QComboBox *theme_cb = new QComboBox();
+       for (const pair<QString, QString>& entry : Themes)
+               theme_cb->addItem(entry.first, entry.second);
+
+       theme_cb->setCurrentIndex(
+               settings.value(GlobalSettings::Key_General_Theme).toInt());
+       connect(theme_cb, SIGNAL(currentIndexChanged(int)),
+               this, SLOT(on_general_theme_changed_changed(int)));
+       general_layout->addRow(tr("User interface theme"), theme_cb);
+
+       QLabel *description_1 = new QLabel(tr("(You may need to restart PulseView for all UI elements to update)"));
+       description_1->setAlignment(Qt::AlignRight);
+       general_layout->addRow(description_1);
+
+       QComboBox *style_cb = new QComboBox();
+       style_cb->addItem(tr("System Default"), "");
+       for (QString& s : QStyleFactory::keys())
+               style_cb->addItem(s, s);
+
+       const QString current_style =
+               settings.value(GlobalSettings::Key_General_Style).toString();
+       if (current_style.isEmpty())
+               style_cb->setCurrentIndex(0);
+       else
+               style_cb->setCurrentIndex(style_cb->findText(current_style, 0));
+
+       connect(style_cb, SIGNAL(currentIndexChanged(int)),
+               this, SLOT(on_general_style_changed(int)));
+       general_layout->addRow(tr("Qt widget style"), style_cb);
+
+       QLabel *description_2 = new QLabel(tr("(Dark themes look best with the Fusion style)"));
+       description_2->setAlignment(Qt::AlignRight);
+       general_layout->addRow(description_2);
+
+       return form;
+}
+
 QWidget *Settings::get_view_settings_form(QWidget *parent) const
 {
+       GlobalSettings settings;
        QCheckBox *cb;
 
        QWidget *form = new QWidget(parent);
@@ -145,13 +267,21 @@ QWidget *Settings::get_view_settings_form(QWidget *parent) const
        QFormLayout *trace_view_layout = new QFormLayout();
        trace_view_group->setLayout(trace_view_layout);
 
-       cb = create_checkbox(GlobalSettings::Key_View_ColouredBG,
-               SLOT(on_view_colouredBG_changed(int)));
-       trace_view_layout->addRow(tr("Use coloured trace &background"), cb);
+       cb = create_checkbox(GlobalSettings::Key_View_ColoredBG,
+               SLOT(on_view_coloredBG_changed(int)));
+       trace_view_layout->addRow(tr("Use colored trace &background"), cb);
 
-       cb = create_checkbox(GlobalSettings::Key_View_AlwaysZoomToFit,
-               SLOT(on_view_alwaysZoomToFit_changed(int)));
-       trace_view_layout->addRow(tr("Constantly perform &zoom-to-fit during capture"), cb);
+       cb = create_checkbox(GlobalSettings::Key_View_ZoomToFitDuringAcq,
+               SLOT(on_view_zoomToFitDuringAcq_changed(int)));
+       trace_view_layout->addRow(tr("Constantly perform &zoom-to-fit during acquisition"), cb);
+
+       cb = create_checkbox(GlobalSettings::Key_View_ZoomToFitAfterAcq,
+               SLOT(on_view_zoomToFitAfterAcq_changed(int)));
+       trace_view_layout->addRow(tr("Perform a zoom-to-&fit when acquisition stops"), cb);
+
+       cb = create_checkbox(GlobalSettings::Key_View_TriggerIsZeroTime,
+               SLOT(on_view_triggerIsZero_changed(int)));
+       trace_view_layout->addRow(tr("Show time zero at the trigger"), cb);
 
        cb = create_checkbox(GlobalSettings::Key_View_StickyScrolling,
                SLOT(on_view_stickyScrolling_changed(int)));
@@ -161,16 +291,76 @@ QWidget *Settings::get_view_settings_form(QWidget *parent) const
                SLOT(on_view_showSamplingPoints_changed(int)));
        trace_view_layout->addRow(tr("Show data &sampling points"), cb);
 
+       cb = create_checkbox(GlobalSettings::Key_View_FillSignalHighAreas,
+               SLOT(on_view_fillSignalHighAreas_changed(int)));
+       trace_view_layout->addRow(tr("Fill high areas of logic signals"), cb);
+
+       ColorButton* high_fill_cb = new ColorButton(parent);
+       high_fill_cb->set_color(QColor::fromRgba(
+               settings.value(GlobalSettings::Key_View_FillSignalHighAreaColor).value<uint32_t>()));
+       connect(high_fill_cb, SIGNAL(selected(QColor)),
+               this, SLOT(on_view_fillSignalHighAreaColor_changed(QColor)));
+       trace_view_layout->addRow(tr("Color to fill high areas of logic signals with"), high_fill_cb);
+
        cb = create_checkbox(GlobalSettings::Key_View_ShowAnalogMinorGrid,
                SLOT(on_view_showAnalogMinorGrid_changed(int)));
-       trace_view_layout->addRow(tr("Show analog minor grid in addition to vdiv grid"), cb);
+       trace_view_layout->addRow(tr("Show analog minor grid in addition to div grid"), cb);
+
+       cb = create_checkbox(GlobalSettings::Key_View_ShowHoverMarker,
+               SLOT(on_view_showHoverMarker_changed(int)));
+       trace_view_layout->addRow(tr("Highlight mouse cursor using a vertical marker line"), cb);
+
+       QSpinBox *snap_distance_sb = new QSpinBox();
+       snap_distance_sb->setRange(0, 1000);
+       snap_distance_sb->setSuffix(tr(" pixels"));
+       snap_distance_sb->setValue(
+               settings.value(GlobalSettings::Key_View_SnapDistance).toInt());
+       connect(snap_distance_sb, SIGNAL(valueChanged(int)), this,
+               SLOT(on_view_snapDistance_changed(int)));
+       trace_view_layout->addRow(tr("Maximum distance from edges before cursors snap to them"), snap_distance_sb);
+
+       ColorButton* cursor_fill_cb = new ColorButton(parent);
+       cursor_fill_cb->set_color(QColor::fromRgba(
+               settings.value(GlobalSettings::Key_View_CursorFillColor).value<uint32_t>()));
+       connect(cursor_fill_cb, SIGNAL(selected(QColor)),
+               this, SLOT(on_view_cursorFillColor_changed(QColor)));
+       trace_view_layout->addRow(tr("Color to fill cursor area with"), cursor_fill_cb);
+
+       QComboBox *thr_disp_mode_cb = new QComboBox();
+       thr_disp_mode_cb->addItem(tr("None"), GlobalSettings::ConvThrDispMode_None);
+       thr_disp_mode_cb->addItem(tr("Background"), GlobalSettings::ConvThrDispMode_Background);
+       thr_disp_mode_cb->addItem(tr("Dots"), GlobalSettings::ConvThrDispMode_Dots);
+       thr_disp_mode_cb->setCurrentIndex(
+               settings.value(GlobalSettings::Key_View_ConversionThresholdDispMode).toInt());
+       connect(thr_disp_mode_cb, SIGNAL(currentIndexChanged(int)),
+               this, SLOT(on_view_conversionThresholdDispMode_changed(int)));
+       trace_view_layout->addRow(tr("Conversion threshold display mode (analog traces only)"), thr_disp_mode_cb);
+
+       QSpinBox *default_div_height_sb = new QSpinBox();
+       default_div_height_sb->setRange(20, 1000);
+       default_div_height_sb->setSuffix(tr(" pixels"));
+       default_div_height_sb->setValue(
+               settings.value(GlobalSettings::Key_View_DefaultDivHeight).toInt());
+       connect(default_div_height_sb, SIGNAL(valueChanged(int)), this,
+               SLOT(on_view_defaultDivHeight_changed(int)));
+       trace_view_layout->addRow(tr("Default analog trace div height"), default_div_height_sb);
+
+       QSpinBox *default_logic_height_sb = new QSpinBox();
+       default_logic_height_sb->setRange(5, 1000);
+       default_logic_height_sb->setSuffix(tr(" pixels"));
+       default_logic_height_sb->setValue(
+               settings.value(GlobalSettings::Key_View_DefaultLogicHeight).toInt());
+       connect(default_logic_height_sb, SIGNAL(valueChanged(int)), this,
+               SLOT(on_view_defaultLogicHeight_changed(int)));
+       trace_view_layout->addRow(tr("Default logic trace height"), default_logic_height_sb);
 
        return form;
 }
 
-QWidget *Settings::get_decoder_settings_form(QWidget *parent) const
+QWidget *Settings::get_decoder_settings_form(QWidget *parent)
 {
 #ifdef ENABLE_DECODE
+       GlobalSettings settings;
        QCheckBox *cb;
 
        QWidget *form = new QWidget(parent);
@@ -187,42 +377,40 @@ QWidget *Settings::get_decoder_settings_form(QWidget *parent) const
                SLOT(on_dec_initialStateConfigurable_changed(int)));
        decoder_layout->addRow(tr("Allow configuration of &initial signal state"), cb);
 
+       // Annotation export settings
+       ann_export_format_ = new QLineEdit();
+       ann_export_format_->setText(
+               settings.value(GlobalSettings::Key_Dec_ExportFormat).toString());
+       connect(ann_export_format_, SIGNAL(textChanged(const QString&)),
+               this, SLOT(on_dec_exportFormat_changed(const QString&)));
+       decoder_layout->addRow(tr("Annotation export format"), ann_export_format_);
+       QLabel *description_1 = new QLabel(tr("%s = sample range; %d: decoder name; %c: row name; %q: use quotations marks"));
+       description_1->setAlignment(Qt::AlignRight);
+       decoder_layout->addRow(description_1);
+       QLabel *description_2 = new QLabel(tr("%1: longest annotation text; %a: all annotation texts"));
+       description_2->setAlignment(Qt::AlignRight);
+       decoder_layout->addRow(description_2);
+
        return form;
 #else
        (void)parent;
+       return nullptr;
 #endif
 }
 
-#ifdef ENABLE_DECODE
-static gint sort_pds(gconstpointer a, gconstpointer b)
-{
-       const struct srd_decoder *sda, *sdb;
-
-       sda = (const struct srd_decoder *)a;
-       sdb = (const struct srd_decoder *)b;
-       return strcmp(sda->id, sdb->id);
-}
-#endif
-
 QWidget *Settings::get_about_page(QWidget *parent) const
 {
-#ifdef ENABLE_DECODE
-       struct srd_decoder *dec;
-#endif
+       Application* a = qobject_cast<Application*>(QApplication::instance());
 
        QLabel *icon = new QLabel();
        icon->setPixmap(QPixmap(QString::fromUtf8(":/icons/pulseview.svg")));
 
-       /* Setup the version field */
-       QLabel *version_info = new QLabel();
-       version_info->setText(tr("%1 %2<br />%3<br /><a href=\"http://%4\">%4</a>")
-               .arg(QApplication::applicationName(),
-               QApplication::applicationVersion(),
+       // Setup the license field with the project homepage link
+       QLabel *gpl_home_info = new QLabel();
+       gpl_home_info->setText(tr("%1<br /><a href=\"http://%2\">%2</a>").arg(
                tr("GNU GPL, version 3 or later"),
                QApplication::organizationDomain()));
-       version_info->setOpenExternalLinks(true);
-
-       shared_ptr<sigrok::Context> context = device_manager_.context();
+       gpl_home_info->setOpenExternalLinks(true);
 
        QString s;
 
@@ -230,107 +418,54 @@ QWidget *Settings::get_about_page(QWidget *parent) const
 
        s.append("<table>");
 
-       /* Library info */
        s.append("<tr><td colspan=\"2\"><b>" +
-               tr("Libraries and features:") + "</b></td></tr>");
-
-       s.append(QString("<tr><td><i>%1</i></td><td>%2</td></tr>")
-               .arg(QString("Qt"), qVersion()));
-       s.append(QString("<tr><td><i>%1</i></td><td>%2</td></tr>")
-               .arg(QString("glibmm"), PV_GLIBMM_VERSION));
-       s.append(QString("<tr><td><i>%1</i></td><td>%2</td></tr>")
-               .arg(QString("Boost"), BOOST_LIB_VERSION));
-
-       s.append(QString("<tr><td><i>%1</i></td><td>%2/%3 (rt: %4/%5)</td></tr>")
-               .arg(QString("libsigrok"), SR_PACKAGE_VERSION_STRING,
-               SR_LIB_VERSION_STRING, sr_package_version_string_get(),
-               sr_lib_version_string_get()));
-
-       GSList *l_orig = sr_buildinfo_libs_get();
-       for (GSList *l = l_orig; l; l = l->next) {
-               GSList *m = (GSList *)l->data;
-               const char *lib = (const char *)m->data;
-               const char *version = (const char *)m->next->data;
-               s.append(QString("<tr><td><i>- %1</i></td><td>%2</td></tr>")
-                       .arg(QString(lib), QString(version)));
-               g_slist_free_full(m, g_free);
-       }
-       g_slist_free(l_orig);
-
-       char *host = sr_buildinfo_host_get();
-       s.append(QString("<tr><td><i>- Host</i></td><td>%1</td></tr>")
-               .arg(QString(host)));
-       g_free(host);
+               tr("Versions, libraries and features:") + "</b></td></tr>");
+       for (pair<QString, QString> &entry : a->get_version_info())
+               s.append(QString("<tr><td><i>%1</i></td><td>%2</td></tr>")
+                       .arg(entry.first, entry.second));
 
-       char *scpi_backends = sr_buildinfo_scpi_backends_get();
-       s.append(QString("<tr><td><i>- SCPI backends</i></td><td>%1</td></tr>")
-               .arg(QString(scpi_backends)));
-       g_free(scpi_backends);
+       s.append("<tr><td colspan=\"2\"></td></tr>");
+       s.append("<tr><td colspan=\"2\"><b>" +
+               tr("Firmware search paths:") + "</b></td></tr>");
+       for (QString &entry : a->get_fw_path_list())
+               s.append(QString("<tr><td colspan=\"2\">%1</td></tr>").arg(entry));
 
 #ifdef ENABLE_DECODE
-       s.append(QString("<tr><td><i>%1</i></td><td>%2/%3 (rt: %4/%5)</td></tr>")
-               .arg(QString("libsigrokdecode"), SRD_PACKAGE_VERSION_STRING,
-               SRD_LIB_VERSION_STRING, srd_package_version_string_get(),
-               srd_lib_version_string_get()));
-
-       l_orig = srd_buildinfo_libs_get();
-       for (GSList *l = l_orig; l; l = l->next) {
-               GSList *m = (GSList *)l->data;
-               const char *lib = (const char *)m->data;
-               const char *version = (const char *)m->next->data;
-               s.append(QString("<tr><td><i>- %1</i></td><td>%2</td></tr>")
-                       .arg(QString(lib), QString(version)));
-               g_slist_free_full(m, g_free);
-       }
-       g_slist_free(l_orig);
-
-       host = srd_buildinfo_host_get();
-       s.append(QString("<tr><td><i>- Host</i></td><td>%1</td></tr>")
-               .arg(QString(host)));
-       g_free(host);
+       s.append("<tr><td colspan=\"2\"></td></tr>");
+       s.append("<tr><td colspan=\"2\"><b>" +
+               tr("Protocol decoder search paths:") + "</b></td></tr>");
+       for (QString &entry : a->get_pd_path_list())
+               s.append(QString("<tr><td colspan=\"2\">%1</td></tr>").arg(entry));
 #endif
 
-       /* Set up the supported field */
        s.append("<tr><td colspan=\"2\"></td></tr>");
        s.append("<tr><td colspan=\"2\"><b>" +
                tr("Supported hardware drivers:") + "</b></td></tr>");
-       for (auto entry : context->drivers()) {
+       for (pair<QString, QString> &entry : a->get_driver_list())
                s.append(QString("<tr><td class=\"id\"><i>%1</i></td><td>%2</td></tr>")
-                       .arg(QString::fromUtf8(entry.first.c_str()),
-                               QString::fromUtf8(entry.second->long_name().c_str())));
-       }
+                       .arg(entry.first, entry.second));
 
        s.append("<tr><td colspan=\"2\"></td></tr>");
        s.append("<tr><td colspan=\"2\"><b>" +
                tr("Supported input formats:") + "</b></td></tr>");
-       for (auto entry : context->input_formats()) {
+       for (pair<QString, QString> &entry : a->get_input_format_list())
                s.append(QString("<tr><td class=\"id\"><i>%1</i></td><td>%2</td></tr>")
-                       .arg(QString::fromUtf8(entry.first.c_str()),
-                               QString::fromUtf8(entry.second->description().c_str())));
-       }
+                       .arg(entry.first, entry.second));
 
        s.append("<tr><td colspan=\"2\"></td></tr>");
        s.append("<tr><td colspan=\"2\"><b>" +
                tr("Supported output formats:") + "</b></td></tr>");
-       for (auto entry : context->output_formats()) {
+       for (pair<QString, QString> &entry : a->get_output_format_list())
                s.append(QString("<tr><td class=\"id\"><i>%1</i></td><td>%2</td></tr>")
-                       .arg(QString::fromUtf8(entry.first.c_str()),
-                               QString::fromUtf8(entry.second->description().c_str())));
-       }
+                       .arg(entry.first, entry.second));
 
 #ifdef ENABLE_DECODE
        s.append("<tr><td colspan=\"2\"></td></tr>");
        s.append("<tr><td colspan=\"2\"><b>" +
                tr("Supported protocol decoders:") + "</b></td></tr>");
-       GSList *sl = g_slist_copy((GSList *)srd_decoder_list());
-       sl = g_slist_sort(sl, sort_pds);
-       for (const GSList *l = sl; l; l = l->next) {
-               dec = (struct srd_decoder *)l->data;
+       for (pair<QString, QString> &entry : a->get_pd_list())
                s.append(QString("<tr><td class=\"id\"><i>%1</i></td><td>%2</td></tr>")
-                       .arg(QString::fromUtf8(dec->id),
-                               QString::fromUtf8(dec->longname)));
-       }
-       g_slist_free(sl);
+                       .arg(entry.first, entry.second));
 #endif
 
        s.append("</table>");
@@ -341,10 +476,14 @@ QWidget *Settings::get_about_page(QWidget *parent) const
        QTextBrowser *support_list = new QTextBrowser();
        support_list->setDocument(supported_doc);
 
-       QGridLayout *layout = new QGridLayout();
-       layout->addWidget(icon, 0, 0, 1, 1);
-       layout->addWidget(version_info, 0, 1, 1, 1);
-       layout->addWidget(support_list, 1, 1, 1, 1);
+       QHBoxLayout *h_layout = new QHBoxLayout();
+       h_layout->setAlignment(Qt::AlignLeft);
+       h_layout->addWidget(icon);
+       h_layout->addWidget(gpl_home_info);
+
+       QVBoxLayout *layout = new QVBoxLayout();
+       layout->addLayout(h_layout);
+       layout->addWidget(support_list);
 
        QWidget *page = new QWidget(parent);
        page->setLayout(layout);
@@ -352,6 +491,65 @@ QWidget *Settings::get_about_page(QWidget *parent) const
        return page;
 }
 
+QWidget *Settings::get_logging_page(QWidget *parent) const
+{
+       GlobalSettings settings;
+
+       // Log level
+       QSpinBox *loglevel_sb = new QSpinBox();
+       loglevel_sb->setMaximum(SR_LOG_SPEW);
+       loglevel_sb->setValue(logging.get_log_level());
+       connect(loglevel_sb, SIGNAL(valueChanged(int)), this,
+               SLOT(on_log_logLevel_changed(int)));
+
+       QHBoxLayout *loglevel_layout = new QHBoxLayout();
+       loglevel_layout->addWidget(new QLabel(tr("Log level:")));
+       loglevel_layout->addWidget(loglevel_sb);
+
+       // Background buffer size
+       QSpinBox *buffersize_sb = new QSpinBox();
+       buffersize_sb->setSuffix(tr(" lines"));
+       buffersize_sb->setMinimum(Logging::MIN_BUFFER_SIZE);
+       buffersize_sb->setMaximum(Logging::MAX_BUFFER_SIZE);
+       buffersize_sb->setValue(
+               settings.value(GlobalSettings::Key_Log_BufferSize).toInt());
+       connect(buffersize_sb, SIGNAL(valueChanged(int)), this,
+               SLOT(on_log_bufferSize_changed(int)));
+
+       QHBoxLayout *buffersize_layout = new QHBoxLayout();
+       buffersize_layout->addWidget(new QLabel(tr("Length of background buffer:")));
+       buffersize_layout->addWidget(buffersize_sb);
+
+       // Save to file
+       QPushButton *save_log_pb = new QPushButton(
+               QIcon::fromTheme("document-save-as", QIcon(":/icons/document-save-as.png")),
+               tr("&Save to File"));
+       connect(save_log_pb, SIGNAL(clicked(bool)),
+               this, SLOT(on_log_saveToFile_clicked(bool)));
+
+       // Pop out
+       QPushButton *pop_out_pb = new QPushButton(
+               QIcon::fromTheme("window-new", QIcon(":/icons/window-new.png")),
+               tr("&Pop out"));
+       connect(pop_out_pb, SIGNAL(clicked(bool)),
+               this, SLOT(on_log_popOut_clicked(bool)));
+
+       QHBoxLayout *control_layout = new QHBoxLayout();
+       control_layout->addLayout(loglevel_layout);
+       control_layout->addLayout(buffersize_layout);
+       control_layout->addWidget(save_log_pb);
+       control_layout->addWidget(pop_out_pb);
+
+       QVBoxLayout *root_layout = new QVBoxLayout();
+       root_layout->addLayout(control_layout);
+       root_layout->addWidget(log_view_);
+
+       QWidget *page = new QWidget(parent);
+       page->setLayout(root_layout);
+
+       return page;
+}
+
 void Settings::accept()
 {
        GlobalSettings settings;
@@ -376,16 +574,66 @@ void Settings::on_page_changed(QListWidgetItem *current, QListWidgetItem *previo
        pages->setCurrentIndex(page_list->row(current));
 }
 
-void Settings::on_view_alwaysZoomToFit_changed(int state)
+void Settings::on_general_theme_changed_changed(int state)
+{
+       GlobalSettings settings;
+       settings.setValue(GlobalSettings::Key_General_Theme, state);
+       settings.apply_theme();
+
+       QMessageBox msg(this);
+       msg.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
+       msg.setIcon(QMessageBox::Question);
+
+       if (settings.current_theme_is_dark()) {
+               msg.setText(tr("You selected a dark theme.\n" \
+                       "Should I set the user-adjustable colors to better suit your choice?\n\n" \
+                       "Please keep in mind that PulseView may need a restart to display correctly."));
+               if (msg.exec() == QMessageBox::Yes)
+                       settings.set_dark_theme_default_colors();
+       } else {
+               msg.setText(tr("You selected a bright theme.\n" \
+                       "Should I set the user-adjustable colors to better suit your choice?\n\n" \
+                       "Please keep in mind that PulseView may need a restart to display correctly."));
+               if (msg.exec() == QMessageBox::Yes)
+                       settings.set_bright_theme_default_colors();
+       }
+}
+
+void Settings::on_general_style_changed(int state)
+{
+       GlobalSettings settings;
+
+       if (state == 0)
+               settings.setValue(GlobalSettings::Key_General_Style, "");
+       else
+               settings.setValue(GlobalSettings::Key_General_Style,
+                       QStyleFactory::keys().at(state - 1));
+
+       settings.apply_theme();
+}
+
+void Settings::on_view_zoomToFitDuringAcq_changed(int state)
+{
+       GlobalSettings settings;
+       settings.setValue(GlobalSettings::Key_View_ZoomToFitDuringAcq, state ? true : false);
+}
+
+void Settings::on_view_zoomToFitAfterAcq_changed(int state)
+{
+       GlobalSettings settings;
+       settings.setValue(GlobalSettings::Key_View_ZoomToFitAfterAcq, state ? true : false);
+}
+
+void Settings::on_view_triggerIsZero_changed(int state)
 {
        GlobalSettings settings;
-       settings.setValue(GlobalSettings::Key_View_AlwaysZoomToFit, state ? true : false);
+       settings.setValue(GlobalSettings::Key_View_TriggerIsZeroTime, state ? true : false);
 }
 
-void Settings::on_view_colouredBG_changed(int state)
+void Settings::on_view_coloredBG_changed(int state)
 {
        GlobalSettings settings;
-       settings.setValue(GlobalSettings::Key_View_ColouredBG, state ? true : false);
+       settings.setValue(GlobalSettings::Key_View_ColoredBG, state ? true : false);
 }
 
 void Settings::on_view_stickyScrolling_changed(int state)
@@ -400,17 +648,136 @@ void Settings::on_view_showSamplingPoints_changed(int state)
        settings.setValue(GlobalSettings::Key_View_ShowSamplingPoints, state ? true : false);
 }
 
+void Settings::on_view_fillSignalHighAreas_changed(int state)
+{
+       GlobalSettings settings;
+       settings.setValue(GlobalSettings::Key_View_FillSignalHighAreas, state ? true : false);
+}
+
+void Settings::on_view_fillSignalHighAreaColor_changed(QColor color)
+{
+       GlobalSettings settings;
+       settings.setValue(GlobalSettings::Key_View_FillSignalHighAreaColor, color.rgba());
+}
+
 void Settings::on_view_showAnalogMinorGrid_changed(int state)
 {
        GlobalSettings settings;
        settings.setValue(GlobalSettings::Key_View_ShowAnalogMinorGrid, state ? true : false);
 }
 
+void Settings::on_view_showHoverMarker_changed(int state)
+{
+       GlobalSettings settings;
+       settings.setValue(GlobalSettings::Key_View_ShowHoverMarker, state ? true : false);
+}
+
+void Settings::on_view_snapDistance_changed(int value)
+{
+       GlobalSettings settings;
+       settings.setValue(GlobalSettings::Key_View_SnapDistance, value);
+}
+
+void Settings::on_view_cursorFillColor_changed(QColor color)
+{
+       GlobalSettings settings;
+       settings.setValue(GlobalSettings::Key_View_CursorFillColor, color.rgba());
+}
+
+void Settings::on_view_conversionThresholdDispMode_changed(int state)
+{
+       GlobalSettings settings;
+       settings.setValue(GlobalSettings::Key_View_ConversionThresholdDispMode, state);
+}
+
+void Settings::on_view_defaultDivHeight_changed(int value)
+{
+       GlobalSettings settings;
+       settings.setValue(GlobalSettings::Key_View_DefaultDivHeight, value);
+}
+
+void Settings::on_view_defaultLogicHeight_changed(int value)
+{
+       GlobalSettings settings;
+       settings.setValue(GlobalSettings::Key_View_DefaultLogicHeight, value);
+}
+
+#ifdef ENABLE_DECODE
 void Settings::on_dec_initialStateConfigurable_changed(int state)
 {
        GlobalSettings settings;
        settings.setValue(GlobalSettings::Key_Dec_InitialStateConfigurable, state ? true : false);
 }
 
+void Settings::on_dec_exportFormat_changed(const QString &text)
+{
+       GlobalSettings settings;
+       settings.setValue(GlobalSettings::Key_Dec_ExportFormat, text);
+}
+#endif
+
+void Settings::on_log_logLevel_changed(int value)
+{
+       logging.set_log_level(value);
+}
+
+void Settings::on_log_bufferSize_changed(int value)
+{
+       GlobalSettings settings;
+       settings.setValue(GlobalSettings::Key_Log_BufferSize, value);
+}
+
+void Settings::on_log_saveToFile_clicked(bool checked)
+{
+       (void)checked;
+
+       const QString file_name = QFileDialog::getSaveFileName(
+               this, tr("Save Log"), "", tr("Log Files (*.txt *.log);;All Files (*)"));
+
+       if (file_name.isEmpty())
+               return;
+
+       QFile file(file_name);
+       if (file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
+               QTextStream out_stream(&file);
+               out_stream << log_view_->toPlainText();
+
+               if (out_stream.status() == QTextStream::Ok) {
+                       QMessageBox msg(this);
+                       msg.setText(tr("Success"));
+                       msg.setInformativeText(tr("Log saved to %1.").arg(file_name));
+                       msg.setStandardButtons(QMessageBox::Ok);
+                       msg.setIcon(QMessageBox::Information);
+                       msg.exec();
+
+                       return;
+               }
+       }
+
+       QMessageBox msg(this);
+       msg.setText(tr("Error"));
+       msg.setInformativeText(tr("File %1 could not be written to.").arg(file_name));
+       msg.setStandardButtons(QMessageBox::Ok);
+       msg.setIcon(QMessageBox::Warning);
+       msg.exec();
+}
+
+void Settings::on_log_popOut_clicked(bool checked)
+{
+       (void)checked;
+
+       // Create the window as a sub-window so it closes when the main window closes
+       QMainWindow *window = new QMainWindow(nullptr, Qt::SubWindow);
+
+       window->setObjectName(QString::fromUtf8("Log Window"));
+       window->setWindowTitle(tr("%1 Log").arg(PV_TITLE));
+
+       // Use same width/height as the settings dialog
+       window->resize(width(), height());
+
+       window->setCentralWidget(create_log_view());
+       window->show();
+}
+
 } // namespace dialogs
 } // namespace pv
index 0ae98e002ca8af56a6a1b7f734ece4bad2875c1c..43988fec9e09b2889237dd7a97dfa86d3558be6e 100644 (file)
 #define PULSEVIEW_PV_SETTINGS_HPP
 
 #include <QCheckBox>
+#include <QColor>
 #include <QDialog>
 #include <QListWidget>
+#include <QPlainTextEdit>
 #include <QStackedWidget>
+#include <QLineEdit>
 
 namespace pv {
 
@@ -31,6 +34,8 @@ class DeviceManager;
 
 namespace dialogs {
 
+class PageListWidget;
+
 class Settings : public QDialog
 {
        Q_OBJECT
@@ -40,27 +45,55 @@ public:
 
        void create_pages();
        QCheckBox *create_checkbox(const QString& key, const char* slot) const;
+       QPlainTextEdit *create_log_view() const;
 
+       QWidget *get_general_settings_form(QWidget *parent) const;
        QWidget *get_view_settings_form(QWidget *parent) const;
-       QWidget *get_decoder_settings_form(QWidget *parent) const;
+       QWidget *get_decoder_settings_form(QWidget *parent);
        QWidget *get_about_page(QWidget *parent) const;
+       QWidget *get_logging_page(QWidget *parent) const;
 
        void accept();
        void reject();
 
 private Q_SLOTS:
        void on_page_changed(QListWidgetItem *current, QListWidgetItem *previous);
-       void on_view_alwaysZoomToFit_changed(int state);
-       void on_view_colouredBG_changed(int state);
+       void on_general_theme_changed_changed(int state);
+       void on_general_style_changed(int state);
+       void on_view_zoomToFitDuringAcq_changed(int state);
+       void on_view_zoomToFitAfterAcq_changed(int state);
+       void on_view_triggerIsZero_changed(int state);
+       void on_view_coloredBG_changed(int state);
        void on_view_stickyScrolling_changed(int state);
        void on_view_showSamplingPoints_changed(int state);
+       void on_view_fillSignalHighAreas_changed(int state);
+       void on_view_fillSignalHighAreaColor_changed(QColor color);
        void on_view_showAnalogMinorGrid_changed(int state);
+       void on_view_showHoverMarker_changed(int state);
+       void on_view_snapDistance_changed(int value);
+       void on_view_cursorFillColor_changed(QColor color);
+       void on_view_conversionThresholdDispMode_changed(int state);
+       void on_view_defaultDivHeight_changed(int value);
+       void on_view_defaultLogicHeight_changed(int value);
+#ifdef ENABLE_DECODE
        void on_dec_initialStateConfigurable_changed(int state);
+       void on_dec_exportFormat_changed(const QString &text);
+#endif
+       void on_log_logLevel_changed(int value);
+       void on_log_bufferSize_changed(int value);
+       void on_log_saveToFile_clicked(bool checked);
+       void on_log_popOut_clicked(bool checked);
 
 private:
        DeviceManager &device_manager_;
-       QListWidget *page_list;
+       PageListWidget *page_list;
        QStackedWidget *pages;
+
+#ifdef ENABLE_DECODE
+       QLineEdit *ann_export_format_;
+#endif
+
+       QPlainTextEdit *log_view_;
 };
 
 } // namespace dialogs
index 0d0a222c2cc5594dc875dfcd0473b169a8c5dd0f..9f4279cea8557a20423c799f0f6abe5785e142ee 100644 (file)
@@ -19,6 +19,7 @@
 
 #include <cassert>
 
+#include <QDebug>
 #include <QMessageBox>
 
 #include "pv/session.hpp"
@@ -78,6 +79,8 @@ void StoreProgress::run()
 
 void StoreProgress::show_error()
 {
+       qDebug() << "Error trying to save:" << session_.error();
+
        QMessageBox msg(parentWidget());
        msg.setText(tr("Failed to save session."));
        msg.setInformativeText(session_.error());
index ee9741030c1098ee8d6c5352dfef6feccfa5fbdf..34dbc8326e8acb0f5db01d4c195764af310c73ab 100644 (file)
 
 #include "globalsettings.hpp"
 
-using std::function;
+#include <QApplication>
+#include <QColor>
+#include <QDebug>
+#include <QFile>
+#include <QFontMetrics>
+#include <QPixmapCache>
+#include <QString>
+#include <QStyle>
+#include <QtGlobal>
+
 using std::map;
-using std::multimap;
+using std::pair;
+using std::string;
+using std::vector;
 
 namespace pv {
 
-const QString GlobalSettings::Key_View_AlwaysZoomToFit = "View_AlwaysZoomToFit";
-const QString GlobalSettings::Key_View_ColouredBG = "View_ColouredBG";
+const vector< pair<QString, QString> > Themes {
+       {"None" , ""},
+       {"QDarkStyleSheet", ":/themes/qdarkstyle/style.qss"},
+       {"DarkStyle", ":/themes/darkstyle/darkstyle.qss"}
+};
+
+const QString GlobalSettings::Key_General_Theme = "General_Theme";
+const QString GlobalSettings::Key_General_Style = "General_Style";
+const QString GlobalSettings::Key_View_ZoomToFitDuringAcq = "View_ZoomToFitDuringAcq";
+const QString GlobalSettings::Key_View_ZoomToFitAfterAcq = "View_ZoomToFitAfterAcq";
+const QString GlobalSettings::Key_View_TriggerIsZeroTime = "View_TriggerIsZeroTime";
+const QString GlobalSettings::Key_View_ColoredBG = "View_ColoredBG";
 const QString GlobalSettings::Key_View_StickyScrolling = "View_StickyScrolling";
 const QString GlobalSettings::Key_View_ShowSamplingPoints = "View_ShowSamplingPoints";
+const QString GlobalSettings::Key_View_FillSignalHighAreas = "View_FillSignalHighAreas";
+const QString GlobalSettings::Key_View_FillSignalHighAreaColor = "View_FillSignalHighAreaColor";
 const QString GlobalSettings::Key_View_ShowAnalogMinorGrid = "View_ShowAnalogMinorGrid";
+const QString GlobalSettings::Key_View_ConversionThresholdDispMode = "View_ConversionThresholdDispMode";
+const QString GlobalSettings::Key_View_DefaultDivHeight = "View_DefaultDivHeight";
+const QString GlobalSettings::Key_View_DefaultLogicHeight = "View_DefaultLogicHeight";
+const QString GlobalSettings::Key_View_ShowHoverMarker = "View_ShowHoverMarker";
+const QString GlobalSettings::Key_View_SnapDistance = "View_SnapDistance";
+const QString GlobalSettings::Key_View_CursorFillColor = "View_CursorFillColor";
 const QString GlobalSettings::Key_Dec_InitialStateConfigurable = "Dec_InitialStateConfigurable";
+const QString GlobalSettings::Key_Dec_ExportFormat = "Dec_ExportFormat";
+const QString GlobalSettings::Key_Log_BufferSize = "Log_BufferSize";
+const QString GlobalSettings::Key_Log_NotifyOfStacktrace = "Log_NotifyOfStacktrace";
 
-multimap< QString, function<void(QVariant)> > GlobalSettings::callbacks_;
+vector<GlobalSettingsInterface*> GlobalSettings::callbacks_;
 bool GlobalSettings::tracking_ = false;
 map<QString, QVariant> GlobalSettings::tracked_changes_;
+QString GlobalSettings::default_style_;
+QPalette GlobalSettings::default_palette_;
 
 GlobalSettings::GlobalSettings() :
-       QSettings()
+       QSettings(),
+       is_dark_theme_(false)
 {
        beginGroup("Settings");
 }
 
+void GlobalSettings::save_internal_defaults()
+{
+       default_style_ = qApp->style()->objectName();
+       if (default_style_.isEmpty())
+               default_style_ = "fusion";
+
+       default_palette_ = QApplication::palette();
+}
+
 void GlobalSettings::set_defaults_where_needed()
 {
-       // Enable coloured trace backgrounds by default
-       if (!contains(Key_View_ColouredBG))
-               setValue(Key_View_ColouredBG, true);
+       // Use no theme by default
+       if (!contains(Key_General_Theme))
+               setValue(Key_General_Theme, 0);
+       if (!contains(Key_General_Style))
+               setValue(Key_General_Style, "");
+
+       // Enable zoom-to-fit after acquisition by default
+       if (!contains(Key_View_ZoomToFitAfterAcq))
+               setValue(Key_View_ZoomToFitAfterAcq, true);
+
+       // Enable colored trace backgrounds by default
+       if (!contains(Key_View_ColoredBG))
+               setValue(Key_View_ColoredBG, true);
+
+       // Enable showing sampling points by default
+       if (!contains(Key_View_ShowSamplingPoints))
+               setValue(Key_View_ShowSamplingPoints, true);
+
+       // Enable filling logic signal high areas by default
+       if (!contains(Key_View_FillSignalHighAreas))
+               setValue(Key_View_FillSignalHighAreas, true);
+
+       if (!contains(Key_View_DefaultDivHeight))
+               setValue(Key_View_DefaultDivHeight,
+               3 * QFontMetrics(QApplication::font()).height());
+
+       if (!contains(Key_View_DefaultLogicHeight))
+               setValue(Key_View_DefaultLogicHeight,
+               2 * QFontMetrics(QApplication::font()).height());
+
+       if (!contains(Key_View_ShowHoverMarker))
+               setValue(Key_View_ShowHoverMarker, true);
+
+       if (!contains(Key_View_SnapDistance))
+               setValue(Key_View_SnapDistance, 15);
+
+       if (!contains(Key_Dec_ExportFormat))
+               setValue(Key_Dec_ExportFormat, "%s %d: %c: %1");
+
+       // Default to 500 lines of backlog
+       if (!contains(Key_Log_BufferSize))
+               setValue(Key_Log_BufferSize, 500);
+
+       // Notify user of existing stack trace by default
+       if (!contains(Key_Log_NotifyOfStacktrace))
+               setValue(Key_Log_NotifyOfStacktrace, true);
+
+       // Default theme is bright, so use its color scheme if undefined
+       if (!contains(Key_View_CursorFillColor))
+               set_bright_theme_default_colors();
+}
+
+void GlobalSettings::set_bright_theme_default_colors()
+{
+       setValue(Key_View_FillSignalHighAreaColor,
+               QColor(0, 0, 0, 5 * 256 / 100).rgba());
+
+       setValue(Key_View_CursorFillColor,
+               QColor(220, 231, 243).rgba());
+}
+
+void GlobalSettings::set_dark_theme_default_colors()
+{
+       setValue(Key_View_FillSignalHighAreaColor,
+               QColor(188, 188, 188, 9 * 256 / 100).rgba());
+
+       setValue(Key_View_CursorFillColor,
+               QColor(60, 60, 60).rgba());
+}
+
+bool GlobalSettings::current_theme_is_dark()
+{
+       return is_dark_theme_;
+}
+
+void GlobalSettings::apply_theme()
+{
+       QString theme_name    = Themes.at(value(Key_General_Theme).toInt()).first;
+       QString resource_name = Themes.at(value(Key_General_Theme).toInt()).second;
+
+       if (!resource_name.isEmpty()) {
+               QFile file(resource_name);
+               file.open(QFile::ReadOnly | QFile::Text);
+               qApp->setStyleSheet(file.readAll());
+       } else
+               qApp->setStyleSheet("");
+
+       qApp->setPalette(default_palette_);
+
+       const QString style = value(Key_General_Style).toString();
+       if (style.isEmpty())
+               qApp->setStyle(default_style_);
+       else
+               qApp->setStyle(style);
+
+       is_dark_theme_ = false;
+
+       if (theme_name.compare("QDarkStyleSheet") == 0) {
+               QPalette dark_palette;
+               dark_palette.setColor(QPalette::Window, QColor(53, 53, 53));
+               dark_palette.setColor(QPalette::WindowText, Qt::white);
+               dark_palette.setColor(QPalette::Base, QColor(42, 42, 42));
+               dark_palette.setColor(QPalette::Dark, QColor(35, 35, 35));
+               dark_palette.setColor(QPalette::Highlight, QColor(42, 130, 218));
+               qApp->setPalette(dark_palette);
+               is_dark_theme_ = true;
+       } else if (theme_name.compare("DarkStyle") == 0) {
+               QPalette dark_palette;
+               dark_palette.setColor(QPalette::Window, QColor(53, 53, 53));
+               dark_palette.setColor(QPalette::WindowText, Qt::white);
+               dark_palette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(127, 127, 127));
+               dark_palette.setColor(QPalette::Base, QColor(42, 42, 42));
+               dark_palette.setColor(QPalette::AlternateBase, QColor(66, 66, 66));
+               dark_palette.setColor(QPalette::ToolTipBase, Qt::white);
+               dark_palette.setColor(QPalette::ToolTipText, QColor(53, 53, 53));
+               dark_palette.setColor(QPalette::Text, Qt::white);
+               dark_palette.setColor(QPalette::Disabled, QPalette::Text, QColor(127, 127, 127));
+               dark_palette.setColor(QPalette::Dark, QColor(35, 35, 35));
+               dark_palette.setColor(QPalette::Shadow, QColor(20, 20, 20));
+               dark_palette.setColor(QPalette::Button, QColor(53, 53, 53));
+               dark_palette.setColor(QPalette::ButtonText, Qt::white);
+               dark_palette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(127, 127, 127));
+               dark_palette.setColor(QPalette::BrightText, Qt::red);
+               dark_palette.setColor(QPalette::Link, QColor(42, 130, 218));
+               dark_palette.setColor(QPalette::Highlight, QColor(42, 130, 218));
+               dark_palette.setColor(QPalette::Disabled, QPalette::Highlight, QColor(80, 80, 80));
+               dark_palette.setColor(QPalette::HighlightedText, Qt::white);
+               dark_palette.setColor(QPalette::Disabled, QPalette::HighlightedText, QColor(127, 127, 127));
+               qApp->setPalette(dark_palette);
+               is_dark_theme_ = true;
+       }
+
+       QPixmapCache::clear();
+}
+
+void GlobalSettings::add_change_handler(GlobalSettingsInterface *cb)
+{
+       callbacks_.push_back(cb);
 }
 
-void GlobalSettings::register_change_handler(const QString key,
-       function<void(QVariant)> cb)
+void GlobalSettings::remove_change_handler(GlobalSettingsInterface *cb)
 {
-       callbacks_.emplace(key, cb);
+       for (auto cb_it = callbacks_.begin(); cb_it != callbacks_.end(); cb_it++)
+               if (*cb_it == cb) {
+                       callbacks_.erase(cb_it);
+                       break;
+               }
 }
 
 void GlobalSettings::setValue(const QString &key, const QVariant &value)
@@ -64,11 +246,12 @@ void GlobalSettings::setValue(const QString &key, const QVariant &value)
 
        QSettings::setValue(key, value);
 
-       // Call all registered callbacks for this key
-       auto range = callbacks_.equal_range(key);
+       // TODO Emulate noquote()
+       qDebug() << "Setting" << key << "changed to" << value;
 
-       for (auto it = range.first; it != range.second; it++)
-               it->second(value);
+       // Call all registered callbacks
+       for (GlobalSettingsInterface *cb : callbacks_)
+               cb->on_setting_changed(key, value);
 }
 
 void GlobalSettings::start_tracking()
@@ -87,10 +270,71 @@ void GlobalSettings::undo_tracked_changes()
 {
        tracking_ = false;
 
-       for (auto entry : tracked_changes_)
+       for (auto& entry : tracked_changes_)
                setValue(entry.first, entry.second);
 
        tracked_changes_.clear();
 }
 
+void GlobalSettings::store_gvariant(QSettings &settings, GVariant *v)
+{
+       const GVariantType *var_type = g_variant_get_type(v);
+       char *var_type_str = g_variant_type_dup_string(var_type);
+
+       QByteArray var_data = QByteArray((const char*)g_variant_get_data(v),
+               g_variant_get_size(v));
+
+       settings.setValue("value", var_data);
+       settings.setValue("type", var_type_str);
+
+       g_free(var_type_str);
+}
+
+GVariant* GlobalSettings::restore_gvariant(QSettings &settings)
+{
+       QString raw_type = settings.value("type").toString();
+       GVariantType *var_type = g_variant_type_new(raw_type.toUtf8());
+
+       QByteArray data = settings.value("value").toByteArray();
+
+       gpointer var_data = g_memdup((gconstpointer)data.constData(),
+               (guint)data.size());
+
+       GVariant *value = g_variant_new_from_data(var_type, var_data,
+               data.size(), false, g_free, var_data);
+
+       g_variant_type_free(var_type);
+
+       return value;
+}
+
+void GlobalSettings::store_variantbase(QSettings &settings, Glib::VariantBase v)
+{
+       const QByteArray var_data = QByteArray((const char*)v.get_data(), v.get_size());
+
+       settings.setValue("value", var_data);
+       settings.setValue("type", QString::fromStdString(v.get_type_string()));
+}
+
+Glib::VariantBase GlobalSettings::restore_variantbase(QSettings &settings)
+{
+       QString raw_type = settings.value("type").toString();
+       GVariantType *var_type = g_variant_type_new(raw_type.toUtf8());
+
+       QByteArray data = settings.value("value").toByteArray();
+
+       gpointer var_data = g_memdup((gconstpointer)data.constData(),
+               (guint)data.size());
+
+       GVariant *value = g_variant_new_from_data(var_type, var_data,
+               data.size(), false, g_free, var_data);
+
+       Glib::VariantBase ret_val = Glib::VariantBase(value, true);
+
+       g_variant_type_free(var_type);
+       g_variant_unref(value);
+
+       return ret_val;
+}
+
 } // namespace pv
index f6a6141fca7ad18ccd1e9276e4eea0789a2dead1..e890a800e306b302f0b5939c28d7c78bcfe537d0 100644 (file)
 #ifndef PULSEVIEW_GLOBALSETTINGS_HPP
 #define PULSEVIEW_GLOBALSETTINGS_HPP
 
-#include <functional>
 #include <map>
 
+#include <glib.h>
+#include <glibmm/variant.h>
+
+#include <QPalette>
 #include <QSettings>
 #include <QString>
 #include <QVariant>
 
-using std::function;
 using std::map;
-using std::multimap;
+using std::pair;
+using std::vector;
 
 namespace pv {
 
+extern const vector< pair<QString, QString> > Themes;
+
+
+class GlobalSettingsInterface
+{
+public:
+       virtual void on_setting_changed(const QString &key, const QVariant &value) = 0;
+};
+
+
 class GlobalSettings : public QSettings
 {
        Q_OBJECT
 
 public:
-       static const QString Key_View_AlwaysZoomToFit;
-       static const QString Key_View_ColouredBG;
+       static const QString Key_General_Theme;
+       static const QString Key_General_Style;
+       static const QString Key_View_ZoomToFitDuringAcq;
+       static const QString Key_View_ZoomToFitAfterAcq;
+       static const QString Key_View_TriggerIsZeroTime;
+       static const QString Key_View_ColoredBG;
        static const QString Key_View_StickyScrolling;
        static const QString Key_View_ShowSamplingPoints;
+       static const QString Key_View_FillSignalHighAreas;
+       static const QString Key_View_FillSignalHighAreaColor;
        static const QString Key_View_ShowAnalogMinorGrid;
+       static const QString Key_View_ConversionThresholdDispMode;
+       static const QString Key_View_DefaultDivHeight;
+       static const QString Key_View_DefaultLogicHeight;
+       static const QString Key_View_ShowHoverMarker;
+       static const QString Key_View_SnapDistance;
+       static const QString Key_View_CursorFillColor;
        static const QString Key_Dec_InitialStateConfigurable;
+       static const QString Key_Dec_ExportFormat;
+       static const QString Key_Log_BufferSize;
+       static const QString Key_Log_NotifyOfStacktrace;
+
+       enum ConvThrDispMode {
+               ConvThrDispMode_None = 0,
+               ConvThrDispMode_Background,
+               ConvThrDispMode_Dots
+       };
 
 public:
        GlobalSettings();
 
+       void save_internal_defaults();
        void set_defaults_where_needed();
+       void set_bright_theme_default_colors();
+       void set_dark_theme_default_colors();
+
+       bool current_theme_is_dark();
+       void apply_theme();
 
-       static void register_change_handler(const QString key,
-               function<void(QVariant)> cb);
+       static void add_change_handler(GlobalSettingsInterface *cb);
+       static void remove_change_handler(GlobalSettingsInterface *cb);
 
        void setValue(const QString& key, const QVariant& value);
 
@@ -73,11 +113,24 @@ public:
         */
        void undo_tracked_changes();
 
+       static void store_gvariant(QSettings &settings, GVariant *v);
+
+       static GVariant* restore_gvariant(QSettings &settings);
+
+       static void store_variantbase(QSettings &settings, Glib::VariantBase v);
+
+       static Glib::VariantBase restore_variantbase(QSettings &settings);
+
 private:
-       static multimap< QString, function<void(QVariant)> > callbacks_;
+       static vector<GlobalSettingsInterface*> callbacks_;
 
        static bool tracking_;
        static map<QString, QVariant> tracked_changes_;
+
+       static QString default_style_;
+       static QPalette default_palette_;
+
+       bool is_dark_theme_;
 };
 
 } // namespace pv
diff --git a/pv/logging.cpp b/pv/logging.cpp
new file mode 100644 (file)
index 0000000..b747ab5
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2018 Soeren Apel <soeren@apelpie.net>
+ *
+ * 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 "logging.hpp"
+#include "globalsettings.hpp"
+
+#include <iostream>
+
+#ifdef ENABLE_DECODE
+#include <libsigrokdecode/libsigrokdecode.h> /* First, so we avoid a _POSIX_C_SOURCE warning. */
+#endif
+
+#include <libsigrokcxx/libsigrokcxx.hpp>
+
+#include <QApplication>
+
+using std::cout;
+using std::endl;
+using std::lock_guard;
+
+namespace pv {
+
+Logging logging;
+
+const int Logging::MIN_BUFFER_SIZE = 10;
+const int Logging::MAX_BUFFER_SIZE = 50000;
+
+static sr_log_callback prev_sr_log_cb;
+static void *prev_sr_log_cb_data;
+
+#ifdef ENABLE_DECODE
+static srd_log_callback prev_srd_log_cb;
+static void *prev_srd_log_cb_data;
+#endif
+
+Logging::~Logging()
+{
+       qInstallMessageHandler(nullptr);
+       if (prev_sr_log_cb)
+               sr_log_callback_set(prev_sr_log_cb, prev_sr_log_cb_data);
+       prev_sr_log_cb = nullptr;
+       prev_sr_log_cb_data = nullptr;
+#ifdef ENABLE_DECODE
+       if (prev_srd_log_cb)
+               srd_log_callback_set(prev_srd_log_cb, prev_srd_log_cb_data);
+       prev_srd_log_cb = nullptr;
+       prev_srd_log_cb_data = nullptr;
+#endif
+
+       GlobalSettings::remove_change_handler(this);
+}
+
+void Logging::init()
+{
+       GlobalSettings settings;
+
+       buffer_size_ =
+               settings.value(GlobalSettings::Key_Log_BufferSize).toInt();
+
+       buffer_.reserve(buffer_size_);
+
+       qInstallMessageHandler(log_pv);
+       sr_log_callback_get(&prev_sr_log_cb, &prev_sr_log_cb_data);
+       sr_log_callback_set(log_sr, nullptr);
+#ifdef ENABLE_DECODE
+       srd_log_callback_get(&prev_srd_log_cb, &prev_srd_log_cb_data);
+       srd_log_callback_set(log_srd, nullptr);
+#endif
+
+       GlobalSettings::add_change_handler(this);
+}
+
+int Logging::get_log_level() const
+{
+       // We assume that libsigrok and libsrd always have the same log level
+       return sr_log_loglevel_get();
+}
+
+void Logging::set_log_level(int level)
+{
+       sr_log_loglevel_set(level);
+#ifdef ENABLE_DECODE
+       srd_log_loglevel_set(level);
+#endif
+}
+
+QString Logging::get_log() const
+{
+       return buffer_.join("<br />\n");
+}
+
+void Logging::log(const QString &text, int source)
+{
+       lock_guard<mutex> log_lock(log_mutex_);
+
+       if (buffer_.size() >= buffer_size_)
+               buffer_.removeFirst();
+
+       QString s;
+
+       if (text.contains("warning", Qt::CaseInsensitive)) {
+               s = QString("<font color=\"darkorange\">%1</font>").arg(text);
+               goto out;
+       }
+
+       if (text.contains("error", Qt::CaseInsensitive)) {
+               s = QString("<font color=\"darkred\">%1</font>").arg(text);
+               goto out;
+       }
+
+       switch (source) {
+       case LogSource_pv:
+               s = QString("<font color=\"darkMagenta\">pv: %1</font>").arg(text);
+               break;
+       case LogSource_sr:
+               s = QString("<font color=\"darkGreen\">sr: %1</font>").arg(text);
+               break;
+       case LogSource_srd:
+               s = QString("<font color=\"olive\">srd: %1</font>").arg(text);
+               break;
+       default:
+               s = text;
+               break;
+       }
+
+out:
+       buffer_.append(s);
+
+       // If we're tearing down the program, sending out notifications to UI
+       // elements that can no longer function properly is a bad idea
+       if (!QApplication::closingDown())
+               logged_text(s);
+}
+
+void Logging::log_pv(QtMsgType type, const QMessageLogContext &context, const QString &msg)
+{
+       (void)type;
+       (void)context;
+
+       logging.log(msg, LogSource_pv);
+
+       cout << msg.toUtf8().data() << endl;
+}
+
+int Logging::log_sr(void *cb_data, int loglevel, const char *format, va_list args)
+{
+       va_list args2;
+
+       (void)cb_data;
+
+       va_copy(args2, args);
+       if (prev_sr_log_cb)
+               prev_sr_log_cb(prev_sr_log_cb_data, loglevel, format, args2);
+       va_end(args2);
+
+       char *text = g_strdup_vprintf(format, args);
+       logging.log(QString::fromUtf8(text), LogSource_sr);
+       g_free(text);
+
+       return SR_OK;
+}
+
+#ifdef ENABLE_DECODE
+int Logging::log_srd(void *cb_data, int loglevel, const char *format, va_list args)
+{
+       va_list args2;
+
+       (void)cb_data;
+
+       va_copy(args2, args);
+       if (prev_srd_log_cb)
+               prev_srd_log_cb(prev_srd_log_cb_data, loglevel, format, args2);
+       va_end(args2);
+
+       char *text = g_strdup_vprintf(format, args);
+       logging.log(QString::fromUtf8(text), LogSource_srd);
+       g_free(text);
+
+       return SR_OK;
+}
+#endif
+
+void Logging::on_setting_changed(const QString &key, const QVariant &value)
+{
+       if (key == GlobalSettings::Key_Log_BufferSize) {
+               // Truncate buffer if needed
+               const int delta = buffer_.size() - value.toInt();
+               if (delta > 0)
+                       buffer_.erase(buffer_.begin(), buffer_.begin() + delta);
+
+               buffer_size_ = value.toInt();
+               buffer_.reserve(buffer_size_);
+       }
+}
+
+} // namespace pv
diff --git a/pv/logging.hpp b/pv/logging.hpp
new file mode 100644 (file)
index 0000000..28f2107
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2018 Soeren Apel <soeren@apelpie.net>
+ *
+ * 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_PV_LOGGING_HPP
+#define PULSEVIEW_PV_LOGGING_HPP
+
+#include "globalsettings.hpp"
+
+#include <mutex>
+
+#include <QtGlobal>
+#include <QObject>
+#include <QString>
+#include <QStringList>
+
+using std::mutex;
+
+namespace pv {
+
+class Logging : public QObject, public GlobalSettingsInterface
+{
+       Q_OBJECT
+
+public:
+       enum LogSource {
+               LogSource_pv,
+               LogSource_sr,
+               LogSource_srd
+       };
+
+       static const int MIN_BUFFER_SIZE;
+       static const int MAX_BUFFER_SIZE;
+
+public:
+       ~Logging();
+       void init();
+
+       int get_log_level() const;
+       void set_log_level(int level);
+
+       QString get_log() const;
+
+       void log(const QString &text, int source);
+
+       static void log_pv(QtMsgType type, const QMessageLogContext &context, const QString &msg);
+
+       static int log_sr(void *cb_data, int loglevel, const char *format, va_list args);
+
+#ifdef ENABLE_DECODE
+       static int log_srd(void *cb_data, int loglevel, const char *format, va_list args);
+#endif
+
+private:
+       void on_setting_changed(const QString &key, const QVariant &value);
+
+Q_SIGNALS:
+       void logged_text(QString s);
+
+private:
+       int buffer_size_;
+       QStringList buffer_;
+       mutable mutex log_mutex_;
+};
+
+extern Logging logging;
+
+} // namespace pv
+
+#endif // PULSEVIEW_PV_LOGGING_HPP
index 8b11d0822d53c8960583e742117fcc76abdc52cd..fd889e9e9176d6017c340f30d31d4b2b105b545d 100644 (file)
@@ -30,6 +30,7 @@
 #include <QAction>
 #include <QApplication>
 #include <QCloseEvent>
+#include <QDebug>
 #include <QDockWidget>
 #include <QHBoxLayout>
 #include <QMessageBox>
 
 #include <libsigrokcxx/libsigrokcxx.hpp>
 
-using std::bind;
 using std::dynamic_pointer_cast;
 using std::make_shared;
-using std::map;
-using std::placeholders::_1;
 using std::shared_ptr;
 using std::string;
 
@@ -77,20 +75,7 @@ MainWindow::MainWindow(DeviceManager &device_manager, QWidget *parent) :
        icon_green_(":/icons/status-green.svg"),
        icon_grey_(":/icons/status-grey.svg")
 {
-       qRegisterMetaType<util::Timestamp>("util::Timestamp");
-       qRegisterMetaType<uint64_t>("uint64_t");
-
-       GlobalSettings::register_change_handler(GlobalSettings::Key_View_ColouredBG,
-               bind(&MainWindow::on_settingViewColouredBg_changed, this, _1));
-
-       GlobalSettings::register_change_handler(GlobalSettings::Key_View_ShowSamplingPoints,
-               bind(&MainWindow::on_settingViewShowSamplingPoints_changed, this, _1));
-
-       GlobalSettings::register_change_handler(GlobalSettings::Key_View_ShowAnalogMinorGrid,
-               bind(&MainWindow::on_settingViewShowAnalogMinorGrid_changed, this, _1));
-
-       GlobalSettings settings;
-       settings.set_defaults_where_needed();
+       GlobalSettings::add_change_handler(this);
 
        setup_ui();
        restore_ui_settings();
@@ -98,10 +83,25 @@ MainWindow::MainWindow(DeviceManager &device_manager, QWidget *parent) :
 
 MainWindow::~MainWindow()
 {
+       GlobalSettings::remove_change_handler(this);
+
        while (!sessions_.empty())
                remove_session(sessions_.front());
 }
 
+void MainWindow::show_session_error(const QString text, const QString info_text)
+{
+       // TODO Emulate noquote()
+       qDebug() << "Notifying user of session error:" << info_text;
+
+       QMessageBox msg;
+       msg.setText(text);
+       msg.setInformativeText(info_text);
+       msg.setStandardButtons(QMessageBox::Ok);
+       msg.setIcon(QMessageBox::Warning);
+       msg.exec();
+}
+
 shared_ptr<views::ViewBase> MainWindow::get_active_view() const
 {
        // If there's only one view, use it...
@@ -120,7 +120,7 @@ shared_ptr<views::ViewBase> MainWindow::get_active_view() const
        }
 
        // Get the view contained in the dock widget
-       for (auto entry : view_docks_)
+       for (auto& entry : view_docks_)
                if (entry.first == dock)
                        return entry.second;
 
@@ -134,7 +134,7 @@ shared_ptr<views::ViewBase> MainWindow::add_view(const QString &title,
        shared_ptr<views::ViewBase> v;
 
        QMainWindow *main_window = nullptr;
-       for (auto entry : session_windows_)
+       for (auto& entry : session_windows_)
                if (entry.first.get() == &session)
                        main_window = entry.second;
 
@@ -169,21 +169,21 @@ shared_ptr<views::ViewBase> MainWindow::add_view(const QString &title,
                QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable);
 
        QAbstractButton *close_btn =
-               dock->findChildren<QAbstractButton*>
-                       ("qt_dockwidget_closebutton").front();
+               dock->findChildren<QAbstractButton*>("qt_dockwidget_closebutton")  // clazy:exclude=detaching-temporary
+                       .front();
 
        connect(close_btn, SIGNAL(clicked(bool)),
                this, SLOT(on_view_close_clicked()));
 
-       connect(&session, SIGNAL(trigger_event(util::Timestamp)),
+       connect(&session, SIGNAL(trigger_event(int, util::Timestamp)),
                qobject_cast<views::ViewBase*>(v.get()),
-               SLOT(trigger_event(util::Timestamp)));
+               SLOT(trigger_event(int, util::Timestamp)));
 
        if (type == views::ViewTypeTrace) {
                views::trace::View *tv =
                        qobject_cast<views::trace::View*>(v.get());
 
-               tv->enable_coloured_bg(settings.value(GlobalSettings::Key_View_ColouredBG).toBool());
+               tv->enable_colored_bg(settings.value(GlobalSettings::Key_View_ColoredBG).toBool());
                tv->enable_show_sampling_points(settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool());
                tv->enable_show_analog_minor_grid(settings.value(GlobalSettings::Key_View_ShowAnalogMinorGrid).toBool());
 
@@ -223,7 +223,7 @@ void MainWindow::remove_view(shared_ptr<views::ViewBase> view)
                        continue;
 
                // Find the dock the view is contained in and remove it
-               for (auto entry : view_docks_)
+               for (auto& entry : view_docks_)
                        if (entry.second == view) {
                                // Remove the view from the session
                                session->deregister_view(view);
@@ -291,7 +291,7 @@ void MainWindow::remove_session(shared_ptr<Session> session)
        session->stop_capture();
        QApplication::processEvents();
 
-       for (shared_ptr<views::ViewBase> view : session->views())
+       for (const shared_ptr<views::ViewBase>& view : session->views())
                remove_view(view);
 
        QMainWindow *window = session_windows_.at(session);
@@ -310,7 +310,7 @@ void MainWindow::remove_session(shared_ptr<Session> session)
                // When there are no more tabs, the height of the QTabWidget
                // drops to zero. We must prevent this to keep the static
                // widgets visible
-               for (QWidget *w : static_tab_widget_->findChildren<QWidget*>())
+               for (QWidget *w : static_tab_widget_->findChildren<QWidget*>())  // clazy:exclude=range-loop
                        w->setMinimumHeight(h);
 
                int margin = static_tab_widget_->layout()->contentsMargins().bottom();
@@ -338,20 +338,26 @@ void MainWindow::add_default_session()
 
        shared_ptr<Session> session = add_session();
 
-       map<string, string> dev_info;
-       shared_ptr<devices::HardwareDevice> other_device, demo_device;
-
-       // Use any available device that's not demo
-       for (shared_ptr<devices::HardwareDevice> dev : device_manager_.devices()) {
-               if (dev->hardware_device()->driver()->name() == "demo") {
+       // Check the list of available devices. Prefer the one that was
+       // found with user supplied scan specs (if applicable). Then try
+       // one of the auto detected devices that are not the demo device.
+       // Pick demo in the absence of "genuine" hardware devices.
+       shared_ptr<devices::HardwareDevice> user_device, other_device, demo_device;
+       for (const shared_ptr<devices::HardwareDevice>& dev : device_manager_.devices()) {
+               if (dev == device_manager_.user_spec_device()) {
+                       user_device = dev;
+               } else if (dev->hardware_device()->driver()->name() == "demo") {
                        demo_device = dev;
                } else {
                        other_device = dev;
                }
        }
-
-       // ...and if there isn't any, just use demo then
-       session->select_device(other_device ? other_device : demo_device);
+       if (user_device)
+               session->select_device(user_device);
+       else if (other_device)
+               session->select_device(other_device);
+       else
+               session->select_device(demo_device);
 }
 
 void MainWindow::save_sessions()
@@ -359,7 +365,7 @@ void MainWindow::save_sessions()
        QSettings settings;
        int id = 0;
 
-       for (shared_ptr<Session> session : sessions_) {
+       for (shared_ptr<Session>& session : sessions_) {
                // Ignore sessions using the demo device or no device at all
                if (session->device()) {
                        shared_ptr<devices::HardwareDevice> device =
@@ -395,6 +401,18 @@ void MainWindow::restore_sessions()
        }
 }
 
+void MainWindow::on_setting_changed(const QString &key, const QVariant &value)
+{
+       if (key == GlobalSettings::Key_View_ColoredBG)
+               on_settingViewColoredBg_changed(value);
+
+       if (key == GlobalSettings::Key_View_ShowSamplingPoints)
+               on_settingViewShowSamplingPoints_changed(value);
+
+       if (key == GlobalSettings::Key_View_ShowAnalogMinorGrid)
+               on_settingViewShowAnalogMinorGrid_changed(value);
+}
+
 void MainWindow::setup_ui()
 {
        setObjectName(QString::fromUtf8("MainWindow"));
@@ -415,8 +433,8 @@ void MainWindow::setup_ui()
        view_show_analog_minor_grid_shortcut_ = new QShortcut(QKeySequence(Qt::Key_G), this, SLOT(on_view_show_analog_minor_grid_shortcut()));
        view_show_analog_minor_grid_shortcut_->setAutoRepeat(false);
 
-       view_coloured_bg_shortcut_ = new QShortcut(QKeySequence(Qt::Key_B), this, SLOT(on_view_coloured_bg_shortcut()));
-       view_coloured_bg_shortcut_->setAutoRepeat(false);
+       view_colored_bg_shortcut_ = new QShortcut(QKeySequence(Qt::Key_B), this, SLOT(on_view_colored_bg_shortcut()));
+       view_colored_bg_shortcut_->setAutoRepeat(false);
 
        // Set up the tab area
        new_session_button_ = new QToolButton();
@@ -511,7 +529,7 @@ void MainWindow::restore_ui_settings()
 shared_ptr<Session> MainWindow::get_tab_session(int index) const
 {
        // Find the session that belongs to the tab's main window
-       for (auto entry : session_windows_)
+       for (auto& entry : session_windows_)
                if (entry.second == session_selector_.widget(index))
                        return entry.first;
 
@@ -522,7 +540,7 @@ void MainWindow::closeEvent(QCloseEvent *event)
 {
        bool data_saved = true;
 
-       for (auto entry : session_windows_)
+       for (auto& entry : session_windows_)
                if (!entry.first->data_saved())
                        data_saved = false;
 
@@ -553,28 +571,11 @@ bool MainWindow::restoreState(const QByteArray &state, int version)
        return false;
 }
 
-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::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_add_view(const QString &title, views::ViewType type,
        Session *session)
 {
        // We get a pointer and need a reference
-       for (shared_ptr<Session> s : sessions_)
+       for (shared_ptr<Session>& s : sessions_)
                if (s.get() == session)
                        add_view(title, type, *s);
 }
@@ -631,7 +632,7 @@ void MainWindow::on_run_stop_clicked()
        switch (session->get_capture_state()) {
        case Session::Stopped:
                session->start_capture([&](QString message) {
-                       session_error("Capture failed", message); });
+                       show_session_error("Capture failed", message); });
                break;
        case Session::AwaitingTrigger:
        case Session::Running:
@@ -652,9 +653,9 @@ void MainWindow::on_session_name_changed()
        Session *session = qobject_cast<Session*>(QObject::sender());
        assert(session);
 
-       for (shared_ptr<views::ViewBase> view : session->views()) {
+       for (const shared_ptr<views::ViewBase>& view : session->views()) {
                // Get the dock that contains the view
-               for (auto entry : view_docks_)
+               for (auto& entry : view_docks_)
                        if (entry.second == view) {
                                entry.first->setObjectName(session->name());
                                entry.first->setWindowTitle(session->name());
@@ -662,7 +663,7 @@ void MainWindow::on_session_name_changed()
        }
 
        // Update the tab widget by finding the main window and the tab from that
-       for (auto entry : session_windows_)
+       for (auto& entry : session_windows_)
                if (entry.first.get() == session) {
                        QMainWindow *window = entry.second;
                        const int index = session_selector_.indexOf(window);
@@ -694,7 +695,7 @@ void MainWindow::on_capture_state_changed(QObject *obj)
 void MainWindow::on_new_view(Session *session)
 {
        // We get a pointer and need a reference
-       for (shared_ptr<Session> s : sessions_)
+       for (shared_ptr<Session>& s : sessions_)
                if (s.get() == session)
                        add_view(session->name(), views::ViewTypeTrace, *s);
 }
@@ -715,7 +716,7 @@ void MainWindow::on_view_close_clicked()
        // Get the view contained in the dock widget
        shared_ptr<views::ViewBase> view;
 
-       for (auto entry : view_docks_)
+       for (auto& entry : view_docks_)
                if (entry.first == dock)
                        view = entry.second;
 
@@ -759,12 +760,12 @@ void MainWindow::on_tab_close_requested(int index)
                remove_session(session);
 }
 
-void MainWindow::on_view_coloured_bg_shortcut()
+void MainWindow::on_view_colored_bg_shortcut()
 {
        GlobalSettings settings;
 
-       bool state = settings.value(GlobalSettings::Key_View_ColouredBG).toBool();
-       settings.setValue(GlobalSettings::Key_View_ColouredBG, !state);
+       bool state = settings.value(GlobalSettings::Key_View_ColoredBG).toBool();
+       settings.setValue(GlobalSettings::Key_View_ColoredBG, !state);
 }
 
 void MainWindow::on_view_sticky_scrolling_shortcut()
@@ -791,18 +792,18 @@ void MainWindow::on_view_show_analog_minor_grid_shortcut()
        settings.setValue(GlobalSettings::Key_View_ShowAnalogMinorGrid, !state);
 }
 
-void MainWindow::on_settingViewColouredBg_changed(const QVariant new_value)
+void MainWindow::on_settingViewColoredBg_changed(const QVariant new_value)
 {
        bool state = new_value.toBool();
 
-       for (auto entry : view_docks_) {
+       for (auto& entry : view_docks_) {
                shared_ptr<views::ViewBase> viewbase = entry.second;
 
                // Only trace views have this setting
                views::trace::View* view =
                                qobject_cast<views::trace::View*>(viewbase.get());
                if (view)
-                       view->enable_coloured_bg(state);
+                       view->enable_colored_bg(state);
        }
 }
 
@@ -810,7 +811,7 @@ void MainWindow::on_settingViewShowSamplingPoints_changed(const QVariant new_val
 {
        bool state = new_value.toBool();
 
-       for (auto entry : view_docks_) {
+       for (auto& entry : view_docks_) {
                shared_ptr<views::ViewBase> viewbase = entry.second;
 
                // Only trace views have this setting
@@ -825,7 +826,7 @@ void MainWindow::on_settingViewShowAnalogMinorGrid_changed(const QVariant new_va
 {
        bool state = new_value.toBool();
 
-       for (auto entry : view_docks_) {
+       for (auto& entry : view_docks_) {
                shared_ptr<views::ViewBase> viewbase = entry.second;
 
                // Only trace views have this setting
index 71cad57817a0554ad832b8710e0dc39dbe8fcc91..6d92b270dc6394f4fd5c2a66bb604766044b8c98 100644 (file)
@@ -30,6 +30,7 @@
 #include <QTabWidget>
 #include <QToolButton>
 
+#include "globalsettings.hpp"
 #include "session.hpp"
 #include "views/viewbase.hpp"
 
@@ -61,7 +62,7 @@ class DecoderMenu;
 #endif
 }
 
-class MainWindow : public QMainWindow
+class MainWindow : public QMainWindow, public GlobalSettingsInterface
 {
        Q_OBJECT
 
@@ -74,6 +75,8 @@ public:
 
        ~MainWindow();
 
+       static void show_session_error(const QString text, const QString info_text);
+
        shared_ptr<views::ViewBase> get_active_view() const;
 
        shared_ptr<views::ViewBase> add_view(const QString &title,
@@ -92,6 +95,8 @@ public:
        void save_sessions();
        void restore_sessions();
 
+       void on_setting_changed(const QString &key, const QVariant &value);
+
 private:
        void setup_ui();
 
@@ -106,11 +111,7 @@ private:
 
        virtual bool restoreState(const QByteArray &state, int version = 0);
 
-       void session_error(const QString text, const QString info_text);
-
 private Q_SLOTS:
-       void show_session_error(const QString text, const QString info_text);
-
        void on_add_view(const QString &title, views::ViewType type,
                Session *session);
 
@@ -130,12 +131,12 @@ private Q_SLOTS:
        void on_tab_changed(int index);
        void on_tab_close_requested(int index);
 
-       void on_view_coloured_bg_shortcut();
+       void on_view_colored_bg_shortcut();
        void on_view_sticky_scrolling_shortcut();
        void on_view_show_sampling_points_shortcut();
        void on_view_show_analog_minor_grid_shortcut();
 
-       void on_settingViewColouredBg_changed(const QVariant new_value);
+       void on_settingViewColoredBg_changed(const QVariant new_value);
        void on_settingViewShowSamplingPoints_changed(const QVariant new_value);
        void on_settingViewShowAnalogMinorGrid_changed(const QVariant new_value);
 
@@ -163,7 +164,7 @@ private:
        QShortcut *view_sticky_scrolling_shortcut_;
        QShortcut *view_show_sampling_points_shortcut_;
        QShortcut *view_show_analog_minor_grid_shortcut_;
-       QShortcut *view_coloured_bg_shortcut_;
+       QShortcut *view_colored_bg_shortcut_;
        QShortcut *run_stop_shortcut_;
        QShortcut *close_application_shortcut_;
        QShortcut *close_current_tab_shortcut_;
index ca142d68582a433eb0626661a459e8000653a5c6..acd7079b7616d0a37a31d682eb7459c3bf0e5738 100644 (file)
 
 #include <map>
 
+#include <QApplication>
 #include <QCheckBox>
+#include <QFontMetrics>
 #include <QFormLayout>
 #include <QGridLayout>
+#include <QHBoxLayout>
 #include <QLabel>
 
 #include "channels.hpp"
 
+#include <pv/session.hpp>
 #include <pv/binding/device.hpp>
+#include <pv/data/logic.hpp>
+#include <pv/data/logicsegment.hpp>
 #include <pv/data/signalbase.hpp>
 #include <pv/devices/device.hpp>
-#include <pv/session.hpp>
-#include <pv/views/trace/signal.hpp>
-
-#include <libsigrokcxx/libsigrokcxx.hpp>
-
-using namespace Qt;
 
+using std::make_shared;
 using std::map;
+using std::out_of_range;
 using std::shared_ptr;
-using std::make_shared;
 using std::unordered_set;
 using std::vector;
 
 using pv::data::SignalBase;
+using pv::data::Logic;
+using pv::data::LogicSegment;
 
 using sigrok::Channel;
 using sigrok::ChannelGroup;
@@ -55,8 +58,16 @@ Channels::Channels(Session &session, QWidget *parent) :
        Popup(parent),
        session_(session),
        updating_channels_(false),
-       enable_all_channels_(tr("Enable All"), this),
-       disable_all_channels_(tr("Disable All"), this),
+       enable_all_channels_(tr("All"), this),
+       disable_all_channels_(tr("All"), this),
+       enable_all_logic_channels_(tr("Logic"), this),
+       disable_all_logic_channels_(tr("Logic"), this),
+       enable_all_analog_channels_(tr("Analog"), this),
+       disable_all_analog_channels_(tr("Analog"), this),
+       enable_all_named_channels_(tr("Named"), this),
+       disable_all_unnamed_channels_(tr("Unnamed"), this),
+       enable_all_changing_channels_(tr("Changing"), this),
+       disable_all_non_changing_channels_(tr("Non-changing"), this),
        check_box_mapper_(this)
 {
        // Create the layout
@@ -69,19 +80,18 @@ Channels::Channels(Session &session, QWidget *parent) :
        map<shared_ptr<Channel>, shared_ptr<SignalBase> > signal_map;
 
        unordered_set< shared_ptr<SignalBase> > sigs;
-       for (const shared_ptr<data::SignalBase> b : session_.signalbases())
+       for (const shared_ptr<data::SignalBase>& b : session_.signalbases())
                sigs.insert(b);
 
        for (const shared_ptr<SignalBase> &sig : sigs)
                signal_map[sig->channel()] = sig;
 
        // Populate channel groups
-       for (auto entry : device->channel_groups()) {
-               shared_ptr<ChannelGroup> group = entry.second;
-               // Make a set of signals, and removed this signals from the
-               // signal map.
+       for (auto& entry : device->channel_groups()) {
+               const shared_ptr<ChannelGroup> group = entry.second;
+               // Make a set of signals and remove these signals from the signal map
                vector< shared_ptr<SignalBase> > group_sigs;
-               for (auto channel : group->channels()) {
+               for (auto& channel : group->channels()) {
                        const auto iter = signal_map.find(channel);
 
                        if (iter == signal_map.end())
@@ -95,31 +105,56 @@ Channels::Channels(Session &session, QWidget *parent) :
        }
 
        // Make a vector of the remaining channels
-       vector< shared_ptr<SignalBase> > global_sigs;
-       for (auto channel : device->channels()) {
+       vector< shared_ptr<SignalBase> > global_analog_sigs, global_logic_sigs;
+       for (auto& channel : device->channels()) {
                const map<shared_ptr<Channel>, shared_ptr<SignalBase> >::
                        const_iterator iter = signal_map.find(channel);
-               if (iter != signal_map.end())
-                       global_sigs.push_back((*iter).second);
+
+               if (iter != signal_map.end()) {
+                       const shared_ptr<SignalBase> signal = (*iter).second;
+                       if (signal->type() == SignalBase::AnalogChannel)
+                               global_analog_sigs.push_back(signal);
+                       else
+                               global_logic_sigs.push_back(signal);
+               }
        }
 
-       // Create a group
-       populate_group(nullptr, global_sigs);
+       // Create the groups for the ungrouped channels
+       populate_group(nullptr, global_logic_sigs);
+       populate_group(nullptr, global_analog_sigs);
 
        // Create the enable/disable all buttons
-       connect(&enable_all_channels_, SIGNAL(clicked()),
-               this, SLOT(enable_all_channels()));
-       connect(&disable_all_channels_, SIGNAL(clicked()),
-               this, SLOT(disable_all_channels()));
-
-       enable_all_channels_.setFlat(true);
-       disable_all_channels_.setFlat(true);
-
-       buttons_bar_.addWidget(&enable_all_channels_);
-       buttons_bar_.addWidget(&disable_all_channels_);
-       buttons_bar_.addStretch(1);
-
-       layout_.addRow(&buttons_bar_);
+       connect(&enable_all_channels_, SIGNAL(clicked()), this, SLOT(enable_all_channels()));
+       connect(&disable_all_channels_, SIGNAL(clicked()), this, SLOT(disable_all_channels()));
+       connect(&enable_all_logic_channels_, SIGNAL(clicked()), this, SLOT(enable_all_logic_channels()));
+       connect(&disable_all_logic_channels_, SIGNAL(clicked()), this, SLOT(disable_all_logic_channels()));
+       connect(&enable_all_analog_channels_, SIGNAL(clicked()), this, SLOT(enable_all_analog_channels()));
+       connect(&disable_all_analog_channels_, SIGNAL(clicked()), this, SLOT(disable_all_analog_channels()));
+       connect(&enable_all_named_channels_, SIGNAL(clicked()), this, SLOT(enable_all_named_channels()));
+       connect(&disable_all_unnamed_channels_, SIGNAL(clicked()), this, SLOT(disable_all_unnamed_channels()));
+       connect(&enable_all_changing_channels_, SIGNAL(clicked()),
+               this, SLOT(enable_all_changing_channels()));
+       connect(&disable_all_non_changing_channels_, SIGNAL(clicked()),
+               this, SLOT(disable_all_non_changing_channels()));
+
+       QLabel *label1 = new QLabel(tr("Disable: "));
+       filter_buttons_bar_.addWidget(label1, 0, 0);
+       filter_buttons_bar_.addWidget(&disable_all_channels_, 0, 1);
+       filter_buttons_bar_.addWidget(&disable_all_logic_channels_, 0, 2);
+       filter_buttons_bar_.addWidget(&disable_all_analog_channels_, 0, 3);
+       filter_buttons_bar_.addWidget(&disable_all_unnamed_channels_, 0, 4);
+       filter_buttons_bar_.addWidget(&disable_all_non_changing_channels_, 0, 5);
+
+       QLabel *label2 = new QLabel(tr("Enable: "));
+       filter_buttons_bar_.addWidget(label2, 1, 0);
+       filter_buttons_bar_.addWidget(&enable_all_channels_, 1, 1);
+       filter_buttons_bar_.addWidget(&enable_all_logic_channels_, 1, 2);
+       filter_buttons_bar_.addWidget(&enable_all_analog_channels_, 1, 3);
+       filter_buttons_bar_.addWidget(&enable_all_named_channels_, 1, 4);
+       filter_buttons_bar_.addWidget(&enable_all_changing_channels_, 1, 5);
+
+       layout_.addItem(new QSpacerItem(0, 15, QSizePolicy::Expanding, QSizePolicy::Expanding));
+       layout_.addRow(&filter_buttons_bar_);
 
        // Connect the check-box signal mapper
        connect(&check_box_mapper_, SIGNAL(mapped(QWidget*)),
@@ -130,14 +165,51 @@ void Channels::set_all_channels(bool set)
 {
        updating_channels_ = true;
 
-       for (map<QCheckBox*, shared_ptr<SignalBase> >::const_iterator i =
-                       check_box_signal_map_.begin();
-                       i != check_box_signal_map_.end(); i++) {
-               const shared_ptr<SignalBase> sig = (*i).second;
+       for (auto& entry : check_box_signal_map_) {
+               QCheckBox *cb = entry.first;
+               const shared_ptr<SignalBase> sig = entry.second;
                assert(sig);
 
                sig->set_enabled(set);
-               (*i).first->setChecked(set);
+               cb->setChecked(set);
+       }
+
+       updating_channels_ = false;
+}
+
+void Channels::enable_channels_conditionally(
+       function<bool (const shared_ptr<data::SignalBase>)> cond_func)
+{
+       updating_channels_ = true;
+
+       for (auto& entry : check_box_signal_map_) {
+               QCheckBox *cb = entry.first;
+               const shared_ptr<SignalBase> sig = entry.second;
+               assert(sig);
+
+               if (cond_func(sig)) {
+                       sig->set_enabled(true);
+                       cb->setChecked(true);
+               }
+       }
+
+       updating_channels_ = false;
+}
+
+void Channels::disable_channels_conditionally(
+       function<bool (const shared_ptr<data::SignalBase>)> cond_func)
+{
+       updating_channels_ = true;
+
+       for (auto& entry : check_box_signal_map_) {
+               QCheckBox *cb = entry.first;
+               const shared_ptr<SignalBase> sig = entry.second;
+               assert(sig);
+
+               if (cond_func(sig)) {
+                       sig->set_enabled(false);
+                       cb->setChecked(false);
+               }
        }
 
        updating_channels_ = false;
@@ -156,10 +228,13 @@ void Channels::populate_group(shared_ptr<ChannelGroup> group,
                binding = make_shared<Device>(group);
 
        // Create a title if the group is going to have any content
-       if ((!sigs.empty() || (binding && !binding->properties().empty())) &&
-               group)
-               layout_.addRow(new QLabel(
-                       QString("<h3>%1</h3>").arg(group->name().c_str())));
+       if ((!sigs.empty() || (binding && !binding->properties().empty())) && group)
+       {
+               QLabel *label = new QLabel(
+                       QString("<h3>%1</h3>").arg(group->name().c_str()));
+               layout_.addRow(label);
+               group_label_map_[group] = label;
+       }
 
        // Create the channel group grid
        QGridLayout *const channel_grid = create_channel_group_grid(sigs);
@@ -181,7 +256,7 @@ QGridLayout* Channels::create_channel_group_grid(
        for (const shared_ptr<SignalBase>& sig : sigs) {
                assert(sig);
 
-               QCheckBox *const checkbox = new QCheckBox(sig->name());
+               QCheckBox *const checkbox = new QCheckBox(sig->display_name());
                check_box_mapper_.setMapping(checkbox, checkbox);
                connect(checkbox, SIGNAL(toggled(bool)),
                        &check_box_mapper_, SLOT(map()));
@@ -201,15 +276,31 @@ void Channels::showEvent(QShowEvent *event)
 {
        pv::widgets::Popup::showEvent(event);
 
+       const shared_ptr<sigrok::Device> device = session_.device()->device();
+       assert(device);
+
+       // Update group labels
+       for (auto& entry : device->channel_groups()) {
+               const shared_ptr<ChannelGroup> group = entry.second;
+
+               try {
+                       QLabel* label = group_label_map_.at(group);
+                       label->setText(QString("<h3>%1</h3>").arg(group->name().c_str()));
+               } catch (out_of_range&) {
+                       // Do nothing
+               }
+       }
+
        updating_channels_ = true;
 
-       for (map<QCheckBox*, shared_ptr<SignalBase> >::const_iterator i =
-                       check_box_signal_map_.begin();
-                       i != check_box_signal_map_.end(); i++) {
-               const shared_ptr<SignalBase> sig = (*i).second;
+       for (auto& entry : check_box_signal_map_) {
+               QCheckBox *cb = entry.first;
+               const shared_ptr<SignalBase> sig = entry.second;
                assert(sig);
 
-               (*i).first->setChecked(sig->enabled());
+               // Update the check box
+               cb->setChecked(sig->enabled());
+               cb->setText(sig->display_name());
        }
 
        updating_channels_ = false;
@@ -244,5 +335,107 @@ void Channels::disable_all_channels()
        set_all_channels(false);
 }
 
+void Channels::enable_all_logic_channels()
+{
+       enable_channels_conditionally([](const shared_ptr<SignalBase> signal)
+               { return signal->type() == SignalBase::LogicChannel; });
+}
+
+void Channels::disable_all_logic_channels()
+{
+       disable_channels_conditionally([](const shared_ptr<SignalBase> signal)
+               { return signal->type() == SignalBase::LogicChannel; });
+}
+
+void Channels::enable_all_analog_channels()
+{
+       enable_channels_conditionally([](const shared_ptr<SignalBase> signal)
+               { return signal->type() == SignalBase::AnalogChannel; });
+}
+
+void Channels::disable_all_analog_channels()
+{
+       disable_channels_conditionally([](const shared_ptr<SignalBase> signal)
+               { return signal->type() == SignalBase::AnalogChannel; });
+}
+
+void Channels::enable_all_named_channels()
+{
+       enable_channels_conditionally([](const shared_ptr<SignalBase> signal)
+               { return signal->name() != signal->internal_name(); });
+}
+
+void Channels::disable_all_unnamed_channels()
+{
+       disable_channels_conditionally([](const shared_ptr<SignalBase> signal)
+               { return signal->name() == signal->internal_name(); });
+}
+
+void Channels::enable_all_changing_channels()
+{
+       enable_channels_conditionally([](const shared_ptr<SignalBase> signal)
+               {
+                       // Never enable channels without sample data
+                       if (!signal->has_samples())
+                               return false;
+
+                       // Non-logic channels are considered to always have a signal
+                       if (signal->type() != SignalBase::LogicChannel)
+                               return true;
+
+                       const shared_ptr<Logic> logic = signal->logic_data();
+                       assert(logic);
+
+                       // If any of the segments has edges, enable this channel
+                       for (shared_ptr<LogicSegment> segment : logic->logic_segments()) {
+                               vector<LogicSegment::EdgePair> edges;
+
+                               segment->get_subsampled_edges(edges,
+                                       0, segment->get_sample_count() - 1,
+                                       LogicSegment::MipMapScaleFactor,
+                                       signal->index());
+
+                               if (edges.size() > 2)
+                                       return true;
+                       }
+
+                       // No edges detected in any of the segments
+                       return false;
+               });
+}
+
+void Channels::disable_all_non_changing_channels()
+{
+       disable_channels_conditionally([](const shared_ptr<SignalBase> signal)
+               {
+                       // Always disable channels without sample data
+                       if (!signal->has_samples())
+                               return true;
+
+                       // Non-logic channels are considered to always have a signal
+                       if (signal->type() != SignalBase::LogicChannel)
+                               return false;
+
+                       const shared_ptr<Logic> logic = signal->logic_data();
+                       assert(logic);
+
+                       // If any of the segments has edges, leave this channel enabled
+                       for (shared_ptr<LogicSegment> segment : logic->logic_segments()) {
+                               vector<LogicSegment::EdgePair> edges;
+
+                               segment->get_subsampled_edges(edges,
+                                       0, segment->get_sample_count() - 1,
+                                       LogicSegment::MipMapScaleFactor,
+                                       signal->index());
+
+                               if (edges.size() > 2)
+                                       return false;
+                       }
+
+                       // No edges detected in any of the segments
+                       return true;
+               });
+}
+
 }  // namespace popups
 }  // namespace pv
index 3701325bc704572758831b8781d800a56cc39065..54d24340d7d85229db92576df8efc17bab24a7fc 100644 (file)
 #ifndef PULSEVIEW_PV_POPUPS_CHANNELS_HPP
 #define PULSEVIEW_PV_POPUPS_CHANNELS_HPP
 
+#include <functional>
 #include <map>
 #include <memory>
 #include <vector>
 
+#include <QCheckBox>
 #include <QFormLayout>
-#include <QHBoxLayout>
+#include <QGridLayout>
+#include <QLabel>
 #include <QPushButton>
 #include <QSignalMapper>
 
 #include <pv/widgets/popup.hpp>
 
+using std::function;
 using std::map;
 using std::shared_ptr;
 using std::vector;
 
-class QCheckBox;
-class QGridLayout;
-
 namespace sigrok {
        class ChannelGroup;
 }
@@ -70,13 +71,17 @@ public:
 private:
        void set_all_channels(bool set);
 
+       void enable_channels_conditionally(
+               function<bool (const shared_ptr<data::SignalBase>)> cond_func);
+       void disable_channels_conditionally(
+               function<bool (const shared_ptr<data::SignalBase>)> cond_func);
+
        void populate_group(shared_ptr<sigrok::ChannelGroup> group,
                const vector< shared_ptr<pv::data::SignalBase> > sigs);
 
        QGridLayout* create_channel_group_grid(
                const vector< shared_ptr<pv::data::SignalBase> > sigs);
 
-private:
        void showEvent(QShowEvent *event);
 
 private Q_SLOTS:
@@ -84,6 +89,14 @@ private Q_SLOTS:
 
        void enable_all_channels();
        void disable_all_channels();
+       void enable_all_logic_channels();
+       void disable_all_logic_channels();
+       void enable_all_analog_channels();
+       void disable_all_analog_channels();
+       void enable_all_named_channels();
+       void disable_all_unnamed_channels();
+       void enable_all_changing_channels();
+       void disable_all_non_changing_channels();
 
 private:
        pv::Session &session_;
@@ -95,10 +108,14 @@ private:
        vector< shared_ptr<pv::binding::Device> > group_bindings_;
        map< QCheckBox*, shared_ptr<pv::data::SignalBase> >
                check_box_signal_map_;
-
-       QHBoxLayout buttons_bar_;
-       QPushButton enable_all_channels_;
-       QPushButton disable_all_channels_;
+       map< shared_ptr<sigrok::ChannelGroup>, QLabel*> group_label_map_;
+
+       QGridLayout filter_buttons_bar_;
+       QPushButton enable_all_channels_, disable_all_channels_;
+       QPushButton enable_all_logic_channels_, disable_all_logic_channels_;
+       QPushButton enable_all_analog_channels_, disable_all_analog_channels_;
+       QPushButton enable_all_named_channels_, disable_all_unnamed_channels_;
+       QPushButton enable_all_changing_channels_, disable_all_non_changing_channels_;
 
        QSignalMapper check_box_mapper_;
 };
index df9113679d2d5ddddd67c169778ea4820166cddc..bfb702cf064ce062462b67e5c7f0d0eba3f822d0 100644 (file)
@@ -49,5 +49,14 @@ pv::binding::Device& DeviceOptions::binding()
        return binding_;
 }
 
+void DeviceOptions::show()
+{
+       // Update device config widgets with the current values supplied by the
+       // driver before actually showing the popup dialog
+       binding_.update_property_widgets();
+
+       Popup::show();
+}
+
 } // namespace popups
 } // namespace pv
index 21f88e90947f038a01f675a7483584ccc90ff776..52b2c5a585dae74e10041033076f54b0bd57bee7 100644 (file)
@@ -44,6 +44,8 @@ public:
 
        pv::binding::Device& binding();
 
+       virtual void show();
+
 private:
        shared_ptr<sigrok::Device> device_;
 
index 0dd1c50f10698702a2397ea604b27d6c285ab230..0cd1604612661730348acd099580f7ac02947725 100644 (file)
@@ -20,6 +20,9 @@
 #include <cassert>
 
 #include <QCheckBox>
+#include <QDebug>
+
+#include <libsigrokcxx/libsigrokcxx.hpp>
 
 #include "bool.hpp"
 
@@ -40,16 +43,19 @@ QWidget* Bool::get_widget(QWidget *parent, bool auto_commit)
        if (!getter_)
                return nullptr;
 
-       Glib::VariantBase variant = getter_();
-       if (!variant.gobj())
+       try {
+               Glib::VariantBase variant = getter_();
+               if (!variant.gobj())
+                       return nullptr;
+       } catch (const sigrok::Error &e) {
+               qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what());
                return nullptr;
-
-       bool value = Glib::VariantBase::cast_dynamic<Glib::Variant<bool>>(
-               variant).get();
+       }
 
        check_box_ = new QCheckBox(name(), parent);
        check_box_->setToolTip(desc());
-       check_box_->setCheckState(value ? Qt::Checked : Qt::Unchecked);
+
+       update_widget();
 
        if (auto_commit)
                connect(check_box_, SIGNAL(stateChanged(int)),
@@ -63,6 +69,27 @@ bool Bool::labeled_widget() const
        return true;
 }
 
+void Bool::update_widget()
+{
+       if (!check_box_)
+               return;
+
+       Glib::VariantBase variant;
+
+       try {
+               variant = getter_();
+       } catch (const sigrok::Error &e) {
+               qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what());
+               return;
+       }
+
+       assert(variant.gobj());
+       bool value = Glib::VariantBase::cast_dynamic<Glib::Variant<bool>>(
+               variant).get();
+
+       check_box_->setCheckState(value ? Qt::Checked : Qt::Unchecked);
+}
+
 void Bool::commit()
 {
        assert(setter_);
@@ -70,8 +97,7 @@ void Bool::commit()
        if (!check_box_)
                return;
 
-       setter_(Glib::Variant<bool>::create(
-               check_box_->checkState() == Qt::Checked));
+       setter_(Glib::Variant<bool>::create(check_box_->checkState() == Qt::Checked));
 }
 
 void Bool::on_state_changed(int)
index ef0916f48523a533d228296549138cd786eb7c29..fd5744fe3830f0d56779b981afca4e85aae2e4ab 100644 (file)
@@ -38,6 +38,7 @@ public:
 
        QWidget* get_widget(QWidget *parent, bool auto_commit);
        bool labeled_widget() const;
+       void update_widget();
 
        void commit();
 
index e1539a127d63dccd9830673a21d66bd07e343f69..f39ae844fc97ce34b4519d4a67546a867e8cdb74 100644 (file)
 
 #include <cassert>
 
+#include <QDebug>
 #include <QDoubleSpinBox>
 
+#include <libsigrokcxx/libsigrokcxx.hpp>
+
 #include "double.hpp"
 
 using boost::optional;
@@ -54,12 +57,14 @@ QWidget* Double::get_widget(QWidget *parent, bool auto_commit)
        if (!getter_)
                return nullptr;
 
-       Glib::VariantBase variant = getter_();
-       if (!variant.gobj())
+       try {
+               Glib::VariantBase variant = getter_();
+               if (!variant.gobj())
+                       return nullptr;
+       } catch (const sigrok::Error &e) {
+               qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what());
                return nullptr;
-
-       double value = Glib::VariantBase::cast_dynamic<Glib::Variant<double>>(
-               variant).get();
+       }
 
        spin_box_ = new QDoubleSpinBox(parent);
        spin_box_->setDecimals(decimals_);
@@ -69,7 +74,7 @@ QWidget* Double::get_widget(QWidget *parent, bool auto_commit)
        if (step_)
                spin_box_->setSingleStep(*step_);
 
-       spin_box_->setValue(value);
+       update_widget();
 
        if (auto_commit)
                connect(spin_box_, SIGNAL(valueChanged(double)),
@@ -78,6 +83,27 @@ QWidget* Double::get_widget(QWidget *parent, bool auto_commit)
        return spin_box_;
 }
 
+void Double::update_widget()
+{
+       if (!spin_box_)
+               return;
+
+       Glib::VariantBase variant;
+
+       try {
+               variant = getter_();
+       } catch (const sigrok::Error &e) {
+               qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what());
+               return;
+       }
+
+       assert(variant.gobj());
+
+       double value = Glib::VariantBase::cast_dynamic<Glib::Variant<double>>(
+               variant).get();
+       spin_box_->setValue(value);
+}
+
 void Double::commit()
 {
        assert(setter_);
index 4e18e9563ff0adc9c05dc076fb5b57e8cf952409..44e9f7e7cb25b94b508ad1b96457454f5b46c7cc 100644 (file)
@@ -47,6 +47,7 @@ public:
        virtual ~Double() = default;
 
        QWidget* get_widget(QWidget *parent, bool auto_commit);
+       void update_widget();
 
        void commit();
 
index d5d571a9ec487396b921cdae5b45855bb7bbabde..d62c901cf4c70d2c3848b3a3875d9d088596b794 100644 (file)
  */
 
 #include <cassert>
+#include <cfloat>
+#include <cmath>
+#include <limits>
+#include <vector>
 
 #include <QComboBox>
+#include <QDebug>
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QSlider>
+
+#include <libsigrokcxx/libsigrokcxx.hpp>
 
 #include "enum.hpp"
 
+using std::abs;
+// Note that "using std::isnan;" is _not_ put here since that would break
+// compilation on some platforms. Use "std::isnan()" instead in checks below.
+using std::numeric_limits;
 using std::pair;
 using std::vector;
 
@@ -34,52 +48,244 @@ Enum::Enum(QString name, QString desc,
        Getter getter, Setter setter) :
        Property(name, desc, getter, setter),
        values_(values),
-       selector_(nullptr)
+       is_range_(false),
+       selector_(nullptr),
+       slider_layout_widget_(nullptr),
+       slider_(nullptr),
+       slider_label_(nullptr)
 {
+       // Try to determine whether the values make up a range, created by e.g.
+       // std_gvar_min_max_step_thresholds()
+
+       vector<double> deltas;
+       double prev_value = 0;
+
+       for (const pair<Glib::VariantBase, QString> &v : values_) {
+               gdouble value;
+               if (v.first.is_of_type(Glib::VariantType("d"))) {
+                       g_variant_get((GVariant*)(v.first.gobj()), "d", &value);
+               } else if (v.first.is_of_type(Glib::VariantType("(dd)"))) {
+                       gdouble dummy;
+                       g_variant_get((GVariant*)(v.first.gobj()), "(dd)", &value, &dummy);
+               } else
+                       break; // Type not d or (dd), so not a range that we can handle
+               deltas.push_back(value - prev_value);
+               prev_value = value;
+       }
+
+       if (deltas.size() > 0) {
+               bool constant_delta = true;
+               double prev_delta = numeric_limits<double>::quiet_NaN();
+
+               bool skip_first = true;
+               for (double delta : deltas) {
+                       // First value is incorrect, it's the delta to 0 since no
+                       // previous value existed yet
+                       if (skip_first) {
+                               skip_first = false;
+                               continue;
+                       }
+                       if (std::isnan(prev_delta))
+                               prev_delta = delta;
+
+                       // 2*DBL_EPSILON doesn't work here, so use a workaround
+                       if (abs(delta - prev_delta) > (delta/10))
+                               constant_delta = false;
+
+                       prev_delta = delta;
+               }
+
+               if (constant_delta)
+                       is_range_ = true;
+       }
 }
 
 QWidget* Enum::get_widget(QWidget *parent, bool auto_commit)
 {
-       if (selector_)
-               return selector_;
-
        if (!getter_)
                return nullptr;
 
-       Glib::VariantBase variant = getter_();
+       Glib::VariantBase variant;
+
+       try {
+                variant = getter_();
+       } catch (const sigrok::Error &e) {
+               qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what());
+               return nullptr;
+       }
+
        if (!variant.gobj())
                return nullptr;
 
-       selector_ = new QComboBox(parent);
-       for (unsigned int i = 0; i < values_.size(); i++) {
-               const pair<Glib::VariantBase, QString> &v = values_[i];
-               selector_->addItem(v.second, qVariantFromValue(v.first));
-               if (v.first.equal(variant))
-                       selector_->setCurrentIndex(i);
+       if (is_range_) {
+               // Use slider
+               if (slider_layout_widget_)
+                       return slider_layout_widget_;
+
+               slider_ = new QSlider();
+               // Sliders can't handle float values, so we just use it to specify
+               // the number of steps that we're away from the range's beginning
+               slider_->setOrientation(Qt::Horizontal);
+               slider_->setMinimum(0);
+               slider_->setMaximum(values_.size() - 1);
+               slider_->setSingleStep(1);
+
+               slider_label_ = new QLabel();
+
+               slider_layout_widget_ = new QWidget(parent);
+               QHBoxLayout *layout = new QHBoxLayout(slider_layout_widget_);
+               layout->addWidget(slider_);
+               layout->addWidget(slider_label_);
+
+               update_widget();
+
+               if (auto_commit)
+                       connect(slider_, SIGNAL(valueChanged(int)),
+                               this, SLOT(on_value_changed(int)));
+
+               return slider_layout_widget_;
+
+       } else {
+               // Use combo box
+               if (selector_)
+                       return selector_;
+
+               selector_ = new QComboBox(parent);
+               for (unsigned int i = 0; i < values_.size(); i++) {
+                       const pair<Glib::VariantBase, QString> &v = values_[i];
+                       selector_->addItem(v.second, qVariantFromValue(v.first));
+               }
+
+               update_widget();
+
+               if (auto_commit)
+                       connect(selector_, SIGNAL(currentIndexChanged(int)),
+                               this, SLOT(on_current_index_changed(int)));
+
+               return selector_;
        }
+}
+
+void Enum::update_widget()
+{
+       Glib::VariantBase variant;
+
+       try {
+               variant = getter_();
+       } catch (const sigrok::Error &e) {
+               qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what());
+               return;
+       }
+
+       assert(variant.gobj());
+
+       if (is_range_) {
+
+               // Use slider
+               if (!slider_layout_widget_)
+                       return;
+
+               for (unsigned int i = 0; i < values_.size(); i++) {
+                       const pair<Glib::VariantBase, QString> &v = values_[i];
+
+                       // g_variant_equal() doesn't handle floating point properly
+                       if (v.first.is_of_type(Glib::VariantType("d"))) {
+                               gdouble a, b;
+                               g_variant_get(variant.gobj(), "d", &a);
+                               g_variant_get((GVariant*)(v.first.gobj()), "d", &b);
+
+                               if (abs(a - b) <= 2 * DBL_EPSILON) {
+                                       slider_->setValue(i);
+                                       slider_label_->setText(v.second);
+                               }
+                       } else {
+                               // Check for "(dd)" type and handle it if it's found
+                               if (v.first.is_of_type(Glib::VariantType("(dd)"))) {
+                                       gdouble a1, a2, b1, b2;
+                                       g_variant_get(variant.gobj(), "(dd)", &a1, &a2);
+                                       g_variant_get((GVariant*)(v.first.gobj()), "(dd)", &b1, &b2);
+
+                                       if ((abs(a1 - b1) <= 2 * DBL_EPSILON) && \
+                                               (abs(a2 - b2) <= 2 * DBL_EPSILON)) {
+                                               slider_->setValue(i);
+                                               slider_label_->setText(v.second);
+                                       }
+
+                               } else {
+                                       qWarning() << "Enum property" << name() << "encountered unsupported type";
+                                       return;
+                               }
+                       }
+               }
 
-       if (auto_commit)
-               connect(selector_, SIGNAL(currentIndexChanged(int)),
-                       this, SLOT(on_current_item_changed(int)));
+       } else {
+               // Use combo box
+               if (!selector_)
+                       return;
 
-       return selector_;
+               for (unsigned int i = 0; i < values_.size(); i++) {
+                       const pair<Glib::VariantBase, QString> &v = values_[i];
+
+                       // g_variant_equal() doesn't handle floating point properly
+                       if (v.first.is_of_type(Glib::VariantType("d"))) {
+                               gdouble a, b;
+                               g_variant_get(variant.gobj(), "d", &a);
+                               g_variant_get((GVariant*)(v.first.gobj()), "d", &b);
+                               if (abs(a - b) <= 2 * DBL_EPSILON)
+                                       selector_->setCurrentIndex(i);
+                       } else {
+                               // Check for "(dd)" type and handle it if it's found
+                               if (v.first.is_of_type(Glib::VariantType("(dd)"))) {
+                                       gdouble a1, a2, b1, b2;
+                                       g_variant_get(variant.gobj(), "(dd)", &a1, &a2);
+                                       g_variant_get((GVariant*)(v.first.gobj()), "(dd)", &b1, &b2);
+                                       if ((abs(a1 - b1) <= 2 * DBL_EPSILON) && \
+                                               (abs(a2 - b2) <= 2 * DBL_EPSILON))
+                                               selector_->setCurrentIndex(i);
+
+                               } else
+                                       // Handle all other types
+                                       if (v.first.equal(variant))
+                                               selector_->setCurrentIndex(i);
+                       }
+               }
+       }
 }
 
 void Enum::commit()
 {
        assert(setter_);
 
-       if (!selector_)
-               return;
+       if (is_range_) {
+               // Use slider
+               if (!slider_layout_widget_)
+                       return;
 
-       const int index = selector_->currentIndex();
-       if (index < 0)
-               return;
+               setter_(values_.at(slider_->value()).first);
+
+               update_widget();
+       } else {
+               // Use combo box
+               if (!selector_)
+                       return;
+
+               const int index = selector_->currentIndex();
+               if (index < 0)
+                       return;
 
-       setter_(selector_->itemData(index).value<Glib::VariantBase>());
+               setter_(selector_->itemData(index).value<Glib::VariantBase>());
+
+               // The combo box needs no update, it already shows the current value
+               // by definition: the user picked it
+       }
+}
+
+void Enum::on_current_index_changed(int)
+{
+       commit();
 }
 
-void Enum::on_current_item_changed(int)
+void Enum::on_value_changed(int)
 {
        commit();
 }
index 0193e238d562b224828bc98ac70b49c105b63dd0..1434e7e9e0419aa301b03b9d21febcd2f0f66a7a 100644 (file)
@@ -33,6 +33,8 @@ using std::vector;
 Q_DECLARE_METATYPE(Glib::VariantBase);
 
 class QComboBox;
+class QLabel;
+class QSlider;
 
 namespace pv {
 namespace prop {
@@ -49,16 +51,23 @@ public:
        virtual ~Enum() = default;
 
        QWidget* get_widget(QWidget *parent, bool auto_commit);
+       void update_widget();
 
        void commit();
 
 private Q_SLOTS:
-       void on_current_item_changed(int);
+       void on_current_index_changed(int);
+       void on_value_changed(int);
 
 private:
        const vector< pair<Glib::VariantBase, QString> > values_;
+       bool is_range_;
 
        QComboBox *selector_;
+
+       QWidget *slider_layout_widget_;
+       QSlider *slider_;
+       QLabel *slider_label_;
 };
 
 }  // namespace prop
index 2930a3ea1e8c96b5d0091e6c2b640050ecb5d09f..3f29951b3d997b7b5b8a299fd34c3a0bf0d6db40 100644 (file)
 #include <cassert>
 #include <cstdint>
 
+#include <QDebug>
 #include <QSpinBox>
 
+#include <libsigrokcxx/libsigrokcxx.hpp>
+
 #include "int.hpp"
 
 using boost::optional;
@@ -47,7 +50,7 @@ Int::Int(QString name,
 
 QWidget* Int::get_widget(QWidget *parent, bool auto_commit)
 {
-       int64_t int_val = 0, range_min = 0;
+       int64_t range_min = 0;
        uint64_t range_max = 0;
 
        if (spin_box_)
@@ -56,7 +59,12 @@ QWidget* Int::get_widget(QWidget *parent, bool auto_commit)
        if (!getter_)
                return nullptr;
 
-       value_ = getter_();
+       try {
+               value_ = getter_();
+       } catch (const sigrok::Error &e) {
+               qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what());
+               return nullptr;
+       }
 
        GVariant *value = value_.gobj();
        if (!value)
@@ -69,25 +77,18 @@ QWidget* Int::get_widget(QWidget *parent, bool auto_commit)
        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.
@@ -107,7 +108,7 @@ QWidget* Int::get_widget(QWidget *parent, bool auto_commit)
        else
                spin_box_->setRange((int)range_min, (int)range_max);
 
-       spin_box_->setValue((int)int_val);
+       update_widget();
 
        if (auto_commit)
                connect(spin_box_, SIGNAL(valueChanged(int)),
@@ -116,6 +117,48 @@ QWidget* Int::get_widget(QWidget *parent, bool auto_commit)
        return spin_box_;
 }
 
+void Int::update_widget()
+{
+       if (!spin_box_)
+               return;
+
+       try {
+               value_ = getter_();
+       } catch (const sigrok::Error &e) {
+               qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what());
+               return;
+       }
+
+       GVariant *value = value_.gobj();
+       assert(value);
+
+       const GVariantType *const type = g_variant_get_type(value);
+       assert(type);
+
+       int64_t int_val = 0;
+
+       if (g_variant_type_equal(type, G_VARIANT_TYPE_BYTE)) {
+               int_val = g_variant_get_byte(value);
+       } else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT16)) {
+               int_val = g_variant_get_int16(value);
+       } else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT16)) {
+               int_val = g_variant_get_uint16(value);
+       } else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT32)) {
+               int_val = g_variant_get_int32(value);
+       } else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT32)) {
+               int_val = g_variant_get_uint32(value);
+       } else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT64)) {
+               int_val = g_variant_get_int64(value);
+       } else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT64)) {
+               int_val = g_variant_get_uint64(value);
+       } else {
+               // Unexpected value type.
+               assert(false);
+       }
+
+       spin_box_->setValue((int)int_val);
+}
+
 void Int::commit()
 {
        assert(setter_);
index 709e02e635eda56ee1f195cd4b9f76733c32b560..f6b7c2d1f1642d64a55a575fbcd7100618cd3c46 100644 (file)
@@ -45,6 +45,7 @@ public:
        virtual ~Int() = default;
 
        QWidget* get_widget(QWidget *parent, bool auto_commit);
+       void update_widget();
 
        void commit();
 
index 322c608490c775803eeebe00b189a1140c3ba3c1..4ce352e11e5dacb2aaacfe4e726e3e94875daaef 100644 (file)
@@ -53,9 +53,9 @@ public:
        const QString& name() const;
        const QString& desc() const;
 
-       virtual QWidget* get_widget(QWidget *parent,
-               bool auto_commit = false) = 0;
+       virtual QWidget* get_widget(QWidget *parent, bool auto_commit = false) = 0;
        virtual bool labeled_widget() const;
+       virtual void update_widget() = 0;
 
        virtual void commit() = 0;
 
@@ -63,7 +63,7 @@ protected:
        const Getter getter_;
        const Setter setter_;
 
-private:
+protected:
        QString name_;
        QString desc_;
 };
index 96a83422e0a3e2a026336845ac20c72c876e7d35..b82b496eed42175f7361cd792a4ab8196036eee7 100644 (file)
 
 #include <cassert>
 
+#include <QDebug>
 #include <QLineEdit>
 #include <QSpinBox>
 
+#include <libsigrokcxx/libsigrokcxx.hpp>
+
 #include "string.hpp"
 
 using std::string;
@@ -48,15 +51,18 @@ QWidget* String::get_widget(QWidget *parent, bool auto_commit)
        if (!getter_)
                return nullptr;
 
-       Glib::VariantBase variant = getter_();
-       if (!variant.gobj())
+       try {
+               Glib::VariantBase variant = getter_();
+               if (!variant.gobj())
+                       return nullptr;
+       } catch (const sigrok::Error &e) {
+               qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what());
                return nullptr;
-
-       string value = Glib::VariantBase::cast_dynamic<Glib::Variant<ustring>>(
-               variant).get();
+       }
 
        line_edit_ = new QLineEdit(parent);
-       line_edit_->setText(QString::fromStdString(value));
+
+       update_widget();
 
        if (auto_commit)
                connect(line_edit_, SIGNAL(textEdited(const QString&)),
@@ -65,6 +71,28 @@ QWidget* String::get_widget(QWidget *parent, bool auto_commit)
        return line_edit_;
 }
 
+void String::update_widget()
+{
+       if (!line_edit_)
+               return;
+
+       Glib::VariantBase variant;
+
+       try {
+               variant = getter_();
+       } catch (const sigrok::Error &e) {
+               qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what());
+               return;
+       }
+
+       assert(variant.gobj());
+
+       string value = Glib::VariantBase::cast_dynamic<Glib::Variant<ustring>>(
+               variant).get();
+
+       line_edit_->setText(QString::fromStdString(value));
+}
+
 void String::commit()
 {
        assert(setter_);
index 8ce8be61ef6312f67842b9b4981d5e0d29a0895c..8282cb7f5928616fb2b64da1b5420c6439948458 100644 (file)
@@ -35,6 +35,7 @@ public:
        String(QString name, QString desc, Getter getter, Setter setter);
 
        QWidget* get_widget(QWidget *parent, bool auto_commit);
+       void update_widget();
 
        void commit();
 
index aa528d191fe6750958bd2cbf8491fe8bb35383d0..c0bcd670de26d0af866bf9cb87705ad506d5a57e 100644 (file)
@@ -17,6 +17,7 @@
  * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <QDebug>
 #include <QFileInfo>
 
 #include <cassert>
 #include <sys/stat.h>
 
 #include "devicemanager.hpp"
+#include "mainwindow.hpp"
 #include "session.hpp"
 
 #include "data/analog.hpp"
 #include "data/analogsegment.hpp"
 #include "data/decode/decoder.hpp"
-#include "data/decoderstack.hpp"
 #include "data/logic.hpp"
 #include "data/logicsegment.hpp"
 #include "data/signalbase.hpp"
@@ -53,6 +54,7 @@
 
 #ifdef ENABLE_DECODE
 #include <libsigrokdecode/libsigrokdecode.h>
+#include "data/decodesignal.hpp"
 #endif
 
 using std::bad_alloc;
@@ -90,6 +92,9 @@ using sigrok::Session;
 using Glib::VariantBase;
 
 namespace pv {
+
+shared_ptr<sigrok::Context> Session::sr_context;
+
 Session::Session(DeviceManager &device_manager, QString name) :
        device_manager_(device_manager),
        default_name_(name),
@@ -172,7 +177,7 @@ void Session::save_settings(QSettings &settings) const
 {
        map<string, string> dev_info;
        list<string> key_list;
-       int stacks = 0, views = 0;
+       int decode_signals = 0, views = 0;
 
        if (device_) {
                shared_ptr<devices::HardwareDevice> hw_device =
@@ -190,7 +195,7 @@ void Session::save_settings(QSettings &settings) const
 
                        dev_info = device_manager_.get_device_info(device_);
 
-                       for (string key : key_list) {
+                       for (string& key : key_list) {
                                if (dev_info.count(key))
                                        settings.setValue(QString::fromUtf8(key.c_str()),
                                                        QString::fromUtf8(dev_info.at(key).c_str()));
@@ -202,7 +207,7 @@ void Session::save_settings(QSettings &settings) const
                }
 
                shared_ptr<devices::SessionFile> sessionfile_device =
-                       dynamic_pointer_cast< devices::SessionFile >(device_);
+                       dynamic_pointer_cast<devices::SessionFile>(device_);
 
                if (sessionfile_device) {
                        settings.setValue("device_type", "sessionfile");
@@ -212,18 +217,22 @@ void Session::save_settings(QSettings &settings) const
                        settings.endGroup();
                }
 
+               shared_ptr<devices::InputFile> inputfile_device =
+                       dynamic_pointer_cast<devices::InputFile>(device_);
+
+               if (inputfile_device) {
+                       settings.setValue("device_type", "inputfile");
+                       settings.beginGroup("device");
+                       inputfile_device->save_meta_to_settings(settings);
+                       settings.endGroup();
+               }
+
                // Save channels and decoders
-               for (shared_ptr<data::SignalBase> base : signalbases_) {
+               for (const shared_ptr<data::SignalBase>& base : signalbases_) {
 #ifdef ENABLE_DECODE
                        if (base->is_decode_signal()) {
-                               shared_ptr<pv::data::DecoderStack> decoder_stack =
-                                               base->decoder_stack();
-                               shared_ptr<data::decode::Decoder> top_decoder =
-                                               decoder_stack->stack().front();
-
-                               settings.beginGroup("decoder_stack" + QString::number(stacks++));
-                               settings.setValue("id", top_decoder->decoder()->id);
-                               settings.setValue("name", top_decoder->decoder()->name);
+                               settings.beginGroup("decode_signal" + QString::number(decode_signals++));
+                               base->save_settings(settings);
                                settings.endGroup();
                        } else
 #endif
@@ -234,7 +243,7 @@ void Session::save_settings(QSettings &settings) const
                        }
                }
 
-               settings.setValue("decoder_stacks", stacks);
+               settings.setValue("decode_signals", decode_signals);
 
                // Save view states and their signal settings
                // Note: main_view must be saved as view0
@@ -242,7 +251,7 @@ void Session::save_settings(QSettings &settings) const
                main_view_->save_settings(settings);
                settings.endGroup();
 
-               for (shared_ptr<views::ViewBase> view : views_) {
+               for (const shared_ptr<views::ViewBase>& view : views_) {
                        if (view != main_view_) {
                                settings.beginGroup("view" + QString::number(views++));
                                view->save_settings(settings);
@@ -291,20 +300,34 @@ void Session::restore_settings(QSettings &settings)
                settings.endGroup();
        }
 
-       if (device_type == "sessionfile") {
-               settings.beginGroup("device");
-               QString filename = settings.value("filename").toString();
-               settings.endGroup();
+       if ((device_type == "sessionfile") || (device_type == "inputfile")) {
+               if (device_type == "sessionfile") {
+                       settings.beginGroup("device");
+                       QString filename = settings.value("filename").toString();
+                       settings.endGroup();
 
-               if (QFileInfo(filename).isReadable()) {
-                       device = make_shared<devices::SessionFile>(device_manager_.context(),
-                               filename.toStdString());
+                       if (QFileInfo(filename).isReadable()) {
+                               device = make_shared<devices::SessionFile>(device_manager_.context(),
+                                       filename.toStdString());
+                       }
+               }
+
+               if (device_type == "inputfile") {
+                       settings.beginGroup("device");
+                       device = make_shared<devices::InputFile>(device_manager_.context(),
+                               settings);
+                       settings.endGroup();
+               }
+
+               if (device) {
                        set_device(device);
 
-                       // TODO Perform error handling
-                       start_capture([](QString infoMessage) { (void)infoMessage; });
+                       start_capture([](QString infoMessage) {
+                               // TODO Emulate noquote()
+                               qDebug() << "Session error:" << infoMessage; });
 
-                       set_name(QFileInfo(filename).fileName());
+                       set_name(QString::fromStdString(
+                               dynamic_pointer_cast<devices::File>(device)->display_name(device_manager_)));
                }
        }
 
@@ -318,14 +341,12 @@ void Session::restore_settings(QSettings &settings)
 
                // Restore decoders
 #ifdef ENABLE_DECODE
-               int stacks = settings.value("decoder_stacks").toInt();
-
-               for (int i = 0; i < stacks; i++) {
-                       settings.beginGroup("decoder_stack" + QString::number(i++));
-
-                       QString id = settings.value("id").toString();
-                       add_decoder(srd_decoder_get_by_id(id.toStdString().c_str()));
+               int decode_signals = settings.value("decode_signals").toInt();
 
+               for (int i = 0; i < decode_signals; i++) {
+                       settings.beginGroup("decode_signal" + QString::number(i));
+                       shared_ptr<data::DecodeSignal> signal = add_decode_signal();
+                       signal->restore_settings(settings);
                        settings.endGroup();
                }
 #endif
@@ -356,8 +377,7 @@ void Session::select_device(shared_ptr<devices::Device> device)
                else
                        set_default_device();
        } catch (const QString &e) {
-               main_bar_->session_error(tr("Failed to Select Device"),
-                       tr("Failed to Select Device"));
+               MainWindow::show_session_error(tr("Failed to select device"), e);
        }
 }
 
@@ -377,20 +397,21 @@ void Session::set_device(shared_ptr<devices::Device> device)
        name_ = default_name_;
        name_changed();
 
-       // Remove all stored data
+       // Remove all stored data and reset all views
        for (shared_ptr<views::ViewBase> view : views_) {
                view->clear_signals();
 #ifdef ENABLE_DECODE
                view->clear_decode_signals();
 #endif
+               view->reset_view_state();
        }
-       for (const shared_ptr<data::SignalData> d : all_signal_data_)
+       for (const shared_ptr<data::SignalData>& d : all_signal_data_)
                d->clear();
        all_signal_data_.clear();
        signalbases_.clear();
        cur_logic_segment_.reset();
 
-       for (auto entry : cur_analog_segments_) {
+       for (auto& entry : cur_analog_segments_) {
                shared_ptr<sigrok::Channel>(entry.first).reset();
                shared_ptr<data::AnalogSegment>(entry.second).reset();
        }
@@ -405,6 +426,7 @@ void Session::set_device(shared_ptr<devices::Device> device)
                device_->open();
        } catch (const QString &e) {
                device_.reset();
+               MainWindow::show_session_error(tr("Failed to open device"), e);
        }
 
        if (device_) {
@@ -430,30 +452,80 @@ void Session::set_default_device()
        // Try and find the demo device and select that by default
        const auto iter = find_if(devices.begin(), devices.end(),
                [] (const shared_ptr<devices::HardwareDevice> &d) {
-                       return d->hardware_device()->driver()->name() == "demo";        });
+                       return d->hardware_device()->driver()->name() == "demo"; });
        set_device((iter == devices.end()) ? devices.front() : *iter);
 }
 
+/**
+ * Convert generic options to data types that are specific to InputFormat.
+ *
+ * @param[in] user_spec Vector of tokenized words, string format.
+ * @param[in] fmt_opts Input format's options, result of InputFormat::options().
+ *
+ * @return Map of options suitable for InputFormat::create_input().
+ */
+map<string, Glib::VariantBase>
+Session::input_format_options(vector<string> user_spec,
+               map<string, shared_ptr<Option>> fmt_opts)
+{
+       map<string, Glib::VariantBase> result;
+
+       for (auto& entry : user_spec) {
+               /*
+                * Split key=value specs. Accept entries without separator
+                * (for simplified boolean specifications).
+                */
+               string key, val;
+               size_t pos = entry.find("=");
+               if (pos == std::string::npos) {
+                       key = entry;
+                       val = "";
+               } else {
+                       key = entry.substr(0, pos);
+                       val = entry.substr(pos + 1);
+               }
+
+               /*
+                * Skip user specifications that are not a member of the
+                * format's set of supported options. Have the text input
+                * spec converted to the required input format specific
+                * data type.
+                */
+               auto found = fmt_opts.find(key);
+               if (found == fmt_opts.end())
+                       continue;
+               shared_ptr<Option> opt = found->second;
+               result[key] = opt->parse_string(val);
+       }
+
+       return result;
+}
+
 void Session::load_init_file(const string &file_name, const string &format)
 {
        shared_ptr<InputFormat> input_format;
+       map<string, Glib::VariantBase> input_opts;
 
        if (!format.empty()) {
                const map<string, shared_ptr<InputFormat> > formats =
                        device_manager_.context()->input_formats();
+               auto user_opts = pv::util::split_string(format, ":");
+               string user_name = user_opts.front();
+               user_opts.erase(user_opts.begin());
                const auto iter = find_if(formats.begin(), formats.end(),
                        [&](const pair<string, shared_ptr<InputFormat> > f) {
-                               return f.first == format; });
+                               return f.first == user_name; });
                if (iter == formats.end()) {
-                       main_bar_->session_error(tr("Error"),
+                       MainWindow::show_session_error(tr("Error"),
                                tr("Unexpected input format: %s").arg(QString::fromStdString(format)));
                        return;
                }
-
                input_format = (*iter).second;
+               input_opts = input_format_options(user_opts,
+                       input_format->options());
        }
 
-       load_file(QString::fromStdString(file_name), input_format);
+       load_file(QString::fromStdString(file_name), input_format, input_opts);
 }
 
 void Session::load_file(QString file_name,
@@ -463,6 +535,10 @@ void Session::load_file(QString file_name,
        const QString errorMessage(
                QString("Failed to load file %1").arg(file_name));
 
+       // In the absence of a caller's format spec, try to auto detect.
+       // Assume "sigrok session file" upon lookup miss.
+       if (!format)
+               format = device_manager_.context()->input_format_match(file_name.toStdString());
        try {
                if (format)
                        set_device(shared_ptr<devices::Device>(
@@ -475,8 +551,8 @@ void Session::load_file(QString file_name,
                                new devices::SessionFile(
                                        device_manager_.context(),
                                        file_name.toStdString())));
-       } catch (Error e) {
-               main_bar_->session_error(tr("Failed to load ") + file_name, e.what());
+       } catch (Error& e) {
+               MainWindow::show_session_error(tr("Failed to load ") + file_name, e.what());
                set_default_device();
                main_bar_->update_device_list();
                return;
@@ -485,7 +561,7 @@ void Session::load_file(QString file_name,
        main_bar_->update_device_list();
 
        start_capture([&, errorMessage](QString infoMessage) {
-               main_bar_->session_error(errorMessage, infoMessage); });
+               MainWindow::show_session_error(errorMessage, infoMessage); });
 
        set_name(QFileInfo(file_name).fileName());
 }
@@ -518,9 +594,11 @@ void Session::start_capture(function<void (const QString)> error_handler)
        }
 
        // Clear signal data
-       for (const shared_ptr<data::SignalData> d : all_signal_data_)
+       for (const shared_ptr<data::SignalData>& d : all_signal_data_)
                d->clear();
 
+       trigger_list_.clear();
+
        // Revert name back to default name (e.g. "Session 1") for real devices
        // as the (possibly saved) data is gone. File devices keep their name.
        shared_ptr<devices::HardwareDevice> hw_device =
@@ -554,7 +632,42 @@ void Session::register_view(shared_ptr<views::ViewBase> view)
 
        views_.push_back(view);
 
+       // Add all device signals
        update_signals();
+
+       // Add all other signals
+       unordered_set< shared_ptr<data::SignalBase> > view_signalbases =
+               view->signalbases();
+
+       views::trace::View *trace_view =
+               qobject_cast<views::trace::View*>(view.get());
+
+       if (trace_view) {
+               for (const shared_ptr<data::SignalBase>& signalbase : signalbases_) {
+                       const int sb_exists = count_if(
+                               view_signalbases.cbegin(), view_signalbases.cend(),
+                               [&](const shared_ptr<data::SignalBase> &sb) {
+                                       return sb == signalbase;
+                               });
+                       // Add the signal to the view as it doesn't have it yet
+                       if (!sb_exists)
+                               switch (signalbase->type()) {
+                               case data::SignalBase::AnalogChannel:
+                               case data::SignalBase::LogicChannel:
+                               case data::SignalBase::DecodeChannel:
+#ifdef ENABLE_DECODE
+                                       trace_view->add_decode_signal(
+                                               dynamic_pointer_cast<data::DecodeSignal>(signalbase));
+#endif
+                                       break;
+                               case data::SignalBase::MathChannel:
+                                       // TBD
+                                       break;
+                               }
+               }
+       }
+
+       signals_changed();
 }
 
 void Session::deregister_view(shared_ptr<views::ViewBase> view)
@@ -571,7 +684,7 @@ void Session::deregister_view(shared_ptr<views::ViewBase> view)
 
 bool Session::has_view(shared_ptr<views::ViewBase> view)
 {
-       for (shared_ptr<views::ViewBase> v : views_)
+       for (shared_ptr<views::ViewBase>& v : views_)
                if (v == view)
                        return true;
 
@@ -582,11 +695,11 @@ double Session::get_samplerate() const
 {
        double samplerate = 0.0;
 
-       for (const shared_ptr<pv::data::SignalData> d : all_signal_data_) {
+       for (const shared_ptr<pv::data::SignalData>& d : all_signal_data_) {
                assert(d);
                const vector< shared_ptr<pv::data::Segment> > segments =
                        d->segments();
-               for (const shared_ptr<pv::data::Segment> &s : segments)
+               for (const shared_ptr<pv::data::Segment>s : segments)
                        samplerate = max(samplerate, s->samplerate());
        }
        // If there is no sample rate given we use samples as unit
@@ -596,73 +709,75 @@ double Session::get_samplerate() const
        return samplerate;
 }
 
+uint32_t Session::get_segment_count() const
+{
+       uint32_t value = 0;
+
+       // Find the highest number of segments
+       for (const shared_ptr<data::SignalData>& data : all_signal_data_)
+               if (data->get_segment_count() > value)
+                       value = data->get_segment_count();
+
+       return value;
+}
+
+vector<util::Timestamp> Session::get_triggers(uint32_t segment_id) const
+{
+       vector<util::Timestamp> result;
+
+       for (const pair<uint32_t, util::Timestamp>& entry : trigger_list_)
+               if (entry.first == segment_id)
+                       result.push_back(entry.second);
+
+       return result;
+}
+
 const unordered_set< shared_ptr<data::SignalBase> > Session::signalbases() const
 {
        return signalbases_;
 }
 
-#ifdef ENABLE_DECODE
-bool Session::add_decoder(srd_decoder *const dec)
+bool Session::all_segments_complete(uint32_t segment_id) const
 {
-       if (!dec)
-               return false;
+       bool all_complete = true;
 
-       map<const srd_channel*, shared_ptr<data::SignalBase> > channels;
-       shared_ptr<data::DecoderStack> decoder_stack;
+       for (const shared_ptr<data::SignalBase>& base : signalbases_)
+               if (!base->segment_is_complete(segment_id))
+                       all_complete = false;
 
-       try {
-               // Create the decoder
-               decoder_stack = make_shared<data::DecoderStack>(*this, dec);
-
-               // Make a list of all the channels
-               vector<const srd_channel*> all_channels;
-               for (const GSList *i = dec->channels; i; i = i->next)
-                       all_channels.push_back((const srd_channel*)i->data);
-               for (const GSList *i = dec->opt_channels; i; i = i->next)
-                       all_channels.push_back((const srd_channel*)i->data);
-
-               // Auto select the initial channels
-               for (const srd_channel *pdch : all_channels)
-                       for (shared_ptr<data::SignalBase> b : signalbases_) {
-                               if (b->logic_data()) {
-                                       if (QString::fromUtf8(pdch->name).toLower().
-                                               contains(b->name().toLower()))
-                                               channels[pdch] = b;
-                               }
-                       }
+       return all_complete;
+}
 
-               assert(decoder_stack);
-               assert(!decoder_stack->stack().empty());
-               assert(decoder_stack->stack().front());
-               decoder_stack->stack().front()->set_channels(channels);
+#ifdef ENABLE_DECODE
+shared_ptr<data::DecodeSignal> Session::add_decode_signal()
+{
+       shared_ptr<data::DecodeSignal> signal;
 
+       try {
                // Create the decode signal
-               shared_ptr<data::SignalBase> signalbase =
-                       make_shared<data::SignalBase>(nullptr, data::SignalBase::DecodeChannel);
+               signal = make_shared<data::DecodeSignal>(*this);
 
-               signalbase->set_decoder_stack(decoder_stack);
-               signalbases_.insert(signalbase);
+               signalbases_.insert(signal);
 
-               for (shared_ptr<views::ViewBase> view : views_)
-                       view->add_decode_signal(signalbase);
-       } catch (runtime_error e) {
-               return false;
+               // Add the decode signal to all views
+               for (shared_ptr<views::ViewBase>& view : views_)
+                       view->add_decode_signal(signal);
+       } catch (runtime_error& e) {
+               remove_decode_signal(signal);
+               return nullptr;
        }
 
        signals_changed();
 
-       // Do an initial decode
-       decoder_stack->begin_decode();
-
-       return true;
+       return signal;
 }
 
-void Session::remove_decode_signal(shared_ptr<data::SignalBase> signalbase)
+void Session::remove_decode_signal(shared_ptr<data::DecodeSignal> signal)
 {
-       signalbases_.erase(signalbase);
+       signalbases_.erase(signal);
 
-       for (shared_ptr<views::ViewBase> view : views_)
-               view->remove_decode_signal(signalbase);
+       for (shared_ptr<views::ViewBase>& view : views_)
+               view->remove_decode_signal(signal);
 
        signals_changed();
 }
@@ -687,7 +802,7 @@ void Session::update_signals()
        if (!device_) {
                signalbases_.clear();
                logic_data_.reset();
-               for (shared_ptr<views::ViewBase> view : views_) {
+               for (shared_ptr<views::ViewBase>& view : views_) {
                        view->clear_signals();
 #ifdef ENABLE_DECODE
                        view->clear_decode_signals();
@@ -702,7 +817,7 @@ void Session::update_signals()
        if (!sr_dev) {
                signalbases_.clear();
                logic_data_.reset();
-               for (shared_ptr<views::ViewBase> view : views_) {
+               for (shared_ptr<views::ViewBase>& view : views_) {
                        view->clear_signals();
 #ifdef ENABLE_DECODE
                        view->clear_decode_signals();
@@ -733,7 +848,7 @@ void Session::update_signals()
        }
 
        // Make the signals list
-       for (shared_ptr<views::ViewBase> viewbase : views_) {
+       for (shared_ptr<views::ViewBase>& viewbase : views_) {
                views::trace::View *trace_view =
                        qobject_cast<views::trace::View*>(viewbase.get());
 
@@ -759,7 +874,7 @@ void Session::update_signals()
                                } else {
                                        // Find the signalbase for this channel if possible
                                        signalbase.reset();
-                                       for (const shared_ptr<data::SignalBase> b : signalbases_)
+                                       for (const shared_ptr<data::SignalBase>& b : signalbases_)
                                                if (b->channel() == channel)
                                                        signalbase = b;
 
@@ -835,13 +950,25 @@ void Session::sample_thread_proc(function<void (const QString)> error_handler)
        if (!device_)
                return;
 
-       cur_samplerate_ = device_->read_config<uint64_t>(ConfigKey::SAMPLERATE);
+       try {
+               cur_samplerate_ = device_->read_config<uint64_t>(ConfigKey::SAMPLERATE);
+       } catch (Error& e) {
+               cur_samplerate_ = 0;
+       }
 
        out_of_memory_ = false;
 
+       {
+               lock_guard<recursive_mutex> lock(data_mutex_);
+               cur_logic_segment_.reset();
+               cur_analog_segments_.clear();
+       }
+       highest_segment_id_ = -1;
+       frame_began_ = false;
+
        try {
                device_->start();
-       } catch (Error e) {
+       } catch (Error& e) {
                error_handler(e.what());
                return;
        }
@@ -851,7 +978,7 @@ void Session::sample_thread_proc(function<void (const QString)> error_handler)
 
        try {
                device_->run();
-       } catch (Error e) {
+       } catch (Error& e) {
                error_handler(e.what());
                set_capture_state(Stopped);
                return;
@@ -860,10 +987,8 @@ void Session::sample_thread_proc(function<void (const QString)> error_handler)
        set_capture_state(Stopped);
 
        // Confirm that SR_DF_END was received
-       if (cur_logic_segment_) {
-               qDebug("SR_DF_END was not received.");
-               assert(false);
-       }
+       if (cur_logic_segment_)
+               qDebug() << "WARNING: SR_DF_END was not received.";
 
        // Optimize memory usage
        free_unused_memory();
@@ -881,34 +1006,79 @@ void Session::sample_thread_proc(function<void (const QString)> error_handler)
 
 void Session::free_unused_memory()
 {
-       for (shared_ptr<data::SignalData> data : all_signal_data_) {
+       for (const shared_ptr<data::SignalData>& data : all_signal_data_) {
                const vector< shared_ptr<data::Segment> > segments = data->segments();
 
-               for (shared_ptr<data::Segment> segment : segments) {
+               for (const shared_ptr<data::Segment>& segment : segments)
                        segment->free_unused_memory();
+       }
+}
+
+void Session::signal_new_segment()
+{
+       int new_segment_id = 0;
+
+       if ((cur_logic_segment_ != nullptr) || !cur_analog_segments_.empty()) {
+
+               // Determine new frame/segment number, assuming that all
+               // signals have the same number of frames/segments
+               if (cur_logic_segment_) {
+                       new_segment_id = logic_data_->get_segment_count() - 1;
+               } else {
+                       shared_ptr<sigrok::Channel> any_channel =
+                               (*cur_analog_segments_.begin()).first;
+
+                       shared_ptr<data::SignalBase> base = signalbase_from_channel(any_channel);
+                       assert(base);
+
+                       shared_ptr<data::Analog> data(base->analog_data());
+                       assert(data);
+
+                       new_segment_id = data->get_segment_count() - 1;
                }
        }
+
+       if (new_segment_id > highest_segment_id_) {
+               highest_segment_id_ = new_segment_id;
+               new_segment(highest_segment_id_);
+       }
+}
+
+void Session::signal_segment_completed()
+{
+       int segment_id = 0;
+
+       for (const shared_ptr<data::SignalBase>& signalbase : signalbases_) {
+               // We only care about analog and logic channels, not derived ones
+               if (signalbase->type() == data::SignalBase::AnalogChannel) {
+                       segment_id = signalbase->analog_data()->get_segment_count() - 1;
+                       break;
+               }
+
+               if (signalbase->type() == data::SignalBase::LogicChannel) {
+                       segment_id = signalbase->logic_data()->get_segment_count() - 1;
+                       break;
+               }
+       }
+
+       if (segment_id >= 0)
+               segment_completed(segment_id);
 }
 
 void Session::feed_in_header()
 {
-       cur_samplerate_ = device_->read_config<uint64_t>(ConfigKey::SAMPLERATE);
+       // Nothing to do here for now
 }
 
 void Session::feed_in_meta(shared_ptr<Meta> meta)
 {
-       for (auto entry : meta->config()) {
+       for (auto& entry : meta->config()) {
                switch (entry.first->id()) {
                case SR_CONF_SAMPLERATE:
-                       // We can't rely on the header to always contain the sample rate,
-                       // so in case it's supplied via a meta packet, we use it.
-                       if (!cur_samplerate_)
-                               cur_samplerate_ = g_variant_get_uint64(entry.second.gobj());
-
-                       /// @todo handle samplerate changes
+                       cur_samplerate_ = g_variant_get_uint64(entry.second.gobj());
                        break;
                default:
-                       // Unknown metadata is not an error.
+                       qDebug() << "Received meta data key" << entry.first->id() << ", ignoring.";
                        break;
                }
        }
@@ -922,7 +1092,7 @@ void Session::feed_in_trigger()
        uint64_t sample_count = 0;
 
        {
-               for (const shared_ptr<pv::data::SignalData> d : all_signal_data_) {
+               for (const shared_ptr<pv::data::SignalData>& d : all_signal_data_) {
                        assert(d);
                        uint64_t temp_count = 0;
 
@@ -936,17 +1106,70 @@ void Session::feed_in_trigger()
                }
        }
 
-       trigger_event(sample_count / get_samplerate());
+       uint32_t segment_id = 0;  // Default segment when no frames are used
+
+       // If a frame began, we'd ideally be able to use the highest segment ID for
+       // the trigger. However, as new segments are only created when logic or
+       // analog data comes in, this doesn't work if the trigger appears right
+       // after the beginning of the frame, before any sample data.
+       // For this reason, we use highest segment ID + 1 if no sample data came in
+       // yet and the highest segment ID otherwise.
+       if (frame_began_) {
+               segment_id = highest_segment_id_;
+               if (!cur_logic_segment_ && (cur_analog_segments_.size() == 0))
+                       segment_id++;
+       }
+
+       // TODO Create timestamp from segment start time + segment's current sample count
+       util::Timestamp timestamp = sample_count / get_samplerate();
+       trigger_list_.emplace_back(segment_id, timestamp);
+       trigger_event(segment_id, timestamp);
 }
 
 void Session::feed_in_frame_begin()
 {
-       if (cur_logic_segment_ || !cur_analog_segments_.empty())
-               frame_began();
+       frame_began_ = true;
+}
+
+void Session::feed_in_frame_end()
+{
+       if (!frame_began_)
+               return;
+
+       {
+               lock_guard<recursive_mutex> lock(data_mutex_);
+
+               if (cur_logic_segment_)
+                       cur_logic_segment_->set_complete();
+
+               for (auto& entry : cur_analog_segments_) {
+                       shared_ptr<data::AnalogSegment> segment = entry.second;
+                       segment->set_complete();
+               }
+
+               cur_logic_segment_.reset();
+               cur_analog_segments_.clear();
+       }
+
+       frame_began_ = false;
+
+       signal_segment_completed();
 }
 
 void Session::feed_in_logic(shared_ptr<Logic> logic)
 {
+       if (logic->data_length() == 0) {
+               qDebug() << "WARNING: Received logic packet with 0 samples.";
+               return;
+       }
+
+       if (!cur_samplerate_)
+               try {
+                       cur_samplerate_ = device_->read_config<uint64_t>(ConfigKey::SAMPLERATE);
+               } catch (Error& e) {
+                       // Do nothing
+               }
+
        lock_guard<recursive_mutex> lock(data_mutex_);
 
        if (!logic_data_) {
@@ -962,14 +1185,11 @@ void Session::feed_in_logic(shared_ptr<Logic> logic)
 
                // Create a new data segment
                cur_logic_segment_ = make_shared<data::LogicSegment>(
-                       *logic_data_, logic->unit_size(), cur_samplerate_);
+                       *logic_data_, logic_data_->get_segment_count(),
+                       logic->unit_size(), cur_samplerate_);
                logic_data_->push_segment(cur_logic_segment_);
 
-               // @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();
+               signal_new_segment();
        }
 
        cur_logic_segment_->append_payload(logic);
@@ -979,21 +1199,31 @@ void Session::feed_in_logic(shared_ptr<Logic> logic)
 
 void Session::feed_in_analog(shared_ptr<Analog> analog)
 {
+       if (analog->num_samples() == 0) {
+               qDebug() << "WARNING: Received analog packet with 0 samples.";
+               return;
+       }
+
+       if (!cur_samplerate_)
+               try {
+                       cur_samplerate_ = device_->read_config<uint64_t>(ConfigKey::SAMPLERATE);
+               } catch (Error& e) {
+                       // Do nothing
+               }
+
        lock_guard<recursive_mutex> lock(data_mutex_);
 
        const vector<shared_ptr<Channel>> channels = analog->channels();
-       const unsigned int channel_count = channels.size();
-       const size_t sample_count = analog->num_samples() / channel_count;
        bool sweep_beginning = false;
 
-       unique_ptr<float> data(new float[analog->num_samples()]);
+       unique_ptr<float[]> data(new float[analog->num_samples() * channels.size()]);
        analog->get_data_as_float(data.get());
 
        if (signalbases_.empty())
                update_signals();
 
        float *channel_data = data.get();
-       for (auto channel : channels) {
+       for (auto& channel : channels) {
                shared_ptr<data::AnalogSegment> segment;
 
                // Try to get the segment of the channel
@@ -1016,18 +1246,20 @@ void Session::feed_in_analog(shared_ptr<Analog> analog)
 
                        // Create a segment, keep it in the maps of channels
                        segment = make_shared<data::AnalogSegment>(
-                               *data, cur_samplerate_);
+                               *data, data->get_segment_count(), cur_samplerate_);
                        cur_analog_segments_[channel] = segment;
 
                        // Push the segment into the analog data.
                        data->push_segment(segment);
+
+                       signal_new_segment();
                }
 
                assert(segment);
 
                // Append the samples in the segment
-               segment->append_interleaved_samples(channel_data++, sample_count,
-                       channel_count);
+               segment->append_interleaved_samples(channel_data++, analog->num_samples(),
+                       channels.size());
        }
 
        if (sweep_beginning) {
@@ -1041,8 +1273,6 @@ void Session::feed_in_analog(shared_ptr<Analog> analog)
 void Session::data_feed_in(shared_ptr<sigrok::Device> device,
        shared_ptr<Packet> packet)
 {
-       static bool frame_began = false;
-
        (void)device;
 
        assert(device);
@@ -1062,15 +1292,10 @@ void Session::data_feed_in(shared_ptr<sigrok::Device> device,
                feed_in_trigger();
                break;
 
-       case SR_DF_FRAME_BEGIN:
-               feed_in_frame_begin();
-               frame_began = true;
-               break;
-
        case SR_DF_LOGIC:
                try {
                        feed_in_logic(dynamic_pointer_cast<Logic>(packet->payload()));
-               } catch (bad_alloc) {
+               } catch (bad_alloc&) {
                        out_of_memory_ = true;
                        device_->stop();
                }
@@ -1079,26 +1304,40 @@ void Session::data_feed_in(shared_ptr<sigrok::Device> device,
        case SR_DF_ANALOG:
                try {
                        feed_in_analog(dynamic_pointer_cast<Analog>(packet->payload()));
-               } catch (bad_alloc) {
+               } catch (bad_alloc&) {
                        out_of_memory_ = true;
                        device_->stop();
                }
                break;
 
+       case SR_DF_FRAME_BEGIN:
+               feed_in_frame_begin();
+               break;
+
        case SR_DF_FRAME_END:
+               feed_in_frame_end();
+               break;
+
        case SR_DF_END:
-       {
+               // Strictly speaking, this is performed when a frame end marker was
+               // received, so there's no point doing this again. However, not all
+               // devices use frames, and for those devices, we need to do it here.
                {
                        lock_guard<recursive_mutex> lock(data_mutex_);
+
+                       if (cur_logic_segment_)
+                               cur_logic_segment_->set_complete();
+
+                       for (auto& entry : cur_analog_segments_) {
+                               shared_ptr<data::AnalogSegment> segment = entry.second;
+                               segment->set_complete();
+                       }
+
                        cur_logic_segment_.reset();
                        cur_analog_segments_.clear();
                }
-               if (frame_began) {
-                       frame_began = false;
-                       frame_ended();
-               }
                break;
-       }
+
        default:
                break;
        }
index c7e1699bbac1193342e35e81df919be33b7fcfe6..2ee31cfee5afb8ef6de44171322d1d20086e755a 100644 (file)
@@ -20,6 +20,7 @@
 #ifndef PULSEVIEW_PV_SESSION_HPP
 #define PULSEVIEW_PV_SESSION_HPP
 
+#include <functional>
 #include <map>
 #include <memory>
 #include <mutex>
@@ -55,11 +56,14 @@ class Device;
 class InputFormat;
 class Logic;
 class Meta;
+class Option;
 class OutputFormat;
 class Packet;
 class Session;
 }  // namespace sigrok
 
+using sigrok::Option;
+
 namespace pv {
 
 class DeviceManager;
@@ -67,6 +71,7 @@ class DeviceManager;
 namespace data {
 class Analog;
 class AnalogSegment;
+class DecodeSignal;
 class Logic;
 class LogicSegment;
 class SignalBase;
@@ -96,6 +101,8 @@ public:
                Running
        };
 
+       static shared_ptr<sigrok::Context> sr_context;
+
 public:
        Session(DeviceManager &device_manager, QString name);
 
@@ -157,6 +164,10 @@ public:
 
        double get_samplerate() const;
 
+       uint32_t get_segment_count() const;
+
+       vector<util::Timestamp> get_triggers(uint32_t segment_id) const;
+
        void register_view(shared_ptr<views::ViewBase> view);
 
        void deregister_view(shared_ptr<views::ViewBase> view);
@@ -165,10 +176,12 @@ public:
 
        const unordered_set< shared_ptr<data::SignalBase> > signalbases() const;
 
+       bool all_segments_complete(uint32_t segment_id) const;
+
 #ifdef ENABLE_DECODE
-       bool add_decoder(srd_decoder *const dec);
+       shared_ptr<data::DecodeSignal> add_decode_signal();
 
-       void remove_decode_signal(shared_ptr<data::SignalBase> signalbase);
+       void remove_decode_signal(shared_ptr<data::DecodeSignal> signal);
 #endif
 
 private:
@@ -179,11 +192,17 @@ private:
        shared_ptr<data::SignalBase> signalbase_from_channel(
                shared_ptr<sigrok::Channel> channel) const;
 
-private:
+       static map<string, Glib::VariantBase> input_format_options(
+               vector<string> user_spec,
+               map<string, shared_ptr<Option>> fmt_opts);
+
        void sample_thread_proc(function<void (const QString)> error_handler);
 
        void free_unused_memory();
 
+       void signal_new_segment();
+       void signal_segment_completed();
+
        void feed_in_header();
 
        void feed_in_meta(shared_ptr<sigrok::Meta> meta);
@@ -191,6 +210,7 @@ private:
        void feed_in_trigger();
 
        void feed_in_frame_begin();
+       void feed_in_frame_end();
 
        void feed_in_logic(shared_ptr<sigrok::Logic> logic);
 
@@ -199,6 +219,27 @@ private:
        void data_feed_in(shared_ptr<sigrok::Device> device,
                shared_ptr<sigrok::Packet> packet);
 
+Q_SIGNALS:
+       void capture_state_changed(int state);
+       void device_changed();
+
+       void signals_changed();
+
+       void name_changed();
+
+       void trigger_event(int segment_id, util::Timestamp location);
+
+       void new_segment(int new_segment_id);
+       void segment_completed(int segment_id);
+
+       void data_received();
+
+       void add_view(const QString &title, views::ViewType type,
+               Session *session);
+
+public Q_SLOTS:
+       void on_data_saved();
+
 private:
        DeviceManager &device_manager_;
        shared_ptr<devices::Device> device_;
@@ -215,39 +256,22 @@ private:
        unordered_set< shared_ptr<data::SignalBase> > signalbases_;
        unordered_set< shared_ptr<data::SignalData> > all_signal_data_;
 
+       /// trigger_list_ contains pairs of <segment_id, timestamp> values.
+       vector< std::pair<uint32_t, util::Timestamp> > trigger_list_;
+
        mutable recursive_mutex data_mutex_;
        shared_ptr<data::Logic> logic_data_;
        uint64_t cur_samplerate_;
        shared_ptr<data::LogicSegment> cur_logic_segment_;
        map< shared_ptr<sigrok::Channel>, shared_ptr<data::AnalogSegment> >
                cur_analog_segments_;
+       int32_t highest_segment_id_;
 
        std::thread sampling_thread_;
 
        bool out_of_memory_;
        bool data_saved_;
-
-Q_SIGNALS:
-       void capture_state_changed(int state);
-       void device_changed();
-
-       void signals_changed();
-
-       void name_changed();
-
-       void trigger_event(util::Timestamp location);
-
-       void frame_began();
-
-       void data_received();
-
-       void frame_ended();
-
-       void add_view(const QString &title, views::ViewType type,
-               Session *session);
-
-public Q_SLOTS:
-       void on_data_saved();
+       bool frame_began_;
 };
 
 } // namespace pv
index cb643682d57023462e38cc8dbd228bcd2182a353..ee1a3a03f096cf5c55d3a0ce16ea3f3c85496669 100644 (file)
@@ -97,7 +97,7 @@ bool StoreSession::start()
        vector< shared_ptr<data::SignalBase> > achannel_list;
        vector< shared_ptr<data::AnalogSegment> > asegment_list;
 
-       for (shared_ptr<data::SignalBase> signal : sigs) {
+       for (const shared_ptr<data::SignalBase>& signal : sigs) {
                if (!signal->enabled())
                        continue;
 
@@ -145,6 +145,7 @@ bool StoreSession::start()
        uint64_t end_sample;
 
        if (sample_range_.first == sample_range_.second) {
+               // No sample range specified, save everything we have
                start_sample_ = 0;
                sample_count_ = any_segment->get_sample_count();
        } else {
@@ -159,6 +160,12 @@ bool StoreSession::start()
                }
        }
 
+       // Make sure the sample range is valid
+       if (start_sample_ > any_segment->get_sample_count()) {
+               error_ = tr("Can't save range without sample data.");
+               return false;
+       }
+
        // Begin storing
        try {
                const auto context = session_.device_manager().context();
@@ -175,7 +182,7 @@ bool StoreSession::start()
                        {{ConfigKey::SAMPLERATE, Glib::Variant<guint64>::create(
                                any_segment->samplerate())}});
                output_->receive(meta);
-       } catch (Error error) {
+       } catch (Error& error) {
                error_ = tr("Error while saving: ") + error.what();
                return false;
        }
@@ -240,8 +247,8 @@ void StoreSession::store_proc(vector< shared_ptr<data::SignalBase> > achannel_li
                                shared_ptr<sigrok::Channel> achannel = (achannel_list.at(i))->channel();
                                shared_ptr<data::AnalogSegment> asegment = asegment_list.at(i);
 
-                               const float *adata =
-                                       asegment->get_samples(start_sample_, start_sample_ + packet_len);
+                               float *adata = new float[packet_len];
+                               asegment->get_samples(start_sample_, start_sample_ + packet_len, adata);
 
                                auto analog = context->create_analog_packet(
                                        vector<shared_ptr<sigrok::Channel> >{achannel},
@@ -257,11 +264,11 @@ void StoreSession::store_proc(vector< shared_ptr<data::SignalBase> > achannel_li
                        }
 
                        if (lsegment) {
-                               const uint8_t* ldata =
-                                       lsegment->get_samples(start_sample_, start_sample_ + packet_len);
+                               const size_t data_size = packet_len * lunit_size;
+                               uint8_t* ldata = new uint8_t[data_size];
+                               lsegment->get_samples(start_sample_, start_sample_ + packet_len, ldata);
 
-                               const size_t length = packet_len * lunit_size;
-                               auto logic = context->create_logic_packet((void*)ldata, length, lunit_size);
+                               auto logic = context->create_logic_packet((void*)ldata, data_size, lunit_size);
                                const string ldata_str = output_->receive(logic);
 
                                if (output_stream_.is_open())
@@ -269,7 +276,7 @@ void StoreSession::store_proc(vector< shared_ptr<data::SignalBase> > achannel_li
 
                                delete[] ldata;
                        }
-               } catch (Error error) {
+               } catch (Error& error) {
                        error_ = tr("Error while saving: ") + error.what();
                        break;
                }
index 22ed3988cfb72ccfb824390c03d1b4354b8dd4ed..e6beb2b3ba704e198c8aff0076787a9d96798fe9 100644 (file)
@@ -35,6 +35,7 @@
 
 #include <boost/algorithm/string/join.hpp>
 
+#include <pv/data/decodesignal.hpp>
 #include <pv/devicemanager.hpp>
 #include <pv/devices/hardwaredevice.hpp>
 #include <pv/devices/inputfile.hpp>
@@ -86,8 +87,7 @@ const uint64_t MainBar::DefaultSampleCount = 1000000;
 const char *MainBar::SettingOpenDirectory = "MainWindow/OpenDirectory";
 const char *MainBar::SettingSaveDirectory = "MainWindow/SaveDirectory";
 
-MainBar::MainBar(Session &session, QWidget *parent,
-               pv::views::trace::View *view) :
+MainBar::MainBar(Session &session, QWidget *parent, pv::views::trace::View *view) :
        StandardBar(session, parent, view, false),
        action_new_view_(new QAction(this)),
        action_open_(new QAction(this)),
@@ -96,8 +96,7 @@ MainBar::MainBar(Session &session, QWidget *parent,
        action_connect_(new QAction(this)),
        open_button_(new QToolButton()),
        save_button_(new QToolButton()),
-       device_selector_(parent, session.device_manager(),
-               action_connect_),
+       device_selector_(parent, session.device_manager(), action_connect_),
        configure_button_(this),
        configure_button_action_(nullptr),
        channels_button_(this),
@@ -147,15 +146,13 @@ MainBar::MainBar(Session &session, QWidget *parent,
        widgets::ExportMenu *menu_file_export = new widgets::ExportMenu(this,
                session.device_manager().context());
        menu_file_export->setTitle(tr("&Export"));
-       connect(menu_file_export,
-               SIGNAL(format_selected(shared_ptr<sigrok::OutputFormat>)),
+       connect(menu_file_export, SIGNAL(format_selected(shared_ptr<sigrok::OutputFormat>)),
                this, SLOT(export_file(shared_ptr<sigrok::OutputFormat>)));
 
        widgets::ImportMenu *menu_file_import = new widgets::ImportMenu(this,
                session.device_manager().context());
        menu_file_import->setTitle(tr("&Import"));
-       connect(menu_file_import,
-               SIGNAL(format_selected(shared_ptr<sigrok::InputFormat>)),
+       connect(menu_file_import, SIGNAL(format_selected(shared_ptr<sigrok::InputFormat>)),
                this, SLOT(import_file(shared_ptr<sigrok::InputFormat>)));
 
        action_connect_->setText(tr("&Connect to Device..."));
@@ -165,27 +162,22 @@ MainBar::MainBar(Session &session, QWidget *parent,
        // Open button
        widgets::ImportMenu *import_menu = new widgets::ImportMenu(this,
                session.device_manager().context(), action_open_);
-       connect(import_menu,
-               SIGNAL(format_selected(shared_ptr<sigrok::InputFormat>)),
-               this,
-               SLOT(import_file(shared_ptr<sigrok::InputFormat>)));
+       connect(import_menu, SIGNAL(format_selected(shared_ptr<sigrok::InputFormat>)),
+               this, SLOT(import_file(shared_ptr<sigrok::InputFormat>)));
 
        open_button_->setMenu(import_menu);
        open_button_->setDefaultAction(action_open_);
        open_button_->setPopupMode(QToolButton::MenuButtonPopup);
 
        // Save button
-       vector<QAction *> open_actions;
+       vector<QAction*> open_actions;
        open_actions.push_back(action_save_as_);
        open_actions.push_back(action_save_selection_as_);
 
        widgets::ExportMenu *export_menu = new widgets::ExportMenu(this,
-               session.device_manager().context(),
-               open_actions);
-       connect(export_menu,
-               SIGNAL(format_selected(shared_ptr<sigrok::OutputFormat>)),
-               this,
-               SLOT(export_file(shared_ptr<sigrok::OutputFormat>)));
+               session.device_manager().context(), open_actions);
+       connect(export_menu, SIGNAL(format_selected(shared_ptr<sigrok::OutputFormat>)),
+               this, SLOT(export_file(shared_ptr<sigrok::OutputFormat>)));
 
        save_button_->setMenu(export_menu);
        save_button_->setDefaultAction(action_save_as_);
@@ -301,8 +293,7 @@ void MainBar::update_sample_rate_selector()
                return;
        }
 
-       const shared_ptr<devices::Device> device =
-               device_selector_.selected_device();
+       const shared_ptr<devices::Device> device = device_selector_.selected_device();
        if (!device)
                return;
 
@@ -311,8 +302,27 @@ void MainBar::update_sample_rate_selector()
 
        const shared_ptr<sigrok::Device> sr_dev = device->device();
 
+       sample_rate_.allow_user_entered_values(false);
+       if (sr_dev->config_check(ConfigKey::EXTERNAL_CLOCK, Capability::GET)) {
+               try {
+                       auto gvar = sr_dev->config_get(ConfigKey::EXTERNAL_CLOCK);
+                       if (gvar.gobj()) {
+                               bool value = Glib::VariantBase::cast_dynamic<Glib::Variant<bool>>(
+                                       gvar).get();
+                               sample_rate_.allow_user_entered_values(value);
+                       }
+               } catch (Error& error) {
+                       // Do nothing
+               }
+       }
+
+
        if (sr_dev->config_check(ConfigKey::SAMPLERATE, Capability::LIST)) {
-               gvar_dict = sr_dev->config_list(ConfigKey::SAMPLERATE);
+               try {
+                       gvar_dict = sr_dev->config_list(ConfigKey::SAMPLERATE);
+               } catch (Error& error) {
+                       qDebug() << tr("Failed to get sample rate list:") << error.what();
+               }
        } else {
                sample_rate_.show_none();
                updating_sample_rate_ = false;
@@ -322,7 +332,7 @@ void MainBar::update_sample_rate_selector()
        if ((gvar_list = g_variant_lookup_value(gvar_dict.gobj(),
                        "samplerate-steps", G_VARIANT_TYPE("at")))) {
                elements = (const uint64_t *)g_variant_get_fixed_array(
-                               gvar_list, &num_elements, sizeof(uint64_t));
+                       gvar_list, &num_elements, sizeof(uint64_t));
 
                const uint64_t min = elements[0];
                const uint64_t max = elements[1];
@@ -347,7 +357,7 @@ void MainBar::update_sample_rate_selector()
        } else if ((gvar_list = g_variant_lookup_value(gvar_dict.gobj(),
                        "samplerates", G_VARIANT_TYPE("at")))) {
                elements = (const uint64_t *)g_variant_get_fixed_array(
-                               gvar_list, &num_elements, sizeof(uint64_t));
+                       gvar_list, &num_elements, sizeof(uint64_t));
                sample_rate_.show_list(elements, num_elements);
                g_variant_unref(gvar_list);
        }
@@ -361,8 +371,7 @@ void MainBar::update_sample_rate_selector_value()
        if (updating_sample_rate_)
                return;
 
-       const shared_ptr<devices::Device> device =
-               device_selector_.selected_device();
+       const shared_ptr<devices::Device> device = device_selector_.selected_device();
        if (!device)
                return;
 
@@ -374,9 +383,8 @@ void MainBar::update_sample_rate_selector_value()
                updating_sample_rate_ = true;
                sample_rate_.set_value(samplerate);
                updating_sample_rate_ = false;
-       } catch (Error error) {
-               qDebug() << "WARNING: Failed to get value of sample rate";
-               return;
+       } catch (Error& error) {
+               qDebug() << tr("Failed to get sample rate:") << error.what();
        }
 }
 
@@ -385,8 +393,7 @@ void MainBar::update_sample_count_selector()
        if (updating_sample_count_)
                return;
 
-       const shared_ptr<devices::Device> device =
-               device_selector_.selected_device();
+       const shared_ptr<devices::Device> device = device_selector_.selected_device();
        if (!device)
                return;
 
@@ -412,10 +419,14 @@ void MainBar::update_sample_count_selector()
        }
 
        if (sr_dev->config_check(ConfigKey::LIMIT_SAMPLES, Capability::LIST)) {
-               auto gvar = sr_dev->config_list(ConfigKey::LIMIT_SAMPLES);
-               if (gvar.gobj())
-                       g_variant_get(gvar.gobj(), "(tt)",
-                               &min_sample_count, &max_sample_count);
+               try {
+                       auto gvar = sr_dev->config_list(ConfigKey::LIMIT_SAMPLES);
+                       if (gvar.gobj())
+                               g_variant_get(gvar.gobj(), "(tt)",
+                                       &min_sample_count, &max_sample_count);
+               } catch (Error& error) {
+                       qDebug() << tr("Failed to get sample limit list:") << error.what();
+               }
        }
 
        min_sample_count = min(max(min_sample_count, MinSampleCount),
@@ -447,8 +458,7 @@ void MainBar::update_device_config_widgets()
 {
        using namespace pv::popups;
 
-       const shared_ptr<devices::Device> device =
-               device_selector_.selected_device();
+       const shared_ptr<devices::Device> device = device_selector_.selected_device();
 
        // Hide the widgets if no device is selected
        channels_button_action_->setVisible(!!device);
@@ -465,8 +475,7 @@ void MainBar::update_device_config_widgets()
 
        // Update the configure popup
        DeviceOptions *const opts = new DeviceOptions(sr_dev, this);
-       configure_button_action_->setVisible(
-               !opts->binding().properties().empty());
+       configure_button_action_->setVisible(!opts->binding().properties().empty());
        configure_button_.set_popup(opts);
 
        // Update the channels popup
@@ -479,12 +488,6 @@ void MainBar::update_device_config_widgets()
        if (sr_dev->config_check(ConfigKey::LIMIT_SAMPLES, Capability::SET))
                sample_count_supported_ = true;
 
-       if (sr_dev->config_check(ConfigKey::LIMIT_FRAMES, Capability::SET)) {
-               sr_dev->config_set(ConfigKey::LIMIT_FRAMES,
-                       Glib::Variant<guint64>::create(1));
-                       on_config_changed();
-       }
-
        // Add notification of reconfigure events
        disconnect(this, SLOT(on_config_changed()));
        connect(&opts->binding(), SIGNAL(config_changed()),
@@ -499,23 +502,20 @@ void MainBar::commit_sample_rate()
 {
        uint64_t sample_rate = 0;
 
-       const shared_ptr<devices::Device> device =
-               device_selector_.selected_device();
+       const shared_ptr<devices::Device> device = device_selector_.selected_device();
        if (!device)
                return;
 
        const shared_ptr<sigrok::Device> sr_dev = device->device();
 
        sample_rate = sample_rate_.value();
-       if (sample_rate == 0)
-               return;
 
        try {
                sr_dev->config_set(ConfigKey::SAMPLERATE,
                        Glib::Variant<guint64>::create(sample_rate));
                update_sample_rate_selector();
-       } catch (Error error) {
-               qDebug() << "Failed to configure samplerate.";
+       } catch (Error& error) {
+               qDebug() << tr("Failed to configure samplerate:") << error.what();
                return;
        }
 
@@ -529,8 +529,7 @@ void MainBar::commit_sample_count()
 {
        uint64_t sample_count = 0;
 
-       const shared_ptr<devices::Device> device =
-               device_selector_.selected_device();
+       const shared_ptr<devices::Device> device = device_selector_.selected_device();
        if (!device)
                return;
 
@@ -542,8 +541,8 @@ void MainBar::commit_sample_count()
                        sr_dev->config_set(ConfigKey::LIMIT_SAMPLES,
                                Glib::Variant<guint64>::create(sample_count));
                        update_sample_count_selector();
-               } catch (Error error) {
-                       qDebug() << "Failed to configure sample count.";
+               } catch (Error& error) {
+                       qDebug() << tr("Failed to configure sample count:") << error.what();
                        return;
                }
        }
@@ -554,13 +553,6 @@ void MainBar::commit_sample_count()
        update_sample_rate_selector();
 }
 
-void MainBar::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 MainBar::show_session_error(const QString text, const QString info_text)
 {
        QMessageBox msg(this);
@@ -575,7 +567,9 @@ void MainBar::add_decoder(srd_decoder *decoder)
 {
 #ifdef ENABLE_DECODE
        assert(decoder);
-       session_.add_decoder(decoder);
+       shared_ptr<data::DecodeSignal> signal = session_.add_decode_signal();
+       if (signal)
+               signal->stack_decoder(decoder);
 #else
        (void)decoder;
 #endif
@@ -600,8 +594,8 @@ void MainBar::export_file(shared_ptr<OutputFormat> format, bool selection_only)
 
                if (!trace_view->cursors()->enabled()) {
                        show_session_error(tr("Missing Cursors"), tr("You need to set the " \
-                                       "cursors before you can save the data enclosed by them " \
-                                       "to a session file (e.g. using the Show Cursors button)."));
+                               "cursors before you can save the data enclosed by them " \
+                               "to a session file (e.g. using the Show Cursors button)."));
                        return;
                }
 
@@ -611,9 +605,16 @@ void MainBar::export_file(shared_ptr<OutputFormat> format, bool selection_only)
                const pv::util::Timestamp& end_time = trace_view->cursors()->second()->time();
 
                const uint64_t start_sample = (uint64_t)max(
-                       (double)0, start_time.convert_to<double>() * samplerate);
+                       0.0, start_time.convert_to<double>() * samplerate);
                const uint64_t end_sample = (uint64_t)max(
-                       (double)0, end_time.convert_to<double>() * samplerate);
+                       0.0, end_time.convert_to<double>() * samplerate);
+
+               if ((start_sample == 0) && (end_sample == 0)) {
+                       // Both cursors are negative and were clamped to 0
+                       show_session_error(tr("Invalid Range"), tr("The cursors don't " \
+                               "define a valid range of samples."));
+                       return;
+               }
 
                sample_range = make_pair(start_sample, end_sample);
        } else {
@@ -671,16 +672,19 @@ void MainBar::import_file(shared_ptr<InputFormat> format)
 
        // Construct the filter
        const vector<string> exts = format->extensions();
-       const QString filter = exts.empty() ? "" :
-               tr("%1 files (*.%2)").arg(
-                       QString::fromStdString(format->description()),
-                       QString::fromStdString(join(exts, ", *.")));
+       const QString filter_exts = exts.empty() ? "" : QString::fromStdString("%1 (%2)").arg(
+               tr("%1 files").arg(QString::fromStdString(format->description())),
+               QString::fromStdString("*.%1").arg(QString::fromStdString(join(exts, " *."))));
+       const QString filter_all = QString::fromStdString("%1 (%2)").arg(
+               tr("All Files"), QString::fromStdString("*"));
+       const QString filter = QString::fromStdString("%1%2%3").arg(
+               exts.empty() ? "" : filter_exts,
+               exts.empty() ? "" : ";;",
+               filter_all);
 
        // Show the file dialog
        const QString file_name = QFileDialog::getOpenFileName(
-               this, tr("Import File"), dir, tr(
-                       "%1 files (*);;All Files (*)").arg(
-                       QString::fromStdString(format->description())));
+               this, tr("Import File"), dir, filter);
 
        if (file_name.isEmpty())
                return;
@@ -706,12 +710,11 @@ void MainBar::import_file(shared_ptr<InputFormat> format)
 void MainBar::on_device_selected()
 {
        shared_ptr<devices::Device> device = device_selector_.selected_device();
-       if (!device) {
-               reset_device_selector();
-               return;
-       }
 
-       session_.select_device(device);
+       if (device)
+               session_.select_device(device);
+       else
+               reset_device_selector();
 }
 
 void MainBar::on_device_changed()
@@ -739,6 +742,10 @@ void MainBar::on_sample_rate_changed()
 
 void MainBar::on_config_changed()
 {
+       // We want to also call update_sample_rate_selector() here in case
+       // the user changed the SR_CONF_EXTERNAL_CLOCK option. However,
+       // commit_sample_rate() does this already, so we don't call it here
+
        commit_sample_count();
        commit_sample_rate();
 }
@@ -816,8 +823,9 @@ void MainBar::add_toolbar_widgets()
 bool MainBar::eventFilter(QObject *watched, QEvent *event)
 {
        if (sample_count_supported_ && (watched == &sample_count_ ||
-                       watched == &sample_rate_) &&
-                       (event->type() == QEvent::ToolTip)) {
+               watched == &sample_rate_) &&
+               (event->type() == QEvent::ToolTip)) {
+
                auto sec = pv::util::Timestamp(sample_count_.value()) / sample_rate_.value();
                QHelpEvent *help_event = static_cast<QHelpEvent*>(event);
 
index 810cab27919f3cca59ed450710ec9701d1bb8e17..e938dbbc01d1dc4c877be7bb7e42b07a460fd5f1 100644 (file)
@@ -100,8 +100,6 @@ public:
        QAction* action_save_selection_as() const;
        QAction* action_connect() const;
 
-       void session_error(const QString text, const QString info_text);
-
 private:
        void run_stop();
 
index 8f2d7d2544cd8e30301a28c56f6537269dbe759a..49b9467c1642737a1a995df5adcb622e2bbddfa1 100644 (file)
@@ -144,6 +144,41 @@ QString format_time_si(const Timestamp& v, SIPrefix prefix,
        return s;
 }
 
+QString format_value_si(double v, SIPrefix prefix, unsigned precision,
+       QString unit, bool sign)
+{
+       if (prefix == SIPrefix::unspecified) {
+               // No prefix given, calculate it
+
+               if (v == 0) {
+                       prefix = SIPrefix::none;
+               } else {
+                       int exp = exponent(SIPrefix::yotta);
+                       prefix = SIPrefix::yocto;
+                       while ((fabs(v) * pow(Timestamp(10), exp)) > 999 &&
+                                       prefix < SIPrefix::yotta) {
+                               prefix = successor(prefix);
+                               exp -= 3;
+                       }
+               }
+       }
+
+       assert(prefix >= SIPrefix::yocto);
+       assert(prefix <= SIPrefix::yotta);
+
+       const double multiplier = pow(10.0, -exponent(prefix));
+
+       QString s;
+       QTextStream ts(&s);
+       if (sign && (v != 0))
+               ts << forcesign;
+       ts.setRealNumberNotation(QTextStream::FixedNotation);
+       ts.setRealNumberPrecision(precision);
+       ts << (v * multiplier) << ' ' << prefix << unit;
+
+       return s;
+}
+
 QString format_time_si_adjusted(const Timestamp& t, SIPrefix prefix,
        unsigned precision, QString unit, bool sign)
 {
@@ -224,5 +259,27 @@ QString format_time_minutes(const Timestamp& t, signed precision, bool sign)
        return s;
 }
 
+/**
+ * Split a string into tokens at occurences of the separator.
+ *
+ * @param[in] text The input string to split.
+ * @param[in] separator The delimiter between tokens.
+ *
+ * @return A vector of broken down tokens.
+ */
+vector<string> split_string(string text, string separator)
+{
+       vector<string> result;
+       size_t pos;
+
+       while ((pos = text.find(separator)) != std::string::npos) {
+               result.push_back(text.substr(0, pos));
+               text = text.substr(pos + separator.length());
+       }
+       result.push_back(text);
+
+       return result;
+}
+
 } // namespace util
 } // namespace pv
index bc71624bf3109fc2cb498778e943ac7acb3480f0..dd7be222b073760b2ec5392ee222617d9717f869 100644 (file)
@@ -21,6 +21,8 @@
 #define PULSEVIEW_UTIL_HPP
 
 #include <cmath>
+#include <string>
+#include <vector>
 
 #ifndef Q_MOC_RUN
 #include <boost/multiprecision/cpp_dec_float.hpp>
@@ -29,6 +31,9 @@
 #include <QMetaType>
 #include <QString>
 
+using std::string;
+using std::vector;
+
 namespace pv {
 namespace util {
 
@@ -78,6 +83,24 @@ QString format_time_si(const Timestamp& v,
        SIPrefix prefix = SIPrefix::unspecified, unsigned precision = 0,
        QString unit = "s", bool sign = true);
 
+/**
+ * Formats a given value into a representation using SI units.
+ *
+ * If 'prefix' is left 'unspecified', the function chooses a prefix so that
+ * the value in front of the decimal point is between 1 and 999.
+ *
+ * @param value The value to format.
+ * @param prefix The SI prefix to use.
+ * @param precision The number of digits after the decimal separator.
+ * @param unit The unit of quantity.
+ * @param sign Whether or not to add a sign also for positive numbers.
+ *
+ * @return The formatted value.
+ */
+QString format_value_si(double v,
+       SIPrefix prefix = SIPrefix::unspecified, unsigned precision = 0,
+       QString unit = "", bool sign = true);
+
 /**
  * Wrapper around 'format_time_si()' that interprets the given 'precision'
  * value as the number of decimal places if the timestamp would be formatted
@@ -112,6 +135,8 @@ QString format_time_si_adjusted(const Timestamp& t, SIPrefix prefix,
 QString format_time_minutes(const Timestamp& t, signed precision = 0,
        bool sign = true);
 
+vector<string> split_string(string text, string separator);
+
 } // namespace util
 } // namespace pv
 
index c52379534166e1b6bfd37ab35271b99a3241c912..cd680085312b07d5249fecca2f51b0c32cdb39de 100644 (file)
@@ -28,6 +28,7 @@
 #include <QApplication>
 #include <QCheckBox>
 #include <QComboBox>
+#include <QDebug>
 #include <QFormLayout>
 #include <QGridLayout>
 #include <QLabel>
@@ -37,6 +38,7 @@
 #include "logicsignal.hpp"
 #include "view.hpp"
 
+#include "pv/util.hpp"
 #include "pv/data/analog.hpp"
 #include "pv/data/analogsegment.hpp"
 #include "pv/data/logic.hpp"
 using std::deque;
 using std::div;
 using std::div_t;
+// Note that "using std::isnan;" is _not_ put here since that would break
+// compilation on some platforms. Use "std::isnan()" instead in checks below.
 using std::max;
 using std::make_pair;
 using std::min;
 using std::numeric_limits;
+using std::out_of_range;
 using std::pair;
 using std::shared_ptr;
 using std::vector;
 
+using pv::data::LogicSegment;
+using pv::data::SignalBase;
+using pv::util::SIPrefix;
+
 namespace pv {
 namespace views {
 namespace trace {
 
-const QColor AnalogSignal::SignalColours[4] = {
+const QColor AnalogSignal::SignalColors[4] = {
        QColor(0xC4, 0xA0, 0x00),       // Yellow
        QColor(0x87, 0x20, 0x7A),       // Magenta
        QColor(0x20, 0x4A, 0x87),       // Blue
        QColor(0x4E, 0x9A, 0x06)        // Green
 };
 
+const QPen AnalogSignal::AxisPen(QColor(0, 0, 0, 30 * 256 / 100), 2);
 const QColor AnalogSignal::GridMajorColor = QColor(0, 0, 0, 40 * 256 / 100);
 const QColor AnalogSignal::GridMinorColor = QColor(0, 0, 0, 20 * 256 / 100);
 
-const QColor AnalogSignal::SamplingPointColour(0x77, 0x77, 0x77);
+const QColor AnalogSignal::SamplingPointColor(0x77, 0x77, 0x77);
+const QColor AnalogSignal::SamplingPointColorLo = QColor(200, 0, 0, 80 * 256 / 100);
+const QColor AnalogSignal::SamplingPointColorNe = QColor(0,   0, 0, 80 * 256 / 100);
+const QColor AnalogSignal::SamplingPointColorHi = QColor(0, 200, 0, 80 * 256 / 100);
+
+const QColor AnalogSignal::ThresholdColor = QColor(0, 0, 0, 30 * 256 / 100);
+const QColor AnalogSignal::ThresholdColorLo = QColor(255, 0, 0, 8 * 256 / 100);
+const QColor AnalogSignal::ThresholdColorNe = QColor(0,   0, 0, 10 * 256 / 100);
+const QColor AnalogSignal::ThresholdColorHi = QColor(0, 255, 0, 8 * 256 / 100);
 
 const int64_t AnalogSignal::TracePaintBlockSize = 1024 * 1024;  // 4 MiB (due to float)
 const float AnalogSignal::EnvelopeThreshold = 64.0f;
@@ -88,22 +106,35 @@ AnalogSignal::AnalogSignal(
        shared_ptr<data::SignalBase> base) :
        Signal(session, base),
        scale_index_(4), // 20 per div
-       scale_index_drag_offset_(0),
-       div_height_(3 * QFontMetrics(QApplication::font()).height()),
        pos_vdivs_(1),
        neg_vdivs_(1),
        resolution_(0),
-       conversion_type_(data::SignalBase::NoConversion),
        display_type_(DisplayBoth),
-       autoranging_(true)
+       autoranging_(true),
+       value_at_hover_pos_(std::numeric_limits<float>::quiet_NaN())
 {
+       axis_pen_ = AxisPen;
+
        pv::data::Analog* analog_data =
                dynamic_cast<pv::data::Analog*>(data().get());
 
-       connect(analog_data, SIGNAL(samples_added(QObject*, uint64_t, uint64_t)),
-               this, SLOT(on_samples_added()));
+       connect(analog_data, SIGNAL(min_max_changed(float, float)),
+               this, SLOT(on_min_max_changed(float, float)));
+
+       GlobalSettings settings;
+       show_sampling_points_ =
+               settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool();
+       fill_high_areas_ =
+               settings.value(GlobalSettings::Key_View_FillSignalHighAreas).toBool();
+       high_fill_color_ = QColor::fromRgba(settings.value(
+               GlobalSettings::Key_View_FillSignalHighAreaColor).value<uint32_t>());
+       show_analog_minor_grid_ =
+               settings.value(GlobalSettings::Key_View_ShowAnalogMinorGrid).toBool();
+       conversion_threshold_disp_mode_ =
+               settings.value(GlobalSettings::Key_View_ConversionThresholdDispMode).toInt();
+       div_height_ = settings.value(GlobalSettings::Key_View_DefaultDivHeight).toInt();
 
-       base_->set_colour(SignalColours[base_->index() % countof(SignalColours)]);
+       base_->set_color(SignalColors[base_->index() % countof(SignalColors)]);
        update_scale();
 }
 
@@ -117,9 +148,9 @@ void AnalogSignal::save_settings(QSettings &settings) const
        settings.setValue("pos_vdivs", pos_vdivs_);
        settings.setValue("neg_vdivs", neg_vdivs_);
        settings.setValue("scale_index", scale_index_);
-       settings.setValue("conversion_type", conversion_type_);
        settings.setValue("display_type", display_type_);
        settings.setValue("autoranging", autoranging_);
+       settings.setValue("div_height", div_height_);
 }
 
 void AnalogSignal::restore_settings(QSettings &settings)
@@ -135,16 +166,22 @@ void AnalogSignal::restore_settings(QSettings &settings)
                update_scale();
        }
 
-       if (settings.contains("conversion_type")) {
-               conversion_type_ = (data::SignalBase::ConversionType)(settings.value("conversion_type").toInt());
-               update_conversion_type();
-       }
-
        if (settings.contains("display_type"))
                display_type_ = (DisplayType)(settings.value("display_type").toInt());
 
        if (settings.contains("autoranging"))
                autoranging_ = settings.value("autoranging").toBool();
+
+       if (settings.contains("div_height")) {
+               const int old_height = div_height_;
+               div_height_ = settings.value("div_height").toInt();
+
+               if ((div_height_ != old_height) && owner_) {
+                       // Call order is important, otherwise the lazy event handler won't work
+                       owner_->extents_changed(false, true);
+                       owner_->row_item_appearance_changed(false, true);
+               }
+       }
 }
 
 pair<int, int> AnalogSignal::v_extents() const
@@ -154,32 +191,51 @@ pair<int, int> AnalogSignal::v_extents() const
        return make_pair(-ph, nh);
 }
 
-int AnalogSignal::scale_handle_offset() const
-{
-       const int h = (pos_vdivs_ + neg_vdivs_) * div_height_;
-
-       return ((scale_index_drag_offset_ - scale_index_) * h / 4) - h / 2;
-}
-
-void AnalogSignal::scale_handle_dragged(int offset)
+void AnalogSignal::paint_back(QPainter &p, ViewItemPaintParams &pp)
 {
-       const int h = (pos_vdivs_ + neg_vdivs_) * div_height_;
-
-       scale_index_ = scale_index_drag_offset_ - (offset + h / 2) / (h / 4);
-
-       update_scale();
-}
+       if (!base_->enabled())
+               return;
 
-void AnalogSignal::scale_handle_drag_release()
-{
-       scale_index_drag_offset_ = scale_index_;
-       update_scale();
-}
+       bool paint_thr_bg =
+               conversion_threshold_disp_mode_ == GlobalSettings::ConvThrDispMode_Background;
+
+       const vector<double> thresholds = base_->get_conversion_thresholds();
+
+       // Only display thresholds if we have some and we show analog samples
+       if ((thresholds.size() > 0) && paint_thr_bg &&
+               ((display_type_ == DisplayAnalog) || (display_type_ == DisplayBoth))) {
+
+               const int visual_y = get_visual_y();
+               const pair<int, int> extents = v_extents();
+               const int top = visual_y + extents.first;
+               const int btm = visual_y + extents.second;
+
+               // Draw high/neutral/low areas
+               if (thresholds.size() == 2) {
+                       int thr_lo = visual_y - thresholds[0] * scale_;
+                       int thr_hi = visual_y - thresholds[1] * scale_;
+                       thr_lo = min(max(thr_lo, top), btm);
+                       thr_hi = min(max(thr_hi, top), btm);
+
+                       p.fillRect(QRectF(pp.left(), top, pp.width(), thr_hi - top),
+                               QBrush(ThresholdColorHi));
+                       p.fillRect(QRectF(pp.left(), thr_hi, pp.width(), thr_lo - thr_hi),
+                               QBrush(ThresholdColorNe));
+                       p.fillRect(QRectF(pp.left(), thr_lo, pp.width(), btm - thr_lo),
+                               QBrush(ThresholdColorLo));
+               } else {
+                       int thr = visual_y - thresholds[0] * scale_;
+                       thr = min(max(thr, top), btm);
+
+                       p.fillRect(QRectF(pp.left(), top, pp.width(), thr - top),
+                               QBrush(ThresholdColorHi));
+                       p.fillRect(QRectF(pp.left(), thr, pp.width(), btm - thr),
+                               QBrush(ThresholdColorLo));
+               }
 
-void AnalogSignal::paint_back(QPainter &p, ViewItemPaintParams &pp)
-{
-       if (base_->enabled()) {
-               Trace::paint_back(p, pp);
+               paint_axis(p, pp, get_visual_y());
+       } else {
+               Signal::paint_back(p, pp);
                paint_axis(p, pp, get_visual_y());
        }
 }
@@ -197,18 +253,14 @@ void AnalogSignal::paint_mid(QPainter &p, ViewItemPaintParams &pp)
        if ((display_type_ == DisplayAnalog) || (display_type_ == DisplayBoth)) {
                paint_grid(p, y, pp.left(), pp.right());
 
-               const deque< shared_ptr<pv::data::AnalogSegment> > &segments =
-                       base_->analog_data()->analog_segments();
-               if (segments.empty())
+               shared_ptr<pv::data::AnalogSegment> segment = get_analog_segment_to_paint();
+               if (!segment || (segment->get_sample_count() == 0))
                        return;
 
-               const shared_ptr<pv::data::AnalogSegment> &segment =
-                       segments.front();
-
                const double pixels_offset = pp.pixels_offset();
                const double samplerate = max(1.0, segment->samplerate());
                const pv::util::Timestamp& start_time = segment->start_time();
-               const int64_t last_sample = segment->get_sample_count() - 1;
+               const int64_t last_sample = (int64_t)segment->get_sample_count() - 1;
                const double samples_per_pixel = samplerate * pp.scale();
                const pv::util::Timestamp start = samplerate * (pp.offset() - start_time);
                const pv::util::Timestamp end = start + samples_per_pixel * pp.width();
@@ -219,22 +271,15 @@ void AnalogSignal::paint_mid(QPainter &p, ViewItemPaintParams &pp)
                        (int64_t)0), last_sample);
 
                if (samples_per_pixel < EnvelopeThreshold)
-                       paint_trace(p, segment, y, pp.left(),
-                               start_sample, end_sample,
+                       paint_trace(p, segment, y, pp.left(), start_sample, end_sample,
                                pixels_offset, samples_per_pixel);
                else
-                       paint_envelope(p, segment, y, pp.left(),
-                               start_sample, end_sample,
+                       paint_envelope(p, segment, y, pp.left(), start_sample, end_sample,
                                pixels_offset, samples_per_pixel);
        }
 
-       if ((display_type_ == DisplayConverted) || (display_type_ == DisplayBoth)) {
-               if (((conversion_type_ == data::SignalBase::A2LConversionByTreshold) ||
-                       (conversion_type_ == data::SignalBase::A2LConversionBySchmittTrigger))) {
-
-                       paint_logic_mid(p, pp);
-               }
-       }
+       if ((display_type_ == DisplayConverted) || (display_type_ == DisplayBoth))
+               paint_logic_mid(p, pp);
 }
 
 void AnalogSignal::paint_fore(QPainter &p, ViewItemPaintParams &pp)
@@ -245,10 +290,19 @@ void AnalogSignal::paint_fore(QPainter &p, ViewItemPaintParams &pp)
        if ((display_type_ == DisplayAnalog) || (display_type_ == DisplayBoth)) {
                const int y = get_visual_y();
 
-               // Show the info section on the right side of the trace
-               const QString infotext = QString("%1 V/div").arg(resolution_);
+               QString infotext;
+
+               // Show the info section on the right side of the trace, including
+               // the value at the hover point when the hover marker is enabled
+               // and we have corresponding data available
+               if (show_hover_marker_ && !std::isnan(value_at_hover_pos_)) {
+                       infotext = QString("[%1] %2 V/div")
+                               .arg(format_value_si(value_at_hover_pos_, SIPrefix::unspecified, 0, "V", false))
+                               .arg(resolution_);
+               } else
+                       infotext = QString("%1 V/div").arg(resolution_);
 
-               p.setPen(base_->colour());
+               p.setPen(base_->color());
                p.setFont(QApplication::font());
 
                const QRectF bounding_rect = QRectF(pp.left(),
@@ -258,16 +312,15 @@ void AnalogSignal::paint_fore(QPainter &p, ViewItemPaintParams &pp)
 
                p.drawText(bounding_rect, Qt::AlignRight | Qt::AlignBottom, infotext);
        }
+
+       if (show_hover_marker_)
+               paint_hover_marker(p);
 }
 
 void AnalogSignal::paint_grid(QPainter &p, int y, int left, int right)
 {
        p.setRenderHint(QPainter::Antialiasing, false);
 
-       GlobalSettings settings;
-       const bool show_analog_minor_grid =
-               settings.value(GlobalSettings::Key_View_ShowAnalogMinorGrid).toBool();
-
        if (pos_vdivs_ > 0) {
                p.setPen(QPen(GridMajorColor, 1, Qt::DashLine));
                for (int i = 1; i <= pos_vdivs_; i++) {
@@ -276,7 +329,7 @@ void AnalogSignal::paint_grid(QPainter &p, int y, int left, int right)
                }
        }
 
-       if ((pos_vdivs_ > 0) && show_analog_minor_grid) {
+       if ((pos_vdivs_ > 0) && show_analog_minor_grid_) {
                p.setPen(QPen(GridMinorColor, 1, Qt::DashLine));
                for (int i = 0; i < pos_vdivs_; i++) {
                        const float dy = i * div_height_;
@@ -297,7 +350,7 @@ void AnalogSignal::paint_grid(QPainter &p, int y, int left, int right)
                }
        }
 
-       if ((pos_vdivs_ > 0) && show_analog_minor_grid) {
+       if ((pos_vdivs_ > 0) && show_analog_minor_grid_) {
                p.setPen(QPen(GridMinorColor, 1, Qt::DashLine));
                for (int i = 0; i < neg_vdivs_; i++) {
                        const float dy = i * div_height_;
@@ -321,55 +374,89 @@ void AnalogSignal::paint_trace(QPainter &p,
        if (end <= start)
                return;
 
+       bool paint_thr_dots =
+               (base_->get_conversion_type() != data::SignalBase::NoConversion) &&
+               (conversion_threshold_disp_mode_ == GlobalSettings::ConvThrDispMode_Dots);
+
+       vector<double> thresholds;
+       if (paint_thr_dots)
+               thresholds = base_->get_conversion_thresholds();
+
        // Calculate and paint the sampling points if enabled and useful
        GlobalSettings settings;
        const bool show_sampling_points =
-               settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool() &&
-               (samples_per_pixel < 0.25);
+               (show_sampling_points_ || paint_thr_dots) && (samples_per_pixel < 0.25);
 
-       p.setPen(base_->colour());
+       p.setPen(base_->color());
 
-       const int64_t points_count = end - start;
+       const int64_t points_count = end - start + 1;
 
        QPointF *points = new QPointF[points_count];
        QPointF *point = points;
 
-       QRectF *sampling_points = nullptr;
-       if (show_sampling_points)
-                sampling_points = new QRectF[points_count];
-       QRectF *sampling_point = sampling_points;
+       vector<QRectF> sampling_points[3];
 
        int64_t sample_count = min(points_count, TracePaintBlockSize);
        int64_t block_sample = 0;
-       const float *sample_block = segment->get_samples(start, start + sample_count);
+       float *sample_block = new float[TracePaintBlockSize];
+       segment->get_samples(start, start + sample_count, sample_block);
+
+       if (show_hover_marker_)
+               reset_pixel_values();
 
        const int w = 2;
-       for (int64_t sample = start; sample != end; sample++, block_sample++) {
+       for (int64_t sample = start; sample <= end; sample++, block_sample++) {
 
+               // Fetch next block of samples if we finished the current one
                if (block_sample == TracePaintBlockSize) {
                        block_sample = 0;
-                       delete[] sample_block;
                        sample_count = min(points_count - sample, TracePaintBlockSize);
-                       sample_block = segment->get_samples(sample, sample + sample_count);
+                       segment->get_samples(sample, sample + sample_count, sample_block);
                }
 
-               const float x = (sample / samples_per_pixel -
-                       pixels_offset) + left;
+               const float abs_x = sample / samples_per_pixel - pixels_offset;
+               const float x = left + abs_x;
 
                *point++ = QPointF(x, y - sample_block[block_sample] * scale_);
 
-               if (show_sampling_points)
-                       *sampling_point++ =
-                               QRectF(x - (w / 2), y - sample_block[block_sample] * scale_ - (w / 2), w, w);
+               // Generate the pixel<->value lookup table for the mouse hover
+               if (show_hover_marker_)
+                       process_next_sample_value(abs_x, sample_block[block_sample]);
+
+               // Create the sampling points if needed
+               if (show_sampling_points) {
+                       int idx = 0;  // Neutral
+
+                       if (paint_thr_dots) {
+                               if (thresholds.size() == 1)
+                                       idx = (sample_block[block_sample] >= thresholds[0]) ? 2 : 1;
+                               else if (thresholds.size() == 2) {
+                                       if (sample_block[block_sample] > thresholds[1])
+                                               idx = 2;  // High
+                                       else if (sample_block[block_sample] < thresholds[0])
+                                               idx = 1;  // Low
+                               }
+                       }
+
+                       sampling_points[idx].emplace_back(x - (w / 2), y - sample_block[block_sample] * scale_ - (w / 2), w, w);
+               }
        }
        delete[] sample_block;
 
        p.drawPolyline(points, points_count);
 
        if (show_sampling_points) {
-               p.setPen(SamplingPointColour);
-               p.drawRects(sampling_points, points_count);
-               delete[] sampling_points;
+               if (paint_thr_dots) {
+                       p.setPen(SamplingPointColorNe);
+                       p.drawRects(sampling_points[0].data(), sampling_points[0].size());
+                       p.setPen(SamplingPointColorLo);
+                       p.drawRects(sampling_points[1].data(), sampling_points[1].size());
+                       p.setPen(SamplingPointColorHi);
+                       p.drawRects(sampling_points[2].data(), sampling_points[2].size());
+               } else {
+                       p.setPen(SamplingPointColor);
+                       p.drawRects(sampling_points[0].data(), sampling_points[0].size());
+               }
        }
 
        delete[] points;
@@ -382,6 +469,10 @@ void AnalogSignal::paint_envelope(QPainter &p,
 {
        using pv::data::AnalogSegment;
 
+       // Note: Envelope painting currently doesn't generate a pixel<->value lookup table
+       if (show_hover_marker_)
+               reset_pixel_values();
+
        AnalogSegment::EnvelopeSection e;
        segment->get_envelope_section(e, start, end, samples_per_pixel);
 
@@ -389,7 +480,7 @@ void AnalogSignal::paint_envelope(QPainter &p,
                return;
 
        p.setPen(QPen(Qt::NoPen));
-       p.setBrush(base_->colour());
+       p.setBrush(base_->color());
 
        QRectF *const rects = new QRectF[e.length];
        QRectF *rect = rects;
@@ -397,8 +488,8 @@ void AnalogSignal::paint_envelope(QPainter &p,
        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 AnalogSegment::EnvelopeSample *const s =
-                       e.samples + sample;
+
+               const AnalogSegment::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
@@ -440,16 +531,12 @@ void AnalogSignal::paint_logic_mid(QPainter &p, ViewItemPaintParams &pp)
        const int nh = min(neg_vdivs_, 1) * div_height_;
        const float high_offset = y - ph + signal_margin + 0.5f;
        const float low_offset = y + nh - signal_margin - 0.5f;
+       const float signal_height = low_offset - high_offset;
 
-       const deque< shared_ptr<pv::data::LogicSegment> > &segments =
-               base_->logic_data()->logic_segments();
-
-       if (segments.empty())
+       shared_ptr<pv::data::LogicSegment> segment = get_logic_segment_to_paint();
+       if (!segment || (segment->get_sample_count() == 0))
                return;
 
-       const shared_ptr<pv::data::LogicSegment> &segment =
-               segments.front();
-
        double samplerate = segment->samplerate();
 
        // Show sample rate as 1Hz when it is unknown
@@ -458,7 +545,7 @@ void AnalogSignal::paint_logic_mid(QPainter &p, ViewItemPaintParams &pp)
 
        const double pixels_offset = pp.pixels_offset();
        const pv::util::Timestamp& start_time = segment->start_time();
-       const int64_t last_sample = segment->get_sample_count() - 1;
+       const int64_t last_sample = (int64_t)segment->get_sample_count() - 1;
        const double samples_per_pixel = samplerate * pp.scale();
        const double pixels_per_sample = 1 / samples_per_pixel;
        const pv::util::Timestamp start = samplerate * (pp.offset() - start_time);
@@ -473,32 +560,62 @@ void AnalogSignal::paint_logic_mid(QPainter &p, ViewItemPaintParams &pp)
                samples_per_pixel / LogicSignal::Oversampling, 0);
        assert(edges.size() >= 2);
 
-       // Check whether we need to paint the sampling points
-       GlobalSettings settings;
-       const bool show_sampling_points =
-               settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool() &&
-               (samples_per_pixel < 0.25);
+       const float first_sample_x =
+               pp.left() + (edges.front().first / samples_per_pixel - pixels_offset);
+       const float last_sample_x =
+               pp.left() + (edges.back().first / samples_per_pixel - pixels_offset);
 
+       // Check whether we need to paint the sampling points
+       const bool show_sampling_points = show_sampling_points_ && (samples_per_pixel < 0.25);
        vector<QRectF> sampling_points;
-       float sampling_point_x = 0.0f;
+       float sampling_point_x = first_sample_x;
        int64_t sampling_point_sample = start_sample;
        const int w = 2;
 
-       if (show_sampling_points) {
+       if (show_sampling_points)
                sampling_points.reserve(end_sample - start_sample + 1);
-               sampling_point_x = (edges.cbegin()->first / samples_per_pixel - pixels_offset) + pp.left();
-       }
+
+       vector<QRectF> high_rects;
+       float rising_edge_x;
+       bool rising_edge_seen = false;
 
        // Paint the edges
        const unsigned int edge_count = edges.size() - 2;
        QLineF *const edge_lines = new QLineF[edge_count];
        line = edge_lines;
 
+       if (edges.front().second) {
+               // Beginning of trace is high
+               rising_edge_x = first_sample_x;
+               rising_edge_seen = true;
+       }
+
        for (auto i = edges.cbegin() + 1; i != edges.cend() - 1; i++) {
-               const float x = ((*i).first / samples_per_pixel -
-                       pixels_offset) + pp.left();
+               // Note: multiple edges occupying a single pixel are represented by an edge
+               // with undefined logic level. This means that only the first falling edge
+               // after a rising edge corresponds to said rising edge - and vice versa. If
+               // more edges with the same logic level follow, they denote multiple edges.
+
+               const float x = pp.left() + ((*i).first / samples_per_pixel - pixels_offset);
                *line++ = QLineF(x, high_offset, x, low_offset);
 
+               if (fill_high_areas_) {
+                       // Any edge terminates a high area
+                       if (rising_edge_seen) {
+                               const int width = x - rising_edge_x;
+                               if (width > 0)
+                                       high_rects.emplace_back(rising_edge_x, high_offset,
+                                               width, signal_height);
+                               rising_edge_seen = false;
+                       }
+
+                       // Only rising edges start high areas
+                       if ((*i).second) {
+                               rising_edge_x = x;
+                               rising_edge_seen = true;
+                       }
+               }
+
                if (show_sampling_points)
                        while (sampling_point_sample < (*i).first) {
                                const float y = (*i).second ? low_offset : high_offset;
@@ -520,7 +637,18 @@ void AnalogSignal::paint_logic_mid(QPainter &p, ViewItemPaintParams &pp)
                        sampling_point_x += pixels_per_sample;
                };
 
-       p.setPen(LogicSignal::EdgeColour);
+       if (fill_high_areas_) {
+               // Add last high rectangle if the signal is still high at the end of the trace
+               if (rising_edge_seen && (edges.cend() - 1)->second)
+                       high_rects.emplace_back(rising_edge_x, high_offset,
+                               last_sample_x - rising_edge_x, signal_height);
+
+               p.setPen(high_fill_color_);
+               p.setBrush(high_fill_color_);
+               p.drawRects((const QRectF*)(high_rects.data()), high_rects.size());
+       }
+
+       p.setPen(LogicSignal::EdgeColor);
        p.drawLines(edge_lines, edge_count);
        delete[] edge_lines;
 
@@ -528,10 +656,10 @@ void AnalogSignal::paint_logic_mid(QPainter &p, ViewItemPaintParams &pp)
        const unsigned int max_cap_line_count = edges.size();
        QLineF *const cap_lines = new QLineF[max_cap_line_count];
 
-       p.setPen(LogicSignal::HighColour);
+       p.setPen(LogicSignal::HighColor);
        paint_logic_caps(p, cap_lines, edges, true, samples_per_pixel,
                pixels_offset, pp.left(), high_offset);
-       p.setPen(LogicSignal::LowColour);
+       p.setPen(LogicSignal::LowColor);
        paint_logic_caps(p, cap_lines, edges, false, samples_per_pixel,
                pixels_offset, pp.left(), low_offset);
 
@@ -539,7 +667,7 @@ void AnalogSignal::paint_logic_mid(QPainter &p, ViewItemPaintParams &pp)
 
        // Paint the sampling points
        if (show_sampling_points) {
-               p.setPen(SamplingPointColour);
+               p.setPen(SamplingPointColor);
                p.drawRects(sampling_points.data(), sampling_points.size());
        }
 }
@@ -563,6 +691,54 @@ void AnalogSignal::paint_logic_caps(QPainter &p, QLineF *const lines,
        p.drawLines(lines, line - lines);
 }
 
+shared_ptr<pv::data::AnalogSegment> AnalogSignal::get_analog_segment_to_paint() const
+{
+       shared_ptr<pv::data::AnalogSegment> segment;
+
+       const deque< shared_ptr<pv::data::AnalogSegment> > &segments =
+               base_->analog_data()->analog_segments();
+
+       if (!segments.empty()) {
+               if (segment_display_mode_ == ShowLastSegmentOnly)
+                       segment = segments.back();
+
+               if ((segment_display_mode_ == ShowSingleSegmentOnly) ||
+                               (segment_display_mode_ == ShowLastCompleteSegmentOnly)) {
+                       try {
+                               segment = segments.at(current_segment_);
+                       } catch (out_of_range&) {
+                               qDebug() << "Current analog segment out of range for signal" << base_->name() << ":" << current_segment_;
+                       }
+               }
+       }
+
+       return segment;
+}
+
+shared_ptr<pv::data::LogicSegment> AnalogSignal::get_logic_segment_to_paint() const
+{
+       shared_ptr<pv::data::LogicSegment> segment;
+
+       const deque< shared_ptr<pv::data::LogicSegment> > &segments =
+               base_->logic_data()->logic_segments();
+
+       if (!segments.empty()) {
+               if (segment_display_mode_ == ShowLastSegmentOnly)
+                       segment = segments.back();
+
+               if ((segment_display_mode_ == ShowSingleSegmentOnly) ||
+                               (segment_display_mode_ == ShowLastCompleteSegmentOnly)) {
+                       try {
+                               segment = segments.at(current_segment_);
+                       } catch (out_of_range&) {
+                               qDebug() << "Current logic segment out of range for signal" << base_->name() << ":" << current_segment_;
+                       }
+               }
+       }
+
+       return segment;
+}
+
 float AnalogSignal::get_resolution(int scale_index)
 {
        const float seq[] = {1.0f, 2.0f, 5.0f};
@@ -580,12 +756,79 @@ void AnalogSignal::update_scale()
        scale_ = div_height_ / resolution_;
 }
 
-void AnalogSignal::update_conversion_type()
+void AnalogSignal::update_conversion_widgets()
 {
-       base_->set_conversion_type(conversion_type_);
+       SignalBase::ConversionType conv_type = base_->get_conversion_type();
 
-       if (owner_)
-               owner_->row_item_appearance_changed(false, true);
+       // Enable or disable widgets depending on conversion state
+       conv_threshold_cb_->setEnabled(conv_type != SignalBase::NoConversion);
+       display_type_cb_->setEnabled(conv_type != SignalBase::NoConversion);
+
+       conv_threshold_cb_->clear();
+
+       vector < pair<QString, int> > presets = base_->get_conversion_presets();
+
+       // Prevent the combo box from firing the "edit text changed" signal
+       // as that would involuntarily select the first entry
+       conv_threshold_cb_->blockSignals(true);
+
+       // Set available options depending on chosen conversion
+       for (pair<QString, int>& preset : presets)
+               conv_threshold_cb_->addItem(preset.first, preset.second);
+
+       map < QString, QVariant > options = base_->get_conversion_options();
+
+       if (conv_type == SignalBase::A2LConversionByThreshold) {
+               const vector<double> thresholds = base_->get_conversion_thresholds(
+                               SignalBase::A2LConversionByThreshold, true);
+               conv_threshold_cb_->addItem(
+                               QString("%1V").arg(QString::number(thresholds[0], 'f', 1)), -1);
+       }
+
+       if (conv_type == SignalBase::A2LConversionBySchmittTrigger) {
+               const vector<double> thresholds = base_->get_conversion_thresholds(
+                               SignalBase::A2LConversionBySchmittTrigger, true);
+               conv_threshold_cb_->addItem(QString("%1V/%2V").arg(
+                               QString::number(thresholds[0], 'f', 1),
+                               QString::number(thresholds[1], 'f', 1)), -1);
+       }
+
+       int preset_id = base_->get_current_conversion_preset();
+       conv_threshold_cb_->setCurrentIndex(
+                       conv_threshold_cb_->findData(preset_id));
+
+       conv_threshold_cb_->blockSignals(false);
+}
+
+vector<data::LogicSegment::EdgePair> AnalogSignal::get_nearest_level_changes(uint64_t sample_pos)
+{
+       assert(base_);
+       assert(owner_);
+
+       // Return if there's no logic data or we're showing only the analog trace
+       if (!base_->logic_data() || (display_type_ == DisplayAnalog))
+               return vector<data::LogicSegment::EdgePair>();
+
+       if (sample_pos == 0)
+               return vector<LogicSegment::EdgePair>();
+
+       shared_ptr<LogicSegment> segment = get_logic_segment_to_paint();
+       if (!segment || (segment->get_sample_count() == 0))
+               return vector<LogicSegment::EdgePair>();
+
+       const View *view = owner_->view();
+       assert(view);
+       const double samples_per_pixel = base_->get_samplerate() * view->scale();
+
+       vector<LogicSegment::EdgePair> edges;
+
+       segment->get_surrounding_edges(edges, sample_pos,
+               samples_per_pixel / LogicSignal::Oversampling, 0);
+
+       if (edges.empty())
+               return vector<LogicSegment::EdgePair>();
+
+       return edges;
 }
 
 void AnalogSignal::perform_autoranging(bool keep_divs, bool force_update)
@@ -599,7 +842,7 @@ void AnalogSignal::perform_autoranging(bool keep_divs, bool force_update)
        static double prev_min = 0, prev_max = 0;
        double min = 0, max = 0;
 
-       for (shared_ptr<pv::data::AnalogSegment> segment : segments) {
+       for (const shared_ptr<pv::data::AnalogSegment>& segment : segments) {
                pair<double, double> mm = segment->get_min_max();
                min = std::min(min, mm.first);
                max = std::max(max, mm.second);
@@ -658,6 +901,64 @@ void AnalogSignal::perform_autoranging(bool keep_divs, bool force_update)
        update_scale();
 }
 
+void AnalogSignal::reset_pixel_values()
+{
+       value_at_pixel_pos_.clear();
+       current_pixel_pos_ = -1;
+       prev_value_at_pixel_ = std::numeric_limits<float>::quiet_NaN();
+}
+
+void AnalogSignal::process_next_sample_value(float x, float value)
+{
+       // Note: NAN is used to indicate the non-existance of a value at this pixel
+
+       if (std::isnan(prev_value_at_pixel_)) {
+               if (x < 0) {
+                       min_value_at_pixel_ = value;
+                       max_value_at_pixel_ = value;
+                       prev_value_at_pixel_ = value;
+                       current_pixel_pos_ = x;
+               } else
+                       prev_value_at_pixel_ = std::numeric_limits<float>::quiet_NaN();
+       }
+
+       const int pixel_pos = (int)(x + 0.5);
+
+       if (pixel_pos > current_pixel_pos_) {
+               if (pixel_pos - current_pixel_pos_ == 1) {
+                       if (std::isnan(prev_value_at_pixel_)) {
+                               value_at_pixel_pos_.push_back(prev_value_at_pixel_);
+                       } else {
+                               // Average the min/max range to create one value for the previous pixel
+                               const float avg = (min_value_at_pixel_ + max_value_at_pixel_) / 2;
+                               value_at_pixel_pos_.push_back(avg);
+                       }
+               } else {
+                       // Interpolate values to create values for the intermediate pixels
+                       const float start_value = prev_value_at_pixel_;
+                       const float end_value = value;
+                       const int steps = fabs(pixel_pos - current_pixel_pos_);
+                       const double gradient = (end_value - start_value) / steps;
+                       for (int i = 0; i < steps; i++) {
+                               if (current_pixel_pos_ + i < 0)
+                                       continue;
+                               value_at_pixel_pos_.push_back(start_value + i * gradient);
+                       }
+               }
+
+               min_value_at_pixel_ = value;
+               max_value_at_pixel_ = value;
+               prev_value_at_pixel_ = value;
+               current_pixel_pos_ = pixel_pos;
+       } else {
+               // Another sample for the same pixel
+               if (value < min_value_at_pixel_)
+                       min_value_at_pixel_ = value;
+               if (value > max_value_at_pixel_)
+                       max_value_at_pixel_ = value;
+       }
+}
+
 void AnalogSignal::populate_popup_form(QWidget *parent, QFormLayout *form)
 {
        // Add the standard options
@@ -665,7 +966,7 @@ void AnalogSignal::populate_popup_form(QWidget *parent, QFormLayout *form)
 
        QFormLayout *const layout = new QFormLayout;
 
-       // Add the number of vdivs
+       // Add div-related settings
        pvdiv_sb_ = new QSpinBox(parent);
        pvdiv_sb_->setRange(0, MaximumVDivs);
        pvdiv_sb_->setValue(pos_vdivs_);
@@ -680,6 +981,15 @@ void AnalogSignal::populate_popup_form(QWidget *parent, QFormLayout *form)
                this, SLOT(on_neg_vdivs_changed(int)));
        layout->addRow(tr("Number of neg vertical divs"), nvdiv_sb_);
 
+       div_height_sb_ = new QSpinBox(parent);
+       div_height_sb_->setRange(20, 1000);
+       div_height_sb_->setSingleStep(5);
+       div_height_sb_->setSuffix(tr(" pixels"));
+       div_height_sb_->setValue(div_height_);
+       connect(div_height_sb_, SIGNAL(valueChanged(int)),
+               this, SLOT(on_div_height_changed(int)));
+       layout->addRow(tr("Div height"), div_height_sb_);
+
        // Add the vertical resolution
        resolution_cb_ = new QComboBox(parent);
 
@@ -713,39 +1023,102 @@ void AnalogSignal::populate_popup_form(QWidget *parent, QFormLayout *form)
        // Add the conversion type dropdown
        conversion_cb_ = new QComboBox();
 
-       conversion_cb_->addItem("none", data::SignalBase::NoConversion);
-       conversion_cb_->addItem("to logic via threshold", data::SignalBase::A2LConversionByTreshold);
-       conversion_cb_->addItem("to logic via schmitt-trigger", data::SignalBase::A2LConversionBySchmittTrigger);
+       conversion_cb_->addItem(tr("none"),
+               SignalBase::NoConversion);
+       conversion_cb_->addItem(tr("to logic via threshold"),
+               SignalBase::A2LConversionByThreshold);
+       conversion_cb_->addItem(tr("to logic via schmitt-trigger"),
+               SignalBase::A2LConversionBySchmittTrigger);
 
-       cur_idx = conversion_cb_->findData(QVariant(conversion_type_));
+       cur_idx = conversion_cb_->findData(QVariant(base_->get_conversion_type()));
        conversion_cb_->setCurrentIndex(cur_idx);
 
-//     layout->addRow(tr("Conversion"), conversion_cb_);
+       layout->addRow(tr("Conversion"), conversion_cb_);
 
        connect(conversion_cb_, SIGNAL(currentIndexChanged(int)),
                this, SLOT(on_conversion_changed(int)));
 
+    // Add the conversion threshold settings
+    conv_threshold_cb_ = new QComboBox();
+    conv_threshold_cb_->setEditable(true);
+
+    layout->addRow(tr("Conversion threshold(s)"), conv_threshold_cb_);
+
+    connect(conv_threshold_cb_, SIGNAL(currentIndexChanged(int)),
+            this, SLOT(on_conv_threshold_changed(int)));
+    connect(conv_threshold_cb_, SIGNAL(editTextChanged(const QString&)),
+            this, SLOT(on_conv_threshold_changed()));  // index will be -1
+
        // Add the display type dropdown
        display_type_cb_ = new QComboBox();
 
-       display_type_cb_->addItem(tr("Analog"), DisplayAnalog);
-       display_type_cb_->addItem(tr("Converted"), DisplayConverted);
-       display_type_cb_->addItem(tr("Both"), DisplayBoth);
+       display_type_cb_->addItem(tr("analog"), DisplayAnalog);
+       display_type_cb_->addItem(tr("converted"), DisplayConverted);
+       display_type_cb_->addItem(tr("analog+converted"), DisplayBoth);
 
        cur_idx = display_type_cb_->findData(QVariant(display_type_));
        display_type_cb_->setCurrentIndex(cur_idx);
 
-//     layout->addRow(tr("Traces to show:"), display_type_cb_);
+       layout->addRow(tr("Show traces for"), display_type_cb_);
 
        connect(display_type_cb_, SIGNAL(currentIndexChanged(int)),
                this, SLOT(on_display_type_changed(int)));
 
+       // Update the conversion widget contents and states
+       update_conversion_widgets();
+
        form->addRow(layout);
 }
 
-void AnalogSignal::on_samples_added()
+void AnalogSignal::hover_point_changed(const QPoint &hp)
+{
+       Signal::hover_point_changed(hp);
+
+       // Note: Even though the view area begins at 0, we exclude 0 because
+       // that's also the value given when the cursor is over the header to the
+       // left of the trace paint area
+       if (hp.x() <= 0) {
+               value_at_hover_pos_ = std::numeric_limits<float>::quiet_NaN();
+       } else {
+               try {
+                       value_at_hover_pos_ = value_at_pixel_pos_.at(hp.x());
+               } catch (out_of_range&) {
+                       value_at_hover_pos_ = std::numeric_limits<float>::quiet_NaN();
+               }
+       }
+}
+
+void AnalogSignal::on_setting_changed(const QString &key, const QVariant &value)
 {
-       perform_autoranging(false, false);
+       Signal::on_setting_changed(key, value);
+
+       if (key == GlobalSettings::Key_View_ShowSamplingPoints)
+               show_sampling_points_ = value.toBool();
+
+       if (key == GlobalSettings::Key_View_FillSignalHighAreas)
+               fill_high_areas_ = value.toBool();
+
+       if (key == GlobalSettings::Key_View_FillSignalHighAreaColor)
+               high_fill_color_ = QColor::fromRgba(value.value<uint32_t>());
+
+       if (key == GlobalSettings::Key_View_ShowAnalogMinorGrid)
+               show_analog_minor_grid_ = value.toBool();
+
+       if (key == GlobalSettings::Key_View_ConversionThresholdDispMode) {
+               conversion_threshold_disp_mode_ = value.toInt();
+
+               if (owner_)
+                       owner_->row_item_appearance_changed(false, true);
+       }
+}
+
+void AnalogSignal::on_min_max_changed(float min, float max)
+{
+       (void)min;
+       (void)max;
+
+       if (autoranging_)
+               perform_autoranging(false, false);
 }
 
 void AnalogSignal::on_pos_vdivs_changed(int vdivs)
@@ -810,6 +1183,18 @@ void AnalogSignal::on_neg_vdivs_changed(int vdivs)
        }
 }
 
+void AnalogSignal::on_div_height_changed(int height)
+{
+       div_height_ = height;
+       update_scale();
+
+       if (owner_) {
+               // Call order is important, otherwise the lazy event handler won't work
+               owner_->extents_changed(false, true);
+               owner_->row_item_appearance_changed(false, true);
+       }
+}
+
 void AnalogSignal::on_resolution_changed(int index)
 {
        scale_index_ = resolution_cb_->itemData(index).toInt();
@@ -835,14 +1220,101 @@ void AnalogSignal::on_autoranging_changed(int state)
 
 void AnalogSignal::on_conversion_changed(int index)
 {
-       data::SignalBase::ConversionType old_conv_type = conversion_type_;
+       SignalBase::ConversionType old_conv_type = base_->get_conversion_type();
 
-       conversion_type_ = (data::SignalBase::ConversionType)(conversion_cb_->itemData(index).toInt());
+       SignalBase::ConversionType conv_type =
+               (SignalBase::ConversionType)(conversion_cb_->itemData(index).toInt());
 
-       if (conversion_type_ != old_conv_type) {
-               base_->set_conversion_type(conversion_type_);
-               update_conversion_type();
+       if (conv_type != old_conv_type) {
+               base_->set_conversion_type(conv_type);
+               update_conversion_widgets();
+
+               if (owner_)
+                       owner_->row_item_appearance_changed(false, true);
+       }
+}
+
+void AnalogSignal::on_conv_threshold_changed(int index)
+{
+       SignalBase::ConversionType conv_type = base_->get_conversion_type();
+
+       // Note: index is set to -1 if the text in the combo box matches none of
+       // the entries in the combo box
+
+       if ((index == -1) && (conv_threshold_cb_->currentText().length() == 0))
+               return;
+
+       // The combo box entry with the custom value has user_data set to -1
+       const int user_data = conv_threshold_cb_->findText(
+                       conv_threshold_cb_->currentText());
+
+       const bool use_custom_thr = (index == -1) || (user_data == -1);
+
+       if (conv_type == SignalBase::A2LConversionByThreshold && use_custom_thr) {
+               // Not one of the preset values, try to parse the combo box text
+               // Note: Regex loosely based on
+               // https://txt2re.com/index-c++.php3?s=0.1V&1&-13
+               QString re1 = "([+-]?\\d*[\\.,]?\\d*)"; // Float value
+               QString re2 = "([a-zA-Z]*)"; // SI unit
+               QRegExp regex(re1 + re2);
+
+               const QString text = conv_threshold_cb_->currentText();
+               if (!regex.exactMatch(text))
+                       return;  // String doesn't match the regex
+
+               QStringList tokens = regex.capturedTexts();
+
+               // For now, we simply assume that the unit is volt without modifiers
+               const double thr = tokens.at(1).toDouble();
+
+               // Only restart the conversion if the threshold was updated.
+               // We're starting a delayed conversion because the user may still be
+               // typing and the UI would lag if we kept on restarting it immediately
+               if (base_->set_conversion_option("threshold_value", thr))
+                       base_->start_conversion(true);
+       }
+
+       if (conv_type == SignalBase::A2LConversionBySchmittTrigger && use_custom_thr) {
+               // Not one of the preset values, try to parse the combo box text
+               // Note: Regex loosely based on
+               // https://txt2re.com/index-c++.php3?s=0.1V/0.2V&2&14&-22&3&15
+               QString re1 = "([+-]?\\d*[\\.,]?\\d*)"; // Float value
+               QString re2 = "([a-zA-Z]*)"; // SI unit
+               QString re3 = "\\/"; // Forward slash, not captured
+               QString re4 = "([+-]?\\d*[\\.,]?\\d*)"; // Float value
+               QString re5 = "([a-zA-Z]*)"; // SI unit
+               QRegExp regex(re1 + re2 + re3 + re4 + re5);
+
+               const QString text = conv_threshold_cb_->currentText();
+               if (!regex.exactMatch(text))
+                       return;  // String doesn't match the regex
+
+               QStringList tokens = regex.capturedTexts();
+
+               // For now, we simply assume that the unit is volt without modifiers
+               const double low_thr = tokens.at(1).toDouble();
+               const double high_thr = tokens.at(3).toDouble();
+
+               // Only restart the conversion if one of the options was updated.
+               // We're starting a delayed conversion because the user may still be
+               // typing and the UI would lag if we kept on restarting it immediately
+               bool o1 = base_->set_conversion_option("threshold_value_low", low_thr);
+               bool o2 = base_->set_conversion_option("threshold_value_high", high_thr);
+               if (o1 || o2)
+                       base_->start_conversion(true);  // Start delayed conversion
        }
+
+       base_->set_conversion_preset((SignalBase::ConversionPreset)index);
+
+       // Immediately start the conversion if we're not using custom values
+       // (i.e. we're using one of the presets)
+       if (!use_custom_thr)
+               base_->start_conversion();
+}
+
+void AnalogSignal::on_delayed_conversion_starter()
+{
+       base_->start_conversion();
 }
 
 void AnalogSignal::on_display_type_changed(int index)
index a1b3eeefac0139e916921a167322404acb1c8ab3..fd724492c80e306dcabdf164fa3cd9b11f4789c2 100644 (file)
 #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_ANALOGSIGNAL_HPP
 #define PULSEVIEW_PV_VIEWS_TRACEVIEW_ANALOGSIGNAL_HPP
 
-#include "signal.hpp"
-
 #include <memory>
 
+#include <QColor>
 #include <QComboBox>
 #include <QSpinBox>
 
+#include <pv/views/trace/signal.hpp>
+
 using std::pair;
 using std::shared_ptr;
 
@@ -46,9 +47,17 @@ class AnalogSignal : public Signal
        Q_OBJECT
 
 private:
-       static const QColor SignalColours[4];
+       static const QPen AxisPen;
+       static const QColor SignalColors[4];
        static const QColor GridMajorColor, GridMinorColor;
-       static const QColor SamplingPointColour;
+       static const QColor SamplingPointColor;
+       static const QColor SamplingPointColorLo;
+       static const QColor SamplingPointColorNe;
+       static const QColor SamplingPointColorHi;
+       static const QColor ThresholdColor;
+       static const QColor ThresholdColorLo;
+       static const QColor ThresholdColorNe;
+       static const QColor ThresholdColorHi;
 
        static const int64_t TracePaintBlockSize;
        static const float EnvelopeThreshold;
@@ -66,8 +75,6 @@ private:
 public:
        AnalogSignal(pv::Session &session, shared_ptr<data::SignalBase> base);
 
-       virtual ~AnalogSignal() = default;
-
        shared_ptr<pv::data::SignalData> data() const;
 
        virtual void save_settings(QSettings &settings) const;
@@ -80,22 +87,6 @@ public:
         */
        pair<int, int> v_extents() const;
 
-       /**
-        * Returns the offset to show the drag handle.
-        */
-       int scale_handle_offset() const;
-
-       /**
-        * Handles the scale handle being dragged to an offset.
-        * @param offset the offset the scale handle was dragged to.
-        */
-       void scale_handle_dragged(int offset);
-
-       /**
-        * @copydoc pv::view::Signal::signal_scale_handle_drag_release()
-        */
-       void scale_handle_drag_release();
-
        /**
         * Paints the background layer of the signal with a QPainter
         * @param p the QPainter to paint into.
@@ -137,6 +128,9 @@ private:
                bool level, double samples_per_pixel, double pixels_offset,
                float x_offset, float y_offset);
 
+       shared_ptr<pv::data::AnalogSegment> get_analog_segment_to_paint() const;
+       shared_ptr<pv::data::LogicSegment> get_logic_segment_to_paint() const;
+
        /**
         * Computes the scale factor from the scale index and vdiv settings.
         */
@@ -144,42 +138,71 @@ private:
 
        void update_scale();
 
-       void update_conversion_type();
+       void update_conversion_widgets();
+
+       /**
+        * Determines the closest level change (i.e. edge) to a given sample, which
+        * is useful for e.g. the "snap to edge" functionality.
+        *
+        * @param sample_pos Sample to use
+        * @return The changes left and right of the given position
+        */
+       virtual vector<data::LogicSegment::EdgePair> get_nearest_level_changes(uint64_t sample_pos);
 
        void perform_autoranging(bool keep_divs, bool force_update);
 
+       void reset_pixel_values();
+       void process_next_sample_value(float x, float value);
+
 protected:
        void populate_popup_form(QWidget *parent, QFormLayout *form);
 
+       virtual void hover_point_changed(const QPoint &hp);
+
 private Q_SLOTS:
-       void on_samples_added();
+       virtual void on_setting_changed(const QString &key, const QVariant &value);
+
+       void on_min_max_changed(float min, float max);
 
        void on_pos_vdivs_changed(int vdivs);
        void on_neg_vdivs_changed(int vdivs);
+       void on_div_height_changed(int height);
 
        void on_resolution_changed(int index);
 
        void on_autoranging_changed(int state);
 
        void on_conversion_changed(int index);
+       void on_conv_threshold_changed(int index=-1);
+       void on_delayed_conversion_starter();
 
        void on_display_type_changed(int index);
 
 private:
-       QComboBox *resolution_cb_, *conversion_cb_, *display_type_cb_;
-       QSpinBox *pvdiv_sb_, *nvdiv_sb_;
+       QComboBox *resolution_cb_, *conversion_cb_, *conv_threshold_cb_,
+               *display_type_cb_;
+       QSpinBox *pvdiv_sb_, *nvdiv_sb_, *div_height_sb_;
 
        float scale_;
        int scale_index_;
-       int scale_index_drag_offset_;
 
        int div_height_;
        int pos_vdivs_, neg_vdivs_;  // divs per positive/negative side
-       float resolution_; // e.g. 10 for 10 V/div
+       float resolution_;  // e.g. 10 for 10 V/div
+
+       bool show_analog_minor_grid_;
+       QColor high_fill_color_;
+       bool show_sampling_points_, fill_high_areas_;
 
-       data::SignalBase::ConversionType conversion_type_;
        DisplayType display_type_;
        bool autoranging_;
+       int conversion_threshold_disp_mode_;
+
+       vector<float> value_at_pixel_pos_;
+       float value_at_hover_pos_;
+       float prev_value_at_pixel_;  // Only used during lookup table update
+       float min_value_at_pixel_, max_value_at_pixel_;  // Only used during lookup table update
+       int current_pixel_pos_;  // Only used during lookup table update
 };
 
 } // namespace trace
index 4fb4b65d9841deec2594422092997db66a386f01..7a375c7aad49410450c5f41f0d6d8c949e6bcd38 100644 (file)
 #include <cstdio>
 #include <limits>
 
-using std::abs; // Force usage of std::abs() instead of C's abs().
+using std::abs; // NOLINT. Force usage of std::abs() instead of C's abs().
 using std::shared_ptr;
 
 namespace pv {
 namespace views {
 namespace trace {
 
-const QColor Cursor::FillColour(52, 101, 164);
+const QColor Cursor::FillColor(52, 101, 164);
 
 Cursor::Cursor(View &view, double time) :
-       TimeMarker(view, FillColour, time)
+       TimeMarker(view, FillColor, time)
 {
 }
 
index ad91b05979e2ac02f0388cc1fe7af77694079f5f..c3960d6d39aa57d5d3bd8c6a02773ccc14eb65e2 100644 (file)
@@ -39,7 +39,7 @@ class Cursor : public TimeMarker
        Q_OBJECT
 
 public:
-       static const QColor FillColour;
+       static const QColor FillColor;
 
 public:
        /**
index 9a8744054bb0792fafded3281bf8686a7fe26c50..933eb8fff33e0c3aaf2ed255dc2a6e00bc5b8754 100644 (file)
  * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <algorithm>
+#include <cassert>
+
+#include <QColor>
+#include <QToolTip>
+
 #include "cursorpair.hpp"
 
+#include "pv/globalsettings.hpp"
 #include "pv/util.hpp"
 #include "ruler.hpp"
 #include "view.hpp"
 
-#include <algorithm>
-#include <cassert>
-
 using std::max;
 using std::make_pair;
 using std::min;
@@ -37,13 +41,25 @@ namespace views {
 namespace trace {
 
 const int CursorPair::DeltaPadding = 8;
-const QColor CursorPair::ViewportFillColour(220, 231, 243);
 
 CursorPair::CursorPair(View &view) :
        TimeItem(view),
        first_(new Cursor(view, 0.0)),
        second_(new Cursor(view, 1.0))
 {
+       GlobalSettings::add_change_handler(this);
+
+       GlobalSettings settings;
+       fill_color_ = QColor::fromRgba(settings.value(
+               GlobalSettings::Key_View_CursorFillColor).value<uint32_t>());
+
+       connect(&view_, SIGNAL(hover_point_changed(const QWidget*, QPoint)),
+               this, SLOT(on_hover_point_changed(const QWidget*, QPoint)));
+}
+
+CursorPair::~CursorPair()
+{
+       GlobalSettings::remove_change_handler(this);
 }
 
 bool CursorPair::enabled() const
@@ -73,9 +89,9 @@ float CursorPair::get_x() const
        return (first_->get_x() + second_->get_x()) / 2.0f;
 }
 
-QPoint CursorPair::point(const QRect &rect) const
+QPoint CursorPair::drag_point(const QRect &rect) const
 {
-       return first_->point(rect);
+       return first_->drag_point(rect);
 }
 
 pv::widgets::Popup* CursorPair::create_popup(QWidget *parent)
@@ -110,38 +126,41 @@ void CursorPair::paint_label(QPainter &p, const QRect &rect, bool hover)
        if (!enabled())
                return;
 
-       const QColor text_colour =
-               ViewItem::select_text_colour(Cursor::FillColour);
+       const QColor text_color = ViewItem::select_text_color(Cursor::FillColor);
+       p.setPen(text_color);
 
-       p.setPen(text_colour);
-       compute_text_size(p);
-       QRectF delta_rect(label_rect(rect));
+       QString text = format_string();
+       text_size_ = p.boundingRect(QRectF(), 0, text).size();
 
+       QRectF delta_rect(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;
-
-               if (selected()) {
-                       p.setBrush(Qt::transparent);
-                       p.setPen(highlight_pen());
-                       p.drawRoundedRect(delta_rect, radius, radius);
-               }
-
-               p.setBrush(hover ? Cursor::FillColour.lighter() :
-                       Cursor::FillColour);
-               p.setPen(Cursor::FillColour.darker());
+       QRectF text_rect(delta_rect.intersected(rect).adjusted(radius, 0, -radius, 0));
+
+       if (text_rect.width() < text_size_.width()) {
+               text = "...";
+               text_size_ = p.boundingRect(QRectF(), 0, text).size();
+               label_incomplete_ = true;
+       } else
+               label_incomplete_ = false;
+
+       if (selected()) {
+               p.setBrush(Qt::transparent);
+               p.setPen(highlight_pen());
                p.drawRoundedRect(delta_rect, radius, radius);
+       }
 
-               delta_rect.adjust(1, 1, -1, -1);
-               p.setPen(Cursor::FillColour.lighter());
-               p.drawRoundedRect(delta_rect, highlight_radius, highlight_radius);
+       p.setBrush(hover ? Cursor::FillColor.lighter() : Cursor::FillColor);
+       p.setPen(Cursor::FillColor.darker());
+       p.drawRoundedRect(delta_rect, radius, radius);
 
-               p.setPen(text_colour);
-               p.drawText(text_rect, Qt::AlignCenter | Qt::AlignVCenter,
-                       format_string());
-       }
+       delta_rect.adjust(1, 1, -1, -1);
+       p.setPen(Cursor::FillColor.lighter());
+       const int highlight_radius = delta_rect.height() / 2 - 2;
+       p.drawRoundedRect(delta_rect, highlight_radius, highlight_radius);
+       label_area_ = delta_rect;
+
+       p.setPen(text_color);
+       p.drawText(text_rect, Qt::AlignCenter | Qt::AlignVCenter, text);
 }
 
 void CursorPair::paint_back(QPainter &p, ViewItemPaintParams &pp)
@@ -150,13 +169,11 @@ void CursorPair::paint_back(QPainter &p, ViewItemPaintParams &pp)
                return;
 
        p.setPen(Qt::NoPen);
-       p.setBrush(QBrush(ViewportFillColour));
+       p.setBrush(fill_color_);
 
        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)pp.width());
+       const int l = (int)max(min(offsets.first, offsets.second), 0.0f);
+       const int r = (int)min(max(offsets.first, offsets.second), (float)pp.width());
 
        p.drawRect(l, pp.top(), r - l, pp.height());
 }
@@ -167,27 +184,39 @@ QString CursorPair::format_string()
        const pv::util::Timestamp diff = abs(second_->time() - first_->time());
 
        const QString s1 = Ruler::format_time_with_distance(
-               diff, diff, prefix, view_.time_unit(), view_.tick_precision(), false);
+               diff, diff, prefix, view_.time_unit(), 12, false);  /* Always use 12 precision digits */
        const QString s2 = util::format_time_si(
                1 / diff, pv::util::SIPrefix::unspecified, 4, "Hz", false);
 
-       return QString("%1 / %2").arg(s1).arg(s2);
+       return QString("%1 / %2").arg(s1s2);
 }
 
-void CursorPair::compute_text_size(QPainter &p)
+pair<float, float> CursorPair::get_cursor_offsets() const
 {
        assert(first_);
        assert(second_);
 
-       text_size_ = p.boundingRect(QRectF(), 0, format_string()).size();
+       return pair<float, float>(first_->get_x(), second_->get_x());
 }
 
-pair<float, float> CursorPair::get_cursor_offsets() const
+void CursorPair::on_setting_changed(const QString &key, const QVariant &value)
 {
-       assert(first_);
-       assert(second_);
+       if (key == GlobalSettings::Key_View_CursorFillColor)
+               fill_color_ = QColor::fromRgba(value.value<uint32_t>());
+}
 
-       return pair<float, float>(first_->get_x(), second_->get_x());
+void CursorPair::on_hover_point_changed(const QWidget* widget, const QPoint& hp)
+{
+       if (widget != view_.ruler())
+               return;
+
+       if (!label_incomplete_)
+               return;
+
+       if (label_area_.contains(hp))
+               QToolTip::showText(view_.mapToGlobal(hp), format_string());
+       else
+               QToolTip::hideText();  // TODO Will break other tooltips when there can be others
 }
 
 } // namespace trace
index 7a73e1980395d16b43c22420ed23bb802e6f0304..9d450df6b69af06e1dcc9a0830f036c9b18c3f23 100644 (file)
 #define PULSEVIEW_PV_VIEWS_TRACEVIEW_CURSORPAIR_HPP
 
 #include "cursor.hpp"
+#include "pv/globalsettings.hpp"
 
 #include <memory>
 
+#include <QColor>
 #include <QPainter>
+#include <QRect>
 
 using std::pair;
 using std::shared_ptr;
@@ -35,11 +38,14 @@ namespace pv {
 namespace views {
 namespace trace {
 
-class CursorPair : public TimeItem
+class View;
+
+class CursorPair : public TimeItem, public GlobalSettingsInterface
 {
+       Q_OBJECT
+
 private:
        static const int DeltaPadding;
-       static const QColor ViewportFillColour;
 
 public:
        /**
@@ -48,7 +54,8 @@ public:
         */
        CursorPair(View &view);
 
-public:
+       ~CursorPair();
+
        /**
         * Returns true if the item is visible and enabled.
         */
@@ -71,11 +78,10 @@ public:
 
        float get_x() const override;
 
-       QPoint point(const QRect &rect) const override;
+       QPoint drag_point(const QRect &rect) const override;
 
        pv::widgets::Popup* create_popup(QWidget *parent) override;
 
-public:
        QRectF label_rect(const QRectF &rect) const override;
 
        /**
@@ -98,14 +104,20 @@ public:
         */
        QString format_string();
 
-       void compute_text_size(QPainter &p);
-
        pair<float, float> get_cursor_offsets() const;
 
+       virtual void on_setting_changed(const QString &key, const QVariant &value) override;
+
+public Q_SLOTS:
+       void on_hover_point_changed(const QWidget* widget, const QPoint &hp);
+
 private:
        shared_ptr<Cursor> first_, second_;
+       QColor fill_color_;
 
        QSizeF text_size_;
+       QRectF label_area_;
+       bool label_incomplete_;
 };
 
 } // namespace trace
index 6ccd3fb87dc085f9c50b9baa109645ac68c9b213..9c7196bfb04f24e1c3322172ce023d12f12f4b36 100644 (file)
@@ -21,21 +21,24 @@ extern "C" {
 #include <libsigrokdecode/libsigrokdecode.h>
 }
 
+#include <limits>
 #include <mutex>
+#include <tuple>
 
 #include <extdef.h>
 
-#include <tuple>
-
 #include <boost/functional/hash.hpp>
 
 #include <QAction>
 #include <QApplication>
 #include <QComboBox>
+#include <QFileDialog>
 #include <QFormLayout>
 #include <QLabel>
 #include <QMenu>
+#include <QMessageBox>
 #include <QPushButton>
+#include <QTextStream>
 #include <QToolTip>
 
 #include "decodetrace.hpp"
@@ -43,87 +46,49 @@ extern "C" {
 #include "viewport.hpp"
 
 #include <pv/globalsettings.hpp>
+#include <pv/session.hpp>
+#include <pv/strnatcmp.hpp>
+#include <pv/data/decodesignal.hpp>
 #include <pv/data/decode/annotation.hpp>
 #include <pv/data/decode/decoder.hpp>
-#include <pv/data/decoderstack.hpp>
 #include <pv/data/logic.hpp>
 #include <pv/data/logicsegment.hpp>
-#include <pv/session.hpp>
-#include <pv/strnatcmp.hpp>
 #include <pv/widgets/decodergroupbox.hpp>
 #include <pv/widgets/decodermenu.hpp>
 
-using std::all_of;
-using std::list;
+using std::abs;
 using std::make_pair;
 using std::max;
-using std::make_pair;
-using std::map;
 using std::min;
+using std::numeric_limits;
 using std::out_of_range;
 using std::pair;
 using std::shared_ptr;
-using std::make_shared;
 using std::tie;
-using std::unordered_set;
 using std::vector;
 
+using pv::data::decode::Annotation;
+using pv::data::decode::Row;
+using pv::data::DecodeChannel;
+using pv::data::DecodeSignal;
+
 namespace pv {
 namespace views {
 namespace trace {
 
-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);
+#define DECODETRACE_COLOR_SATURATION (180) /* 0-255 */
+#define DECODETRACE_COLOR_VALUE (170) /* 0-255 */
+
+const QColor DecodeTrace::ErrorBgColor = QColor(0xEF, 0x29, 0x29);
+const QColor DecodeTrace::NoDecodeColor = QColor(0x88, 0x8A, 0x85);
 
 const int DecodeTrace::ArrowSize = 4;
 const double DecodeTrace::EndCapWidth = 5;
 const int DecodeTrace::RowTitleMargin = 10;
 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)
-};
+const int DecodeTrace::MaxTraceUpdateRate = 1; // No more than 1 Hz
 
 DecodeTrace::DecodeTrace(pv::Session &session,
        shared_ptr<data::SignalBase> signalbase, int index) :
@@ -134,21 +99,41 @@ DecodeTrace::DecodeTrace(pv::Session &session,
        delete_mapper_(this),
        show_hide_mapper_(this)
 {
-       shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
+       decode_signal_ = dynamic_pointer_cast<data::DecodeSignal>(base_);
 
        // Determine shortest string we want to see displayed in full
        QFontMetrics m(QApplication::font());
        min_useful_label_width_ = m.width("XX"); // e.g. two hex characters
 
-       base_->set_name(QString::fromUtf8(decoder_stack->stack().front()->decoder()->name));
-       base_->set_colour(DecodeColours[index % countof(DecodeColours)]);
+       // For the base color, we want to start at a very different color for
+       // every decoder stack, so multiply the index with a number that is
+       // rather close to 180 degrees of the color circle but not a dividend of 360
+       // Note: The offset equals the color of the first annotation
+       QColor color;
+       const int h = (120 + 160 * index) % 360;
+       const int s = DECODETRACE_COLOR_SATURATION;
+       const int v = DECODETRACE_COLOR_VALUE;
+       color.setHsv(h, s, v);
+       base_->set_color(color);
+
+       connect(decode_signal_.get(), SIGNAL(new_annotations()),
+               this, SLOT(on_new_annotations()));
+       connect(decode_signal_.get(), SIGNAL(decode_reset()),
+               this, SLOT(on_decode_reset()));
+       connect(decode_signal_.get(), SIGNAL(decode_finished()),
+               this, SLOT(on_decode_finished()));
+       connect(decode_signal_.get(), SIGNAL(channels_updated()),
+               this, SLOT(on_channels_updated()));
 
-       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)));
+
+       connect(&delayed_trace_updater_, SIGNAL(timeout()),
+               this, SLOT(on_delayed_trace_update()));
+       delayed_trace_updater_.setSingleShot(true);
+       delayed_trace_updater_.setInterval(1000 / MaxTraceUpdateRate);
 }
 
 bool DecodeTrace::enabled() const
@@ -179,32 +164,24 @@ void DecodeTrace::paint_back(QPainter &p, ViewItemPaintParams &pp)
 
 void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp)
 {
-       using namespace pv::data::decode;
-
-       shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
-
        const int text_height = ViewItemPaintParams::text_height();
        row_height_ = (text_height * 6) / 4;
        const int annotation_height = (text_height * 5) / 4;
 
-       assert(decoder_stack);
-       const QString err = decoder_stack->error_message();
-       if (!err.isEmpty()) {
-               draw_unresolved_period(
-                       p, annotation_height, pp.left(), pp.right());
-               draw_error(p, err, pp);
-               return;
-       }
-
        // Set default pen to allow for text width calculation
        p.setPen(Qt::black);
 
        // Iterate through the rows
        int y = get_visual_y();
-       pair<uint64_t, uint64_t> sample_range = get_sample_range(
-               pp.left(), pp.right());
+       pair<uint64_t, uint64_t> sample_range = get_view_sample_range(pp.left(), pp.right());
 
-       const vector<Row> rows(decoder_stack->get_visible_rows());
+       // Just because the view says we see a certain sample range it
+       // doesn't mean we have this many decoded samples, too, so crop
+       // the range to what has been decoded already
+       sample_range.second = min((int64_t)sample_range.second,
+               decode_signal_->get_decoded_sample_count(current_segment_, false));
+
+       const vector<Row> rows = decode_signal_->visible_rows();
 
        visible_rows_.clear();
        for (const Row& row : rows) {
@@ -212,47 +189,41 @@ void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp)
                int row_title_width;
                try {
                        row_title_width = row_title_widths_.at(row);
-               } catch (out_of_range) {
+               } catch (out_of_range&) {
                        const int w = p.boundingRect(QRectF(), 0, row.title()).width() +
                                RowTitleMargin;
                        row_title_widths_[row] = w;
                        row_title_width = w;
                }
 
-               // Determine the row's color
-               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,
-                       sample_range.first, sample_range.second);
+               decode_signal_->get_annotation_subset(annotations, row,
+                       current_segment_, sample_range.first, sample_range.second);
                if (!annotations.empty()) {
                        draw_annotations(annotations, p, annotation_height, pp, y,
-                               base_colour, row_title_width);
-
+                               get_row_color(row.index()), row_title_width);
                        y += row_height_;
-
                        visible_rows_.push_back(row);
                }
        }
 
-       // Draw the hatching
        draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
 
-       if ((int)visible_rows_.size() > max_visible_rows_)
+       if ((int)visible_rows_.size() > max_visible_rows_) {
+               max_visible_rows_ = (int)visible_rows_.size();
+
+               // Call order is important, otherwise the lazy event handler won't work
                owner_->extents_changed(false, true);
+               owner_->row_item_appearance_changed(false, true);
+       }
 
-       // Update the maximum row count if needed
-       max_visible_rows_ = max(max_visible_rows_, (int)visible_rows_.size());
+       const QString err = decode_signal_->error_message();
+       if (!err.isEmpty())
+               draw_error(p, err, pp);
 }
 
 void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp)
 {
-       using namespace pv::data::decode;
-
        assert(row_height_);
 
        for (size_t i = 0; i < visible_rows_.size(); i++) {
@@ -287,27 +258,27 @@ void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp)
                p.setPen(QApplication::palette().color(QPalette::WindowText));
                p.drawText(r, f, h);
        }
+
+       if (show_hover_marker_)
+               paint_hover_marker(p);
 }
 
 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
 {
        using pv::data::decode::Decoder;
 
-       shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
-
        assert(form);
-       assert(parent);
-       assert(decoder_stack);
 
        // Add the standard options
        Trace::populate_popup_form(parent, form);
 
        // Add the decoder options
        bindings_.clear();
-       channel_selectors_.clear();
+       channel_id_map_.clear();
+       init_state_map_.clear();
        decoder_forms_.clear();
 
-       const list< shared_ptr<Decoder> >& stack = decoder_stack->stack();
+       const vector< shared_ptr<Decoder> > &stack = decode_signal_->decoder_stack();
 
        if (stack.empty()) {
                QLabel *const l = new QLabel(
@@ -341,9 +312,9 @@ void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
        form->addRow(stack_button_box);
 }
 
-QMenu* DecodeTrace::create_context_menu(QWidget *parent)
+QMenu* DecodeTrace::create_header_context_menu(QWidget *parent)
 {
-       QMenu *const menu = Trace::create_context_menu(parent);
+       QMenu *const menu = Trace::create_header_context_menu(parent);
 
        menu->addSeparator();
 
@@ -355,14 +326,126 @@ QMenu* DecodeTrace::create_context_menu(QWidget *parent)
        return menu;
 }
 
+QMenu* DecodeTrace::create_view_context_menu(QWidget *parent, QPoint &click_pos)
+{
+       // Get entries from default menu before adding our own
+       QMenu *const menu = new QMenu(parent);
+
+       QMenu* default_menu = Trace::create_view_context_menu(parent, click_pos);
+       if (default_menu) {
+               for (QAction *action : default_menu->actions()) {  // clazy:exclude=range-loop
+                       menu->addAction(action);
+                       if (action->parent() == default_menu)
+                               action->setParent(menu);
+               }
+               delete default_menu;
+
+               // Add separator if needed
+               if (menu->actions().length() > 0)
+                       menu->addSeparator();
+       }
+
+       try {
+               selected_row_ = &visible_rows_[get_row_at_point(click_pos)];
+       } catch (out_of_range&) {
+               selected_row_ = nullptr;
+       }
+
+       // Default sample range is "from here"
+       const pair<uint64_t, uint64_t> sample_range =
+               get_view_sample_range(click_pos.x(), click_pos.x() + 1);
+       selected_sample_range_ = make_pair(sample_range.first, numeric_limits<uint64_t>::max());
+
+       if (decode_signal_->is_paused()) {
+               QAction *const resume =
+                       new QAction(tr("Resume decoding"), this);
+               resume->setIcon(QIcon::fromTheme("media-playback-start",
+                       QIcon(":/icons/media-playback-start.png")));
+               connect(resume, SIGNAL(triggered()), this, SLOT(on_pause_decode()));
+               menu->addAction(resume);
+       } else {
+               QAction *const pause =
+                       new QAction(tr("Pause decoding"), this);
+               pause->setIcon(QIcon::fromTheme("media-playback-pause",
+                       QIcon(":/icons/media-playback-pause.png")));
+               connect(pause, SIGNAL(triggered()), this, SLOT(on_pause_decode()));
+               menu->addAction(pause);
+       }
+
+       menu->addSeparator();
+
+       QAction *const export_all_rows =
+               new QAction(tr("Export all annotations"), this);
+       export_all_rows->setIcon(QIcon::fromTheme("document-save-as",
+               QIcon(":/icons/document-save-as.png")));
+       connect(export_all_rows, SIGNAL(triggered()), this, SLOT(on_export_all_rows()));
+       menu->addAction(export_all_rows);
+
+       QAction *const export_row =
+               new QAction(tr("Export all annotations for this row"), this);
+       export_row->setIcon(QIcon::fromTheme("document-save-as",
+               QIcon(":/icons/document-save-as.png")));
+       connect(export_row, SIGNAL(triggered()), this, SLOT(on_export_row()));
+       menu->addAction(export_row);
+
+       menu->addSeparator();
+
+       QAction *const export_all_rows_from_here =
+               new QAction(tr("Export all annotations, starting here"), this);
+       export_all_rows_from_here->setIcon(QIcon::fromTheme("document-save-as",
+               QIcon(":/icons/document-save-as.png")));
+       connect(export_all_rows_from_here, SIGNAL(triggered()), this, SLOT(on_export_all_rows_from_here()));
+       menu->addAction(export_all_rows_from_here);
+
+       QAction *const export_row_from_here =
+               new QAction(tr("Export annotations for this row, starting here"), this);
+       export_row_from_here->setIcon(QIcon::fromTheme("document-save-as",
+               QIcon(":/icons/document-save-as.png")));
+       connect(export_row_from_here, SIGNAL(triggered()), this, SLOT(on_export_row_from_here()));
+       menu->addAction(export_row_from_here);
+
+       menu->addSeparator();
+
+       QAction *const export_all_rows_with_cursor =
+               new QAction(tr("Export all annotations within cursor range"), this);
+       export_all_rows_with_cursor->setIcon(QIcon::fromTheme("document-save-as",
+               QIcon(":/icons/document-save-as.png")));
+       connect(export_all_rows_with_cursor, SIGNAL(triggered()), this, SLOT(on_export_all_rows_with_cursor()));
+       menu->addAction(export_all_rows_with_cursor);
+
+       QAction *const export_row_with_cursor =
+               new QAction(tr("Export annotations for this row within cursor range"), this);
+       export_row_with_cursor->setIcon(QIcon::fromTheme("document-save-as",
+               QIcon(":/icons/document-save-as.png")));
+       connect(export_row_with_cursor, SIGNAL(triggered()), this, SLOT(on_export_row_with_cursor()));
+       menu->addAction(export_row_with_cursor);
+
+       const View *view = owner_->view();
+       assert(view);
+
+       if (!view->cursors()->enabled()) {
+               export_all_rows_with_cursor->setEnabled(false);
+               export_row_with_cursor->setEnabled(false);
+       }
+
+       return menu;
+}
+
 void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
                QPainter &p, int h, const ViewItemPaintParams &pp, int y,
-               size_t base_colour, int row_title_width)
+               QColor row_color, int row_title_width)
 {
        using namespace pv::data::decode;
 
-       vector<Annotation> a_block;
-       int p_end = INT_MIN;
+       Annotation::Class block_class = 0;
+       bool block_class_uniform = true;
+       qreal block_start = 0;
+       int block_ann_count = 0;
+
+       const Annotation *prev_ann;
+       qreal prev_end = INT_MIN;
+
+       qreal a_end;
 
        double samples_per_pixel, pixels_offset;
        tie(pixels_offset, samples_per_pixel) =
@@ -377,18 +460,21 @@ void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotati
        // Gather all annotations that form a visual "block" and draw them as such
        for (const Annotation &a : annotations) {
 
-               const int a_start = a.start_sample() / samples_per_pixel - pixels_offset;
-               const int a_end = a.end_sample() / samples_per_pixel - pixels_offset;
-               const int a_width = a_end - a_start;
+               const qreal abs_a_start = a.start_sample() / samples_per_pixel;
+               const qreal abs_a_end   = a.end_sample() / samples_per_pixel;
 
-               const int delta = a_end - p_end;
+               const qreal a_start = abs_a_start - pixels_offset;
+               a_end = abs_a_end - pixels_offset;
+
+               const qreal a_width = a_end - a_start;
+               const qreal delta = a_end - prev_end;
 
                bool a_is_separate = false;
 
                // Annotation wider than the threshold for a useful label width?
                if (a_width >= min_useful_label_width_) {
                        for (const QString &ann_text : a.annotations()) {
-                               const int w = p.boundingRect(QRectF(), 0, ann_text).width();
+                               const qreal w = p.boundingRect(QRectF(), 0, ann_text).width();
                                // Annotation wide enough to fit a label? Don't put it in a block then
                                if (w <= a_width) {
                                        a_is_separate = true;
@@ -400,38 +486,49 @@ void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotati
                // Were the previous and this annotation more than a pixel apart?
                if ((abs(delta) > 1) || a_is_separate) {
                        // Block was broken, draw annotations that form the current block
-                       if (a_block.size() == 1) {
-                               draw_annotation(a_block.front(), p, h, pp, y, base_colour,
+                       if (block_ann_count == 1)
+                               draw_annotation(*prev_ann, p, h, pp, y, row_color,
                                        row_title_width);
-                       }
-                       else
-                               draw_annotation_block(a_block, p, h, y, base_colour);
+                       else if (block_ann_count > 0)
+                               draw_annotation_block(block_start, prev_end, block_class,
+                                       block_class_uniform, p, h, y, row_color);
 
-                       a_block.clear();
+                       block_ann_count = 0;
                }
 
                if (a_is_separate) {
-                       draw_annotation(a, p, h, pp, y, base_colour, row_title_width);
+                       draw_annotation(a, p, h, pp, y, row_color, row_title_width);
                        // Next annotation must start a new block. delta will be > 1
-                       // because we set p_end to INT_MIN but that's okay since
-                       // a_block will be empty, so nothing will be drawn
-                       p_end = INT_MIN;
+                       // because we set prev_end to INT_MIN but that's okay since
+                       // block_ann_count will be 0 and nothing will be drawn
+                       prev_end = INT_MIN;
+                       block_ann_count = 0;
                } else {
-                       a_block.push_back(a);
-                       p_end = a_end;
+                       prev_end = a_end;
+                       prev_ann = &a;
+
+                       if (block_ann_count == 0) {
+                               block_start = a_start;
+                               block_class = a.ann_class();
+                               block_class_uniform = true;
+                       } else
+                               if (a.ann_class() != block_class)
+                                       block_class_uniform = false;
+
+                       block_ann_count++;
                }
        }
 
-       if (a_block.size() == 1)
-               draw_annotation(a_block.front(), p, h, pp, y, base_colour,
-                       row_title_width);
-       else
-               draw_annotation_block(a_block, p, h, y, base_colour);
+       if (block_ann_count == 1)
+               draw_annotation(*prev_ann, p, h, pp, y, row_color, row_title_width);
+       else if (block_ann_count > 0)
+               draw_annotation_block(block_start, prev_end, block_class,
+                       block_class_uniform, p, h, y, row_color);
 }
 
 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
        QPainter &p, int h, const ViewItemPaintParams &pp, int y,
-       size_t base_colour, int row_title_width) const
+       QColor row_color, int row_title_width) const
 {
        double samples_per_pixel, pixels_offset;
        tie(pixels_offset, samples_per_pixel) =
@@ -441,9 +538,9 @@ void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
                pixels_offset;
        const double end = a.end_sample() / samples_per_pixel - pixels_offset;
 
-       const size_t colour = (base_colour + a.format()) % countof(Colours);
-       p.setPen(OutlineColours[colour]);
-       p.setBrush(Colours[colour]);
+       QColor color = get_annotation_color(row_color, a.ann_class());
+       p.setPen(color.darker());
+       p.setBrush(color);
 
        if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
                return;
@@ -454,37 +551,13 @@ void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
                draw_range(a, p, h, start, end, y, pp, row_title_width);
 }
 
-void DecodeTrace::draw_annotation_block(
-       vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
-       int y, size_t base_colour) const
+void DecodeTrace::draw_annotation_block(qreal start, qreal end,
+       Annotation::Class ann_class, bool use_ann_format, QPainter &p, int h,
+       int y, QColor row_color) const
 {
-       using namespace pv::data::decode;
-
-       if (annotations.empty())
-               return;
-
-       double samples_per_pixel, pixels_offset;
-       tie(pixels_offset, samples_per_pixel) =
-               get_pixels_offset_samples_per_pixel();
-
-       const double start = annotations.front().start_sample() /
-               samples_per_pixel - pixels_offset;
-       const double end = annotations.back().end_sample() /
-               samples_per_pixel - pixels_offset;
-
        const double top = y + .5 - h / 2;
        const double bottom = y + .5 + h / 2;
 
-       const size_t colour = (base_colour + annotations.front().format()) %
-               countof(Colours);
-
-       // Check if all annotations are of the same type (i.e. we can use one color)
-       // or if we should use a neutral color (i.e. gray)
-       const int format = annotations.front().format();
-       const bool single_format = all_of(
-               annotations.begin(), annotations.end(),
-               [&](const Annotation &a) { return a.format() == format; });
-
        const QRectF rect(start, top, end - start, bottom - top);
        const int r = h / 4;
 
@@ -492,18 +565,27 @@ void DecodeTrace::draw_annotation_block(
        p.setBrush(Qt::white);
        p.drawRoundedRect(rect, r, r);
 
-       p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
-       p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
-               Qt::Dense4Pattern));
+       // If all annotations in this block are of the same type, we can use the
+       // one format that all of these annotations have. Otherwise, we should use
+       // a neutral color (i.e. gray)
+       if (use_ann_format) {
+               const QColor color = get_annotation_color(row_color, ann_class);
+               p.setPen(color.darker());
+               p.setBrush(QBrush(color, Qt::Dense4Pattern));
+       } else {
+               p.setPen(Qt::gray);
+               p.setBrush(QBrush(Qt::gray, Qt::Dense4Pattern));
+       }
+
        p.drawRoundedRect(rect, r, r);
 }
 
 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
-       int h, double x, int y) const
+       int h, qreal 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(),
+       const qreal w = min((qreal)p.boundingRect(QRectF(), 0, text).width(),
                0.0) + h;
        const QRectF rect(x - w / 2, y - h / 2, w, h);
 
@@ -514,11 +596,11 @@ void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &
 }
 
 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
-       int h, double start, double end, int y, const ViewItemPaintParams &pp,
+       int h, qreal start, qreal end, int y, const ViewItemPaintParams &pp,
        int row_title_width) const
 {
-       const double top = y + .5 - h / 2;
-       const double bottom = y + .5 + h / 2;
+       const qreal top = y + .5 - h / 2;
+       const qreal bottom = y + .5 + h / 2;
        const vector<QString> annotations = a.annotations();
 
        // If the two ends are within 1 pixel, draw a vertical line
@@ -527,7 +609,7 @@ void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
                return;
        }
 
-       const double cap_width = min((end - start) / 4, EndCapWidth);
+       const qreal cap_width = min((end - start) / 4, EndCapWidth);
 
        QPointF pts[] = {
                QPointF(start, y + .5f),
@@ -579,87 +661,60 @@ void DecodeTrace::draw_error(QPainter &p, const QString &message,
 {
        const int y = get_visual_y();
 
-       p.setPen(ErrorBgColour.darker());
-       p.setBrush(ErrorBgColour);
+       double samples_per_pixel, pixels_offset;
+       tie(pixels_offset, samples_per_pixel) = get_pixels_offset_samples_per_pixel();
 
-       const QRectF bounding_rect =
-               QRectF(pp.width(), INT_MIN / 2 + y, pp.width(), INT_MAX);
-       const QRectF text_rect = p.boundingRect(bounding_rect,
-               Qt::AlignCenter, message);
-       const float r = text_rect.height() / 4;
+       p.setPen(ErrorBgColor.darker());
+       p.setBrush(ErrorBgColor);
 
-       p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
-               Qt::AbsoluteSize);
+       const QRectF bounding_rect = QRectF(pp.left(), INT_MIN / 2 + y, pp.right(), INT_MAX);
+
+       const QRectF text_rect = p.boundingRect(bounding_rect, Qt::AlignCenter, message);
+       const qreal r = text_rect.height() / 4;
+
+       p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r, Qt::AbsoluteSize);
 
        p.setPen(Qt::black);
        p.drawText(text_rect, message);
 }
 
-void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
-       int right) const
+void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left, int right) const
 {
        using namespace pv::data;
        using pv::data::decode::Decoder;
 
        double samples_per_pixel, pixels_offset;
 
-       shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
-
-       assert(decoder_stack);
-
-       shared_ptr<Logic> data;
-       shared_ptr<data::SignalBase> signalbase;
-
-       const list< shared_ptr<Decoder> > &stack = decoder_stack->stack();
-
-       // 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/segment
-       for (const shared_ptr<Decoder> &dec : stack)
-               if (dec && !dec->channels().empty() &&
-                       ((signalbase = (*dec->channels().begin()).second)) &&
-                       ((data = signalbase->logic_data())))
-                       break;
-
-       if (!data || data->logic_segments().empty())
-               return;
-
-       const shared_ptr<LogicSegment> segment = data->logic_segments().front();
-       assert(segment);
-       const int64_t sample_count = (int64_t)segment->get_sample_count();
+       const int64_t sample_count = decode_signal_->get_working_sample_count(current_segment_);
        if (sample_count == 0)
                return;
 
-       const int64_t samples_decoded = decoder_stack->samples_decoded();
+       const int64_t samples_decoded = decode_signal_->get_decoded_sample_count(current_segment_, true);
        if (sample_count == samples_decoded)
                return;
 
        const int y = get_visual_y();
 
-       tie(pixels_offset, samples_per_pixel) =
-               get_pixels_offset_samples_per_pixel();
+       tie(pixels_offset, samples_per_pixel) = get_pixels_offset_samples_per_pixel();
 
        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);
+       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.setPen(NoDecodeColor);
+       p.setBrush(QBrush(NoDecodeColor, Qt::Dense6Pattern));
        p.drawRect(no_decode_rect);
 }
 
 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
 {
-       shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
-
        assert(owner_);
-       assert(decoder_stack);
 
        const View *view = owner_->view();
        assert(view);
@@ -668,9 +723,9 @@ pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
        assert(scale > 0);
 
        const double pixels_offset =
-               ((view->offset() - decoder_stack->start_time()) / scale).convert_to<double>();
+               ((view->offset() - decode_signal_->start_time()) / scale).convert_to<double>();
 
-       double samplerate = decoder_stack->samplerate();
+       double samplerate = decode_signal_->samplerate();
 
        // Show sample rate as 1Hz when it is unknown
        if (samplerate == 0.0)
@@ -679,7 +734,7 @@ pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
        return make_pair(pixels_offset, samplerate * scale);
 }
 
-pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
+pair<uint64_t, uint64_t> DecodeTrace::get_view_sample_range(
        int x_start, int x_end) const
 {
        double samples_per_pixel, pixels_offset;
@@ -694,6 +749,34 @@ pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
        return make_pair(start, end);
 }
 
+QColor DecodeTrace::get_row_color(int row_index) const
+{
+       // For each row color, use the base color hue and add an offset that's
+       // not a dividend of 360
+
+       QColor color;
+       const int h = (base_->color().toHsv().hue() + 20 * row_index) % 360;
+       const int s = DECODETRACE_COLOR_SATURATION;
+       const int v = DECODETRACE_COLOR_VALUE;
+       color.setHsl(h, s, v);
+
+       return color;
+}
+
+QColor DecodeTrace::get_annotation_color(QColor row_color, int annotation_index) const
+{
+       // For each row color, use the base color hue and add an offset that's
+       // not a dividend of 360 and not a multiple of the row offset
+
+       QColor color(row_color);
+       const int h = (color.toHsv().hue() + 55 * annotation_index) % 360;
+       const int s = DECODETRACE_COLOR_SATURATION;
+       const int v = DECODETRACE_COLOR_VALUE;
+       color.setHsl(h, s, v);
+
+       return color;
+}
+
 int DecodeTrace::get_row_at_point(const QPoint &point)
 {
        if (!row_height_)
@@ -721,31 +804,34 @@ const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
                return QString();
 
        const pair<uint64_t, uint64_t> sample_range =
-               get_sample_range(point.x(), point.x() + 1);
+               get_view_sample_range(point.x(), point.x() + 1);
        const int row = get_row_at_point(point);
        if (row < 0)
                return QString();
 
-       vector<pv::data::decode::Annotation> annotations;
-
-       shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
+       vector<Annotation> annotations;
 
-       assert(decoder_stack);
-       decoder_stack->get_annotation_subset(annotations, visible_rows_[row],
-               sample_range.first, sample_range.second);
+       decode_signal_->get_annotation_subset(annotations, visible_rows_[row],
+               current_segment_, sample_range.first, sample_range.second);
 
        return (annotations.empty()) ?
                QString() : annotations[0].annotations().front();
 }
 
-void DecodeTrace::hover_point_changed()
+void DecodeTrace::hover_point_changed(const QPoint &hp)
 {
+       Trace::hover_point_changed(hp);
+
        assert(owner_);
 
        const View *const view = owner_->view();
        assert(view);
 
-       QPoint hp = view->hover_point();
+       if (hp.x() == 0) {
+               QToolTip::hideText();
+               return;
+       }
+
        QString ann = get_annotation_at_point(hp);
 
        assert(view);
@@ -768,20 +854,20 @@ void DecodeTrace::hover_point_changed()
        // If it did, the tool tip would constantly hide and re-appear.
        // We also push it up by one row so that it appears above the
        // decode trace, not below.
-       hp.setX(hp.x() - (text_size.width() / 2) - padding);
+       QPoint p = hp;
+       p.setX(hp.x() - (text_size.width() / 2) - padding);
 
-       hp.setY(get_visual_y() - (row_height_ / 2) +
+       p.setY(get_visual_y() - (row_height_ / 2) +
                (hover_row * row_height_) -
                row_height_ - text_size.height() - padding);
 
-       QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
+       QToolTip::showText(view->viewport()->mapToGlobal(p), ann);
 }
 
 void DecodeTrace::create_decoder_form(int index,
        shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
        QFormLayout *form)
 {
-       const GSList *l;
        GlobalSettings settings;
 
        assert(dec);
@@ -810,67 +896,40 @@ void DecodeTrace::create_decoder_form(int index,
        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_channel_selector(parent, dec, pdch);
-               QComboBox *const combo_initial_pin = create_channel_selector_initial_pin(parent, dec, pdch);
-
-               connect(combo, SIGNAL(currentIndexChanged(int)),
-                       this, SLOT(on_channel_selected(int)));
-               connect(combo_initial_pin, SIGNAL(currentIndexChanged(int)),
-                       this, SLOT(on_initial_pin_selected(int)));
-
-               QHBoxLayout *const hlayout = new QHBoxLayout;
-               hlayout->addWidget(combo);
-               hlayout->addWidget(combo_initial_pin);
-
-               if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool())
-                       combo_initial_pin->hide();
+       const vector<DecodeChannel> channels = decode_signal_->get_channels();
 
-               decoder_form->addRow(tr("<b>%1</b> (%2) *")
-                       .arg(QString::fromUtf8(pdch->name),
-                            QString::fromUtf8(pdch->desc)), hlayout);
+       // Add the channels
+       for (const DecodeChannel& ch : channels) {
+               // Ignore channels not part of the decoder we create the form for
+               if (ch.decoder_ != dec)
+                       continue;
 
-               const ChannelSelector s = {combo, combo_initial_pin, dec, pdch};
-               channel_selectors_.push_back(s);
-       }
+               QComboBox *const combo = create_channel_selector(parent, &ch);
+               QComboBox *const combo_init_state = create_channel_selector_init_state(parent, &ch);
 
-       // 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_channel_selector(parent, dec, pdch);
-               QComboBox *const combo_initial_pin = create_channel_selector_initial_pin(parent, dec, pdch);
+               channel_id_map_[combo] = ch.id;
+               init_state_map_[combo_init_state] = ch.id;
 
                connect(combo, SIGNAL(currentIndexChanged(int)),
                        this, SLOT(on_channel_selected(int)));
-               connect(combo_initial_pin, SIGNAL(currentIndexChanged(int)),
-                       this, SLOT(on_initial_pin_selected(int)));
+               connect(combo_init_state, SIGNAL(currentIndexChanged(int)),
+                       this, SLOT(on_init_state_changed(int)));
 
                QHBoxLayout *const hlayout = new QHBoxLayout;
                hlayout->addWidget(combo);
-               hlayout->addWidget(combo_initial_pin);
+               hlayout->addWidget(combo_init_state);
 
                if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool())
-                       combo_initial_pin->hide();
-
-               decoder_form->addRow(tr("<b>%1</b> (%2)")
-                       .arg(QString::fromUtf8(pdch->name),
-                            QString::fromUtf8(pdch->desc)), hlayout);
+                       combo_init_state->hide();
 
-               const ChannelSelector s = {combo, combo_initial_pin, dec, pdch};
-               channel_selectors_.push_back(s);
+               const QString required_flag = ch.is_optional ? QString() : QString("*");
+               decoder_form->addRow(tr("<b>%1</b> (%2) %3")
+                       .arg(ch.name, ch.desc, required_flag), hlayout);
        }
 
-       shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
-
        // Add the options
        shared_ptr<binding::Decoder> binding(
-               new binding::Decoder(decoder_stack, dec));
+               new binding::Decoder(decode_signal_, dec));
        binding->add_properties_to_form(decoder_form, true);
 
        bindings_.push_back(binding);
@@ -879,14 +938,11 @@ void DecodeTrace::create_decoder_form(int index,
        decoder_forms_.push_back(group);
 }
 
-QComboBox* DecodeTrace::create_channel_selector(
-       QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
-       const srd_channel *const pdch)
+QComboBox* DecodeTrace::create_channel_selector(QWidget *parent, const DecodeChannel *ch)
 {
-       assert(dec);
-
        const auto sigs(session_.signalbases());
 
+       // Sort signals in natural order
        vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
        sort(sig_list.begin(), sig_list.end(),
                [](const shared_ptr<data::SignalBase> &a,
@@ -894,13 +950,11 @@ QComboBox* DecodeTrace::create_channel_selector(
                        return strnatcasecmp(a->name().toStdString(),
                                b->name().toStdString()) < 0; });
 
-       const auto channel_iter = dec->channels().find(pdch);
-
        QComboBox *selector = new QComboBox(parent);
 
        selector->addItem("-", qVariantFromValue((void*)nullptr));
 
-       if (channel_iter == dec->channels().end())
+       if (!ch->assigned_signal)
                selector->setCurrentIndex(0);
 
        for (const shared_ptr<data::SignalBase> &b : sig_list) {
@@ -909,18 +963,16 @@ QComboBox* DecodeTrace::create_channel_selector(
                        selector->addItem(b->name(),
                                qVariantFromValue((void*)b.get()));
 
-                       if (channel_iter != dec->channels().end() &&
-                               (*channel_iter).second == b)
-                               selector->setCurrentIndex(
-                                       selector->count() - 1);
+                       if (ch->assigned_signal == b.get())
+                               selector->setCurrentIndex(selector->count() - 1);
                }
        }
 
        return selector;
 }
 
-QComboBox* DecodeTrace::create_channel_selector_initial_pin(QWidget *parent,
-       const shared_ptr<data::decode::Decoder> &dec, const srd_channel *const pdch)
+QComboBox* DecodeTrace::create_channel_selector_init_state(QWidget *parent,
+       const DecodeChannel *ch)
 {
        QComboBox *selector = new QComboBox(parent);
 
@@ -928,69 +980,104 @@ QComboBox* DecodeTrace::create_channel_selector_initial_pin(QWidget *parent,
        selector->addItem("1", qVariantFromValue((int)SRD_INITIAL_PIN_HIGH));
        selector->addItem("X", qVariantFromValue((int)SRD_INITIAL_PIN_SAME_AS_SAMPLE0));
 
-       // Default to index 2 (SRD_INITIAL_PIN_SAME_AS_SAMPLE0).
-       const int idx = (!dec->initial_pins()) ? 2 : dec->initial_pins()->data[pdch->order];
-       selector->setCurrentIndex(idx);
+       selector->setCurrentIndex(ch->initial_pin_state);
 
        selector->setToolTip("Initial (assumed) pin value before the first sample");
 
        return selector;
 }
 
-void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
+void DecodeTrace::export_annotations(vector<Annotation> *annotations) const
 {
-       assert(dec);
+       using namespace pv::data::decode;
+
+       GlobalSettings settings;
+       const QString dir = settings.value("MainWindow/SaveDirectory").toString();
 
-       map<const srd_channel*, shared_ptr<data::SignalBase> > channel_map;
+       const QString file_name = QFileDialog::getSaveFileName(
+               owner_->view(), tr("Export annotations"), dir, tr("Text Files (*.txt);;All Files (*)"));
 
-       const unordered_set< shared_ptr<data::SignalBase> >
-               sigs(session_.signalbases());
+       if (file_name.isEmpty())
+               return;
 
-       GArray *const initial_pins = g_array_sized_new(FALSE, TRUE,
-               sizeof(uint8_t), channel_selectors_.size());
-       g_array_set_size(initial_pins, channel_selectors_.size());
+       QString format = settings.value(GlobalSettings::Key_Dec_ExportFormat).toString();
+       const QString quote = format.contains("%q") ? "\"" : "";
+       format = format.remove("%q");
 
-       for (const ChannelSelector &s : channel_selectors_) {
-               if (s.decoder_ != dec)
-                       break;
+       QFile file(file_name);
+       if (file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
+               QTextStream out_stream(&file);
 
-               const data::SignalBase *const selection =
-                       (data::SignalBase*)s.combo_->itemData(
-                               s.combo_->currentIndex()).value<void*>();
+               for (Annotation &ann : *annotations) {
+                       const QString sample_range = QString("%1-%2") \
+                               .arg(QString::number(ann.start_sample()), QString::number(ann.end_sample()));
 
-               for (shared_ptr<data::SignalBase> sig : sigs)
-                       if (sig.get() == selection) {
-                               channel_map[s.pdch_] = sig;
-                               break;
-                       }
+                       const QString class_name = quote + ann.row()->class_name() + quote;
 
-               int selection_initial_pin = s.combo_initial_pin_->itemData(
-                       s.combo_initial_pin_->currentIndex()).value<int>();
+                       QString all_ann_text;
+                       for (const QString &s : ann.annotations())
+                               all_ann_text = all_ann_text + quote + s + quote + ",";
+                       all_ann_text.chop(1);
 
-               initial_pins->data[s.pdch_->order] = selection_initial_pin;
+                       const QString first_ann_text = quote + ann.annotations().front() + quote;
+
+                       QString out_text = format;
+                       out_text = out_text.replace("%s", sample_range);
+                       out_text = out_text.replace("%d",
+                               quote + QString::fromUtf8(ann.row()->decoder()->name) + quote);
+                       out_text = out_text.replace("%c", class_name);
+                       out_text = out_text.replace("%1", first_ann_text);
+                       out_text = out_text.replace("%a", all_ann_text);
+                       out_stream << out_text << '\n';
+               }
+
+               if (out_stream.status() == QTextStream::Ok)
+                       return;
        }
 
-       dec->set_channels(channel_map);
-       dec->set_initial_pins(initial_pins);
+       QMessageBox msg(owner_->view());
+       msg.setText(tr("Error"));
+       msg.setInformativeText(tr("File %1 could not be written to.").arg(file_name));
+       msg.setStandardButtons(QMessageBox::Ok);
+       msg.setIcon(QMessageBox::Warning);
+       msg.exec();
+}
+
+void DecodeTrace::on_new_annotations()
+{
+       if (!delayed_trace_updater_.isActive())
+               delayed_trace_updater_.start();
 }
 
-void DecodeTrace::commit_channels()
+void DecodeTrace::on_delayed_trace_update()
 {
-       shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
+       if (owner_)
+               owner_->row_item_appearance_changed(false, true);
+}
 
-       assert(decoder_stack);
-       for (shared_ptr<data::decode::Decoder> dec : decoder_stack->stack())
-               commit_decoder_channels(dec);
+void DecodeTrace::on_decode_reset()
+{
+       visible_rows_.clear();
+       max_visible_rows_ = 0;
 
-       decoder_stack->begin_decode();
+       if (owner_)
+               owner_->row_item_appearance_changed(false, true);
 }
 
-void DecodeTrace::on_new_decode_data()
+void DecodeTrace::on_decode_finished()
 {
        if (owner_)
                owner_->row_item_appearance_changed(false, true);
 }
 
+void DecodeTrace::on_pause_decode()
+{
+       if (decode_signal_->is_paused())
+               decode_signal_->resume_decode();
+       else
+               decode_signal_->pause_decode();
+}
+
 void DecodeTrace::delete_pressed()
 {
        on_delete();
@@ -998,67 +1085,174 @@ void DecodeTrace::delete_pressed()
 
 void DecodeTrace::on_delete()
 {
-       session_.remove_decode_signal(base_);
+       session_.remove_decode_signal(decode_signal_);
 }
 
 void DecodeTrace::on_channel_selected(int)
 {
-       commit_channels();
+       QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
+
+       // Determine signal that was selected
+       const data::SignalBase *signal =
+               (data::SignalBase*)cb->itemData(cb->currentIndex()).value<void*>();
+
+       // Determine decode channel ID this combo box is the channel selector for
+       const uint16_t id = channel_id_map_.at(cb);
+
+       decode_signal_->assign_signal(id, signal);
+}
+
+void DecodeTrace::on_channels_updated()
+{
+       if (owner_)
+               owner_->row_item_appearance_changed(false, true);
 }
 
-void DecodeTrace::on_initial_pin_selected(int)
+void DecodeTrace::on_init_state_changed(int)
 {
-       commit_channels();
+       QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
+
+       // Determine inital pin state that was selected
+       int init_state = cb->itemData(cb->currentIndex()).value<int>();
+
+       // Determine decode channel ID this combo box is the channel selector for
+       const uint16_t id = init_state_map_.at(cb);
+
+       decode_signal_->set_initial_pin_state(id, init_state);
 }
 
 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
 {
-       shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
-
-       assert(decoder);
-       assert(decoder_stack);
-       decoder_stack->push(make_shared<data::decode::Decoder>(decoder));
-       decoder_stack->begin_decode();
+       decode_signal_->stack_decoder(decoder);
 
        create_popup_form();
 }
 
 void DecodeTrace::on_delete_decoder(int index)
 {
-       shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
+       decode_signal_->remove_decoder(index);
 
-       decoder_stack->remove(index);
+       // Force re-calculation of the trace height, see paint_mid()
+       max_visible_rows_ = 0;
+       owner_->extents_changed(false, true);
 
        // Update the popup
        create_popup_form();
-
-       decoder_stack->begin_decode();
 }
 
 void DecodeTrace::on_show_hide_decoder(int index)
 {
-       using pv::data::decode::Decoder;
+       const bool state = decode_signal_->toggle_decoder_visibility(index);
 
-       shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
+       assert(index < (int)decoder_forms_.size());
+       decoder_forms_[index]->set_decoder_visible(state);
 
-       const list< shared_ptr<Decoder> > stack(decoder_stack->stack());
+       if (!state) {
+               // Force re-calculation of the trace height, see paint_mid()
+               max_visible_rows_ = 0;
+               owner_->extents_changed(false, true);
+       }
 
-       // Find the decoder in the stack
-       auto iter = stack.cbegin();
-       for (int i = 0; i < index; i++, iter++)
-               assert(iter != stack.end());
+       if (owner_)
+               owner_->row_item_appearance_changed(false, true);
+}
 
-       shared_ptr<Decoder> dec = *iter;
-       assert(dec);
+void DecodeTrace::on_export_row()
+{
+       selected_sample_range_ = make_pair(0, numeric_limits<uint64_t>::max());
+       on_export_row_from_here();
+}
 
-       const bool show = !dec->shown();
-       dec->show(show);
+void DecodeTrace::on_export_all_rows()
+{
+       selected_sample_range_ = make_pair(0, numeric_limits<uint64_t>::max());
+       on_export_all_rows_from_here();
+}
 
-       assert(index < (int)decoder_forms_.size());
-       decoder_forms_[index]->set_decoder_visible(show);
+void DecodeTrace::on_export_row_with_cursor()
+{
+       const View *view = owner_->view();
+       assert(view);
 
-       if (owner_)
-               owner_->row_item_appearance_changed(false, true);
+       if (!view->cursors()->enabled())
+               return;
+
+       const double samplerate = session_.get_samplerate();
+
+       const pv::util::Timestamp& start_time = view->cursors()->first()->time();
+       const pv::util::Timestamp& end_time = view->cursors()->second()->time();
+
+       const uint64_t start_sample = (uint64_t)max(
+               0.0, start_time.convert_to<double>() * samplerate);
+       const uint64_t end_sample = (uint64_t)max(
+               0.0, end_time.convert_to<double>() * samplerate);
+
+       // Are both cursors negative and thus were clamped to 0?
+       if ((start_sample == 0) && (end_sample == 0))
+               return;
+
+       selected_sample_range_ = make_pair(start_sample, end_sample);
+       on_export_row_from_here();
+}
+
+void DecodeTrace::on_export_all_rows_with_cursor()
+{
+       const View *view = owner_->view();
+       assert(view);
+
+       if (!view->cursors()->enabled())
+               return;
+
+       const double samplerate = session_.get_samplerate();
+
+       const pv::util::Timestamp& start_time = view->cursors()->first()->time();
+       const pv::util::Timestamp& end_time = view->cursors()->second()->time();
+
+       const uint64_t start_sample = (uint64_t)max(
+               0.0, start_time.convert_to<double>() * samplerate);
+       const uint64_t end_sample = (uint64_t)max(
+               0.0, end_time.convert_to<double>() * samplerate);
+
+       // Are both cursors negative and thus were clamped to 0?
+       if ((start_sample == 0) && (end_sample == 0))
+               return;
+
+       selected_sample_range_ = make_pair(start_sample, end_sample);
+       on_export_all_rows_from_here();
+}
+
+void DecodeTrace::on_export_row_from_here()
+{
+       using namespace pv::data::decode;
+
+       if (!selected_row_)
+               return;
+
+       vector<Annotation> *annotations = new vector<Annotation>();
+
+       decode_signal_->get_annotation_subset(*annotations, *selected_row_,
+               current_segment_, selected_sample_range_.first, selected_sample_range_.second);
+
+       if (annotations->empty())
+               return;
+
+       export_annotations(annotations);
+       delete annotations;
+}
+
+void DecodeTrace::on_export_all_rows_from_here()
+{
+       using namespace pv::data::decode;
+
+       vector<Annotation> *annotations = new vector<Annotation>();
+
+       decode_signal_->get_annotation_subset(*annotations, current_segment_,
+                       selected_sample_range_.first, selected_sample_range_.second);
+
+       if (!annotations->empty())
+               export_annotations(annotations);
+
+       delete annotations;
 }
 
 } // namespace trace
index e49d12292863766668627fe68cb10b51dc3b88ea..3d25c3e4d7410f10853204ba21116dc363610c40 100644 (file)
 #include <memory>
 #include <vector>
 
+#include <QColor>
+#include <QComboBox>
 #include <QSignalMapper>
+#include <QTimer>
 
 #include <pv/binding/decoder.hpp>
+#include <pv/data/decode/annotation.hpp>
 #include <pv/data/decode/row.hpp>
 #include <pv/data/signalbase.hpp>
 
@@ -42,22 +46,18 @@ using std::vector;
 struct srd_channel;
 struct srd_decoder;
 
-class QComboBox;
-
 namespace pv {
 
 class Session;
 
 namespace data {
-class DecoderStack;
-class SignalBase;
+struct DecodeChannel;
+class DecodeSignal;
 
 namespace decode {
-class Annotation;
 class Decoder;
-class Row;
-}
 }
+}  // namespace data
 
 namespace widgets {
 class DecoderGroupBox;
@@ -71,26 +71,15 @@ class DecodeTrace : public Trace
        Q_OBJECT
 
 private:
-       struct ChannelSelector
-       {
-               const QComboBox *combo_;
-               const QComboBox *combo_initial_pin_;
-               const 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 QColor ErrorBgColor;
+       static const QColor NoDecodeColor;
 
        static const int ArrowSize;
        static const double EndCapWidth;
        static const int RowTitleMargin;
        static const int DrawPadding;
 
-       static const QColor Colours[16];
-       static const QColor OutlineColours[16];
+       static const int MaxTraceUpdateRate;
 
 public:
        DecodeTrace(pv::Session &session, shared_ptr<data::SignalBase> signalbase,
@@ -98,8 +87,6 @@ public:
 
        bool enabled() const;
 
-       const shared_ptr<pv::data::DecoderStack>& decoder() const;
-
        shared_ptr<data::SignalBase> base() const;
 
        /**
@@ -131,27 +118,30 @@ public:
 
        void populate_popup_form(QWidget *parent, QFormLayout *form);
 
-       QMenu* create_context_menu(QWidget *parent);
+       QMenu* create_header_context_menu(QWidget *parent);
+
+       virtual QMenu* create_view_context_menu(QWidget *parent, QPoint &click_pos);
 
        void delete_pressed();
 
 private:
        void draw_annotations(vector<pv::data::decode::Annotation> annotations,
                QPainter &p, int h, const ViewItemPaintParams &pp, int y,
-               size_t base_colour, int row_title_width);
+               QColor row_color, int row_title_width);
 
        void draw_annotation(const pv::data::decode::Annotation &a, QPainter &p,
                int h, const ViewItemPaintParams &pp, int y,
-               size_t base_colour, int row_title_width) const;
+               QColor row_color, int row_title_width) const;
 
-       void draw_annotation_block(vector<pv::data::decode::Annotation> annotations,
-               QPainter &p, int h, int y, size_t base_colour) const;
+       void draw_annotation_block(qreal start, qreal end,
+               pv::data::decode::Annotation::Class ann_class, bool use_ann_format,
+               QPainter &p, int h, int y, QColor row_color) const;
 
        void draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
-               int h, double x, int y) const;
+               int h, qreal x, int y) const;
 
        void draw_range(const pv::data::decode::Annotation &a, QPainter &p,
-               int h, double start, double end, int y, const ViewItemPaintParams &pp,
+               int h, qreal start, qreal end, int y, const ViewItemPaintParams &pp,
                int row_title_width) const;
 
        void draw_error(QPainter &p, const QString &message,
@@ -169,7 +159,10 @@ private:
         * @return Returns a pair containing the start sample and the end
         *      sample that correspond to the start and end coordinates.
         */
-       pair<uint64_t, uint64_t> get_sample_range(int x_start, int x_end) const;
+       pair<uint64_t, uint64_t> get_view_sample_range(int x_start, int x_end) const;
+
+       QColor get_row_color(int row_index) const;
+       QColor get_annotation_color(QColor row_color, int annotation_index) const;
 
        int get_row_at_point(const QPoint &point);
 
@@ -180,28 +173,29 @@ private:
                QWidget *parent, QFormLayout *form);
 
        QComboBox* create_channel_selector(QWidget *parent,
-               const shared_ptr<pv::data::decode::Decoder> &dec,
-               const srd_channel *const pdch);
+               const data::DecodeChannel *ch);
+       QComboBox* create_channel_selector_init_state(QWidget *parent,
+               const data::DecodeChannel *ch);
 
-       QComboBox* create_channel_selector_initial_pin(QWidget *parent,
-               const shared_ptr<pv::data::decode::Decoder> &dec,
-               const srd_channel *const pdch);
-
-       void commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec);
-
-       void commit_channels();
+       void export_annotations(vector<data::decode::Annotation> *annotations) const;
 
 public:
-       void hover_point_changed();
+       virtual void hover_point_changed(const QPoint &hp);
 
 private Q_SLOTS:
-       void on_new_decode_data();
+       void on_new_annotations();
+       void on_delayed_trace_update();
+       void on_decode_reset();
+       void on_decode_finished();
+       void on_pause_decode();
 
        void on_delete();
 
        void on_channel_selected(int);
 
-       void on_initial_pin_selected(int);
+       void on_channels_updated();
+
+       void on_init_state_changed(int);
 
        void on_stack_decoder(srd_decoder *decoder);
 
@@ -209,15 +203,26 @@ private Q_SLOTS:
 
        void on_show_hide_decoder(int index);
 
+       void on_export_row();
+       void on_export_all_rows();
+       void on_export_row_with_cursor();
+       void on_export_all_rows_with_cursor();
+       void on_export_row_from_here();
+       void on_export_all_rows_from_here();
+
 private:
        pv::Session &session_;
+       shared_ptr<data::DecodeSignal> decode_signal_;
 
        vector<data::decode::Row> visible_rows_;
-       uint64_t decode_start_, decode_end_;
 
+       map<QComboBox*, uint16_t> channel_id_map_;  // channel selector -> decode channel ID
+       map<QComboBox*, uint16_t> init_state_map_;  // init state selector -> decode channel ID
        list< shared_ptr<pv::binding::Decoder> > bindings_;
 
-       list<ChannelSelector> channel_selectors_;
+       data::decode::Row *selected_row_;
+       pair<uint64_t, uint64_t> selected_sample_range_;
+
        vector<pv::widgets::DecoderGroupBox*> decoder_forms_;
 
        map<data::decode::Row, int> row_title_widths_;
@@ -226,6 +231,8 @@ private:
        int min_useful_label_width_;
 
        QSignalMapper delete_mapper_, show_hide_mapper_;
+
+       QTimer delayed_trace_updater_;
 };
 
 } // namespace trace
index 1db843e08193e5508d8298da841e82389be102fe..64ef1047d03e6b5057753ccca7fe0a468aefee44 100644 (file)
@@ -36,16 +36,16 @@ namespace pv {
 namespace views {
 namespace trace {
 
-const QColor Flag::FillColour(0x73, 0xD2, 0x16);
+const QColor Flag::FillColor(0x73, 0xD2, 0x16);
 
 Flag::Flag(View &view, const pv::util::Timestamp& time, const QString &text) :
-       TimeMarker(view, FillColour, time),
+       TimeMarker(view, FillColor, time),
        text_(text)
 {
 }
 
 Flag::Flag(const Flag &flag) :
-       TimeMarker(flag.view_, FillColour, flag.time_),
+       TimeMarker(flag.view_, FillColor, flag.time_),
        enable_shared_from_this<Flag>(flag)
 {
 }
@@ -66,7 +66,7 @@ pv::widgets::Popup* Flag::create_popup(QWidget *parent)
 
        Popup *const popup = TimeMarker::create_popup(parent);
        popup->set_position(parent->mapToGlobal(
-               point(parent->rect())), Popup::Bottom);
+               drag_point(parent->rect())), Popup::Bottom);
 
        QFormLayout *const form = (QFormLayout*)popup->layout();
 
@@ -81,7 +81,7 @@ pv::widgets::Popup* Flag::create_popup(QWidget *parent)
        return popup;
 }
 
-QMenu* Flag::create_context_menu(QWidget *parent)
+QMenu* Flag::create_header_context_menu(QWidget *parent)
 {
        QMenu *const menu = new QMenu(parent);
 
index 4f707bddf3caaa5d9c9fc233950769510041bf21..4bf6ebd5db7f974a9857e733ce0b49c8cbde046f 100644 (file)
@@ -32,12 +32,16 @@ namespace pv {
 namespace views {
 namespace trace {
 
+/**
+ * The Flag class represents items on the @ref Ruler that mark important
+ * events on the timeline to the user. They are editable and thus non-static.
+ */
 class Flag : public TimeMarker, public enable_shared_from_this<Flag>
 {
        Q_OBJECT
 
 public:
-       static const QColor FillColour;
+       static const QColor FillColor;
 
 public:
        /**
@@ -65,7 +69,7 @@ public:
 
        pv::widgets::Popup* create_popup(QWidget *parent);
 
-       QMenu* create_context_menu(QWidget *parent);
+       QMenu* create_header_context_menu(QWidget *parent);
 
        void delete_pressed();
 
index 58096dc07df4893687724b8f35c7b58a096925ee..d7da7e03706bdab45389f127c8bc874871a3c74a 100644 (file)
@@ -103,12 +103,12 @@ void Header::paintEvent(QPaintEvent*)
 
        stable_sort(items.begin(), items.end(),
                [](const shared_ptr<RowItem> &a, const shared_ptr<RowItem> &b) {
-                       return a->point(QRect()).y() < b->point(QRect()).y(); });
+                       return a->drag_point(QRect()).y() < b->drag_point(QRect()).y(); });
 
        QPainter painter(this);
        painter.setRenderHint(QPainter::Antialiasing);
 
-       for (const shared_ptr<RowItem> r : items) {
+       for (const shared_ptr<RowItem>& r : items) {
                assert(r);
 
                const bool highlight = !item_dragging_ &&
@@ -125,7 +125,7 @@ void Header::contextMenuEvent(QContextMenuEvent *event)
        if (!r)
                return;
 
-       QMenu *menu = r->create_context_menu(this);
+       QMenu *menu = r->create_header_context_menu(this);
        if (!menu)
                menu = new QMenu(this);
 
@@ -142,7 +142,7 @@ void Header::contextMenuEvent(QContextMenuEvent *event)
                menu->addAction(group);
        }
 
-       menu->exec(event->globalPos());
+       menu->popup(event->globalPos());
 }
 
 void Header::keyPressEvent(QKeyEvent *event)
@@ -201,7 +201,7 @@ void Header::on_ungroup()
                restart = false;
                const vector< shared_ptr<TraceGroup> > groups(
                        view_.list_by_type<TraceGroup>());
-               for (const shared_ptr<TraceGroup> tg : groups)
+               for (const shared_ptr<TraceGroup>& tg : groups)
                        if (tg->selected()) {
                                tg->ungroup();
                                restart = true;
index 0038abb1a8f9528825908b794537025fe7d2e518..5d2ba4b7ce1c9e794a5ffa8f27cf1c8b4b02f04c 100644 (file)
@@ -37,6 +37,12 @@ class TraceTreeItem;
 class View;
 class ViewItem;
 
+/**
+ * The Header class provides an area for @ref Trace labels to be shown,
+ * trace-related settings to be edited, trace groups to be shown and similar.
+ * Essentially, it is the main management area of the @ref View itself and
+ * shown on the left-hand side of the trace area.
+ */
 class Header : public MarginWidget
 {
        Q_OBJECT
index 9913d6873ddfd28639c36d1348203c1a921f9ddd..f9ab16f102cdf5328613d81957a5d4563602d07e 100644 (file)
@@ -46,6 +46,7 @@ using std::max;
 using std::make_pair;
 using std::min;
 using std::none_of;
+using std::out_of_range;
 using std::pair;
 using std::shared_ptr;
 using std::vector;
@@ -56,18 +57,20 @@ using sigrok::Trigger;
 using sigrok::TriggerMatch;
 using sigrok::TriggerMatchType;
 
+using pv::data::LogicSegment;
+
 namespace pv {
 namespace views {
 namespace trace {
 
 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::SamplingPointColour(0x77, 0x77, 0x77);
+const QColor LogicSignal::EdgeColor(0x80, 0x80, 0x80);
+const QColor LogicSignal::HighColor(0x00, 0xC0, 0x00);
+const QColor LogicSignal::LowColor(0xC0, 0x00, 0x00);
+const QColor LogicSignal::SamplingPointColor(0x77, 0x77, 0x77);
 
-const QColor LogicSignal::SignalColours[10] = {
+const QColor LogicSignal::SignalColors[10] = {
        QColor(0x16, 0x19, 0x1A),       // Black
        QColor(0x8F, 0x52, 0x02),       // Brown
        QColor(0xCC, 0x00, 0x00),       // Red
@@ -80,7 +83,7 @@ const QColor LogicSignal::SignalColours[10] = {
        QColor(0xEE, 0xEE, 0xEC),       // White
 };
 
-QColor LogicSignal::TriggerMarkerBackgroundColour = QColor(0xED, 0xD4, 0x00);
+QColor LogicSignal::TriggerMarkerBackgroundColor = QColor(0xED, 0xD4, 0x00);
 const int LogicSignal::TriggerMarkerPadding = 2;
 const char* LogicSignal::TriggerMarkerIcons[8] = {
        nullptr,
@@ -101,8 +104,8 @@ LogicSignal::LogicSignal(
        shared_ptr<devices::Device> device,
        shared_ptr<data::SignalBase> base) :
        Signal(session, base),
-       signal_height_(QFontMetrics(QApplication::font()).height() * 2),
        device_(device),
+       trigger_types_(get_trigger_types()),
        trigger_none_(nullptr),
        trigger_rising_(nullptr),
        trigger_high_(nullptr),
@@ -112,7 +115,16 @@ LogicSignal::LogicSignal(
 {
        shared_ptr<Trigger> trigger;
 
-       base_->set_colour(SignalColours[base->index() % countof(SignalColours)]);
+       base_->set_color(SignalColors[base->index() % countof(SignalColors)]);
+
+       GlobalSettings settings;
+       signal_height_ = settings.value(GlobalSettings::Key_View_DefaultLogicHeight).toInt();
+       show_sampling_points_ =
+               settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool();
+       fill_high_areas_ =
+               settings.value(GlobalSettings::Key_View_FillSignalHighAreas).toBool();
+       high_fill_color_ = QColor::fromRgba(settings.value(
+               GlobalSettings::Key_View_FillSignalHighAreaColor).value<uint32_t>());
 
        /* Populate this channel's trigger setting with whatever we
         * find in the current session trigger, if anything. */
@@ -134,23 +146,30 @@ shared_ptr<pv::data::Logic> LogicSignal::logic_data() const
        return base_->logic_data();
 }
 
-pair<int, int> LogicSignal::v_extents() const
+void LogicSignal::save_settings(QSettings &settings) const
 {
-       const int signal_margin =
-               QFontMetrics(QApplication::font()).height() / 2;
-       return make_pair(-signal_height_ - signal_margin, signal_margin);
+       settings.setValue("trace_height", signal_height_);
 }
 
-int LogicSignal::scale_handle_offset() const
+void LogicSignal::restore_settings(QSettings &settings)
 {
-       return -signal_height_;
+       if (settings.contains("trace_height")) {
+               const int old_height = signal_height_;
+               signal_height_ = settings.value("trace_height").toInt();
+
+               if ((signal_height_ != old_height) && owner_) {
+                       // Call order is important, otherwise the lazy event handler won't work
+                       owner_->extents_changed(false, true);
+                       owner_->row_item_appearance_changed(false, true);
+               }
+       }
 }
 
-void LogicSignal::scale_handle_dragged(int offset)
+pair<int, int> LogicSignal::v_extents() const
 {
-       const int font_height = QFontMetrics(QApplication::font()).height();
-       const int units = (-offset / font_height);
-       signal_height_ = ((units < 1) ? 1 : units) * font_height;
+       const int signal_margin =
+               QFontMetrics(QApplication::font()).height() / 2;
+       return make_pair(-signal_height_ - signal_margin, signal_margin);
 }
 
 void LogicSignal::paint_mid(QPainter &p, ViewItemPaintParams &pp)
@@ -167,16 +186,13 @@ void LogicSignal::paint_mid(QPainter &p, ViewItemPaintParams &pp)
        if (!base_->enabled())
                return;
 
-       const float high_offset = y - signal_height_ + 0.5f;
        const float low_offset = y + 0.5f;
+       const float high_offset = low_offset - signal_height_;
 
-       const deque< shared_ptr<pv::data::LogicSegment> > &segments =
-               base_->logic_data()->logic_segments();
-       if (segments.empty())
+       shared_ptr<LogicSegment> segment = get_logic_segment_to_paint();
+       if (!segment || (segment->get_sample_count() == 0))
                return;
 
-       const shared_ptr<pv::data::LogicSegment> &segment = segments.front();
-
        double samplerate = segment->samplerate();
 
        // Show sample rate as 1Hz when it is unknown
@@ -185,7 +201,7 @@ void LogicSignal::paint_mid(QPainter &p, ViewItemPaintParams &pp)
 
        const double pixels_offset = pp.pixels_offset();
        const pv::util::Timestamp& start_time = segment->start_time();
-       const int64_t last_sample = segment->get_sample_count() - 1;
+       const int64_t last_sample = (int64_t)segment->get_sample_count() - 1;
        const double samples_per_pixel = samplerate * pp.scale();
        const double pixels_per_sample = 1 / samples_per_pixel;
        const pv::util::Timestamp start = samplerate * (pp.offset() - start_time);
@@ -200,32 +216,62 @@ void LogicSignal::paint_mid(QPainter &p, ViewItemPaintParams &pp)
                samples_per_pixel / Oversampling, base_->index());
        assert(edges.size() >= 2);
 
-       // Check whether we need to paint the sampling points
-       GlobalSettings settings;
-       const bool show_sampling_points =
-               settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool() &&
-               (samples_per_pixel < 0.25);
+       const float first_sample_x =
+               pp.left() + (edges.front().first / samples_per_pixel - pixels_offset);
+       const float last_sample_x =
+               pp.left() + (edges.back().first / samples_per_pixel - pixels_offset);
 
+       // Check whether we need to paint the sampling points
+       const bool show_sampling_points = show_sampling_points_ && (samples_per_pixel < 0.25);
        vector<QRectF> sampling_points;
-       float sampling_point_x = 0.0f;
+       float sampling_point_x = first_sample_x;
        int64_t sampling_point_sample = start_sample;
        const int w = 2;
 
-       if (show_sampling_points) {
+       if (show_sampling_points)
                sampling_points.reserve(end_sample - start_sample + 1);
-               sampling_point_x = (edges.cbegin()->first / samples_per_pixel - pixels_offset) + pp.left();
-       }
+
+       vector<QRectF> high_rects;
+       float rising_edge_x;
+       bool rising_edge_seen = false;
 
        // Paint the edges
        const unsigned int edge_count = edges.size() - 2;
        QLineF *const edge_lines = new QLineF[edge_count];
        line = edge_lines;
 
+       if (edges.front().second) {
+               // Beginning of trace is high
+               rising_edge_x = first_sample_x;
+               rising_edge_seen = true;
+       }
+
        for (auto i = edges.cbegin() + 1; i != edges.cend() - 1; i++) {
-               const float x = ((*i).first / samples_per_pixel -
-                       pixels_offset) + pp.left();
+               // Note: multiple edges occupying a single pixel are represented by an edge
+               // with undefined logic level. This means that only the first falling edge
+               // after a rising edge corresponds to said rising edge - and vice versa. If
+               // more edges with the same logic level follow, they denote multiple edges.
+
+               const float x = pp.left() + ((*i).first / samples_per_pixel - pixels_offset);
                *line++ = QLineF(x, high_offset, x, low_offset);
 
+               if (fill_high_areas_) {
+                       // Any edge terminates a high area
+                       if (rising_edge_seen) {
+                               const int width = x - rising_edge_x;
+                               if (width > 0)
+                                       high_rects.emplace_back(rising_edge_x, high_offset,
+                                               width, signal_height_);
+                               rising_edge_seen = false;
+                       }
+
+                       // Only rising edges start high areas
+                       if ((*i).second) {
+                               rising_edge_x = x;
+                               rising_edge_seen = true;
+                       }
+               }
+
                if (show_sampling_points)
                        while (sampling_point_sample < (*i).first) {
                                const float y = (*i).second ? low_offset : high_offset;
@@ -247,7 +293,18 @@ void LogicSignal::paint_mid(QPainter &p, ViewItemPaintParams &pp)
                        sampling_point_x += pixels_per_sample;
                };
 
-       p.setPen(EdgeColour);
+       if (fill_high_areas_) {
+               // Add last high rectangle if the signal is still high at the end of the trace
+               if (rising_edge_seen && (edges.cend() - 1)->second)
+                       high_rects.emplace_back(rising_edge_x, high_offset,
+                               last_sample_x - rising_edge_x, signal_height_);
+
+               p.setPen(high_fill_color_);
+               p.setBrush(high_fill_color_);
+               p.drawRects((const QRectF*)(high_rects.data()), high_rects.size());
+       }
+
+       p.setPen(EdgeColor);
        p.drawLines(edge_lines, edge_count);
        delete[] edge_lines;
 
@@ -255,10 +312,10 @@ void LogicSignal::paint_mid(QPainter &p, ViewItemPaintParams &pp)
        const unsigned int max_cap_line_count = edges.size();
        QLineF *const cap_lines = new QLineF[max_cap_line_count];
 
-       p.setPen(HighColour);
+       p.setPen(HighColor);
        paint_caps(p, cap_lines, edges, true, samples_per_pixel,
                pixels_offset, pp.left(), high_offset);
-       p.setPen(LowColour);
+       p.setPen(LowColor);
        paint_caps(p, cap_lines, edges, false, samples_per_pixel,
                pixels_offset, pp.left(), low_offset);
 
@@ -266,48 +323,79 @@ void LogicSignal::paint_mid(QPainter &p, ViewItemPaintParams &pp)
 
        // Paint the sampling points
        if (show_sampling_points) {
-               p.setPen(SamplingPointColour);
+               p.setPen(SamplingPointColor);
                p.drawRects(sampling_points.data(), sampling_points.size());
        }
 }
 
 void LogicSignal::paint_fore(QPainter &p, ViewItemPaintParams &pp)
 {
-       // Draw the trigger marker
-       if (!trigger_match_ || !base_->enabled())
-               return;
+       if (base_->enabled()) {
+               if (trigger_match_) {
+                       // Draw the trigger marker
+                       const int y = get_visual_y();
+
+                       for (int32_t type_id : trigger_types_) {
+                               const TriggerMatchType *const type =
+                                       TriggerMatchType::get(type_id);
+                               if (trigger_match_ != type || type_id < 0 ||
+                                       (size_t)type_id >= countof(TriggerMarkerIcons) ||
+                                       !TriggerMarkerIcons[type_id])
+                                       continue;
 
-       const int y = get_visual_y();
-       const vector<int32_t> trig_types = get_trigger_types();
-       for (int32_t type_id : trig_types) {
-               const TriggerMatchType *const type =
-                       TriggerMatchType::get(type_id);
-               if (trigger_match_ != type || type_id < 0 ||
-                       (size_t)type_id >= countof(TriggerMarkerIcons) ||
-                       !TriggerMarkerIcons[type_id])
-                       continue;
-
-               const QPixmap *const pixmap = get_pixmap(
-                       TriggerMarkerIcons[type_id]);
-               if (!pixmap)
-                       continue;
-
-               const float pad = TriggerMarkerPadding - 0.5f;
-               const QSize size = pixmap->size();
-               const QPoint point(
-                       pp.right() - size.width() - pad * 2,
-                       y - (signal_height_ + size.height()) / 2);
-
-               p.setPen(QPen(TriggerMarkerBackgroundColour.darker()));
-               p.setBrush(TriggerMarkerBackgroundColour);
-               p.drawRoundedRect(QRectF(point, size).adjusted(
-                       -pad, -pad, pad, pad), pad, pad);
-               p.drawPixmap(point, *pixmap);
-
-               break;
+                               const QPixmap *const pixmap = get_pixmap(
+                                       TriggerMarkerIcons[type_id]);
+                               if (!pixmap)
+                                       continue;
+
+                               const float pad = TriggerMarkerPadding - 0.5f;
+                               const QSize size = pixmap->size();
+                               const QPoint point(
+                                       pp.right() - size.width() - pad * 2,
+                                       y - (signal_height_ + size.height()) / 2);
+
+                               p.setPen(QPen(TriggerMarkerBackgroundColor.darker()));
+                               p.setBrush(TriggerMarkerBackgroundColor);
+                               p.drawRoundedRect(QRectF(point, size).adjusted(
+                                       -pad, -pad, pad, pad), pad, pad);
+                               p.drawPixmap(point, *pixmap);
+
+                               break;
+                       }
+               }
+
+               if (show_hover_marker_)
+                       paint_hover_marker(p);
        }
 }
 
+vector<LogicSegment::EdgePair> LogicSignal::get_nearest_level_changes(uint64_t sample_pos)
+{
+       assert(base_);
+       assert(owner_);
+
+       if (sample_pos == 0)
+               return vector<LogicSegment::EdgePair>();
+
+       shared_ptr<LogicSegment> segment = get_logic_segment_to_paint();
+       if (!segment || (segment->get_sample_count() == 0))
+               return vector<LogicSegment::EdgePair>();
+
+       const View *view = owner_->view();
+       assert(view);
+       const double samples_per_pixel = base_->get_samplerate() * view->scale();
+
+       vector<LogicSegment::EdgePair> edges;
+
+       segment->get_surrounding_edges(edges, sample_pos,
+               samples_per_pixel / Oversampling, base_->index());
+
+       if (edges.empty())
+               return vector<LogicSegment::EdgePair>();
+
+       return edges;
+}
+
 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,
@@ -327,6 +415,31 @@ void LogicSignal::paint_caps(QPainter &p, QLineF *const lines,
        p.drawLines(lines, line - lines);
 }
 
+shared_ptr<pv::data::LogicSegment> LogicSignal::get_logic_segment_to_paint() const
+{
+       shared_ptr<pv::data::LogicSegment> segment;
+
+       const deque< shared_ptr<pv::data::LogicSegment> > &segments =
+               base_->logic_data()->logic_segments();
+
+       if (!segments.empty()) {
+               if (segment_display_mode_ == ShowLastSegmentOnly) {
+                       segment = segments.back();
+               }
+
+       if ((segment_display_mode_ == ShowSingleSegmentOnly) ||
+               (segment_display_mode_ == ShowLastCompleteSegmentOnly)) {
+                       try {
+                               segment = segments.at(current_segment_);
+                       } catch (out_of_range&) {
+                               qDebug() << "Current logic segment out of range for signal" << base_->name() << ":" << current_segment_;
+                       }
+               }
+       }
+
+       return segment;
+}
+
 void LogicSignal::init_trigger_actions(QWidget *parent)
 {
        trigger_none_ = new QAction(*get_icon(":/icons/trigger-none.svg"),
@@ -439,6 +552,16 @@ void LogicSignal::populate_popup_form(QWidget *parent, QFormLayout *form)
 {
        Signal::populate_popup_form(parent, form);
 
+       signal_height_sb_ = new QSpinBox(parent);
+       signal_height_sb_->setRange(5, 1000);
+       signal_height_sb_->setSingleStep(5);
+       signal_height_sb_->setSuffix(tr(" pixels"));
+       signal_height_sb_->setValue(signal_height_);
+       connect(signal_height_sb_, SIGNAL(valueChanged(int)),
+               this, SLOT(on_signal_height_changed(int)));
+       form->addRow(tr("Trace height"), signal_height_sb_);
+
+       // Trigger settings
        const vector<int32_t> trig_types = get_trigger_types();
 
        if (!trig_types.empty()) {
@@ -454,6 +577,12 @@ void LogicSignal::populate_popup_form(QWidget *parent, QFormLayout *form)
                        trigger_bar_->addAction(action);
                        action->setChecked(trigger_match_ == type);
                }
+
+               // Only allow triggers to be changed when we're stopped
+               if (session_.get_capture_state() != Session::Stopped)
+                       for (QAction* action : trigger_bar_->findChildren<QAction*>())  // clazy:exclude=range-loop
+                               action->setEnabled(false);
+
                form->addRow(tr("Trigger"), trigger_bar_);
        }
 }
@@ -518,6 +647,20 @@ const QPixmap* LogicSignal::get_pixmap(const char *path)
        return pixmap_cache_.take(path);
 }
 
+void LogicSignal::on_setting_changed(const QString &key, const QVariant &value)
+{
+       Signal::on_setting_changed(key, value);
+
+       if (key == GlobalSettings::Key_View_ShowSamplingPoints)
+               show_sampling_points_ = value.toBool();
+
+       if (key == GlobalSettings::Key_View_FillSignalHighAreas)
+               fill_high_areas_ = value.toBool();
+
+       if (key == GlobalSettings::Key_View_FillSignalHighAreaColor)
+               high_fill_color_ = QColor::fromRgba(value.value<uint32_t>());
+}
+
 void LogicSignal::on_trigger()
 {
        QAction *action;
@@ -531,6 +674,17 @@ void LogicSignal::on_trigger()
        modify_trigger();
 }
 
+void LogicSignal::on_signal_height_changed(int height)
+{
+       signal_height_ = height;
+
+       if (owner_) {
+               // Call order is important, otherwise the lazy event handler won't work
+               owner_->extents_changed(false, true);
+               owner_->row_item_appearance_changed(false, true);
+       }
+}
+
 } // namespace trace
 } // namespace views
 } // namespace pv
index 2f5c66baa219fb69dcb8e9e42e08de1c01ad1849..b170e2c08d76da930f5b6d482fad9ad1ce4cb6d4 100644 (file)
@@ -21,6 +21,9 @@
 #define PULSEVIEW_PV_VIEWS_TRACEVIEW_LOGICSIGNAL_HPP
 
 #include <QCache>
+#include <QColor>
+#include <QDebug>
+#include <QSpinBox>
 
 #include "signal.hpp"
 
@@ -57,14 +60,14 @@ class LogicSignal : public Signal
 public:
        static const float Oversampling;
 
-       static const QColor EdgeColour;
-       static const QColor HighColour;
-       static const QColor LowColour;
-       static const QColor SamplingPointColour;
+       static const QColor EdgeColor;
+       static const QColor HighColor;
+       static const QColor LowColor;
+       static const QColor SamplingPointColor;
 
-       static const QColor SignalColours[10];
+       static const QColor SignalColors[10];
 
-       static QColor TriggerMarkerBackgroundColour;
+       static QColor TriggerMarkerBackgroundColor;
        static const int TriggerMarkerPadding;
        static const char* TriggerMarkerIcons[8];
 
@@ -78,23 +81,15 @@ public:
 
        shared_ptr<pv::data::Logic> logic_data() const;
 
+       virtual void save_settings(QSettings &settings) const;
+       virtual void restore_settings(QSettings &settings);
+
        /**
         * Computes the vertical extents of the contents of this row item.
         * @return A pair containing the minimum and maximum y-values.
         */
        pair<int, int> v_extents() const;
 
-       /**
-        * Returns the offset to show the drag handle.
-        */
-       int scale_handle_offset() const;
-
-       /**
-        * Handles the scale handle being dragged to an offset.
-        * @param offset the offset the scale handle was dragged to.
-        */
-       void scale_handle_dragged(int offset);
-
        /**
         * Paints the mid-layer of the signal with a QPainter
         * @param p the QPainter to paint into.
@@ -109,12 +104,23 @@ public:
         */
        virtual void paint_fore(QPainter &p, ViewItemPaintParams &pp);
 
+       /**
+        * Determines the closest level change (i.e. edge) to a given sample, which
+        * is useful for e.g. the "snap to edge" functionality.
+        *
+        * @param sample_pos Sample to use
+        * @return The changes left and right of the given position
+        */
+       virtual vector<data::LogicSegment::EdgePair> get_nearest_level_changes(uint64_t sample_pos);
+
 private:
        void 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);
 
+       shared_ptr<pv::data::LogicSegment> get_logic_segment_to_paint() const;
+
        void init_trigger_actions(QWidget *parent);
 
        const vector<int32_t> get_trigger_types() const;
@@ -128,14 +134,23 @@ private:
        static const QPixmap* get_pixmap(const char *path);
 
 private Q_SLOTS:
+       void on_setting_changed(const QString &key, const QVariant &value);
+
        void on_trigger();
 
+       void on_signal_height_changed(int height);
+
 private:
        int signal_height_;
+       QColor high_fill_color_;
+       bool show_sampling_points_, fill_high_areas_;
 
        shared_ptr<pv::devices::Device> device_;
 
+       QSpinBox *signal_height_sb_;
+
        const sigrok::TriggerMatchType *trigger_match_;
+       const vector<int32_t> trigger_types_;
        QToolBar *trigger_bar_;
        QAction *trigger_none_;
        QAction *trigger_rising_;
index fa12c2b5b8d9257e8d5e24b885c4a711ee34055d..86ec069bc2370147b76b9cb16bfc606655f2cabf 100644 (file)
@@ -53,13 +53,17 @@ void MarginWidget::show_popup(const shared_ptr<ViewItem> &item)
 
 void MarginWidget::contextMenuEvent(QContextMenuEvent *event)
 {
+       event->setAccepted(false);
+
        const shared_ptr<ViewItem> r = get_mouse_over_item(mouse_point_);
        if (!r)
                return;
 
-       QMenu *menu = r->create_context_menu(this);
-       if (menu)
-               menu->exec(event->globalPos());
+       QMenu *menu = r->create_header_context_menu(this);
+       if (menu) {
+               event->setAccepted(true);
+               menu->popup(event->globalPos());
+       }
 }
 
 void MarginWidget::keyPressEvent(QKeyEvent *event)
index 11a02e9b4ebb1f1748fb1ef0f840f951365c7c46..c57043d47f309d801f3ed5e5b905584c56c3613d 100644 (file)
@@ -23,8 +23,9 @@ namespace pv {
 namespace views {
 namespace trace {
 
-void RowItem::hover_point_changed()
+void RowItem::hover_point_changed(const QPoint &hp)
 {
+       (void)hp;
 }
 
 } // namespace trace
index ae91d4bea486895d1e33587f5548f08a763b8d7d..d2a205fb6c3505acac0e492cee1d6f97d544660d 100644 (file)
@@ -31,7 +31,7 @@ class RowItem : public ViewItem
        Q_OBJECT
 
 public:
-       virtual void hover_point_changed();
+       virtual void hover_point_changed(const QPoint &hp);
 };
 
 } // namespace trace
index 5455b2099ba77abf7315a4e9fa11a98c5494d499..acea8a368b457469e44c11e758f95f935a56e68e 100644 (file)
 
 #include <QApplication>
 #include <QFontMetrics>
+#include <QMenu>
 #include <QMouseEvent>
 
+#include <pv/globalsettings.hpp>
+
 #include "ruler.hpp"
 #include "view.hpp"
 
 using namespace Qt;
 
 using std::function;
+using std::max;
+using std::min;
 using std::shared_ptr;
 using std::vector;
 
@@ -37,7 +42,6 @@ namespace views {
 namespace trace {
 
 const float Ruler::RulerHeight = 2.5f; // x Text Height
-const int Ruler::MinorTickSubdivision = 4;
 
 const float Ruler::HoverArrowSize = 0.5f; // x Text Height
 
@@ -46,8 +50,8 @@ Ruler::Ruler(View &parent) :
 {
        setMouseTracking(true);
 
-       connect(&view_, SIGNAL(hover_point_changed()),
-               this, SLOT(hover_point_changed()));
+       connect(&view_, SIGNAL(hover_point_changed(const QWidget*, QPoint)),
+               this, SLOT(on_hover_point_changed(const QWidget*, QPoint)));
        connect(&view_, SIGNAL(offset_changed()),
                this, SLOT(invalidate_tick_position_cache()));
        connect(&view_, SIGNAL(scale_changed()),
@@ -110,6 +114,51 @@ QString Ruler::format_time_with_distance(
                return pv::util::format_time_minutes(t, precision, sign);
 }
 
+pv::util::Timestamp Ruler::get_time_from_x_pos(uint32_t x) const
+{
+       return view_.ruler_offset() + ((double)x + 0.5) * view_.scale();
+}
+
+void Ruler::contextMenuEvent(QContextMenuEvent *event)
+{
+       MarginWidget::contextMenuEvent(event);
+
+       // Don't show a context menu if the MarginWidget found a widget that shows one
+       if (event->isAccepted())
+               return;
+
+       context_menu_x_pos_ = event->pos().x();
+
+       QMenu *const menu = new QMenu(this);
+
+       QAction *const create_marker = new QAction(tr("Create marker here"), this);
+       connect(create_marker, SIGNAL(triggered()), this, SLOT(on_createMarker()));
+       menu->addAction(create_marker);
+
+       QAction *const set_zero_position = new QAction(tr("Set as zero point"), this);
+       connect(set_zero_position, SIGNAL(triggered()), this, SLOT(on_setZeroPosition()));
+       menu->addAction(set_zero_position);
+
+       QAction *const toggle_hover_marker = new QAction(this);
+       connect(toggle_hover_marker, SIGNAL(triggered()), this, SLOT(on_toggleHoverMarker()));
+       menu->addAction(toggle_hover_marker);
+
+       GlobalSettings settings;
+       const bool hover_marker_shown =
+               settings.value(GlobalSettings::Key_View_ShowHoverMarker).toBool();
+       toggle_hover_marker->setText(hover_marker_shown ?
+               tr("Disable mouse hover marker") : tr("Enable mouse hover marker"));
+
+       event->setAccepted(true);
+       menu->popup(event->globalPos());
+}
+
+void Ruler::resizeEvent(QResizeEvent*)
+{
+       // the tick calculation depends on the width of this widget
+       invalidate_tick_position_cache();
+}
+
 vector< shared_ptr<ViewItem> > Ruler::items()
 {
        const vector< shared_ptr<TimeItem> > time_items(view_.time_items());
@@ -126,6 +175,11 @@ shared_ptr<ViewItem> Ruler::get_mouse_over_item(const QPoint &pt)
        return nullptr;
 }
 
+void Ruler::mouseDoubleClickEvent(QMouseEvent *event)
+{
+       view_.add_flag(get_time_from_x_pos(event->x()));
+}
+
 void Ruler::paintEvent(QPaintEvent*)
 {
        if (!tick_position_cache_) {
@@ -140,9 +194,10 @@ void Ruler::paintEvent(QPaintEvent*)
 
                tick_position_cache_ = calculate_tick_positions(
                        view_.tick_period(),
-                       view_.offset(),
+                       view_.ruler_offset(),
                        view_.scale(),
                        width(),
+                       view_.minor_tick_count(),
                        ffunc);
        }
 
@@ -159,10 +214,18 @@ void Ruler::paintEvent(QPaintEvent*)
        p.setPen(palette().color(foregroundRole()));
 
        for (const auto& tick: tick_position_cache_->major) {
-               p.drawText(tick.first, ValueMargin, 0, text_height,
+               const int leftedge = 0;
+               const int rightedge = width();
+               const int x_tick = tick.first;
+               if ((x_tick > leftedge) && (x_tick < rightedge)) {
+                       const int x_left_bound = QFontMetrics(font()).width(tick.second) / 2;
+                       const int x_right_bound = rightedge - x_left_bound;
+                       const int x_legend = min(max(x_tick, x_left_bound), x_right_bound);
+                       p.drawText(x_legend, ValueMargin, 0, text_height,
                                AlignCenter | AlignTop | TextDontClip, tick.second);
-               p.drawLine(QPointF(tick.first, major_tick_y1),
+                       p.drawLine(QPointF(x_tick, major_tick_y1),
                        QPointF(tick.first, ruler_height));
+               }
        }
 
        for (const auto& tick: tick_position_cache_->minor) {
@@ -189,22 +252,48 @@ void Ruler::paintEvent(QPaintEvent*)
        }
 }
 
-Ruler::TickPositions Ruler::calculate_tick_positions(
+void Ruler::draw_hover_mark(QPainter &p, int text_height)
+{
+       const int x = view_.hover_point().x();
+
+       if (x == -1)
+               return;
+
+       p.setPen(QPen(Qt::NoPen));
+       p.setBrush(QBrush(palette().color(foregroundRole())));
+
+       const int b = RulerHeight * text_height;
+       const float hover_arrow_size = HoverArrowSize * text_height;
+       const QPointF points[] = {
+               QPointF(x, b),
+               QPointF(x - hover_arrow_size, b - hover_arrow_size),
+               QPointF(x + hover_arrow_size, b - hover_arrow_size)
+       };
+       p.drawPolygon(points, countof(points));
+}
+
+int Ruler::calculate_text_height() const
+{
+       return QFontMetrics(font()).ascent();
+}
+
+TickPositions Ruler::calculate_tick_positions(
        const pv::util::Timestamp& major_period,
        const pv::util::Timestamp& offset,
        const double scale,
        const int width,
+       const unsigned int minor_tick_count,
        function<QString(const pv::util::Timestamp&)> format_function)
 {
        TickPositions tp;
 
-       const pv::util::Timestamp minor_period = major_period / MinorTickSubdivision;
+       const pv::util::Timestamp minor_period = major_period / minor_tick_count;
        const pv::util::Timestamp first_major_division = floor(offset / major_period);
        const pv::util::Timestamp first_minor_division = ceil(offset / minor_period);
        const pv::util::Timestamp t0 = first_major_division * major_period;
 
        int division = (round(first_minor_division -
-               first_major_division * MinorTickSubdivision)).convert_to<int>() - 1;
+               first_major_division * minor_tick_count)).convert_to<int>() - 1;
 
        double x;
 
@@ -212,9 +301,9 @@ Ruler::TickPositions Ruler::calculate_tick_positions(
                pv::util::Timestamp t = t0 + division * minor_period;
                x = ((t - offset) / scale).convert_to<double>();
 
-               if (division % MinorTickSubdivision == 0) {
+               if (division % minor_tick_count == 0) {
                        // Recalculate 't' without using 'minor_period' which is a fraction
-                       t = t0 + division / MinorTickSubdivision * major_period;
+                       t = t0 + division / minor_tick_count * major_period;
                        tp.major.emplace_back(x, format_function(t));
                } else {
                        tp.minor.emplace_back(x);
@@ -226,50 +315,34 @@ Ruler::TickPositions Ruler::calculate_tick_positions(
        return tp;
 }
 
-void Ruler::mouseDoubleClickEvent(QMouseEvent *event)
+void Ruler::on_hover_point_changed(const QWidget* widget, const QPoint &hp)
 {
-       view_.add_flag(view_.offset() + ((double)event->x() + 0.5) * view_.scale());
-}
-
-void Ruler::draw_hover_mark(QPainter &p, int text_height)
-{
-       const int x = view_.hover_point().x();
+       (void)widget;
+       (void)hp;
 
-       if (x == -1)
-               return;
-
-       p.setPen(QPen(Qt::NoPen));
-       p.setBrush(QBrush(palette().color(foregroundRole())));
-
-       const int b = RulerHeight * text_height;
-       const float hover_arrow_size = HoverArrowSize * text_height;
-       const QPointF points[] = {
-               QPointF(x, b),
-               QPointF(x - hover_arrow_size, b - hover_arrow_size),
-               QPointF(x + hover_arrow_size, b - hover_arrow_size)
-       };
-       p.drawPolygon(points, countof(points));
+       update();
 }
 
-int Ruler::calculate_text_height() const
+void Ruler::invalidate_tick_position_cache()
 {
-       return QFontMetrics(font()).ascent();
+       tick_position_cache_ = boost::none;
 }
 
-void Ruler::hover_point_changed()
+void Ruler::on_createMarker()
 {
-       update();
+       view_.add_flag(get_time_from_x_pos(mouse_down_point_.x()));
 }
 
-void Ruler::invalidate_tick_position_cache()
+void Ruler::on_setZeroPosition()
 {
-       tick_position_cache_ = boost::none;
+       view_.set_zero_position(get_time_from_x_pos(mouse_down_point_.x()));
 }
 
-void Ruler::resizeEvent(QResizeEvent*)
+void Ruler::on_toggleHoverMarker()
 {
-       // the tick calculation depends on the width of this widget
-       invalidate_tick_position_cache();
+       GlobalSettings settings;
+       const bool state = settings.value(GlobalSettings::Key_View_ShowHoverMarker).toBool();
+       settings.setValue(GlobalSettings::Key_View_ShowHoverMarker, !state);
 }
 
 } // namespace trace
index 40aae48daa3faed09cb258b49f02599d5f942e02..b14148728c579b8c8f2885c8a76859fae976a3ca 100644 (file)
@@ -46,6 +46,17 @@ namespace trace {
 class TimeItem;
 class ViewItem;
 
+struct TickPositions
+{
+       vector<pair<double, QString>> major;
+       vector<double> minor;
+};
+
+/**
+ * The Ruler class manages and displays the time scale above the trace canvas.
+ * It may also contain @ref TimeItem instances used to identify or highlight
+ * time-related information.
+ */
 class Ruler : public MarginWidget
 {
        Q_OBJECT
@@ -58,15 +69,12 @@ private:
        /// Height of the ruler in multipes of the text height
        static const float RulerHeight;
 
-       static const int MinorTickSubdivision;
-
        /// Height of the hover arrow in multiples of the text height
        static const float HoverArrowSize;
 
 public:
        Ruler(View &parent);
 
-public:
        QSize sizeHint() const override;
 
        /**
@@ -109,6 +117,12 @@ public:
                unsigned precision = 0,
                bool sign = true);
 
+       pv::util::Timestamp get_time_from_x_pos(uint32_t x) const;
+
+protected:
+       virtual void contextMenuEvent(QContextMenuEvent *event) override;
+       void resizeEvent(QResizeEvent*) override;
+
 private:
        /**
         * Gets the time items.
@@ -123,10 +137,10 @@ private:
         */
        shared_ptr<ViewItem> get_mouse_over_item(const QPoint &pt) override;
 
-       void paintEvent(QPaintEvent *event) override;
-
        void mouseDoubleClickEvent(QMouseEvent *event) override;
 
+       void paintEvent(QPaintEvent *event) override;
+
        /**
         * Draw a hover arrow under the cursor position.
         * @param p The painter to draw into.
@@ -136,23 +150,11 @@ private:
 
        int calculate_text_height() const;
 
-       struct TickPositions
-       {
-               vector<pair<double, QString>> major;
-               vector<double> minor;
-       };
-
-       /**
-        * Holds the tick positions so that they don't have to be recalculated on
-        * every redraw. Set by 'paintEvent()' when needed.
-        */
-       boost::optional<TickPositions> tick_position_cache_;
-
        /**
         * Calculates the major and minor tick positions.
         *
         * @param major_period The period between the major ticks.
-        * @param offset The time at the left border of the ruler.
+        * @param offset The virtual time at the left border of the ruler.
         * @param scale The scale in seconds per pixel.
         * @param width the Width of the ruler.
         * @param format_function A function used to format the major tick times.
@@ -165,16 +167,26 @@ private:
                const pv::util::Timestamp& offset,
                const double scale,
                const int width,
+               const unsigned int minor_tick_count,
                function<QString(const pv::util::Timestamp&)> format_function);
 
-protected:
-       void resizeEvent(QResizeEvent*) override;
-
 private Q_SLOTS:
-       void hover_point_changed();
+       void on_hover_point_changed(const QWidget* widget, const QPoint &hp);
 
-       // Resets the 'tick_position_cache_'.
        void invalidate_tick_position_cache();
+
+       void on_createMarker();
+       void on_setZeroPosition();
+       void on_toggleHoverMarker();
+
+private:
+       /**
+        * Holds the tick positions so that they don't have to be recalculated on
+        * every redraw. Set by 'paintEvent()' when needed.
+        */
+       boost::optional<TickPositions> tick_position_cache_;
+
+       uint32_t context_menu_x_pos_;
 };
 
 } // namespace trace
index a55598cf3199854aacf98c4dad12558a6cecc4cb..0c0cde05e18aa4d9d9f5cab9b748743b7fbffc7c 100644 (file)
@@ -36,7 +36,6 @@
 #include "view.hpp"
 
 using std::shared_ptr;
-using std::make_shared;
 
 namespace pv {
 namespace views {
@@ -63,8 +62,6 @@ Signal::Signal(pv::Session &session,
        shared_ptr<data::SignalBase> channel) :
        Trace(channel),
        session_(session),
-       scale_handle_(make_shared<SignalScaleHandle>(*this)),
-       items_({scale_handle_}),
        name_widget_(nullptr)
 {
        assert(base_);
@@ -75,7 +72,7 @@ Signal::Signal(pv::Session &session,
 
 void Signal::set_name(QString name)
 {
-       Trace::set_name(name);
+       base_->set_name(name);
 
        if (name != name_widget_->currentText())
                name_widget_->setEditText(name);
@@ -101,11 +98,6 @@ void Signal::restore_settings(QSettings &settings)
        (void)settings;
 }
 
-const ViewItemOwner::item_list& Signal::child_items() const
-{
-       return items_;
-}
-
 void Signal::paint_back(QPainter &p, ViewItemPaintParams &pp)
 {
        if (base_->enabled())
@@ -135,12 +127,12 @@ void Signal::populate_popup_form(QWidget *parent, QFormLayout *form)
 
        form->addRow(tr("Name"), name_widget_);
 
-       add_colour_option(parent, form);
+       add_color_option(parent, form);
 }
 
-QMenu* Signal::create_context_menu(QWidget *parent)
+QMenu* Signal::create_header_context_menu(QWidget *parent)
 {
-       QMenu *const menu = Trace::create_context_menu(parent);
+       QMenu *const menu = Trace::create_header_context_menu(parent);
 
        menu->addSeparator();
 
index 83472818034a8592bd60108607f7ef19fd7e8f09..1b9f254330a79fd150f6f73165f5cb910a76b5be 100644 (file)
@@ -27,7 +27,8 @@
 
 #include <cstdint>
 
-#include "signalscalehandle.hpp"
+#include <pv/data/logicsegment.hpp>
+
 #include "trace.hpp"
 #include "viewitemowner.hpp"
 
@@ -45,6 +46,15 @@ class SignalData;
 namespace views {
 namespace trace {
 
+/**
+ * The Signal class represents a series of numeric values that can be drawn.
+ * This is the main difference to the more generic @ref Trace class.
+ *
+ * It is generally accepted that Signal instances consider themselves to be
+ * individual channels on e.g. an oscilloscope, though it should be kept in
+ * mind that virtual signals (e.g. math) will also be served by the Signal
+ * class.
+ */
 class Signal : public Trace, public ViewItemOwner
 {
        Q_OBJECT
@@ -60,6 +70,15 @@ public:
 
        virtual shared_ptr<pv::data::SignalData> data() const = 0;
 
+       /**
+        * Determines the closest level change (i.e. edge) to a given sample, which
+        * is useful for e.g. the "snap to edge" functionality.
+        *
+        * @param sample_pos Sample to use
+        * @return The changes left and right of the given position
+        */
+       virtual vector<data::LogicSegment::EdgePair> get_nearest_level_changes(uint64_t sample_pos) = 0;
+
        /**
         * Returns true if the trace is visible and enabled.
         */
@@ -71,35 +90,14 @@ public:
 
        virtual void restore_settings(QSettings &settings);
 
-       /**
-        * Returns a list of row items owned by this object.
-        */
-       const item_list& child_items() const;
-
        void paint_back(QPainter &p, ViewItemPaintParams &pp);
 
        virtual void populate_popup_form(QWidget *parent, QFormLayout *form);
 
-       QMenu* create_context_menu(QWidget *parent);
+       QMenu* create_header_context_menu(QWidget *parent);
 
        void delete_pressed();
 
-       /**
-        * Returns the offset to show the drag handle.
-        */
-       virtual int scale_handle_offset() const = 0;
-
-       /**
-        * Handles the scale handle being dragged to an offset.
-        * @param offset the offset the scale handle was dragged to.
-        */
-       virtual void scale_handle_dragged(int offset) = 0;
-
-       /**
-        * Handles the scale handle being being released.
-        */
-       virtual void scale_handle_released() {};
-
 protected Q_SLOTS:
        virtual void on_name_changed(const QString &text);
 
@@ -110,9 +108,6 @@ protected Q_SLOTS:
 protected:
        pv::Session &session_;
 
-       const shared_ptr<SignalScaleHandle> scale_handle_;
-       const item_list items_;
-
        QComboBox *name_widget_;
 };
 
diff --git a/pv/views/trace/signalscalehandle.cpp b/pv/views/trace/signalscalehandle.cpp
deleted file mode 100644 (file)
index 11f9bde..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * This file is part of the PulseView project.
- *
- * Copyright (C) 2015 Joel Holdsworth <joel@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, see <http://www.gnu.org/licenses/>.
- */
-
-#include <algorithm>
-
-#include <QRadialGradient>
-
-#include "signal.hpp"
-#include "signalscalehandle.hpp"
-#include "tracetreeitemowner.hpp"
-
-using std::max;
-using std::min;
-
-namespace pv {
-namespace views {
-namespace trace {
-
-SignalScaleHandle::SignalScaleHandle(Signal &owner) :
-       owner_(owner)
-{
-}
-
-bool SignalScaleHandle::enabled() const
-{
-       return selected() || owner_.selected();
-}
-
-void SignalScaleHandle::select(bool select)
-{
-       ViewItem::select(select);
-       owner_.owner()->row_item_appearance_changed(true, true);
-}
-
-void SignalScaleHandle::drag_release()
-{
-       RowItem::drag_release();
-       owner_.scale_handle_released();
-       owner_.owner()->row_item_appearance_changed(true, true);
-}
-
-void SignalScaleHandle::drag_by(const QPoint &delta)
-{
-       owner_.scale_handle_dragged(
-               drag_point_.y() + delta.y() - owner_.get_visual_y());
-       owner_.owner()->row_item_appearance_changed(true, true);
-}
-
-QPoint SignalScaleHandle::point(const QRect &rect) const
-{
-       return owner_.point(rect) + QPoint(0, owner_.scale_handle_offset());
-}
-
-QRectF SignalScaleHandle::hit_box_rect(const ViewItemPaintParams &pp) const
-{
-       const int text_height = ViewItemPaintParams::text_height();
-       const double x = -pp.pixels_offset() - text_height / 2;
-       const double min_x = pp.left() + text_height;
-       const double max_x = pp.right() - text_height * 2;
-       return QRectF(min(max(x, min_x), max_x),
-               owner_.get_visual_y() + owner_.scale_handle_offset() -
-                       text_height / 2,
-               text_height, text_height);
-}
-
-void SignalScaleHandle::paint_fore(QPainter &p, ViewItemPaintParams &pp)
-{
-       if (!enabled())
-               return;
-
-       const QRectF r(hit_box_rect(pp));
-       const QPointF c = (r.topLeft() + 2 * r.center()) / 3;
-       QRadialGradient gradient(c, r.width(), c);
-
-       if (selected()) {
-               gradient.setColorAt(0.0, QColor(255, 255, 255));
-               gradient.setColorAt(0.75, QColor(192, 192, 192));
-               gradient.setColorAt(1.0, QColor(128, 128, 128));
-       } else {
-               gradient.setColorAt(0.0, QColor(192, 192, 192));
-               gradient.setColorAt(0.75, QColor(128, 128, 128));
-               gradient.setColorAt(1.0, QColor(128, 128, 128));
-       }
-
-       p.setBrush(QBrush(gradient));
-       p.setPen(QColor(128, 128, 128));
-       p.drawEllipse(r);
-}
-
-} // namespace trace
-} // namespace views
-} // namespace pv
diff --git a/pv/views/trace/signalscalehandle.hpp b/pv/views/trace/signalscalehandle.hpp
deleted file mode 100644 (file)
index 54eb59e..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * This file is part of the PulseView project.
- *
- * Copyright (C) 2015 Joel Holdsworth <joel@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, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_SIGNALSCALEHANDLE_HPP
-#define PULSEVIEW_PV_VIEWS_TRACEVIEW_SIGNALSCALEHANDLE_HPP
-
-#include "rowitem.hpp"
-
-namespace pv {
-namespace views {
-namespace trace {
-
-class Signal;
-
-/**
- * A row item owned by a @c Signal that implements the v-scale adjustment grab
- * handle.
- */
-class SignalScaleHandle : public RowItem
-{
-       Q_OBJECT
-public:
-       /**
-        * Constructor
-        */
-       explicit SignalScaleHandle(Signal &owner);
-
-public:
-       /**
-        * Returns true if the parent item is enabled.
-        */
-       bool enabled() const;
-
-       /**
-        * Selects or deselects the signal.
-        */
-       void select(bool select = true);
-
-       /**
-        * Sets this item into the un-dragged state.
-        */
-       void drag_release();
-
-       /**
-        * Drags the item to a delta relative to the drag point.
-        * @param delta the offset from the drag point.
-        */
-       void drag_by(const QPoint &delta);
-
-       /**
-        * Get the drag point.
-        * @param rect the rectangle of the widget area.
-        */
-       QPoint point(const QRect &rect) const;
-
-       /**
-        * Computes the outline rectangle of the viewport hit-box.
-        * @param rect the rectangle of the viewport area.
-        * @return Returns the rectangle of the hit-box.
-        */
-       QRectF hit_box_rect(const ViewItemPaintParams &pp) const;
-
-       /**
-        * Paints the foreground layer of the item with a QPainter
-        * @param p the QPainter to paint into.
-        * @param pp the painting parameters object to paint with.
-        */
-       void paint_fore(QPainter &p, ViewItemPaintParams &pp);
-
-private:
-       Signal &owner_;
-};
-
-} // namespace trace
-} // namespace views
-} // namespace pv
-
-#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_SIGNALSCALEHANDLE_HPP
index bc2c3ba8a3cb9380710eb7462d51663a9987eca8..2269750600a984b2aba799cc9ca42f03015cb285 100644 (file)
@@ -41,8 +41,12 @@ StandardBar::StandardBar(Session &session, QWidget *parent,
        action_view_zoom_in_(new QAction(this)),
        action_view_zoom_out_(new QAction(this)),
        action_view_zoom_fit_(new QAction(this)),
-       action_view_zoom_one_to_one_(new QAction(this)),
-       action_view_show_cursors_(new QAction(this))
+       action_view_show_cursors_(new QAction(this)),
+       segment_display_mode_selector_(new QToolButton(this)),
+       action_sdm_last_(new QAction(this)),
+       action_sdm_last_complete_(new QAction(this)),
+       action_sdm_single_(new QAction(this)),
+       segment_selector_(new QSpinBox(this))
 {
        setObjectName(QString::fromUtf8("StandardBar"));
 
@@ -70,13 +74,6 @@ StandardBar::StandardBar(Session &session, QWidget *parent,
        connect(action_view_zoom_fit_, SIGNAL(triggered(bool)),
                this, SLOT(on_actionViewZoomFit_triggered(bool)));
 
-       action_view_zoom_one_to_one_->setText(tr("Zoom to O&ne-to-One"));
-       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));
-       connect(action_view_zoom_one_to_one_, SIGNAL(triggered(bool)),
-               this, SLOT(on_actionViewZoomOneToOne_triggered()));
-
        action_view_show_cursors_->setCheckable(true);
        action_view_show_cursors_->setIcon(QIcon(":/icons/show-cursors.svg"));
        action_view_show_cursors_->setShortcut(QKeySequence(Qt::Key_C));
@@ -84,9 +81,53 @@ StandardBar::StandardBar(Session &session, QWidget *parent,
                this, SLOT(on_actionViewShowCursors_triggered()));
        action_view_show_cursors_->setText(tr("Show &Cursors"));
 
+       action_sdm_last_->setIcon(QIcon(":/icons/view-displaymode-last_segment.svg"));
+       action_sdm_last_->setText(tr("Display last segment only"));
+       connect(action_sdm_last_, SIGNAL(triggered(bool)),
+               this, SLOT(on_actionSDMLast_triggered()));
+
+       action_sdm_last_complete_->setIcon(QIcon(":/icons/view-displaymode-last_complete_segment.svg"));
+       action_sdm_last_complete_->setText(tr("Display last complete segment only"));
+       connect(action_sdm_last_complete_, SIGNAL(triggered(bool)),
+               this, SLOT(on_actionSDMLastComplete_triggered()));
+
+       action_sdm_single_->setIcon(QIcon(":/icons/view-displaymode-single_segment.svg"));
+       action_sdm_single_->setText(tr("Display a single segment"));
+       connect(action_view_show_cursors_, SIGNAL(triggered(bool)),
+               this, SLOT(on_actionSDMSingle_triggered()));
+
+       segment_display_mode_selector_->addAction(action_sdm_last_);
+       segment_display_mode_selector_->addAction(action_sdm_last_complete_);
+       segment_display_mode_selector_->addAction(action_sdm_single_);
+       segment_display_mode_selector_->setPopupMode(QToolButton::InstantPopup);
+       segment_display_mode_selector_->hide();
+
+       segment_selector_->setMinimum(1);
+       segment_selector_->hide();
+
+       connect(&session_, SIGNAL(new_segment(int)),
+               this, SLOT(on_new_segment(int)));
+
+       connect(&session_, SIGNAL(segment_completed(int)),
+               view_, SLOT(on_segment_completed(int)));
+
+       connect(segment_selector_, SIGNAL(valueChanged(int)),
+               this, SLOT(on_segment_selected(int)));
+       connect(view_, SIGNAL(segment_changed(int)),
+               this, SLOT(on_segment_changed(int)));
+
+       connect(this, SIGNAL(segment_selected(int)),
+               view_, SLOT(on_segment_changed(int)));
+
+       connect(view_, SIGNAL(segment_display_mode_changed(int, bool)),
+               this, SLOT(on_segment_display_mode_changed(int, bool)));
+
        connect(view_, SIGNAL(always_zoom_to_fit_changed(bool)),
                this, SLOT(on_always_zoom_to_fit_changed(bool)));
 
+       connect(view_, SIGNAL(cursor_state_changed(bool)),
+               this, SLOT(on_cursor_state_changed(bool)));
+
        if (add_default_widgets)
                add_toolbar_widgets();
 }
@@ -102,9 +143,24 @@ void StandardBar::add_toolbar_widgets()
        addAction(action_view_zoom_in_);
        addAction(action_view_zoom_out_);
        addAction(action_view_zoom_fit_);
-       addAction(action_view_zoom_one_to_one_);
        addSeparator();
        addAction(action_view_show_cursors_);
+       multi_segment_actions_.push_back(addSeparator());
+       multi_segment_actions_.push_back(addWidget(segment_display_mode_selector_));
+       multi_segment_actions_.push_back(addWidget(segment_selector_));
+       addSeparator();
+
+       // Hide the multi-segment UI until we know that there are multiple segments
+       show_multi_segment_ui(false);
+}
+
+void StandardBar::show_multi_segment_ui(const bool state)
+{
+       for (QAction* action : multi_segment_actions_)
+               action->setVisible(state);
+
+       on_segment_display_mode_changed(view_->segment_display_mode(),
+               view_->segment_is_selectable());
 }
 
 QAction* StandardBar::action_view_zoom_in() const
@@ -122,11 +178,6 @@ QAction* StandardBar::action_view_zoom_fit() const
        return action_view_zoom_fit_;
 }
 
-QAction* StandardBar::action_view_zoom_one_to_one() const
-{
-       return action_view_zoom_one_to_one_;
-}
-
 QAction* StandardBar::action_view_show_cursors() const
 {
        return action_view_show_cursors_;
@@ -147,25 +198,106 @@ void StandardBar::on_actionViewZoomFit_triggered(bool checked)
        view_->zoom_fit(checked);
 }
 
-void StandardBar::on_actionViewZoomOneToOne_triggered()
-{
-       view_->zoom_one_to_one();
-}
-
 void StandardBar::on_actionViewShowCursors_triggered()
 {
-       const bool show = !view_->cursors_shown();
+       const bool show = action_view_show_cursors_->isChecked();
+
        if (show)
                view_->centre_cursors();
 
        view_->show_cursors(show);
 }
 
+void StandardBar::on_actionSDMLast_triggered()
+{
+       view_->set_segment_display_mode(Trace::ShowLastSegmentOnly);
+}
+
+void StandardBar::on_actionSDMLastComplete_triggered()
+{
+       view_->set_segment_display_mode(Trace::ShowLastCompleteSegmentOnly);
+}
+
+void StandardBar::on_actionSDMSingle_triggered()
+{
+       view_->set_segment_display_mode(Trace::ShowSingleSegmentOnly);
+}
+
 void StandardBar::on_always_zoom_to_fit_changed(bool state)
 {
        action_view_zoom_fit_->setChecked(state);
 }
 
+void StandardBar::on_new_segment(int new_segment_id)
+{
+       if (new_segment_id > 0) {
+               show_multi_segment_ui(true);
+               segment_selector_->setMaximum(new_segment_id + 1);
+       } else
+               show_multi_segment_ui(false);
+}
+
+void StandardBar::on_segment_changed(int segment_id)
+{
+       // We need to adjust the value by 1 because internally, segments
+       // start at 0 while they start with 1 for the spinbox
+       const uint32_t ui_segment_id = segment_id + 1;
+
+       // This is called when the current segment was changed
+       // by other parts of the UI, e.g. the view itself
+
+       // Make sure our value isn't limited by a too low maximum
+       // Note: this can happen if on_segment_changed() is called before
+       // on_new_segment()
+       if ((uint32_t)segment_selector_->maximum() < ui_segment_id)
+               segment_selector_->setMaximum(ui_segment_id);
+
+       segment_selector_->setValue(ui_segment_id);
+}
+
+void StandardBar::on_segment_selected(int ui_segment_id)
+{
+       // We need to adjust the value by 1 because internally, segments
+       // start at 0 while they start with 1 for the spinbox
+       const uint32_t segment_id = ui_segment_id - 1;
+
+       // This is called when the user selected a segment using the spin box
+       // or when the value of the spinbox was assigned a new value. Since we
+       // only care about the former, we filter out the latter:
+       if (segment_id == view_->current_segment())
+               return;
+
+       // No matter which segment display mode we were in, we now show a single segment
+       if (view_->segment_display_mode() != Trace::ShowSingleSegmentOnly)
+               on_actionSDMSingle_triggered();
+
+       segment_selected(segment_id);
+}
+
+void StandardBar::on_segment_display_mode_changed(int mode, bool segment_selectable)
+{
+       segment_selector_->setReadOnly(!segment_selectable);
+
+       switch ((Trace::SegmentDisplayMode)mode) {
+       case Trace::ShowLastSegmentOnly:
+               segment_display_mode_selector_->setDefaultAction(action_sdm_last_);
+               break;
+       case Trace::ShowLastCompleteSegmentOnly:
+               segment_display_mode_selector_->setDefaultAction(action_sdm_last_complete_);
+               break;
+       case Trace::ShowSingleSegmentOnly:
+               segment_display_mode_selector_->setDefaultAction(action_sdm_single_);
+               break;
+       default:
+               break;
+       }
+}
+
+void StandardBar::on_cursor_state_changed(bool show)
+{
+       action_view_show_cursors_->setChecked(show);
+}
+
 } // namespace trace
 } // namespace views
 } // namespace pv
index 09c7907f3da3f4fc5fdb55bfa548267bd0fbce28..9c27f43ee171a4e8b9bfde6a3393672c758876c6 100644 (file)
 #include <cstdint>
 
 #include <QAction>
+#include <QSpinBox>
 #include <QToolBar>
+#include <QToolButton>
 #include <QWidget>
 
 #include <pv/session.hpp>
 
+#include "trace.hpp"
+
 namespace pv {
 
 class MainWindow;
@@ -55,21 +59,31 @@ public:
        QAction* action_view_zoom_in() const;
        QAction* action_view_zoom_out() const;
        QAction* action_view_zoom_fit() const;
-       QAction* action_view_zoom_one_to_one() const;
        QAction* action_view_show_cursors() const;
 
 protected:
        virtual void add_toolbar_widgets();
 
+       virtual void show_multi_segment_ui(const bool state);
+
        Session &session_;
        trace::View *view_;
 
        QAction *const action_view_zoom_in_;
        QAction *const action_view_zoom_out_;
        QAction *const action_view_zoom_fit_;
-       QAction *const action_view_zoom_one_to_one_;
        QAction *const action_view_show_cursors_;
 
+       QToolButton *segment_display_mode_selector_;
+       QAction *const action_sdm_last_;
+       QAction *const action_sdm_last_complete_;
+       QAction *const action_sdm_single_;
+
+       QSpinBox *segment_selector_;
+
+Q_SIGNALS:
+       void segment_selected(int segment_id);
+
 protected Q_SLOTS:
        void on_actionViewZoomIn_triggered();
 
@@ -77,11 +91,22 @@ protected Q_SLOTS:
 
        void on_actionViewZoomFit_triggered(bool checked);
 
-       void on_actionViewZoomOneToOne_triggered();
-
        void on_actionViewShowCursors_triggered();
+       void on_cursor_state_changed(bool show);
+
+       void on_actionSDMLast_triggered();
+       void on_actionSDMLastComplete_triggered();
+       void on_actionSDMSingle_triggered();
 
        void on_always_zoom_to_fit_changed(bool state);
+
+       void on_new_segment(int new_segment_id);
+       void on_segment_changed(int segment_id);
+       void on_segment_selected(int ui_segment_id);
+       void on_segment_display_mode_changed(int mode, bool segment_selectable);
+
+private:
+       vector<QAction*> multi_segment_actions_;
 };
 
 } // namespace trace
index bd07e9b28c9383f1c4a48fbe3cd3bdd6b40ae6b4..47a7da3c503bbd616d5085a66b4ea5c8c103c74d 100644 (file)
@@ -17,6 +17,7 @@
  * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
+#include "signal.hpp"
 #include "timeitem.hpp"
 #include "view.hpp"
 
@@ -30,8 +31,13 @@ TimeItem::TimeItem(View &view) :
 
 void TimeItem::drag_by(const QPoint &delta)
 {
-       set_time(view_.offset() + (drag_point_.x() + delta.x() - 0.5) *
-               view_.scale());
+       int64_t sample_num = view_.get_nearest_level_change(drag_point_ + delta);
+
+       if (sample_num > -1)
+               set_time(sample_num / view_.get_signal_under_mouse_cursor()->base()->get_samplerate());
+       else
+               set_time(view_.offset() + (drag_point_.x() + delta.x() - 0.5) *
+                       view_.scale());
 }
 
 } // namespace trace
index dd8d3c4a67c16a982b740475418c9c3827bace12..ba858254f786aadf0477a61bfc495d1a53dd786e 100644 (file)
@@ -28,8 +28,11 @@ namespace trace {
 
 class View;
 
+/**
+ * The TimeItem class represents items on the @ref Ruler. It is generic in
+ * nature, not making assumptions about the kind of item shown.
+ */
 class TimeItem : public ViewItem
-
 {
        Q_OBJECT
 
index 487496d9fd102d1c8c3f72295b0669259fbdf702..266007a57053c220f59a87fe80eb630438a413c0 100644 (file)
@@ -44,9 +44,9 @@ namespace trace {
 const int TimeMarker::ArrowSize = 4;
 
 TimeMarker::TimeMarker(
-       View &view, const QColor &colour, const pv::util::Timestamp& time) :
+       View &view, const QColor &color, const pv::util::Timestamp& time) :
        TimeItem(view),
-       colour_(colour),
+       color_(color),
        time_(time),
        value_action_(nullptr),
        value_widget_(nullptr),
@@ -78,9 +78,11 @@ float TimeMarker::get_x() const
        return roundf(((time_ - view_.offset()) / view_.scale()).convert_to<float>()) + 0.5f;
 }
 
-QPoint TimeMarker::point(const QRect &rect) const
+QPoint TimeMarker::drag_point(const QRect &rect) const
 {
-       return QPoint(get_x(), rect.bottom());
+       (void)rect;
+
+       return QPoint(get_x(), view_.mapFromGlobal(QCursor::pos()).y());
 }
 
 QRectF TimeMarker::label_rect(const QRectF &rect) const
@@ -140,18 +142,18 @@ void TimeMarker::paint_label(QPainter &p, const QRect &rect, bool hover)
        }
 
        p.setPen(Qt::transparent);
-       p.setBrush(hover ? colour_.lighter() : colour_);
+       p.setBrush(hover ? color_.lighter() : color_);
        p.drawPolygon(points, countof(points));
 
-       p.setPen(colour_.lighter());
+       p.setPen(color_.lighter());
        p.setBrush(Qt::transparent);
        p.drawPolygon(highlight_points, countof(highlight_points));
 
-       p.setPen(colour_.darker());
+       p.setPen(color_.darker());
        p.setBrush(Qt::transparent);
        p.drawPolygon(points, countof(points));
 
-       p.setPen(select_text_colour(colour_));
+       p.setPen(select_text_color(color_));
        p.drawText(r, Qt::AlignCenter | Qt::AlignVCenter, get_text());
 }
 
@@ -161,7 +163,7 @@ void TimeMarker::paint_fore(QPainter &p, ViewItemPaintParams &pp)
                return;
 
        const float x = get_x();
-       p.setPen(colour_.darker());
+       p.setPen(color_.darker());
        p.drawLine(QPointF(x, pp.top()), QPointF(x, pp.bottom()));
 }
 
@@ -171,7 +173,7 @@ pv::widgets::Popup* TimeMarker::create_popup(QWidget *parent)
 
        Popup *const popup = new Popup(parent);
        popup->set_position(parent->mapToGlobal(
-               point(parent->rect())), Popup::Bottom);
+               drag_point(parent->rect())), Popup::Bottom);
 
        QFormLayout *const form = new QFormLayout(popup);
        popup->setLayout(form);
index 7c49948b497179a29a32fd22cfcc3e633c2a78f1..48afc16bb77e9c346ec75e80c06ab3564873ecc1 100644 (file)
@@ -41,6 +41,10 @@ namespace trace {
 
 class View;
 
+/**
+ * The TimeMarker class represents items on the @ref Ruler that highlight a
+ * single point in time to the user. Aside from this, it is generic in nature.
+ */
 class TimeMarker : public TimeItem
 {
        Q_OBJECT
@@ -52,10 +56,10 @@ protected:
        /**
         * Constructor.
         * @param view A reference to the view that owns this marker.
-        * @param colour A reference to the colour of this cursor.
+        * @param color A reference to the color of this cursor.
         * @param time The time to set the flag to.
         */
-       TimeMarker(View &view, const QColor &colour, const pv::util::Timestamp& time);
+       TimeMarker(View &view, const QColor &color, const pv::util::Timestamp& time);
 
 public:
        /**
@@ -74,7 +78,7 @@ public:
         * Gets the arrow-tip point of the time marker.
         * @param rect the rectangle of the ruler area.
         */
-       QPoint point(const QRect &rect) const override;
+       QPoint drag_point(const QRect &rect) const override;
 
        /**
         * Computes the outline rectangle of a label.
@@ -116,7 +120,7 @@ private Q_SLOTS:
        void on_value_changed(const pv::util::Timestamp& value);
 
 protected:
-       const QColor &colour_;
+       const QColor &color_;
 
        pv::util::Timestamp time_;
 
index 46d9edb9ba14bbcfdf2069b42390415b34a6299c..5c854aed17e2aa3db35f4b668ec6d5ad4b09662d 100644 (file)
 #include <QFormLayout>
 #include <QKeyEvent>
 #include <QLineEdit>
+#include <QMenu>
 
+#include "ruler.hpp"
 #include "trace.hpp"
 #include "tracepalette.hpp"
 #include "view.hpp"
 
 #include "pv/globalsettings.hpp"
-#include "pv/widgets/colourbutton.hpp"
+#include "pv/widgets/colorbutton.hpp"
 #include "pv/widgets/popup.hpp"
 
 using std::pair;
@@ -45,18 +47,32 @@ namespace trace {
 const QPen Trace::AxisPen(QColor(0, 0, 0, 30 * 256 / 100));
 const int Trace::LabelHitPadding = 2;
 
-const QColor Trace::BrightGrayBGColour = QColor(0, 0, 0, 10 * 255 / 100);
-const QColor Trace::DarkGrayBGColour = QColor(0, 0, 0, 15 * 255 / 100);
+const QColor Trace::BrightGrayBGColor = QColor(0, 0, 0, 10 * 255 / 100);
+const QColor Trace::DarkGrayBGColor = QColor(0, 0, 0, 15 * 255 / 100);
 
 Trace::Trace(shared_ptr<data::SignalBase> channel) :
        base_(channel),
+       axis_pen_(AxisPen),
+       segment_display_mode_(ShowLastSegmentOnly),  // Will be overwritten by View
+       current_segment_(0),
        popup_(nullptr),
        popup_form_(nullptr)
 {
        connect(channel.get(), SIGNAL(name_changed(const QString&)),
                this, SLOT(on_name_changed(const QString&)));
-       connect(channel.get(), SIGNAL(colour_changed(const QColor&)),
-               this, SLOT(on_colour_changed(const QColor&)));
+       connect(channel.get(), SIGNAL(color_changed(const QColor&)),
+               this, SLOT(on_color_changed(const QColor&)));
+
+       GlobalSettings::add_change_handler(this);
+
+       GlobalSettings settings;
+       show_hover_marker_ =
+               settings.value(GlobalSettings::Key_View_ShowHoverMarker).toBool();
+}
+
+Trace::~Trace()
+{
+       GlobalSettings::remove_change_handler(this);
 }
 
 shared_ptr<data::SignalBase> Trace::base() const
@@ -64,11 +80,49 @@ shared_ptr<data::SignalBase> Trace::base() const
        return base_;
 }
 
+bool Trace::is_selectable(QPoint pos) const
+{
+       // True if the header was clicked, false if the trace area was clicked
+       const View *view = owner_->view();
+       assert(view);
+
+       return (pos.x() <= view->header_width());
+}
+
+bool Trace::is_draggable(QPoint pos) const
+{
+       // While the header label that belongs to this trace is draggable,
+       // the trace itself shall not be. Hence we return true if the header
+       // was clicked and false if the trace area was clicked
+       const View *view = owner_->view();
+       assert(view);
+
+       return (pos.x() <= view->header_width());
+}
+
+void Trace::set_segment_display_mode(SegmentDisplayMode mode)
+{
+       segment_display_mode_ = mode;
+
+       if (owner_)
+               owner_->row_item_appearance_changed(true, true);
+}
+
+void Trace::on_setting_changed(const QString &key, const QVariant &value)
+{
+       if (key == GlobalSettings::Key_View_ShowHoverMarker)
+               show_hover_marker_ = value.toBool();
+
+       // Force a repaint since many options alter the way traces look
+       if (owner_)
+               owner_->row_item_appearance_changed(false, true);
+}
+
 void Trace::paint_label(QPainter &p, const QRect &rect, bool hover)
 {
        const int y = get_visual_y();
 
-       p.setBrush(base_->colour());
+       p.setBrush(base_->color());
 
        if (!enabled())
                return;
@@ -102,28 +156,56 @@ void Trace::paint_label(QPainter &p, const QRect &rect, bool hover)
        }
 
        p.setPen(Qt::transparent);
-       p.setBrush(hover ? base_->colour().lighter() : base_->colour());
+       p.setBrush(hover ? base_->color().lighter() : base_->color());
        p.drawPolygon(points, countof(points));
 
-       p.setPen(base_->colour().lighter());
+       p.setPen(base_->color().lighter());
        p.setBrush(Qt::transparent);
        p.drawPolygon(highlight_points, countof(highlight_points));
 
-       p.setPen(base_->colour().darker());
+       p.setPen(base_->color().darker());
        p.setBrush(Qt::transparent);
        p.drawPolygon(points, countof(points));
 
        // Paint the text
-       p.setPen(select_text_colour(base_->colour()));
+       p.setPen(select_text_color(base_->color()));
        p.setFont(QApplication::font());
        p.drawText(QRectF(r.x(), r.y(),
                r.width() - label_arrow_length, r.height()),
                Qt::AlignCenter | Qt::AlignVCenter, base_->name());
 }
 
-QMenu* Trace::create_context_menu(QWidget *parent)
+QMenu* Trace::create_header_context_menu(QWidget *parent)
+{
+       QMenu *const menu = ViewItem::create_header_context_menu(parent);
+
+       return menu;
+}
+
+QMenu* Trace::create_view_context_menu(QWidget *parent, QPoint &click_pos)
 {
-       QMenu *const menu = ViewItem::create_context_menu(parent);
+       context_menu_x_pos_ = click_pos.x();
+
+       // Get entries from default menu before adding our own
+       QMenu *const menu = new QMenu(parent);
+
+       QMenu* default_menu = TraceTreeItem::create_view_context_menu(parent, click_pos);
+       if (default_menu) {
+               for (QAction *action : default_menu->actions()) {  // clazy:exclude=range-loop
+                       menu->addAction(action);
+                       if (action->parent() == default_menu)
+                               action->setParent(menu);
+               }
+               delete default_menu;
+
+               // Add separator if needed
+               if (menu->actions().length() > 0)
+                       menu->addSeparator();
+       }
+
+       QAction *const create_marker_here = new QAction(tr("Create marker here"), this);
+       connect(create_marker_here, SIGNAL(triggered()), this, SLOT(on_create_marker_here()));
+       menu->addAction(create_marker_here);
 
        return menu;
 }
@@ -134,7 +216,7 @@ pv::widgets::Popup* Trace::create_popup(QWidget *parent)
 
        popup_ = new Popup(parent);
        popup_->set_position(parent->mapToGlobal(
-               point(parent->rect())), Popup::Right);
+               drag_point(parent->rect())), Popup::Right);
 
        create_popup_form();
 
@@ -159,15 +241,47 @@ QRectF Trace::label_rect(const QRectF &rect) const
                label_size.height());
 }
 
+QRectF Trace::hit_box_rect(const ViewItemPaintParams &pp) const
+{
+       // This one is only for the trace itself, excluding the header area
+       const View *view = owner_->view();
+       assert(view);
+
+       pair<int, int> extents = v_extents();
+       const int top = pp.top() + get_visual_y() + extents.first;
+       const int height = extents.second - extents.first;
+
+       return QRectF(pp.left() + view->header_width(), top,
+               pp.width() - view->header_width(), height);
+}
+
+void Trace::set_current_segment(const int segment)
+{
+       current_segment_ = segment;
+}
+
+int Trace::get_current_segment() const
+{
+       return current_segment_;
+}
+
+void Trace::hover_point_changed(const QPoint &hp)
+{
+       (void)hp;
+
+       if (owner_)
+               owner_->row_item_appearance_changed(false, true);
+}
+
 void Trace::paint_back(QPainter &p, ViewItemPaintParams &pp)
 {
        const View *view = owner_->view();
        assert(view);
 
-       if (view->coloured_bg())
-               p.setBrush(base_->bgcolour());
+       if (view->colored_bg())
+               p.setBrush(base_->bgcolor());
        else
-               p.setBrush(pp.next_bg_colour_state() ? BrightGrayBGColour : DarkGrayBGColour);
+               p.setBrush(pp.next_bg_color_state() ? BrightGrayBGColor : DarkGrayBGColor);
 
        p.setPen(QPen(Qt::NoPen));
 
@@ -180,24 +294,44 @@ void Trace::paint_axis(QPainter &p, ViewItemPaintParams &pp, int y)
 {
        p.setRenderHint(QPainter::Antialiasing, false);
 
-       p.setPen(AxisPen);
+       p.setPen(axis_pen_);
        p.drawLine(QPointF(pp.left(), y), QPointF(pp.right(), y));
 
        p.setRenderHint(QPainter::Antialiasing, true);
 }
 
-void Trace::add_colour_option(QWidget *parent, QFormLayout *form)
+void Trace::add_color_option(QWidget *parent, QFormLayout *form)
 {
-       using pv::widgets::ColourButton;
+       using pv::widgets::ColorButton;
 
-       ColourButton *const colour_button = new ColourButton(
+       ColorButton *const color_button = new ColorButton(
                TracePalette::Rows, TracePalette::Cols, parent);
-       colour_button->set_palette(TracePalette::Colours);
-       colour_button->set_colour(base_->colour());
-       connect(colour_button, SIGNAL(selected(const QColor&)),
-               this, SLOT(on_colouredit_changed(const QColor&)));
+       color_button->set_palette(TracePalette::Colors);
+       color_button->set_color(base_->color());
+       connect(color_button, SIGNAL(selected(const QColor&)),
+               this, SLOT(on_coloredit_changed(const QColor&)));
+
+       form->addRow(tr("Color"), color_button);
+}
+
+void Trace::paint_hover_marker(QPainter &p)
+{
+       const View *view = owner_->view();
+       assert(view);
+
+       const int x = view->hover_point().x();
+
+       if (x == -1)
+               return;
+
+       p.setPen(QPen(QColor(Qt::lightGray)));
 
-       form->addRow(tr("Colour"), colour_button);
+       const pair<int, int> extents = v_extents();
+
+       p.setRenderHint(QPainter::Antialiasing, false);
+       p.drawLine(x, get_visual_y() + extents.first,
+               x, get_visual_y() + extents.second);
+       p.setRenderHint(QPainter::Antialiasing, true);
 }
 
 void Trace::create_popup_form()
@@ -228,17 +362,7 @@ void Trace::populate_popup_form(QWidget *parent, QFormLayout *form)
                this, SLOT(on_nameedit_changed(const QString&)));
        form->addRow(tr("Name"), name_edit);
 
-       add_colour_option(parent, form);
-}
-
-void Trace::set_name(QString name)
-{
-       base_->set_name(name);
-}
-
-void Trace::set_colour(QColor colour)
-{
-       base_->set_colour(colour);
+       add_color_option(parent, form);
 }
 
 void Trace::on_name_changed(const QString &text)
@@ -252,10 +376,10 @@ void Trace::on_name_changed(const QString &text)
        }
 }
 
-void Trace::on_colour_changed(const QColor &colour)
+void Trace::on_color_changed(const QColor &color)
 {
-       /* This event handler is called by SignalBase when the colour was changed there */
-       (void)colour;
+       /* This event handler is called by SignalBase when the color was changed there */
+       (void)color;
 
        if (owner_)
                owner_->row_item_appearance_changed(true, true);
@@ -270,13 +394,24 @@ void Trace::on_popup_closed()
 void Trace::on_nameedit_changed(const QString &name)
 {
        /* This event handler notifies SignalBase that the name changed */
-       set_name(name);
+       base_->set_name(name);
+}
+
+void Trace::on_coloredit_changed(const QColor &color)
+{
+       /* This event handler notifies SignalBase that the color changed */
+       base_->set_color(color);
 }
 
-void Trace::on_colouredit_changed(const QColor &colour)
+void Trace::on_create_marker_here() const
 {
-       /* This event handler notifies SignalBase that the colour changed */
-       set_colour(colour);
+       View *view = owner_->view();
+       assert(view);
+
+       const Ruler *ruler = view->ruler();
+       QPoint p = ruler->mapFrom(view, QPoint(context_menu_x_pos_, 0));
+
+       view->add_flag(ruler->get_time_from_x_pos(p.x()));
 }
 
 } // namespace trace
index 88b4ea6b032a7d202896e4aa9264bb0ccb3aa7fe..2b98811b32ea3506865fc8db5e9e4924644539c7 100644 (file)
@@ -30,6 +30,7 @@
 
 #include "tracetreeitem.hpp"
 
+#include <pv/globalsettings.hpp>
 #include "pv/data/signalbase.hpp"
 
 using std::shared_ptr;
@@ -49,19 +50,51 @@ class Popup;
 namespace views {
 namespace trace {
 
-class Trace : public TraceTreeItem
+/**
+ * The Trace class represents a @ref TraceTreeItem which occupies some vertical
+ * space on the canvas and spans across its entire width, essentially showing
+ * a time series of values, events, objects or similar. While easily confused
+ * with @ref Signal, the difference is that Trace may represent anything that
+ * can be drawn, not just numeric values. One example is a @ref DecodeTrace.
+ *
+ * For this reason, Trace is more generic and contains properties and helpers
+ * that benefit any kind of time series items.
+ */
+class Trace : public TraceTreeItem, public GlobalSettingsInterface
 {
        Q_OBJECT
 
+public:
+       /**
+        * Allowed values for the multi-segment display mode.
+        *
+        * Note: Consider these locations when updating the list:
+        * *
+        * @ref View::set_segment_display_mode
+        * @ref View::on_segment_changed
+        * @ref AnalogSignal::get_analog_segment_to_paint
+        * @ref AnalogSignal::get_logic_segment_to_paint
+        * @ref LogicSignal::get_logic_segment_to_paint
+        * @ref StandardBar
+        */
+       enum SegmentDisplayMode {
+               ShowLastSegmentOnly = 1,
+               ShowLastCompleteSegmentOnly,
+               ShowSingleSegmentOnly,
+               ShowAllSegments,
+               ShowAccumulatedIntensity
+       };
+
 private:
        static const QPen AxisPen;
        static const int LabelHitPadding;
 
-       static const QColor BrightGrayBGColour;
-       static const QColor DarkGrayBGColour;
+       static const QColor BrightGrayBGColor;
+       static const QColor DarkGrayBGColor;
 
 protected:
        Trace(shared_ptr<data::SignalBase> channel);
+       ~Trace();
 
 public:
        /**
@@ -70,14 +103,21 @@ public:
        shared_ptr<data::SignalBase> base() const;
 
        /**
-        * Sets the name of the signal.
+        * Returns true if the item may be selected.
         */
-       virtual void set_name(QString name);
+       virtual bool is_selectable(QPoint pos) const;
 
        /**
-        * Set the colour of the signal.
+        * Returns true if the item may be dragged/moved.
         */
-       virtual void set_colour(QColor colour);
+       virtual bool is_draggable(QPoint pos) const;
+
+       /**
+        * Configures the segment display mode to use.
+        */
+       virtual void set_segment_display_mode(SegmentDisplayMode mode);
+
+       virtual void on_setting_changed(const QString &key, const QVariant &value);
 
        /**
         * Paints the signal label.
@@ -87,7 +127,9 @@ public:
         */
        virtual void paint_label(QPainter &p, const QRect &rect, bool hover);
 
-       virtual QMenu* create_context_menu(QWidget *parent);
+       virtual QMenu* create_header_context_menu(QWidget *parent);
+
+       virtual QMenu* create_view_context_menu(QWidget *parent, QPoint &click_pos);
 
        pv::widgets::Popup* create_popup(QWidget *parent);
 
@@ -98,6 +140,20 @@ public:
         */
        QRectF label_rect(const QRectF &rect) const;
 
+       /**
+        * Computes the outline rectangle of the viewport hit-box.
+        * @param rect the rectangle of the viewport area.
+        * @return Returns the rectangle of the hit-box.
+        * @remarks The default implementation returns an empty hit-box.
+        */
+       virtual QRectF hit_box_rect(const ViewItemPaintParams &pp) const;
+
+       void set_current_segment(const int segment);
+
+       int get_current_segment() const;
+
+       virtual void hover_point_changed(const QPoint &hp);
+
 protected:
        /**
         * Paints the background layer of the signal with a QPainter.
@@ -114,7 +170,13 @@ protected:
         */
        void paint_axis(QPainter &p, ViewItemPaintParams &pp, int y);
 
-       void add_colour_option(QWidget *parent, QFormLayout *form);
+       /**
+        * Draw a hover marker under the cursor position.
+        * @param p The painter to draw into.
+        */
+       void paint_hover_marker(QPainter &p);
+
+       void add_color_option(QWidget *parent, QFormLayout *form);
 
        void create_popup_form();
 
@@ -123,17 +185,28 @@ protected:
 protected Q_SLOTS:
        virtual void on_name_changed(const QString &text);
 
-       virtual void on_colour_changed(const QColor &colour);
+       virtual void on_color_changed(const QColor &color);
 
        void on_popup_closed();
 
 private Q_SLOTS:
        void on_nameedit_changed(const QString &name);
 
-       void on_colouredit_changed(const QColor &colour);
+       void on_coloredit_changed(const QColor &color);
+
+       void on_create_marker_here() const;
 
 protected:
        shared_ptr<data::SignalBase> base_;
+       QPen axis_pen_;
+
+       SegmentDisplayMode segment_display_mode_;
+       bool show_hover_marker_;
+
+       uint32_t context_menu_x_pos_;
+
+       /// The ID of the currently displayed segment
+       int current_segment_;
 
 private:
        pv::widgets::Popup *popup_;
index 9bcc29e9bb5e40f351c3b9aa725c69b7fbf691a6..3cbca90934d68a8417e17d5feb6bc70fd79bb3a6 100644 (file)
@@ -39,7 +39,7 @@ namespace trace {
 const int TraceGroup::Padding = 8;
 const int TraceGroup::Width = 12;
 const int TraceGroup::LineThickness = 5;
-const QColor TraceGroup::LineColour(QColor(0x55, 0x57, 0x53));
+const QColor TraceGroup::LineColor(QColor(0x55, 0x57, 0x53));
 
 TraceGroup::~TraceGroup()
 {
@@ -85,8 +85,8 @@ pair<int, int> TraceGroup::v_extents() const
 void TraceGroup::paint_label(QPainter &p, const QRect &rect, bool hover)
 {
        const QRectF r = label_rect(rect).adjusted(
-               LineThickness / 2, LineThickness / 2,
-               -LineThickness / 2, -LineThickness / 2);
+               LineThickness / 2.0, LineThickness / 2.0,
+               -LineThickness / 2.0, -LineThickness / 2.0);
 
        // Paint the label
        const QPointF points[] = {
@@ -104,10 +104,10 @@ void TraceGroup::paint_label(QPainter &p, const QRect &rect, bool hover)
                p.drawPolyline(points, countof(points));
        }
 
-       p.setPen(QPen(QBrush(LineColour.darker()), LineThickness,
+       p.setPen(QPen(QBrush(LineColor.darker()), LineThickness,
                Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin));
        p.drawPolyline(points, countof(points));
-       p.setPen(QPen(QBrush(hover ? LineColour.lighter() : LineColour),
+       p.setPen(QPen(QBrush(hover ? LineColor.lighter() : LineColor),
                LineThickness - 2, Qt::SolidLine, Qt::SquareCap,
                Qt::RoundJoin));
        p.drawPolyline(points, countof(points));
@@ -116,7 +116,7 @@ void TraceGroup::paint_label(QPainter &p, const QRect &rect, bool hover)
 QRectF TraceGroup::label_rect(const QRectF &rect) const
 {
        QRectF child_rect;
-       for (const shared_ptr<ViewItem> r : child_items())
+       for (const shared_ptr<ViewItem>& r : child_items())
                if (r && r->enabled())
                        child_rect = child_rect.united(r->label_rect(rect));
 
@@ -133,7 +133,7 @@ bool TraceGroup::pt_in_label_rect(int left, int right, const QPoint &point)
        return false;
 }
 
-QMenu* TraceGroup::create_context_menu(QWidget *parent)
+QMenu* TraceGroup::create_header_context_menu(QWidget *parent)
 {
        QMenu *const menu = new QMenu(parent);
 
@@ -156,40 +156,6 @@ int TraceGroup::owner_visual_v_offset() const
        return owner_ ? visual_v_offset() + owner_->owner_visual_v_offset() : 0;
 }
 
-void TraceGroup::restack_items()
-{
-       vector<shared_ptr<TraceTreeItem>> items(trace_tree_child_items());
-
-       // Sort by the centre line of the extents
-       stable_sort(items.begin(), items.end(),
-               [](const shared_ptr<TraceTreeItem> &a, const shared_ptr<TraceTreeItem> &b) {
-                       const auto aext = a->v_extents();
-                       const auto bext = b->v_extents();
-                       return a->layout_v_offset() +
-                                       (aext.first + aext.second) / 2 <
-                               b->layout_v_offset() +
-                                       (bext.first + bext.second) / 2;
-               });
-
-       int total_offset = 0;
-       for (shared_ptr<TraceTreeItem> r : items) {
-               const pair<int, int> extents = r->v_extents();
-               if (extents.first == 0 && extents.second == 0)
-                       continue;
-
-               // We position disabled traces, so that they are close to the
-               // animation target positon should they be re-enabled
-               if (r->enabled())
-                       total_offset += -extents.first;
-
-               if (!r->dragging())
-                       r->set_layout_v_offset(total_offset);
-
-               if (r->enabled())
-                       total_offset += extents.second;
-       }
-}
-
 unsigned int TraceGroup::depth() const
 {
        return owner_ ? owner_->depth() + 1 : 0;
@@ -200,7 +166,7 @@ void TraceGroup::ungroup()
        const vector<shared_ptr<TraceTreeItem>> items(trace_tree_child_items());
        clear_child_items();
 
-       for (shared_ptr<TraceTreeItem> r : items)
+       for (const shared_ptr<TraceTreeItem>& r : items)
                owner_->add_child_item(r);
 
        owner_->remove_child_item(shared_from_this());
index 6a72318abf87d79edb272ad242ad95258fcd587e..4a793665886856971b52e56d8647a96ab6fc92af 100644 (file)
@@ -37,7 +37,7 @@ private:
        static const int Padding;
        static const int Width;
        static const int LineThickness;
-       static const QColor LineColour;
+       static const QColor LineColor;
 
 public:
        /**
@@ -102,7 +102,7 @@ public:
         */
        bool pt_in_label_rect(int left, int right, const QPoint &point);
 
-       QMenu* create_context_menu(QWidget *parent);
+       QMenu* create_header_context_menu(QWidget *parent);
 
        pv::widgets::Popup* create_popup(QWidget *parent);
 
@@ -111,8 +111,6 @@ public:
         */
        int owner_visual_v_offset() const;
 
-       void restack_items();
-
        /**
         * Returns the number of nested parents that this row item owner has.
         */
index 762eafc3828f11bc322fa220a2cb26d848903729..e0497319224b9869e143febd4dd94c8cd433b83e 100644 (file)
@@ -23,9 +23,9 @@ namespace pv {
 namespace views {
 namespace trace {
 
-const QColor TracePalette::Colours[Cols * Rows] = {
+const QColor TracePalette::Colors[Cols * Rows] = {
 
-       // Light Colours
+       // Light Colors
        QColor(0xFC, 0xE9, 0x4F),       // Butter
        QColor(0xFC, 0xAF, 0x3E),       // Orange
        QColor(0xE9, 0xB9, 0x6E),       // Chocolate
@@ -35,7 +35,7 @@ const QColor TracePalette::Colours[Cols * Rows] = {
        QColor(0xCF, 0x72, 0xC3),       // Magenta
        QColor(0xEF, 0x29, 0x29),       // Scarlet Red
 
-       // Mid Colours
+       // Mid Colors
        QColor(0xED, 0xD4, 0x00),       // Butter
        QColor(0xF5, 0x79, 0x00),       // Orange
        QColor(0xC1, 0x7D, 0x11),       // Chocolate
@@ -45,7 +45,7 @@ const QColor TracePalette::Colours[Cols * Rows] = {
        QColor(0xA3, 0x34, 0x96),       // Magenta
        QColor(0xCC, 0x00, 0x00),       // Scarlet Red
 
-       // Dark Colours
+       // Dark Colors
        QColor(0xC4, 0xA0, 0x00),       // Butter
        QColor(0xCE, 0x5C, 0x00),       // Orange
        QColor(0x8F, 0x59, 0x02),       // Chocolate
index c3a8701742f501fee481a8d653c1cb32300ebc8e..7819ee6c44dcde4576ed0ed4fd81b561df40a014 100644 (file)
@@ -31,7 +31,7 @@ class TracePalette
 public:
        static const unsigned int Cols = 8;
        static const unsigned int Rows = 4;
-       static const QColor Colours[Cols * Rows];
+       static const QColor Colors[Cols * Rows];
 };
 
 } // namespace trace
index 470b6ad39b4565ef8e0c624fd20e0ccc84d263da..31b43ca1bd253c9cb51c28ad5b9593ed6c377d86 100644 (file)
@@ -133,7 +133,7 @@ void TraceTreeItem::drag_by(const QPoint &delta)
                owner_->owner_visual_v_offset());
 }
 
-QPoint TraceTreeItem::point(const QRect &rect) const
+QPoint TraceTreeItem::drag_point(const QRect &rect) const
 {
        return QPoint(rect.right(), get_visual_y());
 }
index 7a4bf4622de5bf11cdd45744acceb198d88eca05..3605aa1a3b9c3fd031ca32211f92498f55ed355b 100644 (file)
@@ -111,7 +111,7 @@ public:
         * Gets the arrow-tip point of the row item marker.
         * @param rect the rectangle of the header area.
         */
-       QPoint point(const QRect &rect) const;
+       QPoint drag_point(const QRect &rect) const;
 
        /**
         * Computes the vertical extents of the contents of this row item.
index a65c621737cfccfc453bbc9718b22b31212ff1e3..ccf9b9123637ea498abfe800145eca070f03d211 100644 (file)
@@ -89,7 +89,7 @@ pair<int, int> TraceTreeItemOwner::v_extents() const
        bool has_children = false;
 
        pair<int, int> extents(INT_MAX, INT_MIN);
-       for (const shared_ptr<TraceTreeItem> t : trace_tree_child_items()) {
+       for (const shared_ptr<TraceTreeItem>& t : trace_tree_child_items()) {
                assert(t);
                if (!t->enabled())
                        continue;
@@ -112,6 +112,36 @@ pair<int, int> TraceTreeItemOwner::v_extents() const
 
 void TraceTreeItemOwner::restack_items()
 {
+       vector<shared_ptr<TraceTreeItem>> items(trace_tree_child_items());
+
+       // Sort by the centre line of the extents
+       stable_sort(items.begin(), items.end(),
+               [](const shared_ptr<TraceTreeItem> &a, const shared_ptr<TraceTreeItem> &b) {
+                       const auto aext = a->v_extents();
+                       const auto bext = b->v_extents();
+                       return a->layout_v_offset() +
+                                       (aext.first + aext.second) / 2 <
+                               b->layout_v_offset() +
+                                       (bext.first + bext.second) / 2;
+               });
+
+       int total_offset = 0;
+       for (shared_ptr<TraceTreeItem> r : items) {
+               const pair<int, int> extents = r->v_extents();
+               if (extents.first == 0 && extents.second == 0)
+                       continue;
+
+               // We position disabled traces, so that they are close to the
+               // animation target positon should they be re-enabled
+               if (r->enabled())
+                       total_offset += -extents.first;
+
+               if (!r->dragging())
+                       r->set_layout_v_offset(total_offset);
+
+               if (r->enabled())
+                       total_offset += extents.second;
+       }
 }
 
 } // namespace trace
index 31afba1c538e3214af64c32039276cc82746221d..3311f3502ba2d92540939a3ad9c5102550d07dda 100644 (file)
@@ -24,7 +24,7 @@ namespace pv {
 namespace views {
 namespace trace {
 
-const QColor TriggerMarker::Colour(0x00, 0x00, 0xB0);
+const QColor TriggerMarker::Color(0x00, 0x00, 0xB0);
 
 TriggerMarker::TriggerMarker(View &view, const pv::util::Timestamp& time) :
        TimeItem(view),
@@ -43,8 +43,9 @@ bool TriggerMarker::enabled() const
        return true;
 }
 
-bool TriggerMarker::is_draggable() const
+bool TriggerMarker::is_draggable(QPoint pos) const
 {
+       (void)pos;
        return false;
 }
 
@@ -60,9 +61,12 @@ float TriggerMarker::get_x() const
        return ((time_ - view_.offset()) / view_.scale()).convert_to<float>();
 }
 
-QPoint TriggerMarker::point(const QRect &rect) const
+QPoint TriggerMarker::drag_point(const QRect &rect) const
 {
-       return QPoint(get_x(), rect.bottom());
+       (void)rect;
+
+       // The trigger marker cannot be moved, so there is no drag point
+       return QPoint(INT_MIN, INT_MIN);
 }
 
 void TriggerMarker::paint_fore(QPainter &p, ViewItemPaintParams &pp)
@@ -70,7 +74,7 @@ void TriggerMarker::paint_fore(QPainter &p, ViewItemPaintParams &pp)
        if (!enabled())
                return;
 
-       QPen pen(Colour);
+       QPen pen(Color);
        pen.setStyle(Qt::DashLine);
 
        const float x = get_x();
index 09017c0b882a10e57c2219c4e756e38adaa06687..a97fefb0d3faf8c571e347f6b7ddfa240c31af39 100644 (file)
 
 #include "timeitem.hpp"
 
+#include <QPoint>
+
 namespace pv {
 namespace views {
 namespace trace {
 
+/**
+ * The TriggerMarker class is used to show to the user at what point in time
+ * a trigger occured. It is not editable by the user.
+ */
 class TriggerMarker : public TimeItem
 {
        Q_OBJECT
 
 public:
-       static const QColor Colour;
+       static const QColor Color;
 
 public:
        /**
@@ -54,7 +60,7 @@ public:
        /**
          Returns true if the item may be dragged/moved.
         */
-       bool is_draggable() const override;
+       bool is_draggable(QPoint pos) const override;
 
        /**
         * Sets the time of the marker.
@@ -67,7 +73,7 @@ public:
         * Gets the arrow-tip point of the time marker.
         * @param rect the rectangle of the ruler area.
         */
-       QPoint point(const QRect &rect) const override;
+       QPoint drag_point(const QRect &rect) const override;
 
        /**
         * Paints the foreground layer of the item with a QPainter
index f1f8eb0c18ca0bbb32f5174fd852f6f0e0305273..79277b4e17074708b0f1e3718ef6eeb4996d2c93 100644 (file)
@@ -38,6 +38,7 @@
 #include <QApplication>
 #include <QEvent>
 #include <QFontMetrics>
+#include <QMenu>
 #include <QMouseEvent>
 #include <QScrollBar>
 #include <QVBoxLayout>
@@ -73,12 +74,12 @@ using pv::util::Timestamp;
 using std::back_inserter;
 using std::copy_if;
 using std::count_if;
-using std::dynamic_pointer_cast;
 using std::inserter;
 using std::max;
 using std::make_pair;
 using std::make_shared;
 using std::min;
+using std::numeric_limits;
 using std::pair;
 using std::set;
 using std::set_difference;
@@ -125,24 +126,11 @@ bool CustomScrollArea::viewportEvent(QEvent *event)
 
 View::View(Session &session, bool is_main_view, QWidget *parent) :
        ViewBase(session, is_main_view, parent),
+
+       // Note: Place defaults in View::reset_view_state(), not here
        splitter_(new QSplitter()),
-       scale_(1e-3),
-       offset_(0),
-       updating_scroll_(false),
-       settings_restored_(false),
-       sticky_scrolling_(false), // Default setting is set in MainWindow::setup_ui()
-       always_zoom_to_fit_(false),
-       tick_period_(0),
-       tick_prefix_(pv::util::SIPrefix::yocto),
-       tick_precision_(0),
-       time_unit_(util::TimeUnit::Time),
-       show_cursors_(false),
-       cursors_(new CursorPair(*this)),
-       next_flag_text_('A'),
-       trigger_markers_(),
-       hover_point_(-1, -1),
-       scroll_needs_defaults_(true),
-       saved_v_offset_(0)
+       header_was_shrunk_(false),  // The splitter remains unchanged after a reset, so this goes here
+       sticky_scrolling_(false)  // Default setting is set in MainWindow::setup_ui()
 {
        QVBoxLayout *root_layout = new QVBoxLayout(this);
        root_layout->setContentsMargins(0, 0, 0, 0);
@@ -190,7 +178,10 @@ View::View(Session &session, bool is_main_view, QWidget *parent) :
 
        // Set up settings and event handlers
        GlobalSettings settings;
-       coloured_bg_ = settings.value(GlobalSettings::Key_View_ColouredBG).toBool();
+       colored_bg_ = settings.value(GlobalSettings::Key_View_ColoredBG).toBool();
+       snap_distance_ = settings.value(GlobalSettings::Key_View_SnapDistance).toInt();
+
+       GlobalSettings::add_change_handler(this);
 
        connect(scrollarea_->horizontalScrollBar(), SIGNAL(valueChanged(int)),
                this, SLOT(h_scroll_value_changed(int)));
@@ -210,9 +201,6 @@ View::View(Session &session, bool is_main_view, QWidget *parent) :
        connect(splitter_, SIGNAL(splitterMoved(int, int)),
                this, SLOT(on_splitter_moved()));
 
-       connect(this, SIGNAL(hover_point_changed()),
-               this, SLOT(on_hover_point_changed()));
-
        connect(&lazy_event_handler_, SIGNAL(timeout()),
                this, SLOT(process_sticky_events()));
        lazy_event_handler_.setSingleShot(true);
@@ -225,8 +213,51 @@ View::View(Session &session, bool is_main_view, QWidget *parent) :
        ruler_->raise();
        header_->raise();
 
+       reset_view_state();
+}
+
+View::~View()
+{
+       GlobalSettings::remove_change_handler(this);
+}
+
+void View::reset_view_state()
+{
+       ViewBase::reset_view_state();
+
+       segment_display_mode_ = Trace::ShowLastSegmentOnly;
+       segment_selectable_ = false;
+       scale_ = 1e-3;
+       offset_ = 0;
+       ruler_offset_ = 0;
+       updating_scroll_ = false;
+       settings_restored_ = false;
+       always_zoom_to_fit_ = false;
+       tick_period_ = 0;
+       tick_prefix_ = pv::util::SIPrefix::yocto;
+       tick_precision_ = 0;
+       time_unit_ = util::TimeUnit::Time;
+       show_cursors_ = false;
+       cursors_ = make_shared<CursorPair>(*this);
+       next_flag_text_ = 'A';
+       trigger_markers_.clear();
+       hover_widget_ = nullptr;
+       hover_point_ = QPoint(-1, -1);
+       scroll_needs_defaults_ = true;
+       saved_v_offset_ = 0;
+       scale_at_acq_start_ = 0;
+       offset_at_acq_start_ = 0;
+       suppress_zoom_to_fit_after_acq_ = false;
+
+       show_cursors_ = false;
+       cursor_state_changed(show_cursors_);
+       flags_.clear();
+
        // Update the zoom state
        calculate_tick_spacing();
+
+       // Make sure the standard bar's segment selector is in sync
+       set_segment_display_mode(segment_display_mode_);
 }
 
 Session& View::session()
@@ -254,6 +285,12 @@ void View::add_signal(const shared_ptr<Signal> signal)
 {
        ViewBase::add_signalbase(signal->base());
        signals_.insert(signal);
+
+       signal->set_segment_display_mode(segment_display_mode_);
+       signal->set_current_segment(current_segment_);
+
+       connect(signal->base().get(), SIGNAL(name_changed(const QString&)),
+               this, SLOT(on_signal_name_changed()));
 }
 
 #ifdef ENABLE_DECODE
@@ -262,17 +299,23 @@ void View::clear_decode_signals()
        decode_traces_.clear();
 }
 
-void View::add_decode_signal(shared_ptr<data::SignalBase> signalbase)
+void View::add_decode_signal(shared_ptr<data::DecodeSignal> signal)
 {
        shared_ptr<DecodeTrace> d(
-               new DecodeTrace(session_, signalbase, decode_traces_.size()));
+               new DecodeTrace(session_, signal, decode_traces_.size()));
        decode_traces_.push_back(d);
+
+       d->set_segment_display_mode(segment_display_mode_);
+       d->set_current_segment(current_segment_);
+
+       connect(signal.get(), SIGNAL(name_changed(const QString&)),
+               this, SLOT(on_signal_name_changed()));
 }
 
-void View::remove_decode_signal(shared_ptr<data::SignalBase> signalbase)
+void View::remove_decode_signal(shared_ptr<data::DecodeSignal> signal)
 {
        for (auto i = decode_traces_.begin(); i != decode_traces_.end(); i++)
-               if ((*i)->base() == signalbase) {
+               if ((*i)->base() == signal) {
                        decode_traces_.erase(i);
                        signals_changed();
                        return;
@@ -280,6 +323,11 @@ void View::remove_decode_signal(shared_ptr<data::SignalBase> signalbase)
 }
 #endif
 
+shared_ptr<Signal> View::get_signal_under_mouse_cursor() const
+{
+       return signal_under_mouse_cursor_;
+}
+
 View* View::view()
 {
        return this;
@@ -300,6 +348,11 @@ const Viewport* View::viewport() const
        return viewport_;
 }
 
+const Ruler* View::ruler() const
+{
+       return ruler_;
+}
+
 void View::save_settings(QSettings &settings) const
 {
        settings.setValue("scale", scale_);
@@ -307,13 +360,22 @@ void View::save_settings(QSettings &settings) const
                scrollarea_->verticalScrollBar()->sliderPosition());
 
        settings.setValue("splitter_state", splitter_->saveState());
+       settings.setValue("segment_display_mode", segment_display_mode_);
 
-       stringstream ss;
-       boost::archive::text_oarchive oa(ss);
-       oa << boost::serialization::make_nvp("offset", offset_);
-       settings.setValue("offset", QString::fromStdString(ss.str()));
+       {
+               stringstream ss;
+               boost::archive::text_oarchive oa(ss);
+               oa << boost::serialization::make_nvp("ruler_shift", ruler_shift_);
+               settings.setValue("ruler_shift", QString::fromStdString(ss.str()));
+       }
+       {
+               stringstream ss;
+               boost::archive::text_oarchive oa(ss);
+               oa << boost::serialization::make_nvp("offset", offset_);
+               settings.setValue("offset", QString::fromStdString(ss.str()));
+       }
 
-       for (shared_ptr<Signal> signal : signals_) {
+       for (const shared_ptr<Signal>& signal : signals_) {
                settings.beginGroup(signal->base()->internal_name());
                signal->save_settings(settings);
                settings.endGroup();
@@ -328,20 +390,42 @@ void View::restore_settings(QSettings &settings)
        if (settings.contains("scale"))
                set_scale(settings.value("scale").toDouble());
 
+       if (settings.contains("ruler_shift")) {
+               util::Timestamp shift;
+               stringstream ss;
+               ss << settings.value("ruler_shift").toString().toStdString();
+
+               try {
+                       boost::archive::text_iarchive ia(ss);
+                       ia >> boost::serialization::make_nvp("ruler_shift", shift);
+                       ruler_shift_ = shift;
+               } catch (boost::archive::archive_exception&) {
+                       qDebug() << "Could not restore the view ruler shift";
+               }
+       }
+
        if (settings.contains("offset")) {
                util::Timestamp offset;
                stringstream ss;
                ss << settings.value("offset").toString().toStdString();
 
-               boost::archive::text_iarchive ia(ss);
-               ia >> boost::serialization::make_nvp("offset", offset);
-
-               set_offset(offset);
+               try {
+                       boost::archive::text_iarchive ia(ss);
+                       ia >> boost::serialization::make_nvp("offset", offset);
+                       // This also updates ruler_offset_
+                       set_offset(offset);
+               } catch (boost::archive::archive_exception&) {
+                       qDebug() << "Could not restore the view offset";
+               }
        }
 
        if (settings.contains("splitter_state"))
                splitter_->restoreState(settings.value("splitter_state").toByteArray());
 
+       if (settings.contains("segment_display_mode"))
+               set_segment_display_mode(
+                       (Trace::SegmentDisplayMode)(settings.value("segment_display_mode").toInt()));
+
        for (shared_ptr<Signal> signal : signals_) {
                settings.beginGroup(signal->base()->internal_name());
                signal->restore_settings(settings);
@@ -356,17 +440,24 @@ void View::restore_settings(QSettings &settings)
        }
 
        settings_restored_ = true;
+       suppress_zoom_to_fit_after_acq_ = true;
+
+       // Update the ruler so that it uses the new scale
+       calculate_tick_spacing();
 }
 
 vector< shared_ptr<TimeItem> > View::time_items() const
 {
        const vector<shared_ptr<Flag>> f(flags());
        vector<shared_ptr<TimeItem>> items(f.begin(), f.end());
-       items.push_back(cursors_);
-       items.push_back(cursors_->first());
-       items.push_back(cursors_->second());
 
-       for (auto trigger_marker : trigger_markers_)
+       if (cursors_) {
+               items.push_back(cursors_);
+               items.push_back(cursors_->first());
+               items.push_back(cursors_->second());
+       }
+
+       for (auto& trigger_marker : trigger_markers_)
                items.push_back(trigger_marker);
 
        return items;
@@ -385,17 +476,44 @@ void View::set_scale(double scale)
        }
 }
 
+void View::set_offset(const pv::util::Timestamp& offset, bool force_update)
+{
+       if ((offset_ != offset) || force_update) {
+               offset_ = offset;
+               ruler_offset_ = offset_ + ruler_shift_;
+               offset_changed();
+       }
+}
+
 const Timestamp& View::offset() const
 {
        return offset_;
 }
 
-void View::set_offset(const pv::util::Timestamp& offset)
+const Timestamp& View::ruler_offset() const
 {
-       if (offset_ != offset) {
-               offset_ = offset;
-               offset_changed();
-       }
+       return ruler_offset_;
+}
+
+void View::set_zero_position(const pv::util::Timestamp& position)
+{
+       // ruler shift is a negative offset and the new zero position is relative
+       // to the current offset. Hence, we adjust the ruler shift only by the
+       // difference.
+       ruler_shift_ = -(position + (-ruler_shift_));
+
+       // Force an immediate update of the offsets
+       set_offset(offset_, true);
+       ruler_->update();
+}
+
+void View::reset_zero_position()
+{
+       ruler_shift_ = 0;
+
+       // Force an immediate update of the offsets
+       set_offset(offset_, true);
+       ruler_->update();
 }
 
 int View::owner_visual_v_offset() const
@@ -415,6 +533,11 @@ unsigned int View::depth() const
        return 0;
 }
 
+uint32_t View::current_segment() const
+{
+       return current_segment_;
+}
+
 pv::util::SIPrefix View::tick_prefix() const
 {
        return tick_prefix_;
@@ -446,6 +569,11 @@ const pv::util::Timestamp& View::tick_period() const
        return tick_period_;
 }
 
+unsigned int View::minor_tick_count() const
+{
+       return minor_tick_count_;
+}
+
 void View::set_tick_period(const pv::util::Timestamp& tick_period)
 {
        if (tick_period_ != tick_period) {
@@ -467,6 +595,91 @@ void View::set_time_unit(pv::util::TimeUnit time_unit)
        }
 }
 
+void View::set_current_segment(uint32_t segment_id)
+{
+       current_segment_ = segment_id;
+
+       for (const shared_ptr<Signal>& signal : signals_)
+               signal->set_current_segment(current_segment_);
+#ifdef ENABLE_DECODE
+       for (shared_ptr<DecodeTrace>& dt : decode_traces_)
+               dt->set_current_segment(current_segment_);
+#endif
+
+       vector<util::Timestamp> triggers = session_.get_triggers(current_segment_);
+
+       trigger_markers_.clear();
+       for (util::Timestamp timestamp : triggers)
+               trigger_markers_.push_back(make_shared<TriggerMarker>(*this, timestamp));
+
+       // When enabled, the first trigger for this segment is used as the zero position
+       GlobalSettings settings;
+       bool trigger_is_zero_time = settings.value(GlobalSettings::Key_View_TriggerIsZeroTime).toBool();
+
+       if (trigger_is_zero_time && (triggers.size() > 0))
+               set_zero_position(triggers.front());
+
+       viewport_->update();
+
+       segment_changed(segment_id);
+}
+
+bool View::segment_is_selectable() const
+{
+       return segment_selectable_;
+}
+
+Trace::SegmentDisplayMode View::segment_display_mode() const
+{
+       return segment_display_mode_;
+}
+
+void View::set_segment_display_mode(Trace::SegmentDisplayMode mode)
+{
+       segment_display_mode_ = mode;
+
+       for (const shared_ptr<Signal>& signal : signals_)
+               signal->set_segment_display_mode(mode);
+
+       uint32_t last_segment = session_.get_segment_count() - 1;
+
+       switch (mode) {
+       case Trace::ShowLastSegmentOnly:
+               if (current_segment_ != last_segment)
+                       set_current_segment(last_segment);
+               break;
+
+       case Trace::ShowLastCompleteSegmentOnly:
+               // Do nothing if we only have one segment so far
+               if (last_segment > 0) {
+                       // If the last segment isn't complete, the previous one must be
+                       uint32_t segment_id =
+                               (session_.all_segments_complete(last_segment)) ?
+                               last_segment : last_segment - 1;
+
+                       if (current_segment_ != segment_id)
+                               set_current_segment(segment_id);
+               }
+               break;
+
+       case Trace::ShowSingleSegmentOnly:
+       case Trace::ShowAllSegments:
+       case Trace::ShowAccumulatedIntensity:
+       default:
+               // Current segment remains as-is
+               break;
+       }
+
+       segment_selectable_ = true;
+
+       if ((mode == Trace::ShowAllSegments) || (mode == Trace::ShowAccumulatedIntensity))
+               segment_selectable_ = false;
+
+       viewport_->update();
+
+       segment_display_mode_changed((int)mode, segment_selectable_);
+}
+
 void View::zoom(double steps)
 {
        zoom(steps, viewport_->width() / 2);
@@ -502,23 +715,6 @@ void View::zoom_fit(bool gui_state)
        set_scale_offset(scale.convert_to<double>(), extents.first);
 }
 
-void View::zoom_one_to_one()
-{
-       using pv::data::SignalData;
-
-       // Make a set of all the visible data objects
-       set< shared_ptr<SignalData> > visible_data = get_visible_data();
-       if (visible_data.empty())
-               return;
-
-       assert(viewport_);
-       const int w = viewport_->width();
-       if (w <= 0)
-               return;
-
-       set_zoom(1.0 / session_.get_samplerate(), w / 2);
-}
-
 void View::set_scale_offset(double scale, const Timestamp& offset)
 {
        // Disable sticky scrolling / always zoom to fit when acquisition runs
@@ -551,7 +747,7 @@ set< shared_ptr<SignalData> > View::get_visible_data() const
 {
        // Make a set of all the visible data objects
        set< shared_ptr<SignalData> > visible_data;
-       for (const shared_ptr<Signal> sig : signals_)
+       for (const shared_ptr<Signal>& sig : signals_)
                if (sig->enabled())
                        visible_data.insert(sig->data());
 
@@ -562,9 +758,9 @@ pair<Timestamp, Timestamp> View::get_time_extents() const
 {
        boost::optional<Timestamp> left_time, right_time;
        const set< shared_ptr<SignalData> > visible_data = get_visible_data();
-       for (const shared_ptr<SignalData> d : visible_data) {
+       for (const shared_ptr<SignalData>& d : visible_data) {
                const vector< shared_ptr<Segment> > segments = d->segments();
-               for (const shared_ptr<Segment> &s : segments) {
+               for (const shared_ptr<Segment>s : segments) {
                        double samplerate = s->samplerate();
                        samplerate = (samplerate <= 0.0) ? 1.0 : samplerate;
 
@@ -599,15 +795,15 @@ void View::enable_show_analog_minor_grid(bool state)
        viewport_->update();
 }
 
-void View::enable_coloured_bg(bool state)
+void View::enable_colored_bg(bool state)
 {
-       coloured_bg_ = state;
+       colored_bg_ = state;
        viewport_->update();
 }
 
-bool View::coloured_bg() const
+bool View::colored_bg() const
 {
-       return coloured_bg_;
+       return colored_bg_;
 }
 
 bool View::cursors_shown() const
@@ -618,17 +814,21 @@ bool View::cursors_shown() const
 void View::show_cursors(bool show)
 {
        show_cursors_ = show;
+       cursor_state_changed(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();
+       if (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();
+       }
 }
 
 shared_ptr<CursorPair> View::cursors() const
@@ -669,6 +869,112 @@ const QPoint& View::hover_point() const
        return hover_point_;
 }
 
+const QWidget* View::hover_widget() const
+{
+       return hover_widget_;
+}
+
+int64_t View::get_nearest_level_change(const QPoint &p)
+{
+       // Is snapping disabled?
+       if (snap_distance_ == 0)
+               return -1;
+
+       struct entry_t {
+               entry_t(shared_ptr<Signal> s) :
+                       signal(s), delta(numeric_limits<int64_t>::max()), sample(-1), is_dense(false) {}
+               shared_ptr<Signal> signal;
+               int64_t delta;
+               int64_t sample;
+               bool is_dense;
+       };
+
+       vector<entry_t> list;
+
+       // Create list of signals to consider
+       if (signal_under_mouse_cursor_)
+               list.emplace_back(signal_under_mouse_cursor_);
+       else
+               for (shared_ptr<Signal> s : signals_) {
+                       if (!s->enabled())
+                               continue;
+
+                       list.emplace_back(s);
+               }
+
+       // Get data for listed signals
+       for (entry_t &e : list) {
+               // Calculate sample number from cursor position
+               const double samples_per_pixel = e.signal->base()->get_samplerate() * scale();
+               const int64_t x_offset = offset().convert_to<double>() / scale();
+               const int64_t sample_num = max(((x_offset + p.x()) * samples_per_pixel), 0.0);
+
+               vector<data::LogicSegment::EdgePair> edges =
+                       e.signal->get_nearest_level_changes(sample_num);
+
+               if (edges.empty())
+                       continue;
+
+               // Check first edge
+               const int64_t first_sample_delta = abs(sample_num - edges.front().first);
+               const int64_t first_delta = first_sample_delta / samples_per_pixel;
+               e.delta = first_delta;
+               e.sample = edges.front().first;
+
+               // Check second edge if available
+               if (edges.size() == 2) {
+                       // Note: -1 because this is usually the right edge and sample points are left-aligned
+                       const int64_t second_sample_delta = abs(sample_num - edges.back().first - 1);
+                       const int64_t second_delta = second_sample_delta / samples_per_pixel;
+
+                       // If both edges are too close, we mark this signal as being dense
+                       if ((first_delta + second_delta) <= snap_distance_)
+                               e.is_dense = true;
+
+                       if (second_delta < first_delta) {
+                               e.delta = second_delta;
+                               e.sample = edges.back().first;
+                       }
+               }
+       }
+
+       // Look for the best match: non-dense first, then dense
+       entry_t *match = nullptr;
+
+       for (entry_t &e : list) {
+               if (e.delta > snap_distance_ || e.is_dense)
+                       continue;
+
+               if (match) {
+                       if (e.delta < match->delta)
+                               match = &e;
+               } else
+                       match = &e;
+       }
+
+       if (!match) {
+               for (entry_t &e : list) {
+                       if (!e.is_dense)
+                               continue;
+
+                       if (match) {
+                               if (e.delta < match->delta)
+                                       match = &e;
+                       } else
+                               match = &e;
+               }
+       }
+
+       if (match) {
+               // Somewhat ugly hack to make TimeItem::drag_by() work
+               signal_under_mouse_cursor_ = match->signal;
+
+               return match->sample;
+       }
+
+       return -1;
+}
+
 void View::restack_all_trace_tree_items()
 {
        // Make a list of owners that is sorted from deepest first
@@ -691,8 +997,38 @@ void View::restack_all_trace_tree_items()
                i->animate_to_layout_v_offset();
 }
 
-void View::trigger_event(util::Timestamp location)
+int View::header_width() const
 {
+        return header_->extended_size_hint().width();
+}
+
+void View::on_setting_changed(const QString &key, const QVariant &value)
+{
+       if (key == GlobalSettings::Key_View_TriggerIsZeroTime)
+               on_settingViewTriggerIsZeroTime_changed(value);
+
+       if (key == GlobalSettings::Key_View_SnapDistance) {
+               GlobalSettings settings;
+               snap_distance_ = settings.value(GlobalSettings::Key_View_SnapDistance).toInt();
+       }
+}
+
+void View::trigger_event(int segment_id, util::Timestamp location)
+{
+       // TODO This doesn't work if we're showing multiple segments at once
+       if ((uint32_t)segment_id != current_segment_)
+               return;
+
+       // Set zero location if the Key_View_TriggerIsZeroTime setting is set and
+       // if this is the first trigger for this segment.
+       GlobalSettings settings;
+       bool trigger_is_zero_time = settings.value(GlobalSettings::Key_View_TriggerIsZeroTime).toBool();
+
+       size_t trigger_count = session_.get_triggers(current_segment_).size();
+
+       if (trigger_is_zero_time && trigger_count == 1)
+               set_zero_position(location);
+
        trigger_markers_.push_back(make_shared<TriggerMarker>(*this, location));
 }
 
@@ -757,6 +1093,7 @@ void View::calculate_tick_spacing()
                                (ScaleUnits[unit++] + tp_margin);
                } while (tp_with_margin < min_period && unit < countof(ScaleUnits));
 
+               minor_tick_count_ = (unit == 2) ? 4 : 5;
                tick_period = order_decimal * ScaleUnits[unit - 1];
                tick_prefix = static_cast<pv::util::SIPrefix>(
                        (order - pv::util::exponent(pv::util::SIPrefix::yocto)) / 3);
@@ -849,8 +1186,10 @@ void View::update_scroll()
                vscrollbar->setRange(extents.first - areaSize.height(),
                        extents.second);
 
-       if (scroll_needs_defaults_)
+       if (scroll_needs_defaults_) {
                set_scroll_default();
+               scroll_needs_defaults_ = false;
+       }
 }
 
 void View::reset_scroll()
@@ -877,21 +1216,29 @@ void View::set_scroll_default()
                set_v_offset(extents.first);
 }
 
-bool View::header_was_shrunk() const
+void View::determine_if_header_was_shrunk()
 {
-       const int header_pane_width = splitter_->sizes().front();
-       const int header_width = header_->extended_size_hint().width();
+       const int header_pane_width =
+               splitter_->sizes().front();  // clazy:exclude=detaching-temporary
 
        // Allow for a slight margin of error so that we also accept
        // slight differences when e.g. a label name change increased
        // the overall width
-       return (header_pane_width < (header_width - 10));
+       header_was_shrunk_ = (header_pane_width < (header_width() - 10));
 }
 
-void View::expand_header_to_fit()
+void View::resize_header_to_fit()
 {
+       // Setting the maximum width of the header widget doesn't work as
+       // expected because the splitter would allow the user to make the
+       // pane wider than that, creating empty space as a result.
+       // To make this work, we stricly enforce the maximum width by
+       // expanding the header unless the user shrunk it on purpose.
+       // As we're then setting the width of the header pane, we set the
+       // splitter to the maximum allowed position.
+
        int splitter_area_width = 0;
-       for (int w : splitter_->sizes())
+       for (int w : splitter_->sizes())  // clazy:exclude=range-loop
                splitter_area_width += w;
 
        // Make sure the header has enough horizontal space to show all labels fully
@@ -917,8 +1264,8 @@ TraceTreeItemOwner* View::find_prevalent_trace_group(
        vector<TraceTreeItemOwner*> owner_list;
 
        // Make a set and a list of all the owners
-       for (const auto &channel : group->channels()) {
-               for (auto entry : signal_map) {
+       for (const autochannel : group->channels()) {
+               for (auto& entry : signal_map) {
                        if (entry.first->channel() == channel) {
                                TraceTreeItemOwner *const o = (entry.second)->owner();
                                owner_list.push_back(o);
@@ -951,8 +1298,8 @@ vector< shared_ptr<Trace> > View::extract_new_traces_for_channels(
 {
        vector< shared_ptr<Trace> > filtered_traces;
 
-       for (const auto &channel : channels) {
-               for (auto entry : signal_map) {
+       for (const autochannel : channels) {
+               for (auto& entry : signal_map) {
                        if (entry.first->channel() == channel) {
                                shared_ptr<Trace> trace = entry.second;
                                const auto list_iter = add_list.find(trace);
@@ -973,7 +1320,7 @@ void View::determine_time_unit()
        // Check whether we know the sample rate and hence can use time as the unit
        if (time_unit_ == util::TimeUnit::Samples) {
                // Check all signals but...
-               for (const shared_ptr<Signal> signal : signals_) {
+               for (const shared_ptr<Signal>& signal : signals_) {
                        const shared_ptr<SignalData> data = signal->data();
 
                        // ...only check first segment of each
@@ -992,21 +1339,24 @@ bool View::eventFilter(QObject *object, QEvent *event)
        const QEvent::Type type = event->type();
        if (type == QEvent::MouseMove) {
 
+               if (object)
+                       hover_widget_ = qobject_cast<QWidget*>(object);
+
                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);
+                       hover_point_ = mouse_event->pos();
                else if (object == header_)
                        hover_point_ = QPoint(0, mouse_event->y());
                else
                        hover_point_ = QPoint(-1, -1);
 
-               hover_point_changed();
+               update_hover_point();
 
        } else if (type == QEvent::Leave) {
                hover_point_ = QPoint(-1, -1);
-               hover_point_changed();
+               update_hover_point();
        } else if (type == QEvent::Show) {
 
                // This is somewhat of a hack, unfortunately. We cannot use
@@ -1018,8 +1368,10 @@ bool View::eventFilter(QObject *object, QEvent *event)
                // resized to their final sizes.
                update_layout();
 
-               if (!settings_restored_)
-                       expand_header_to_fit();
+               if (settings_restored_)
+                       determine_if_header_was_shrunk();
+               else
+                       resize_header_to_fit();
 
                if (scroll_needs_defaults_) {
                        set_scroll_default();
@@ -1035,6 +1387,19 @@ bool View::eventFilter(QObject *object, QEvent *event)
        return QObject::eventFilter(object, event);
 }
 
+void View::contextMenuEvent(QContextMenuEvent *event)
+{
+       QPoint pos = event->pos() - QPoint(0, ruler_->sizeHint().height());
+
+       const shared_ptr<ViewItem> r = viewport_->get_mouse_over_item(pos);
+       if (!r)
+               return;
+
+       QMenu *menu = r->create_view_context_menu(this, pos);
+       if (menu)
+               menu->popup(event->globalPos());
+}
+
 void View::resizeEvent(QResizeEvent* event)
 {
        // Only adjust the top margin if we shrunk vertically
@@ -1044,6 +1409,31 @@ void View::resizeEvent(QResizeEvent* event)
        update_layout();
 }
 
+void View::update_hover_point()
+{
+       // Determine signal that the mouse cursor is hovering over
+       signal_under_mouse_cursor_.reset();
+       if (hover_widget_ == this) {
+               for (const shared_ptr<Signal>& s : signals_) {
+                       const pair<int, int> extents = s->v_extents();
+                       const int top = s->get_visual_y() + extents.first;
+                       const int btm = s->get_visual_y() + extents.second;
+                       if ((hover_point_.y() >= top) && (hover_point_.y() <= btm)
+                               && s->base()->enabled())
+                               signal_under_mouse_cursor_ = s;
+               }
+       }
+
+       // Update all trace tree items
+       const vector<shared_ptr<TraceTreeItem>> trace_tree_items(
+               list_by_type<TraceTreeItem>());
+       for (const shared_ptr<TraceTreeItem>& r : trace_tree_items)
+               r->hover_point_changed(hover_point_);
+
+       // Notify any other listeners
+       hover_point_changed(hover_widget_, hover_point_);
+}
+
 void View::row_item_appearance_changed(bool label, bool content)
 {
        if (label)
@@ -1071,20 +1461,23 @@ void View::extents_changed(bool horz, bool vert)
                (horz ? TraceTreeItemHExtentsChanged : 0) |
                (vert ? TraceTreeItemVExtentsChanged : 0);
 
+       lazy_event_handler_.stop();
        lazy_event_handler_.start();
 }
 
+void View::on_signal_name_changed()
+{
+       if (!header_was_shrunk_)
+               resize_header_to_fit();
+}
+
 void View::on_splitter_moved()
 {
-       // Setting the maximum width of the header widget doesn't work as
-       // expected because the splitter would allow the user to make the
-       // pane wider than that, creating empty space as a result.
-       // To make this work, we stricly enforce the maximum width by
-       // expanding the header unless the user shrunk it on purpose.
-       // As we're then setting the width of the header pane, we set the
-       // splitter to the maximum allowed position.
-       if (!header_was_shrunk())
-               expand_header_to_fit();
+       // The header can only shrink when the splitter is moved manually
+       determine_if_header_was_shrunk();
+
+       if (!header_was_shrunk_)
+               resize_header_to_fit();
 }
 
 void View::h_scroll_value_changed(int value)
@@ -1125,6 +1518,7 @@ void View::signals_changed()
 
        vector< shared_ptr<Channel> > channels;
        shared_ptr<sigrok::Device> sr_dev;
+       bool signals_added_or_removed = false;
 
        // Do we need to set the vertical scrollbar to its default position later?
        // We do if there are no traces, i.e. the scroll bar has no range set
@@ -1168,12 +1562,12 @@ void View::signals_changed()
        // Make a look-up table of sigrok Channels to pulseview Signals
        unordered_map<shared_ptr<data::SignalBase>, shared_ptr<Signal> >
                signal_map;
-       for (const shared_ptr<Signal> &sig : signals_)
+       for (const shared_ptr<Signal>sig : signals_)
                signal_map[sig->base()] = sig;
 
        // Populate channel groups
        if (sr_dev)
-               for (auto entry : sr_dev->channel_groups()) {
+               for (auto& entry : sr_dev->channel_groups()) {
                        const shared_ptr<sigrok::ChannelGroup> &group = entry.second;
 
                        if (group->channels().size() <= 1)
@@ -1199,7 +1593,7 @@ void View::signals_changed()
                        // Add the traces to the group
                        const pair<int, int> prev_v_extents = owner->v_extents();
                        int offset = prev_v_extents.second - prev_v_extents.first;
-                       for (shared_ptr<Trace> trace : new_traces_in_group) {
+                       for (const shared_ptr<Trace>& trace : new_traces_in_group) {
                                assert(trace);
                                owner->add_child_item(trace);
 
@@ -1234,7 +1628,7 @@ void View::signals_changed()
        if (non_grouped_logic_signals.size() > 0) {
                const shared_ptr<TraceGroup> non_grouped_trace_group(
                        make_shared<TraceGroup>());
-               for (shared_ptr<Trace> trace : non_grouped_logic_signals)
+               for (const shared_ptr<Trace>& trace : non_grouped_logic_signals)
                        non_grouped_trace_group->add_child_item(trace);
 
                non_grouped_trace_group->restack_items();
@@ -1252,10 +1646,11 @@ void View::signals_changed()
                add_traces.begin(), add_traces.end());
 
        // Remove any removed traces
-       for (shared_ptr<Trace> trace : remove_traces) {
+       for (const shared_ptr<Trace>& trace : remove_traces) {
                TraceTreeItemOwner *const owner = trace->owner();
                assert(owner);
                owner->remove_child_item(trace);
+               signals_added_or_removed = true;
        }
 
        // Remove any empty trace groups
@@ -1280,13 +1675,12 @@ void View::signals_changed()
 
                if (item->enabled())
                        offset += extents.second;
+               signals_added_or_removed = true;
        }
 
 
-       if (!new_top_level_items.empty())
-               // Expand the header pane because the header should become fully
-               // visible when new signals are added
-               expand_header_to_fit();
+       if (signals_added_or_removed && !header_was_shrunk_)
+               resize_header_to_fit();
 
        update_layout();
 
@@ -1299,16 +1693,20 @@ void View::signals_changed()
 
 void View::capture_state_updated(int state)
 {
+       GlobalSettings settings;
+
        if (state == Session::Running) {
                set_time_unit(util::TimeUnit::Samples);
 
                trigger_markers_.clear();
 
+               scale_at_acq_start_ = scale_;
+               offset_at_acq_start_ = offset_;
+
                // Activate "always zoom to fit" if the setting is enabled and we're
                // the main view of this session (other trace views may be used for
                // zooming and we don't want to mess them up)
-               GlobalSettings settings;
-               bool state = settings.value(GlobalSettings::Key_View_AlwaysZoomToFit).toBool();
+               bool state = settings.value(GlobalSettings::Key_View_ZoomToFitDuringAcq).toBool();
                if (is_main_view_ && state) {
                        always_zoom_to_fit_ = true;
                        always_zoom_to_fit_changed(always_zoom_to_fit_);
@@ -1316,6 +1714,10 @@ void View::capture_state_updated(int state)
 
                // Enable sticky scrolling if the setting is enabled
                sticky_scrolling_ = settings.value(GlobalSettings::Key_View_StickyScrolling).toBool();
+
+               // Reset all traces to segment 0
+               current_segment_ = 0;
+               set_current_segment(current_segment_);
        }
 
        if (state == Session::Stopped) {
@@ -1330,9 +1732,64 @@ void View::capture_state_updated(int state)
                        always_zoom_to_fit_ = false;
                        always_zoom_to_fit_changed(always_zoom_to_fit_);
                }
+
+               bool zoom_to_fit_after_acq =
+                       settings.value(GlobalSettings::Key_View_ZoomToFitAfterAcq).toBool();
+
+               // Only perform zoom-to-fit if the user hasn't altered the viewport and
+               // we didn't restore settings in the meanwhile
+               if (zoom_to_fit_after_acq &&
+                       !suppress_zoom_to_fit_after_acq_ &&
+                       (scale_ == scale_at_acq_start_) &&
+                       (offset_ == offset_at_acq_start_))
+                       zoom_fit(false);  // We're stopped, so the GUI state doesn't matter
+
+               suppress_zoom_to_fit_after_acq_ = false;
        }
 }
 
+void View::on_new_segment(int new_segment_id)
+{
+       on_segment_changed(new_segment_id);
+}
+
+void View::on_segment_completed(int segment_id)
+{
+       on_segment_changed(segment_id);
+}
+
+void View::on_segment_changed(int segment)
+{
+       switch (segment_display_mode_) {
+       case Trace::ShowLastSegmentOnly:
+       case Trace::ShowSingleSegmentOnly:
+               set_current_segment(segment);
+               break;
+
+       case Trace::ShowLastCompleteSegmentOnly:
+               // Only update if all segments are complete
+               if (session_.all_segments_complete(segment))
+                       set_current_segment(segment);
+               break;
+
+       case Trace::ShowAllSegments:
+       case Trace::ShowAccumulatedIntensity:
+       default:
+               break;
+       }
+}
+
+void View::on_settingViewTriggerIsZeroTime_changed(const QVariant new_value)
+{
+       if (new_value.toBool()) {
+               // The first trigger for this segment is used as the zero position
+               vector<util::Timestamp> triggers = session_.get_triggers(current_segment_);
+               if (triggers.size() > 0)
+                       set_zero_position(triggers.front());
+       } else
+               reset_zero_position();
+}
+
 void View::perform_delayed_view_update()
 {
        if (always_zoom_to_fit_) {
@@ -1368,14 +1825,6 @@ void View::process_sticky_events()
        sticky_events_ = 0;
 }
 
-void View::on_hover_point_changed()
-{
-       const vector<shared_ptr<TraceTreeItem>> trace_tree_items(
-               list_by_type<TraceTreeItem>());
-       for (shared_ptr<TraceTreeItem> r : trace_tree_items)
-               r->hover_point_changed();
-}
-
 } // namespace trace
 } // namespace views
 } // namespace pv
index bfa7ed3eaa5b4ed16e909aa75cb22587760aee76..a6655d2f492ab941617fc95f7c694463b1aa6645 100644 (file)
 #include <QSizeF>
 #include <QSplitter>
 
-#include <pv/data/signaldata.hpp>
+#include <pv/globalsettings.hpp>
 #include <pv/util.hpp>
+#include <pv/data/signaldata.hpp>
 #include <pv/views/viewbase.hpp>
 
 #include "cursorpair.hpp"
 #include "flag.hpp"
+#include "trace.hpp"
 #include "tracetreeitemowner.hpp"
 
 using std::list;
@@ -62,12 +64,10 @@ namespace views {
 
 namespace trace {
 
-class CursorHeader;
 class DecodeTrace;
 class Header;
 class Ruler;
 class Signal;
-class Trace;
 class Viewport;
 class TriggerMarker;
 
@@ -80,7 +80,7 @@ public:
        bool viewportEvent(QEvent *event);
 };
 
-class View : public ViewBase, public TraceTreeItemOwner
+class View : public ViewBase, public TraceTreeItemOwner, public GlobalSettingsInterface
 {
        Q_OBJECT
 
@@ -101,6 +101,14 @@ private:
 public:
        explicit View(Session &session, bool is_main_view=false, QWidget *parent = nullptr);
 
+       ~View();
+
+       /**
+        * Resets the view to its default state after construction. It does however
+        * not reset the signal bases or any other connections with the session.
+        */
+       virtual void reset_view_state();
+
        Session& session();
        const Session& session() const;
 
@@ -116,11 +124,13 @@ public:
 #ifdef ENABLE_DECODE
        virtual void clear_decode_signals();
 
-       virtual void add_decode_signal(shared_ptr<data::SignalBase> signalbase);
+       virtual void add_decode_signal(shared_ptr<data::DecodeSignal> signal);
 
-       virtual void remove_decode_signal(shared_ptr<data::SignalBase> signalbase);
+       virtual void remove_decode_signal(shared_ptr<data::DecodeSignal> signal);
 #endif
 
+       shared_ptr<Signal> get_signal_under_mouse_cursor() const;
+
        /**
         * Returns the view of the owner.
         */
@@ -135,6 +145,8 @@ public:
 
        const Viewport* viewport() const;
 
+       const Ruler* ruler() const;
+
        virtual void save_settings(QSettings &settings) const;
 
        virtual void restore_settings(QSettings &settings);
@@ -150,11 +162,21 @@ public:
        double scale() const;
 
        /**
-        * Returns the time offset of the left edge of the view in
-        * seconds.
+        * Returns the internal view version of the time offset of the left edge
+        * of the view in seconds.
         */
        const pv::util::Timestamp& offset() const;
 
+       /**
+        * Returns the ruler version of the time offset of the left edge
+        * of the view in seconds.
+        */
+       const pv::util::Timestamp& ruler_offset() const;
+
+       void set_zero_position(const pv::util::Timestamp& position);
+
+       void reset_zero_position();
+
        /**
         * Returns the vertical scroll offset.
         */
@@ -180,6 +202,11 @@ public:
         */
        const pv::util::Timestamp& tick_period() const;
 
+       /**
+        * Returns number of minor division ticks per time marking.
+        */
+       unsigned int minor_tick_count() const;
+
        /**
         * Returns the unit of time currently used.
         */
@@ -190,13 +217,25 @@ public:
         */
        unsigned int depth() const;
 
+       /**
+        * Returns the currently displayed segment, starting at 0.
+        */
+       uint32_t current_segment() const;
+
+       /**
+        * Returns whether the currently shown segment can be influenced
+        * (selected) or not.
+        */
+       bool segment_is_selectable() const;
+
+       Trace::SegmentDisplayMode segment_display_mode() const;
+       void set_segment_display_mode(Trace::SegmentDisplayMode mode);
+
        void zoom(double steps);
        void zoom(double steps, int offset);
 
        void zoom_fit(bool gui_state);
 
-       void zoom_one_to_one();
-
        /**
         * Sets the scale and offset.
         * @param scale The new view scale in seconds per pixel.
@@ -209,15 +248,15 @@ public:
        pair<pv::util::Timestamp, pv::util::Timestamp> get_time_extents() const;
 
        /**
-        * Enables or disables coloured trace backgrounds. If they're not
-        * coloured then they will use alternating colors.
+        * Enables or disables colored trace backgrounds. If they're not
+        * colored then they will use alternating colors.
         */
-       void enable_coloured_bg(bool state);
+       void enable_colored_bg(bool state);
 
        /**
-        * Returns true if the trace background should be drawn with a coloured background.
+        * Returns true if the trace background should be drawn with a colored background.
         */
-       bool coloured_bg() const;
+       bool colored_bg() const;
 
        /**
         * Enable or disable showing sampling points.
@@ -265,11 +304,25 @@ public:
        vector< shared_ptr<Flag> > flags() const;
 
        const QPoint& hover_point() const;
+       const QWidget* hover_widget() const;
+
+       /**
+        * Determines the closest level change (i.e. edge) to a given point, which
+        * is useful for e.g. the "snap to edge" functionality.
+        *
+        * @param p The current position of the mouse cursor
+        * @return The sample number of the nearest level change or -1 if none
+        */
+       int64_t get_nearest_level_change(const QPoint &p);
 
        void restack_all_trace_tree_items();
 
+       int header_width() const;
+
+       void on_setting_changed(const QString &key, const QVariant &value);
+
 Q_SIGNALS:
-       void hover_point_changed();
+       void hover_point_changed(const QWidget* widget, const QPoint &hp);
 
        void selection_changed();
 
@@ -295,8 +348,18 @@ Q_SIGNALS:
        /// Emitted when the time_unit changed.
        void time_unit_changed();
 
+       /// Emitted when the currently selected segment changed
+       void segment_changed(int segment_id);
+
+       /// Emitted when the multi-segment display mode changed
+       /// @param mode is a value of Trace::SegmentDisplayMode
+       void segment_display_mode_changed(int mode, bool segment_selectable);
+
+       /// Emitted when the cursors are shown/hidden
+       void cursor_state_changed(bool show);
+
 public Q_SLOTS:
-       void trigger_event(util::Timestamp location);
+       void trigger_event(int segment_id, util::Timestamp location);
 
 private:
        void get_scroll_layout(double &length, pv::util::Timestamp &offset) const;
@@ -323,9 +386,9 @@ private:
 
        void set_scroll_default();
 
-       bool header_was_shrunk() const;
+       void determine_if_header_was_shrunk();
 
-       void expand_header_to_fit();
+       void resize_header_to_fit();
 
        void update_layout();
 
@@ -345,8 +408,12 @@ private:
 
        bool eventFilter(QObject *object, QEvent *event);
 
+       virtual void contextMenuEvent(QContextMenuEvent *event);
+
        void resizeEvent(QResizeEvent *event);
 
+       void update_hover_point();
+
 public:
        void row_item_appearance_changed(bool label, bool content);
        void time_item_appearance_changed(bool label, bool content);
@@ -355,6 +422,7 @@ public:
 
 private Q_SLOTS:
 
+       void on_signal_name_changed();
        void on_splitter_moved();
 
        void h_scroll_value_changed(int value);
@@ -363,17 +431,21 @@ private Q_SLOTS:
        void signals_changed();
        void capture_state_updated(int state);
 
+       void on_new_segment(int new_segment_id);
+       void on_segment_completed(int new_segment_id);
+       void on_segment_changed(int segment);
+
+       void on_settingViewTriggerIsZeroTime_changed(const QVariant new_value);
+
        virtual void perform_delayed_view_update();
 
        void process_sticky_events();
 
-       void on_hover_point_changed();
-
        /**
-        * Sets the 'offset_' member and emits the 'offset_changed'
+        * Sets the 'offset_' and ruler_offset_ members and emits the 'offset_changed'
         * signal if needed.
         */
-       void set_offset(const pv::util::Timestamp& offset);
+       void set_offset(const pv::util::Timestamp& offset, bool force_update = false);
 
        /**
         * Sets the 'scale_' member and emits the 'scale_changed'
@@ -405,6 +477,11 @@ private Q_SLOTS:
         */
        void set_time_unit(pv::util::TimeUnit time_unit);
 
+       /**
+        * Sets the current segment with the first segment starting at 0.
+        */
+       void set_current_segment(uint32_t segment_id);
+
 private:
        CustomScrollArea *scrollarea_;
        Viewport *viewport_;
@@ -418,21 +495,30 @@ private:
        vector< shared_ptr<DecodeTrace> > decode_traces_;
 #endif
 
+       Trace::SegmentDisplayMode segment_display_mode_;
+
+       /// Signals whether the user can change the currently shown segment.
+       bool segment_selectable_;
+
        /// The view time scale in seconds per pixel.
        double scale_;
 
-       /// The view time offset in seconds.
+       /// The internal view version of the time offset in seconds.
        pv::util::Timestamp offset_;
+       /// The ruler version of the time offset in seconds.
+       pv::util::Timestamp ruler_offset_;
 
        bool updating_scroll_;
        bool settings_restored_;
+       bool header_was_shrunk_;
 
        bool sticky_scrolling_;
-       bool coloured_bg_;
+       bool colored_bg_;
        bool always_zoom_to_fit_;
 
        pv::util::Timestamp tick_period_;
        pv::util::SIPrefix tick_prefix_;
+       unsigned int minor_tick_count_;
        unsigned int tick_precision_;
        util::TimeUnit time_unit_;
 
@@ -444,7 +530,10 @@ private:
 
        vector< shared_ptr<TriggerMarker> > trigger_markers_;
 
+       QWidget* hover_widget_;
        QPoint hover_point_;
+       shared_ptr<Signal> signal_under_mouse_cursor_;
+       uint16_t snap_distance_;
 
        unsigned int sticky_events_;
        QTimer lazy_event_handler_;
@@ -454,6 +543,16 @@ private:
 
        // A nonzero value indicates the v offset to restore. See View::resizeEvent()
        int saved_v_offset_;
+
+       // These are used to determine whether the view was altered after acq started
+       double scale_at_acq_start_;
+       pv::util::Timestamp offset_at_acq_start_;
+
+       // Used to suppress performing a "zoom to fit" when the session stops. This
+       // is needed when the view's settings are restored before acquisition ends.
+       // In that case we want to keep the restored settings, not have a "zoom to fit"
+       // mess them up.
+       bool suppress_zoom_to_fit_after_acq_;
 };
 
 } // namespace trace
index 61bc99f5f98bba157a1d2eae1ade7078bb892539..2dd8ade8c1fddb03c718c79179c4161c278dc461 100644 (file)
@@ -39,6 +39,12 @@ ViewItem::ViewItem() :
 {
 }
 
+bool ViewItem::is_selectable(QPoint pos) const
+{
+       (void)pos;
+       return true;
+}
+
 bool ViewItem::selected() const
 {
        return selected_;
@@ -49,8 +55,9 @@ void ViewItem::select(bool select)
        selected_ = select;
 }
 
-bool ViewItem::is_draggable() const
+bool ViewItem::is_draggable(QPoint pos) const
 {
+       (void)pos;
        return true;
 }
 
@@ -61,8 +68,7 @@ bool ViewItem::dragging() const
 
 void ViewItem::drag()
 {
-       if (is_draggable())
-               drag_point_ = point(QRect());
+       drag_point_ = drag_point(QRect());
 }
 
 void ViewItem::drag_release()
@@ -82,12 +88,19 @@ QRectF ViewItem::hit_box_rect(const ViewItemPaintParams &pp) const
        return QRectF();
 }
 
-QMenu* ViewItem::create_context_menu(QWidget *parent)
+QMenu* ViewItem::create_header_context_menu(QWidget *parent)
 {
        context_parent_ = parent;
        return new QMenu(parent);
 }
 
+QMenu* ViewItem::create_view_context_menu(QWidget *parent, QPoint &click_pos)
+{
+       (void)parent;
+       (void)click_pos;
+       return nullptr;
+}
+
 widgets::Popup* ViewItem::create_popup(QWidget *parent)
 {
        (void)parent;
@@ -130,7 +143,7 @@ void ViewItem::paint_fore(QPainter &p, ViewItemPaintParams &pp)
        (void)pp;
 }
 
-QColor ViewItem::select_text_colour(QColor background)
+QColor ViewItem::select_text_color(QColor background)
 {
        return (background.lightness() > 110) ? Qt::black : Qt::white;
 }
index e1cc20040bd8ab33f3cded664454ebce23835136..5ce3bb608ec68156ba06c53b12409619e9772ec9 100644 (file)
@@ -23,6 +23,7 @@
 #include <list>
 
 #include <QPen>
+#include <QPoint>
 
 #include "viewitempaintparams.hpp"
 
@@ -52,12 +53,16 @@ public:
 public:
        ViewItem();
 
-public:
        /**
         * Returns true if the item is visible and enabled.
         */
        virtual bool enabled() const = 0;
 
+       /**
+        * Returns true if the item may be selected.
+        */
+       virtual bool is_selectable(QPoint pos) const;
+
        /**
         * Returns true if the item has been selected by the user.
         */
@@ -71,7 +76,7 @@ public:
        /**
         * Returns true if the item may be dragged/moved.
         */
-       virtual bool is_draggable() const;
+       virtual bool is_draggable(QPoint pos) const;
 
        /**
         * Returns true if the item is being dragged.
@@ -98,7 +103,7 @@ public:
         * Get the drag point.
         * @param rect the rectangle of the widget area.
         */
-       virtual QPoint point(const QRect &rect) const = 0;
+       virtual QPoint drag_point(const QRect &rect) const = 0;
 
        /**
         * Computes the outline rectangle of a label.
@@ -145,17 +150,18 @@ public:
         */
        virtual void paint_fore(QPainter &p, ViewItemPaintParams &pp);
 
-public:
        /**
-        * Gets the text colour.
-        * @remarks This colour is computed by comparing the lightness
-        * of the trace colour against a threshold to determine whether
+        * Gets the text color.
+        * @remarks This color is computed by comparing the lightness
+        * of the trace color against a threshold to determine whether
         * white or black would be more visible.
         */
-       static QColor select_text_colour(QColor background);
+       static QColor select_text_color(QColor background);
 
 public:
-       virtual QMenu* create_context_menu(QWidget *parent);
+       virtual QMenu* create_header_context_menu(QWidget *parent);
+
+       virtual QMenu* create_view_context_menu(QWidget *parent, QPoint &click_pos);
 
        virtual pv::widgets::Popup* create_popup(QWidget *parent);
 
index 855cb1afae3d5937ffe5b0cb05091cd1ba8412c2..0e880df6888d2c99345680cfaffdc2fa82566bdd 100644 (file)
@@ -27,6 +27,11 @@ namespace pv {
 namespace views {
 namespace trace {
 
+const ViewItemOwner::item_list& ViewItemOwner::child_items() const
+{
+       return items_;
+}
+
 ViewItemOwner::iterator ViewItemOwner::begin()
 {
        return iterator(this, items_.begin());
index b8dbe202fd427732974727d49ad30b5ec208fb27..29f74b5aff7ddcdfb47cf2fad387ee98e2b11b55 100644 (file)
@@ -50,7 +50,7 @@ public:
        /**
         * Returns a list of row items owned by this object.
         */
-       virtual const item_list& child_items() const = 0;
+       virtual const item_list& child_items() const;
 
        /**
         * Returns a depth-first iterator at the beginning of the child ViewItem
index 2d9cfbb5ab42fe7fbc3dad7d62797d3e59e1ec7e..eb5a354546b1dc5a813d96beededd1adecfc7647 100644 (file)
@@ -33,7 +33,7 @@ ViewItemPaintParams::ViewItemPaintParams(
        rect_(rect),
        scale_(scale),
        offset_(offset),
-       bg_colour_state_(false)
+       bg_color_state_(false)
 {
        assert(scale > 0.0);
 }
index f39085ce6712e91ff678168dbb807175672f3ae6..725a4336bbcb4944d3ca09992ba9da293523ee6c 100644 (file)
@@ -75,9 +75,9 @@ public:
                return (offset_ / scale_).convert_to<double>();
        }
 
-       bool next_bg_colour_state() {
-               const bool state = bg_colour_state_;
-               bg_colour_state_ = !bg_colour_state_;
+       bool next_bg_color_state() {
+               const bool state = bg_color_state_;
+               bg_color_state_ = !bg_color_state_;
                return state;
        }
 
@@ -90,7 +90,7 @@ private:
        QRect rect_;
        double scale_;
        pv::util::Timestamp offset_;
-       bool bg_colour_state_;
+       bool bg_color_state_;
 };
 
 } // namespace trace
index d426a133717596a8c8d396b9f6e4f08e146cef13..83abb7b6cbd5273f699a204d2720e4404f6c00e2 100644 (file)
@@ -37,7 +37,7 @@ using std::abs;
 using std::back_inserter;
 using std::copy;
 using std::dynamic_pointer_cast;
-using std::none_of; // Used in assert()s.
+using std::none_of; // NOLINT. Used in assert()s.
 using std::shared_ptr;
 using std::stable_sort;
 using std::vector;
@@ -64,9 +64,9 @@ shared_ptr<ViewItem> Viewport::get_mouse_over_item(const QPoint &pt)
        return nullptr;
 }
 
-void Viewport::item_hover(const shared_ptr<ViewItem> &item)
+void Viewport::item_hover(const shared_ptr<ViewItem> &item, QPoint pos)
 {
-       if (item && item->is_draggable())
+       if (item && item->is_draggable(pos))
                setCursor(dynamic_pointer_cast<RowItem>(item) ?
                        Qt::SizeVerCursor : Qt::SizeHorCursor);
        else
@@ -165,7 +165,7 @@ void Viewport::paintEvent(QPaintEvent*)
 
        stable_sort(row_items.begin(), row_items.end(),
                [](const shared_ptr<RowItem> &a, const shared_ptr<RowItem> &b) {
-                       return a->point(QRect()).y() < b->point(QRect()).y(); });
+                       return a->drag_point(QRect()).y() < b->drag_point(QRect()).y(); });
 
        const vector< shared_ptr<TimeItem> > time_items(view_.time_items());
        assert(none_of(time_items.begin(), time_items.end(),
@@ -177,11 +177,11 @@ void Viewport::paintEvent(QPaintEvent*)
        for (LayerPaintFunc *paint_func = layer_paint_funcs;
                        *paint_func; paint_func++) {
                ViewItemPaintParams time_pp(rect(), view_.scale(), view_.offset());
-               for (const shared_ptr<TimeItem> t : time_items)
+               for (const shared_ptr<TimeItem>& t : time_items)
                        (t.get()->*(*paint_func))(p, time_pp);
 
                ViewItemPaintParams row_pp(rect(), view_.scale(), view_.offset());
-               for (const shared_ptr<RowItem> r : row_items)
+               for (const shared_ptr<RowItem>& r : row_items)
                        (r.get()->*(*paint_func))(p, row_pp);
        }
 
index ab67f69f3ee2d618895b6d5595a7251f97ef538c..1b77a0d0699e7264170f869f39097216d923103b 100644 (file)
@@ -22,6 +22,7 @@
 
 #include <boost/optional.hpp>
 
+#include <QPoint>
 #include <QTimer>
 #include <QTouchEvent>
 
@@ -48,14 +49,6 @@ class Viewport : public ViewWidget
 public:
        explicit Viewport(View &parent);
 
-private:
-       /**
-        * Indicates when a view item is being hovered over.
-        * @param item The item that is being hovered over, or @c nullptr
-        * if no view item is being hovered over.
-        */
-       void item_hover(const shared_ptr<ViewItem> &item);
-
        /**
         * Gets the first view item which has a hit-box that contains @c pt .
         * @param pt the point to search with.
@@ -64,6 +57,14 @@ private:
         */
        shared_ptr<ViewItem> get_mouse_over_item(const QPoint &pt);
 
+private:
+       /**
+        * Indicates when a view item is being hovered over.
+        * @param item The item that is being hovered over, or @c nullptr
+        * if no view item is being hovered over.
+        */
+       void item_hover(const shared_ptr<ViewItem> &item, QPoint pos);
+
        /**
         * Sets this item into the dragged state.
         */
index 5a1aa3c3ea447e27356c8ace74273b2f1da63333..73c361f47d802dd469b8ad59f9a2f4e47f285060 100644 (file)
@@ -50,9 +50,10 @@ void ViewWidget::clear_selection()
                i->select(false);
 }
 
-void ViewWidget::item_hover(const shared_ptr<ViewItem> &item)
+void ViewWidget::item_hover(const shared_ptr<ViewItem> &item, QPoint pos)
 {
        (void)item;
+       (void)pos;
 }
 
 void ViewWidget::item_clicked(const shared_ptr<ViewItem> &item)
@@ -76,7 +77,7 @@ bool ViewWidget::accept_drag() const
        if (any_row_items_selected && !any_time_items_selected) {
                // Check all the drag items share a common owner
                TraceTreeItemOwner *item_owner = nullptr;
-               for (shared_ptr<TraceTreeItem> r : trace_tree_items)
+               for (const shared_ptr<TraceTreeItem>& r : trace_tree_items)
                        if (r->dragging()) {
                                if (!item_owner)
                                        item_owner = r->owner();
@@ -106,7 +107,7 @@ void ViewWidget::drag_items(const QPoint &delta)
        // Drag the row items
        const vector< shared_ptr<RowItem> > row_items(
                view_.list_by_type<RowItem>());
-       for (shared_ptr<RowItem> r : row_items)
+       for (const shared_ptr<RowItem>& r : row_items)
                if (r->dragging()) {
                        r->drag_by(delta);
 
@@ -120,7 +121,7 @@ void ViewWidget::drag_items(const QPoint &delta)
        TraceTreeItemOwner *item_owner = nullptr;
        const vector< shared_ptr<TraceTreeItem> > trace_tree_items(
                view_.list_by_type<TraceTreeItem>());
-       for (shared_ptr<TraceTreeItem> i : trace_tree_items)
+       for (const shared_ptr<TraceTreeItem>& i : trace_tree_items)
                if (i->dragging())
                        item_owner = i->owner();
 
@@ -169,7 +170,7 @@ void ViewWidget::mouse_left_press_event(QMouseEvent *event)
                clear_selection();
 
        // Set the signal selection state if the item has been clicked
-       if (mouse_down_item_) {
+       if (mouse_down_item_ && mouse_down_item_->is_selectable(event->pos())) {
                if (ctrl_pressed)
                        mouse_down_item_->select(!mouse_down_item_->selected());
                else
@@ -180,7 +181,7 @@ void ViewWidget::mouse_left_press_event(QMouseEvent *event)
        bool item_dragged = false;
        const auto items = this->items();
        for (auto &i : items)
-               if (i->selected()) {
+               if (i->selected() && i->is_draggable(event->pos())) {
                        item_dragged = true;
                        i->drag();
                }
@@ -251,17 +252,21 @@ void ViewWidget::mousePressEvent(QMouseEvent *event)
 {
        assert(event);
 
-       /* Ignore right click events as they will open context menus when
+       if (event->button() & Qt::LeftButton) {
+               mouse_down_point_ = event->pos();
+               mouse_down_item_ = get_mouse_over_item(event->pos());
+               mouse_left_press_event(event);
+       }
+
+       /* Don't forward right click events as they will open context menus when
         * used on trace labels. Those menus prevent ViewWidget::mouseReleaseEvent()
         * to be triggered upon button release, making mouse_down_item_
         * hold the last reference to a view item that might have been deleted
         * from the context menu, preventing it from being freed as intended.
+        * TODO Remove this once context menus are handled separately
         */
-       if (event->button() & Qt::LeftButton) {
+       if (event->button() & Qt::RightButton)
                mouse_down_point_ = event->pos();
-               mouse_down_item_ = get_mouse_over_item(event->pos());
-               mouse_left_press_event(event);
-       }
 }
 
 void ViewWidget::mouseReleaseEvent(QMouseEvent *event)
@@ -281,7 +286,7 @@ void ViewWidget::mouseMoveEvent(QMouseEvent *event)
        mouse_point_ = event->pos();
 
        if (!event->buttons())
-               item_hover(get_mouse_over_item(event->pos()));
+               item_hover(get_mouse_over_item(event->pos()), event->pos());
        else if (event->buttons() & Qt::LeftButton) {
                if (!item_dragging_) {
                        if ((event->pos() - mouse_down_point_).manhattanLength() <
index e4fb73c8255debedb73963acddc0984e635a0433..f4928e67f885bcd948c10ae0b72a7da03bff5d5b 100644 (file)
@@ -22,6 +22,7 @@
 
 #include <memory>
 
+#include <QPoint>
 #include <QWidget>
 
 using std::shared_ptr;
@@ -49,7 +50,7 @@ protected:
         * if no view item is being hovered over.
         * @remarks the default implementation does nothing.
         */
-       virtual void item_hover(const shared_ptr<ViewItem> &item);
+       virtual void item_hover(const shared_ptr<ViewItem> &item, QPoint pos);
 
        /**
         * Indicates the event an a view item has been clicked.
index e86a543ed69ae3f3e3d033e344f307a72b39a316..3c9bc8c0eebc045b034eb8088464f5a4e98d5547 100644 (file)
@@ -26,6 +26,7 @@
 
 #include "pv/session.hpp"
 #include "pv/util.hpp"
+#include "pv/data/segment.hpp"
 
 using std::shared_ptr;
 
@@ -35,6 +36,7 @@ namespace views {
 const int ViewBase::MaxViewAutoUpdateRate = 25; // No more than 25 Hz
 
 ViewBase::ViewBase(Session &session, bool is_main_view, QWidget *parent) :
+       // Note: Place defaults in ViewBase::reset_view_state(), not here
        session_(session),
        is_main_view_(is_main_view)
 {
@@ -44,6 +46,8 @@ ViewBase::ViewBase(Session &session, bool is_main_view, QWidget *parent) :
                this, SLOT(signals_changed()));
        connect(&session_, SIGNAL(capture_state_changed(int)),
                this, SLOT(capture_state_updated(int)));
+       connect(&session_, SIGNAL(new_segment(int)),
+               this, SLOT(on_new_segment(int)));
 
        connect(&delayed_view_updater_, SIGNAL(timeout()),
                this, SLOT(perform_delayed_view_update()));
@@ -51,6 +55,12 @@ ViewBase::ViewBase(Session &session, bool is_main_view, QWidget *parent) :
        delayed_view_updater_.setInterval(1000 / MaxViewAutoUpdateRate);
 }
 
+void ViewBase::reset_view_state()
+{
+       ruler_shift_ = 0;
+       current_segment_ = 0;
+}
+
 Session& ViewBase::session()
 {
        return session_;
@@ -72,11 +82,11 @@ unordered_set< shared_ptr<data::SignalBase> > ViewBase::signalbases() const
 
 void ViewBase::clear_signalbases()
 {
-       for (shared_ptr<data::SignalBase> signalbase : signalbases_) {
+       for (const shared_ptr<data::SignalBase>& signalbase : signalbases_) {
                disconnect(signalbase.get(), SIGNAL(samples_cleared()),
                        this, SLOT(on_data_updated()));
-               disconnect(signalbase.get(), SIGNAL(samples_added(QObject*, uint64_t, uint64_t)),
-                       this, SLOT(on_data_updated()));
+               disconnect(signalbase.get(), SIGNAL(samples_added(uint64_t, uint64_t, uint64_t)),
+                       this, SLOT(on_samples_added(uint64_t, uint64_t, uint64_t)));
        }
 
        signalbases_.clear();
@@ -88,8 +98,8 @@ void ViewBase::add_signalbase(const shared_ptr<data::SignalBase> signalbase)
 
        connect(signalbase.get(), SIGNAL(samples_cleared()),
                this, SLOT(on_data_updated()));
-       connect(signalbase.get(), SIGNAL(samples_added(QObject*, uint64_t, uint64_t)),
-               this, SLOT(on_data_updated()));
+       connect(signalbase.get(), SIGNAL(samples_added(uint64_t, uint64_t, uint64_t)),
+               this, SLOT(on_samples_added(uint64_t, uint64_t, uint64_t)));
 }
 
 #ifdef ENABLE_DECODE
@@ -97,14 +107,14 @@ void ViewBase::clear_decode_signals()
 {
 }
 
-void ViewBase::add_decode_signal(shared_ptr<data::SignalBase> signalbase)
+void ViewBase::add_decode_signal(shared_ptr<data::DecodeSignal> signal)
 {
-       (void)signalbase;
+       (void)signal;
 }
 
-void ViewBase::remove_decode_signal(shared_ptr<data::SignalBase> signalbase)
+void ViewBase::remove_decode_signal(shared_ptr<data::DecodeSignal> signal)
 {
-       (void)signalbase;
+       (void)signal;
 }
 #endif
 
@@ -118,8 +128,9 @@ void ViewBase::restore_settings(QSettings &settings)
        (void)settings;
 }
 
-void ViewBase::trigger_event(util::Timestamp location)
+void ViewBase::trigger_event(int segment_id, util::Timestamp location)
 {
+       (void)segment_id;
        (void)location;
 }
 
@@ -127,6 +138,16 @@ void ViewBase::signals_changed()
 {
 }
 
+void ViewBase::on_new_segment(int new_segment_id)
+{
+       (void)new_segment_id;
+}
+
+void ViewBase::on_segment_completed(int new_segment_id)
+{
+       (void)new_segment_id;
+}
+
 void ViewBase::capture_state_updated(int state)
 {
        (void)state;
@@ -136,6 +157,19 @@ void ViewBase::perform_delayed_view_update()
 {
 }
 
+void ViewBase::on_samples_added(uint64_t segment_id, uint64_t start_sample,
+       uint64_t end_sample)
+{
+       (void)start_sample;
+       (void)end_sample;
+
+       if (segment_id != current_segment_)
+               return;
+
+       if (!delayed_view_updater_.isActive())
+               delayed_view_updater_.start();
+}
+
 void ViewBase::on_data_updated()
 {
        if (!delayed_view_updater_.isActive())
index 61430136f5b032a347bf495ea7cb6ecd56cce45f..b524c1797d0d1478ba4bd1eac36ee538a400d69a 100644 (file)
 #include <pv/data/signalbase.hpp>
 #include <pv/util.hpp>
 
+#ifdef ENABLE_DECODE
+#include <pv/data/decodesignal.hpp>
+#endif
+
 using std::shared_ptr;
 using std::unordered_set;
 
@@ -61,6 +65,12 @@ private:
 public:
        explicit ViewBase(Session &session, bool is_main_view = false, QWidget *parent = nullptr);
 
+       /**
+        * Resets the view to its default state after construction. It does however
+        * not reset the signal bases or any other connections with the session.
+        */
+       virtual void reset_view_state();
+
        Session& session();
        const Session& session() const;
 
@@ -78,9 +88,9 @@ public:
 #ifdef ENABLE_DECODE
        virtual void clear_decode_signals();
 
-       virtual void add_decode_signal(shared_ptr<data::SignalBase> signalbase);
+       virtual void add_decode_signal(shared_ptr<data::DecodeSignal> signal);
 
-       virtual void remove_decode_signal(shared_ptr<data::SignalBase> signalbase);
+       virtual void remove_decode_signal(shared_ptr<data::DecodeSignal> signal);
 #endif
 
        virtual void save_settings(QSettings &settings) const;
@@ -88,12 +98,17 @@ public:
        virtual void restore_settings(QSettings &settings);
 
 public Q_SLOTS:
-       virtual void trigger_event(util::Timestamp location);
+       virtual void trigger_event(int segment_id, util::Timestamp location);
        virtual void signals_changed();
        virtual void capture_state_updated(int state);
+       virtual void on_new_segment(int new_segment_id);
+       virtual void on_segment_completed(int new_segment_id);
        virtual void perform_delayed_view_update();
 
 private Q_SLOTS:
+       void on_samples_added(uint64_t segment_id, uint64_t start_sample,
+               uint64_t end_sample);
+
        void on_data_updated();
 
 protected:
@@ -101,10 +116,14 @@ protected:
 
        const bool is_main_view_;
 
+       util::Timestamp ruler_shift_;
        util::TimeUnit time_unit_;
 
        unordered_set< shared_ptr<data::SignalBase> > signalbases_;
 
+       /// The ID of the currently displayed segment
+       uint32_t current_segment_;
+
        QTimer delayed_view_updater_;
 };
 
diff --git a/pv/widgets/colorbutton.cpp b/pv/widgets/colorbutton.cpp
new file mode 100644 (file)
index 0000000..702fdde
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel@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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "colorbutton.hpp"
+
+#include <cassert>
+
+#include <QApplication>
+#include <QColorDialog>
+#include <QPainter>
+
+namespace pv {
+namespace widgets {
+
+const int ColorButton::SwatchMargin = 7;
+
+ColorButton::ColorButton(QWidget *parent) :
+       QPushButton("", parent),
+       popup_(nullptr)
+{
+       connect(this, SIGNAL(clicked(bool)), this, SLOT(on_clicked(bool)));
+}
+
+ColorButton::ColorButton(int rows, int cols, QWidget *parent) :
+       QPushButton("", parent),
+       popup_(new ColorPopup(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)));
+}
+
+ColorPopup* ColorButton::popup()
+{
+       return popup_;
+}
+
+const QColor& ColorButton::color() const
+{
+       return cur_color_;
+}
+
+void ColorButton::set_color(QColor color)
+{
+       cur_color_ = color;
+
+       if (popup_) {
+               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() == color) {
+                                       popup_->well_array().setSelected(r, c);
+                                       popup_->well_array().setCurrent(r, c);
+                                       return;
+                               }
+       }
+}
+
+void ColorButton::set_palette(const QColor *const palette)
+{
+       assert(palette);
+       assert(popup_);
+
+       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 ColorButton::on_clicked(bool)
+{
+       if (popup_) {
+               popup_->set_position(mapToGlobal(rect().center()), Popup::Bottom);
+               popup_->show();
+       } else {
+               QColorDialog dlg(this);
+               dlg.setOption(QColorDialog::ShowAlphaChannel);
+               dlg.setOption(QColorDialog::DontUseNativeDialog);
+               connect(&dlg, SIGNAL(colorSelected(const QColor)),
+                       this, SLOT(on_color_selected(const QColor)));
+               dlg.setCurrentColor(cur_color_);
+               dlg.exec();
+       }
+}
+
+void ColorButton::on_selected(int row, int col)
+{
+       assert(popup_);
+
+       cur_color_ = popup_->well_array().cellBrush(row, col).color();
+       selected(cur_color_);
+}
+
+void ColorButton::on_color_selected(const QColor& color)
+{
+       cur_color_ = color;
+       selected(cur_color_);
+}
+
+void ColorButton::paintEvent(QPaintEvent *event)
+{
+       QPushButton::paintEvent(event);
+
+       QPainter p(this);
+
+       const QRect r = rect().adjusted(SwatchMargin, SwatchMargin,
+               -SwatchMargin, -SwatchMargin);
+       p.setPen(QApplication::palette().color(QPalette::Dark));
+       p.setBrush(QBrush(cur_color_));
+       p.drawRect(r);
+}
+
+}  // namespace widgets
+}  // namespace pv
diff --git a/pv/widgets/colorbutton.hpp b/pv/widgets/colorbutton.hpp
new file mode 100644 (file)
index 0000000..4726c1b
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel@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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PULSEVIEW_PV_WIDGETS_COLORBUTTON_HPP
+#define PULSEVIEW_PV_WIDGETS_COLORBUTTON_HPP
+
+#include <QPushButton>
+
+#include "colorpopup.hpp"
+
+namespace pv {
+namespace widgets {
+
+class ColorButton : public QPushButton
+{
+       Q_OBJECT;
+
+private:
+       static const int SwatchMargin;
+
+public:
+       /**
+        * Construct a ColorButton instance that uses a QColorDialog
+        */
+       ColorButton(QWidget *parent);
+
+       /**
+        * Construct a ColorButton instance that uses a ColorPopup
+        */
+       ColorButton(int rows, int cols, QWidget *parent);
+
+       ColorPopup* popup();
+
+       const QColor& color() const;
+
+       void set_color(QColor color);
+
+       void set_palette(const QColor *const palette);
+
+private:
+       void paintEvent(QPaintEvent *event);
+
+private Q_SLOTS:
+       void on_clicked(bool);
+       void on_selected(int row, int col);
+       void on_color_selected(const QColor& color);
+
+Q_SIGNALS:
+       void selected(const QColor &color);
+
+private:
+       ColorPopup* popup_;
+       QColor cur_color_;
+};
+
+}  // namespace widgets
+}  // namespace pv
+
+#endif // PULSEVIEW_PV_WIDGETS_COLORBUTTON_HPP
diff --git a/pv/widgets/colorpopup.cpp b/pv/widgets/colorpopup.cpp
new file mode 100644 (file)
index 0000000..c4f914e
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel@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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "colorpopup.hpp"
+
+namespace pv {
+namespace widgets {
+
+ColorPopup::ColorPopup(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(color_selected(int, int)));
+}
+
+WellArray& ColorPopup::well_array()
+{
+       return well_array_;
+}
+
+void ColorPopup::color_selected(int, int)
+{
+       close();
+}
+
+}  // namespace widgets
+}  // namespace pv
diff --git a/pv/widgets/colorpopup.hpp b/pv/widgets/colorpopup.hpp
new file mode 100644 (file)
index 0000000..23fd746
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2013 Joel Holdsworth <joel@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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PULSEVIEW_PV_WIDGETS_COLORPOPUP_HPP
+#define PULSEVIEW_PV_WIDGETS_COLORPOPUP_HPP
+
+#include "popup.hpp"
+#include "wellarray.hpp"
+
+#include <QVBoxLayout>
+
+namespace pv {
+namespace widgets {
+
+class ColorPopup : public Popup
+{
+       Q_OBJECT
+
+public:
+       ColorPopup(int rows, int cols, QWidget *parent);
+
+       WellArray& well_array();
+
+Q_SIGNALS:
+       void selected(int row, int col);
+
+private Q_SLOTS:
+       void color_selected(int, int);
+
+private:
+       WellArray well_array_;
+       QVBoxLayout layout_;
+};
+
+}  // namespace widgets
+}  // namespace pv
+
+#endif // PULSEVIEW_PV_WIDGETS_COLORPOPUP_HPP
diff --git a/pv/widgets/colourbutton.cpp b/pv/widgets/colourbutton.cpp
deleted file mode 100644 (file)
index 01a22b6..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * This file is part of the PulseView project.
- *
- * Copyright (C) 2013 Joel Holdsworth <joel@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, see <http://www.gnu.org/licenses/>.
- */
-
-#include "colourbutton.hpp"
-
-#include <cassert>
-
-#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 *event)
-{
-       QPushButton::paintEvent(event);
-
-       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);
-}
-
-}  // namespace widgets
-}  // namespace pv
diff --git a/pv/widgets/colourbutton.hpp b/pv/widgets/colourbutton.hpp
deleted file mode 100644 (file)
index 9dbb2b2..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * This file is part of the PulseView project.
- *
- * Copyright (C) 2013 Joel Holdsworth <joel@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, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef PULSEVIEW_PV_WIDGETS_COLOURBUTTON_HPP
-#define PULSEVIEW_PV_WIDGETS_COLOURBUTTON_HPP
-
-#include <QPushButton>
-
-#include "colourpopup.hpp"
-
-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 *event);
-
-private Q_SLOTS:
-       void on_clicked(bool);
-
-       void on_selected(int row, int col);
-
-Q_SIGNALS:
-       void selected(const QColor &colour);
-
-private:
-       ColourPopup popup_;
-       QColor cur_colour_;
-};
-
-}  // namespace widgets
-}  // namespace pv
-
-#endif // PULSEVIEW_PV_WIDGETS_COLOURBUTTON_HPP
diff --git a/pv/widgets/colourpopup.cpp b/pv/widgets/colourpopup.cpp
deleted file mode 100644 (file)
index 170385b..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * This file is part of the PulseView project.
- *
- * Copyright (C) 2013 Joel Holdsworth <joel@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, see <http://www.gnu.org/licenses/>.
- */
-
-#include "colourpopup.hpp"
-
-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)));
-}
-
-WellArray& ColourPopup::well_array()
-{
-       return well_array_;
-}
-
-void ColourPopup::colour_selected(int, int)
-{
-       close();
-}
-
-}  // namespace widgets
-}  // namespace pv
diff --git a/pv/widgets/colourpopup.hpp b/pv/widgets/colourpopup.hpp
deleted file mode 100644 (file)
index 6e4de20..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * This file is part of the PulseView project.
- *
- * Copyright (C) 2013 Joel Holdsworth <joel@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, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef PULSEVIEW_PV_WIDGETS_COLOURPOPUP_HPP
-#define PULSEVIEW_PV_WIDGETS_COLOURPOPUP_HPP
-
-#include "popup.hpp"
-#include "wellarray.hpp"
-
-#include <QVBoxLayout>
-
-namespace pv {
-namespace widgets {
-
-class ColourPopup : public Popup
-{
-       Q_OBJECT
-
-public:
-       ColourPopup(int rows, int cols, QWidget *parent);
-
-       WellArray& well_array();
-
-Q_SIGNALS:
-       void selected(int row, int col);
-
-private Q_SLOTS:
-       void colour_selected(int, int);
-
-private:
-       WellArray well_array_;
-       QVBoxLayout layout_;
-};
-
-}  // namespace widgets
-}  // namespace pv
-
-#endif // PULSEVIEW_PV_WIDGETS_COLOURPOPUP_HPP
index 5e54589372c98373b79fef15db8a7b47ce339016..28b54babdfb9aad3c0e67e2a732b22ac79cbe918 100644 (file)
@@ -30,9 +30,9 @@ DecoderMenu::DecoderMenu(QWidget *parent, bool first_level_decoder) :
        QMenu(parent),
        mapper_(this)
 {
-       GSList *l = g_slist_sort(g_slist_copy(
+       GSList *li = g_slist_sort(g_slist_copy(
                (GSList*)srd_decoder_list()), decoder_name_cmp);
-       for (; l; l = l->next) {
+       for (GSList *l = li; l; l = l->next) {
                const srd_decoder *const d = (srd_decoder*)l->data;
                assert(d);
 
@@ -46,7 +46,7 @@ DecoderMenu::DecoderMenu(QWidget *parent, bool first_level_decoder) :
                                &mapper_, SLOT(map()));
                }
        }
-       g_slist_free(l);
+       g_slist_free(li);
 
        connect(&mapper_, SIGNAL(mapped(QObject*)),
                this, SLOT(on_action(QObject*)));
index bb18b792788cd3592eba4b46ddcb967cd17e89a7..6a12aa25c446904651b60bfe6de58e13d27eaa12 100644 (file)
@@ -91,7 +91,7 @@ void DeviceToolButton::update_device_list()
        menu_.setDefaultAction(connect_action_);
        menu_.addSeparator();
 
-       for (weak_ptr<Device> dev_weak_ptr : devices_) {
+       for (weak_ptr<Device>& dev_weak_ptr : devices_) {
                shared_ptr<Device> dev(dev_weak_ptr.lock());
                if (!dev)
                        continue;
@@ -117,7 +117,7 @@ void DeviceToolButton::on_action(QObject *action)
        selected_device_.reset();
 
        Device *const dev = (Device*)((QAction*)action)->data().value<void*>();
-       for (weak_ptr<Device> dev_weak_ptr : devices_) {
+       for (weak_ptr<Device>& dev_weak_ptr : devices_) {
                shared_ptr<Device> dev_ptr(dev_weak_ptr);
                if (dev_ptr.get() == dev) {
                        selected_device_ = shared_ptr<Device>(dev_ptr);
index 116dad50aad72a98000f306937bb1df9863e46d3..66f7f06f5d87192a2ea77cbafee44af21cec294c 100644 (file)
@@ -53,7 +53,7 @@ public:
 
        bool eventFilter(QObject *obj, QEvent *event);
 
-       void show();
+       virtual void show();
 
 private:
        bool space_for_arrow() const;
index b4943225dd8d44ddae54fad59b7231e70a8c6e3f..12f5969eb707c9997c212e1d39f838541c1f5588 100644 (file)
@@ -47,6 +47,9 @@ SweepTimingWidget::SweepTimingWidget(const char *suffix,
 
        connect(&list_, SIGNAL(currentIndexChanged(int)),
                this, SIGNAL(value_changed()));
+       connect(&list_, SIGNAL(editTextChanged(const QString&)),
+               this, SIGNAL(value_changed()));
+
        connect(&value_, SIGNAL(editingFinished()),
                this, SIGNAL(value_changed()));
 
@@ -58,6 +61,11 @@ SweepTimingWidget::SweepTimingWidget(const char *suffix,
        show_none();
 }
 
+void SweepTimingWidget::allow_user_entered_values(bool value)
+{
+       list_.setEditable(value);
+}
+
 void SweepTimingWidget::show_none()
 {
        value_type_ = None;
@@ -144,9 +152,14 @@ uint64_t SweepTimingWidget::value() const
                return (uint64_t)value_.value();
        case List:
        {
+               if (list_.isEditable()) {
+                       uint64_t value;
+                       sr_parse_sizestring(list_.currentText().toUtf8().data(), &value);
+                       return value;
+               }
+
                const int index = list_.currentIndex();
-               return (index >= 0) ? list_.itemData(
-                       index).value<uint64_t>() : 0;
+               return (index >= 0) ? list_.itemData(index).value<uint64_t>() : 0;
        }
        default:
                // Unexpected value type
@@ -159,19 +172,25 @@ 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;
+       if (list_.isEditable()) {
+               char *const s = sr_si_string_u64(value, suffix_);
+               list_.lineEdit()->setText(QString::fromUtf8(s));
+               g_free(s);
+       } else {
+               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);
+               list_.setCurrentIndex(best_match);
+       }
 }
 
 }  // namespace widgets
index 0b61bf4a64c9ef03815b37c12b9e05e58cc9e251..46d12d8e1e47c79687e4c8796bce51c762bf6ccc 100644 (file)
@@ -46,6 +46,8 @@ private:
 public:
        SweepTimingWidget(const char *suffix, QWidget *parent = nullptr);
 
+       void allow_user_entered_values(bool value);
+
        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);
index f1c25dc01fde9e0c39694594296c2bf265b6d2e5..4c67ca114d0f4ba236a4428bab30a7fcbdfa61bb 100644 (file)
@@ -52,8 +52,8 @@ struct WellArrayData;
 class WellArray : public QWidget
 {
     Q_OBJECT
-    Q_PROPERTY(int selectedColumn READ selectedColumn)
-    Q_PROPERTY(int selectedRow READ selectedRow)
+    Q_PROPERTY(int selectedColumn READ selectedColumn)  // clazy-exclude:qproperty-without-notify
+    Q_PROPERTY(int selectedRow READ selectedRow)  // clazy-exclude:qproperty-without-notify
 
 public:
     WellArray(int rows, int cols, QWidget* parent = nullptr);
index a81f186c8493ec61afce9833023550e4c85fec18..a17b04b9eb885d8de55f5bc507d0974eae554668 100644 (file)
@@ -17,8 +17,8 @@
  * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef SIGNALHANDLER_HPP
-#define SIGNALHANDLER_HPP
+#ifndef PULSEVIEW_PV_SIGNALHANDLER_HPP
+#define PULSEVIEW_PV_SIGNALHANDLER_HPP
 
 #include <QObject>
 
@@ -51,4 +51,4 @@ private:
        static int sockets_[2];
 };
 
-#endif // SIGNALHANDLER_HPP
+#endif // PULSEVIEW_PV_SIGNALHANDLER_HPP
index 8cab18feb3db969f59d5a53813f9b07885a2c04c..300532e11394744e407b124feee4e903fed22ec1 100644 (file)
 ##
 
 set(pulseview_TEST_SOURCES
+       ${PROJECT_SOURCE_DIR}/pv/application.cpp
        ${PROJECT_SOURCE_DIR}/pv/devicemanager.cpp
        ${PROJECT_SOURCE_DIR}/pv/globalsettings.cpp
+       ${PROJECT_SOURCE_DIR}/pv/logging.cpp
+       ${PROJECT_SOURCE_DIR}/pv/mainwindow.cpp
        ${PROJECT_SOURCE_DIR}/pv/session.cpp
        ${PROJECT_SOURCE_DIR}/pv/storesession.cpp
        ${PROJECT_SOURCE_DIR}/pv/util.cpp
@@ -41,6 +44,7 @@ set(pulseview_TEST_SOURCES
        ${PROJECT_SOURCE_DIR}/pv/devices/sessionfile.cpp
        ${PROJECT_SOURCE_DIR}/pv/dialogs/connect.cpp
        ${PROJECT_SOURCE_DIR}/pv/dialogs/inputoutputoptions.cpp
+       ${PROJECT_SOURCE_DIR}/pv/dialogs/settings.cpp
        ${PROJECT_SOURCE_DIR}/pv/dialogs/storeprogress.cpp
        ${PROJECT_SOURCE_DIR}/pv/prop/bool.cpp
        ${PROJECT_SOURCE_DIR}/pv/prop/double.cpp
@@ -61,7 +65,6 @@ set(pulseview_TEST_SOURCES
        ${PROJECT_SOURCE_DIR}/pv/views/trace/rowitem.cpp
        ${PROJECT_SOURCE_DIR}/pv/views/trace/ruler.cpp
        ${PROJECT_SOURCE_DIR}/pv/views/trace/signal.cpp
-       ${PROJECT_SOURCE_DIR}/pv/views/trace/signalscalehandle.cpp
        ${PROJECT_SOURCE_DIR}/pv/views/trace/timeitem.cpp
        ${PROJECT_SOURCE_DIR}/pv/views/trace/timemarker.cpp
        ${PROJECT_SOURCE_DIR}/pv/views/trace/trace.cpp
@@ -78,8 +81,8 @@ set(pulseview_TEST_SOURCES
        ${PROJECT_SOURCE_DIR}/pv/views/trace/viewwidget.cpp
        ${PROJECT_SOURCE_DIR}/pv/views/viewbase.cpp
        ${PROJECT_SOURCE_DIR}/pv/views/trace/standardbar.cpp
-       ${PROJECT_SOURCE_DIR}/pv/widgets/colourbutton.cpp
-       ${PROJECT_SOURCE_DIR}/pv/widgets/colourpopup.cpp
+       ${PROJECT_SOURCE_DIR}/pv/widgets/colorbutton.cpp
+       ${PROJECT_SOURCE_DIR}/pv/widgets/colorpopup.cpp
        ${PROJECT_SOURCE_DIR}/pv/widgets/devicetoolbutton.cpp
        ${PROJECT_SOURCE_DIR}/pv/widgets/exportmenu.cpp
        ${PROJECT_SOURCE_DIR}/pv/widgets/importmenu.cpp
@@ -98,7 +101,11 @@ set(pulseview_TEST_SOURCES
 
 # This list includes only QObject derived class headers.
 set(pulseview_TEST_HEADERS
+       ${PROJECT_SOURCE_DIR}/pv/application.hpp
+       ${PROJECT_SOURCE_DIR}/pv/devicemanager.hpp
        ${PROJECT_SOURCE_DIR}/pv/globalsettings.hpp
+       ${PROJECT_SOURCE_DIR}/pv/logging.hpp
+       ${PROJECT_SOURCE_DIR}/pv/mainwindow.hpp
        ${PROJECT_SOURCE_DIR}/pv/session.hpp
        ${PROJECT_SOURCE_DIR}/pv/storesession.hpp
        ${PROJECT_SOURCE_DIR}/pv/binding/device.hpp
@@ -110,6 +117,7 @@ set(pulseview_TEST_HEADERS
        ${PROJECT_SOURCE_DIR}/pv/devices/device.hpp
        ${PROJECT_SOURCE_DIR}/pv/dialogs/connect.hpp
        ${PROJECT_SOURCE_DIR}/pv/dialogs/inputoutputoptions.hpp
+       ${PROJECT_SOURCE_DIR}/pv/dialogs/settings.hpp
        ${PROJECT_SOURCE_DIR}/pv/dialogs/storeprogress.hpp
        ${PROJECT_SOURCE_DIR}/pv/popups/channels.hpp
        ${PROJECT_SOURCE_DIR}/pv/popups/deviceoptions.hpp
@@ -129,7 +137,6 @@ set(pulseview_TEST_HEADERS
        ${PROJECT_SOURCE_DIR}/pv/views/trace/rowitem.hpp
        ${PROJECT_SOURCE_DIR}/pv/views/trace/ruler.hpp
        ${PROJECT_SOURCE_DIR}/pv/views/trace/signal.hpp
-       ${PROJECT_SOURCE_DIR}/pv/views/trace/signalscalehandle.hpp
        ${PROJECT_SOURCE_DIR}/pv/views/trace/timeitem.hpp
        ${PROJECT_SOURCE_DIR}/pv/views/trace/timemarker.hpp
        ${PROJECT_SOURCE_DIR}/pv/views/trace/trace.hpp
@@ -142,8 +149,8 @@ set(pulseview_TEST_HEADERS
        ${PROJECT_SOURCE_DIR}/pv/views/trace/viewwidget.hpp
        ${PROJECT_SOURCE_DIR}/pv/views/viewbase.hpp
        ${PROJECT_SOURCE_DIR}/pv/views/trace/standardbar.hpp
-       ${PROJECT_SOURCE_DIR}/pv/widgets/colourbutton.hpp
-       ${PROJECT_SOURCE_DIR}/pv/widgets/colourpopup.hpp
+       ${PROJECT_SOURCE_DIR}/pv/widgets/colorbutton.hpp
+       ${PROJECT_SOURCE_DIR}/pv/widgets/colorpopup.hpp
        ${PROJECT_SOURCE_DIR}/pv/widgets/devicetoolbutton.hpp
        ${PROJECT_SOURCE_DIR}/pv/widgets/exportmenu.hpp
        ${PROJECT_SOURCE_DIR}/pv/widgets/importmenu.hpp
@@ -157,7 +164,7 @@ set(pulseview_TEST_HEADERS
 if(ENABLE_DECODE)
        list(APPEND pulseview_TEST_SOURCES
                ${PROJECT_SOURCE_DIR}/pv/binding/decoder.cpp
-               ${PROJECT_SOURCE_DIR}/pv/data/decoderstack.cpp
+               ${PROJECT_SOURCE_DIR}/pv/data/decodesignal.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
@@ -165,11 +172,10 @@ if(ENABLE_DECODE)
                ${PROJECT_SOURCE_DIR}/pv/views/trace/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.hpp
+               ${PROJECT_SOURCE_DIR}/pv/data/decodesignal.hpp
                ${PROJECT_SOURCE_DIR}/pv/views/trace/decodetrace.hpp
                ${PROJECT_SOURCE_DIR}/pv/widgets/decodergroupbox.hpp
                ${PROJECT_SOURCE_DIR}/pv/widgets/decodermenu.hpp
index d1c35ee1a34aa2f8ebf94f703b9c1be5167fd3df..1a67899c952c7bd6a95378ab027bc47d6e59f6a5 100644 (file)
@@ -34,7 +34,7 @@ BOOST_AUTO_TEST_SUITE(SegmentTest)
 /* --- For debugging only
 BOOST_AUTO_TEST_CASE(SmallSize8Single)
 {
-       Segment s(1, sizeof(uint8_t));
+       Segment s(0, 1, sizeof(uint8_t));
        uint32_t num_samples = 10;
 
        //----- Chunk size << pv::data::Segment::MaxChunkSize @ 8bit, added in 1 call ----//
@@ -47,17 +47,18 @@ BOOST_AUTO_TEST_CASE(SmallSize8Single)
 
        BOOST_CHECK(s.get_sample_count() == num_samples);
 
+       uint8_t *sample_data = new uint8_t[1];
        for (uint32_t i = 0; i < num_samples; i++) {
-               uint8_t* sample_data = s.get_raw_samples(i, 1);
+               s.get_raw_samples(i, 1, sample_data);
                BOOST_CHECK_EQUAL(*sample_data, i);
-               delete[] sample_data;
        }
+       delete[] sample_data;
 } */
 
 /* --- For debugging only
 BOOST_AUTO_TEST_CASE(MediumSize8Single)
 {
-       Segment s(1, sizeof(uint8_t));
+       Segment s(0, 1, sizeof(uint8_t));
        uint32_t num_samples = pv::data::Segment::MaxChunkSize;
 
        //----- Chunk size == pv::data::Segment::MaxChunkSize @ 8bit, added in 1 call ----//
@@ -70,17 +71,18 @@ BOOST_AUTO_TEST_CASE(MediumSize8Single)
 
        BOOST_CHECK(s.get_sample_count() == num_samples);
 
+       uint8_t *sample_data = new uint8_t[1];
        for (uint32_t i = 0; i < num_samples; i++) {
-               uint8_t* sample_data = s.get_raw_samples(i, 1);
+               s.get_raw_samples(i, 1, sample_data);
                BOOST_CHECK_EQUAL(*sample_data, i % 256);
-               delete[] sample_data;
        }
+       delete[] sample_data;
 } */
 
 /* --- For debugging only
 BOOST_AUTO_TEST_CASE(MaxSize8Single)
 {
-       Segment s(1, sizeof(uint8_t));
+       Segment s(0, 1, sizeof(uint8_t));
 
        // We want to see proper behavior across chunk boundaries
        uint32_t num_samples = 2*pv::data::Segment::MaxChunkSize;
@@ -95,17 +97,18 @@ BOOST_AUTO_TEST_CASE(MaxSize8Single)
 
        BOOST_CHECK(s.get_sample_count() == num_samples);
 
+       uint8_t *sample_data = new uint8_t[1];
        for (uint32_t i = 0; i < num_samples; i++) {
-               uint8_t* sample_data = s.get_raw_samples(i, 1);
+               s.get_raw_samples(i, 1, sample_data);
                BOOST_CHECK_EQUAL(*sample_data, i % 256);
-               delete[] sample_data;
        }
+       delete[] sample_data;
 } */
 
 /* --- For debugging only
 BOOST_AUTO_TEST_CASE(MediumSize24Single)
 {
-       Segment s(1, 3);
+       Segment s(0, 1, 3);
 
        // Chunk size is num*unit_size, so with pv::data::Segment::MaxChunkSize/unit_size, we reach the maximum size
        uint32_t num_samples = pv::data::Segment::MaxChunkSize / 3;
@@ -120,19 +123,20 @@ BOOST_AUTO_TEST_CASE(MediumSize24Single)
 
        BOOST_CHECK(s.get_sample_count() == num_samples);
 
+       uint8_t *sample_data = new uint8_t[3];
        for (uint32_t i = 0; i < num_samples; i++) {
-               uint8_t* sample_data = s.get_raw_samples(i, 1);
+               s.get_raw_samples(i, 1, sample_data);
                BOOST_CHECK_EQUAL(*((uint8_t*)sample_data),      3*i    % 256);
                BOOST_CHECK_EQUAL(*((uint8_t*)(sample_data+1)), (3*i+1) % 256);
                BOOST_CHECK_EQUAL(*((uint8_t*)(sample_data+2)), (3*i+2) % 256);
-               delete[] sample_data;
        }
+       delete[] sample_data;
 } */
 
 /* --- For debugging only
 BOOST_AUTO_TEST_CASE(MediumSize32Single)
 {
-       Segment s(1, sizeof(uint32_t));
+       Segment s(0, 1, sizeof(uint32_t));
 
        // Chunk size is num*unit_size, so with pv::data::Segment::MaxChunkSize/unit_size, we reach the maximum size
        uint32_t num_samples = pv::data::Segment::MaxChunkSize / sizeof(uint32_t);
@@ -147,17 +151,18 @@ BOOST_AUTO_TEST_CASE(MediumSize32Single)
 
        BOOST_CHECK(s.get_sample_count() == num_samples);
 
+       uint8_t *sample_data = new uint8_t[sizeof(uint32_t)];
        for (uint32_t i = 0; i < num_samples; i++) {
-               uint8_t* sample_data = s.get_raw_samples(i, 1);
+               s.get_raw_samples(i, 1, sample_data);
                BOOST_CHECK_EQUAL(*((uint32_t*)sample_data), i);
-               delete[] sample_data;
        }
+       delete[] sample_data;
 } */
 
 /* --- For debugging only
 BOOST_AUTO_TEST_CASE(MaxSize32Single)
 {
-       Segment s(1, sizeof(uint32_t));
+       Segment s(0, 1, sizeof(uint32_t));
 
        // Chunk size is num*unit_size, so with pv::data::Segment::MaxChunkSize/unit_size, we reach the maximum size
        // Also, we want to see proper behavior across chunk boundaries
@@ -173,17 +178,18 @@ BOOST_AUTO_TEST_CASE(MaxSize32Single)
 
        BOOST_CHECK(s.get_sample_count() == num_samples);
 
+       uint8_t *sample_data = new uint8_t[sizeof(uint32_t)];
        for (uint32_t i = 0; i < num_samples; i++) {
-               uint8_t* sample_data = s.get_raw_samples(i, 1);
+               s.get_raw_samples(i, 1, sample_data);
                BOOST_CHECK_EQUAL(*((uint32_t*)sample_data), i);
-               delete[] sample_data;
        }
+       delete[] sample_data;
 } */
 
 /* --- For debugging only
 BOOST_AUTO_TEST_CASE(MediumSize32Multi)
 {
-       Segment s(1, sizeof(uint32_t));
+       Segment s(0, 1, sizeof(uint32_t));
 
        // Chunk size is num*unit_size, so with pv::data::Segment::MaxChunkSize/unit_size, we reach the maximum size
        uint32_t num_samples = pv::data::Segment::MaxChunkSize / sizeof(uint32_t);
@@ -197,16 +203,17 @@ BOOST_AUTO_TEST_CASE(MediumSize32Multi)
 
        BOOST_CHECK(s.get_sample_count() == num_samples);
 
+       uint8_t *sample_data = new uint8_t[sizeof(uint32_t)];
        for (uint32_t i = 0; i < num_samples; i++) {
-               uint8_t* sample_data = s.get_raw_samples(i, 1);
+               s.get_raw_samples(i, 1, sample_data);
                BOOST_CHECK_EQUAL(*((uint32_t*)sample_data), i);
-               delete[] sample_data;
        }
+       delete[] sample_data;
 } */
 
 BOOST_AUTO_TEST_CASE(MaxSize32Multi)
 {
-       Segment s(1, sizeof(uint32_t));
+       Segment s(0, 1, sizeof(uint32_t));
 
        // Chunk size is num*unit_size, so with pv::data::Segment::MaxChunkSize/unit_size, we reach the maximum size
        uint32_t num_samples = 2*(pv::data::Segment::MaxChunkSize / sizeof(uint32_t));
@@ -220,13 +227,13 @@ BOOST_AUTO_TEST_CASE(MaxSize32Multi)
 
        BOOST_CHECK(s.get_sample_count() == num_samples);
 
+       uint8_t *sample_data = new uint8_t[sizeof(uint32_t) * num_samples];
        for (uint32_t i = 0; i < num_samples; i++) {
-               uint8_t* sample_data = s.get_raw_samples(i, 1);
+               s.get_raw_samples(i, 1, sample_data);
                BOOST_CHECK_EQUAL(*((uint32_t*)sample_data), i);
-               delete[] sample_data;
        }
 
-       uint8_t* sample_data = s.get_raw_samples(0, num_samples);
+       s.get_raw_samples(0, num_samples, sample_data);
        for (uint32_t i = 0; i < num_samples; i++) {
                BOOST_CHECK_EQUAL(*((uint32_t*)(sample_data + i * sizeof(uint32_t))), i);
        }
@@ -235,7 +242,7 @@ BOOST_AUTO_TEST_CASE(MaxSize32Multi)
 
 BOOST_AUTO_TEST_CASE(MaxSize32MultiAtOnce)
 {
-       Segment s(1, sizeof(uint32_t));
+       Segment s(0, 1, sizeof(uint32_t));
 
        // Chunk size is num*unit_size, so with pv::data::Segment::MaxChunkSize/unit_size, we reach the maximum size
        uint32_t num_samples = 3*(pv::data::Segment::MaxChunkSize / sizeof(uint32_t));
@@ -250,13 +257,13 @@ BOOST_AUTO_TEST_CASE(MaxSize32MultiAtOnce)
 
        BOOST_CHECK(s.get_sample_count() == num_samples);
 
+       uint8_t *sample_data = new uint8_t[sizeof(uint32_t) * num_samples];
        for (uint32_t i = 0; i < num_samples; i++) {
-               uint8_t* sample_data = s.get_raw_samples(i, 1);
+               s.get_raw_samples(i, 1, sample_data);
                BOOST_CHECK_EQUAL(*((uint32_t*)sample_data), i);
-               delete[] sample_data;
        }
 
-       uint8_t* sample_data = s.get_raw_samples(0, num_samples);
+       s.get_raw_samples(0, num_samples, sample_data);
        for (uint32_t i = 0; i < num_samples; i++) {
                BOOST_CHECK_EQUAL(*((uint32_t*)(sample_data + i * sizeof(uint32_t))), i);
        }
@@ -265,7 +272,7 @@ BOOST_AUTO_TEST_CASE(MaxSize32MultiAtOnce)
 
 BOOST_AUTO_TEST_CASE(MaxSize32MultiIterated)
 {
-       Segment s(1, sizeof(uint32_t));
+       Segment s(0, 1, sizeof(uint32_t));
 
        // Chunk size is num*unit_size, so with pv::data::Segment::MaxChunkSize/unit_size, we reach the maximum size
        uint32_t num_samples = 2*(pv::data::Segment::MaxChunkSize / sizeof(uint32_t));
@@ -279,15 +286,14 @@ BOOST_AUTO_TEST_CASE(MaxSize32MultiIterated)
 
        BOOST_CHECK(s.get_sample_count() == num_samples);
 
-       pv::data::SegmentRawDataIterator* it = s.begin_raw_sample_iteration(0);
+       pv::data::SegmentDataIterator* it = s.begin_sample_iteration(0);
 
        for (uint32_t i = 0; i < num_samples; i++) {
-               uint8_t* sample_data = it->value;
-               BOOST_CHECK_EQUAL(*((uint32_t*)sample_data), i);
-               s.continue_raw_sample_iteration(it, 1);
+               BOOST_CHECK_EQUAL(*((uint32_t*)s.get_iterator_value(it)), i);
+               s.continue_sample_iteration(it, 1);
        }
 
-       s.end_raw_sample_iteration(it);
+       s.end_sample_iteration(it);
 }
 
 BOOST_AUTO_TEST_SUITE_END()
index 37ba9c8673e6db327977ac6caba48c3872862c1d..5cef38d5ce20211c1ca8e329ca8e05f0a62629a1 100644 (file)
@@ -42,9 +42,10 @@ BOOST_AUTO_TEST_CASE(tick_position_test_0)
        const pv::util::Timestamp offset("0");
        const double scale(0.001);
        const int width(500);
+       const unsigned int minor_tick_count(4);
 
-       const Ruler::TickPositions ts = Ruler::calculate_tick_positions(
-               major_period, offset, scale, width, format);
+       const TickPositions ts = Ruler::calculate_tick_positions(
+               major_period, offset, scale, width, minor_tick_count, format);
 
        BOOST_REQUIRE_EQUAL(ts.major.size(), 6);
 
@@ -88,9 +89,10 @@ BOOST_AUTO_TEST_CASE(tick_position_test_1)
        const pv::util::Timestamp offset("-0.463");
        const double scale(0.001);
        const int width(500);
+       const unsigned int minor_tick_count(4);
 
-       const Ruler::TickPositions ts = Ruler::calculate_tick_positions(
-               major_period, offset, scale, width, format);
+       const TickPositions ts = Ruler::calculate_tick_positions(
+               major_period, offset, scale, width, minor_tick_count, format);
 
        BOOST_REQUIRE_EQUAL(ts.major.size(), 5);
 
@@ -132,9 +134,10 @@ BOOST_AUTO_TEST_CASE(tick_position_test_2)
        const pv::util::Timestamp offset("8");
        const double scale(0.129746);
        const int width(580);
+       const unsigned int minor_tick_count(4);
 
-       const Ruler::TickPositions ts = Ruler::calculate_tick_positions(
-               major_period, offset, scale, width, format);
+       const TickPositions ts = Ruler::calculate_tick_positions(
+               major_period, offset, scale, width, minor_tick_count, format);
 
        const double mp = 5;
        const int off = 8;
diff --git a/themes/darkstyle/darkstyle.qss b/themes/darkstyle/darkstyle.qss
new file mode 100644 (file)
index 0000000..889ef95
--- /dev/null
@@ -0,0 +1,346 @@
+QScrollArea {
+  background-color: transparent; /* Workaround for widget background on Windows, see https://stackoverflow.com/questions/25012224/qt-widget-background-differs-from-linux-to-windows/25088075 */
+}
+QToolTip{
+  color:#ffffff;
+  background-color:palette(base);
+  border:1px solid palette(highlight);
+  border-radius:4px;
+}
+QStatusBar{
+  background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
+  color:palette(mid);
+}
+QMenuBar{
+  background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
+  border-bottom:2px solid rgba(25,25,25,75);
+}
+QMenuBar::item{
+  spacing:2px;
+  padding:3px 4px;
+  background:transparent;
+}
+QMenuBar::item:selected{
+  background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(106,106,106,255),stop:1 rgba(106,106,106,75));
+  border-left:1px solid rgba(106,106,106,127);
+  border-right:1px solid rgba(106,106,106,127);
+}
+QMenuBar::item:pressed{
+  background-color:palette(highlight);
+  border-left:1px solid rgba(25,25,25,127);
+  border-right:1px solid rgba(25,25,25,127);
+}
+QMenu{
+  background-color:palette(window);
+  border:1px solid palette(shadow);
+}
+QMenu::item{
+  padding:3px 25px 3px 25px;
+  border:1px solid transparent;
+}
+QMenu::item:disabled{
+  background-color:rgba(35,35,35,127);
+  color:palette(disabled);
+}
+QMenu::item:selected{
+  border-color:rgba(147,191,236,127);
+  background:palette(highlight);
+}
+QMenu::icon:checked{
+  background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
+  border:1px solid palette(highlight);
+  border-radius:2px;
+}
+QMenu::separator{
+  height:1px;
+  background:palette(alternate-base);
+  margin-left:5px;
+  margin-right:5px;
+}
+QMenu::indicator{
+  width:18px;
+  height:18px;
+}
+QMenu::indicator:non-exclusive:checked{
+  image:url(:/themes/darkstyle/icon_checkbox_checked.png);
+  padding-left:2px;
+}
+QMenu::indicator:non-exclusive:unchecked{
+  image:url(:/themes/darkstyle/icon_checkbox_unchecked.png);
+  padding-left:2px;
+}
+QMenu::indicator:exclusive:checked{
+  image:url(:/themes/darkstyle/icon_radiobutton_checked.png);
+  padding-left:2px;
+}
+QMenu::indicator:exclusive:unchecked{
+  image:url(:/themes/darkstyle/icon_radiobutton_unchecked.png);
+  padding-left:2px;
+}
+QToolBar::top{
+  background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
+  border-bottom:3px solid qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
+}
+QToolBar::bottom{
+  background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
+  border-top:3px solid qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
+}
+QToolBar::left{
+  background-color:qlineargradient(x1:0,y1:0,x2:1,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
+  border-right:3px solid qlineargradient(x1:0,y1:0,x2:1,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
+}
+QToolBar::right{
+  background-color:qlineargradient(x1:1,y1:0,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
+  border-left:3px solid qlineargradient(x1:1,y1:0,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
+}
+QMainWindow::separator{
+  width:6px;
+  height:5px;
+  padding:2px;
+}
+QSplitter::handle:horizontal{
+  width:10px;
+}
+QSplitter::handle:vertical{
+  height:10px;
+}
+QMainWindow::separator:hover,QSplitter::handle:hover{
+  background:palette(highlight);
+}
+QDockWidget::title{
+  padding:4px;
+  background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
+  border:1px solid rgba(25,25,25,75);
+  border-bottom:2px solid rgba(25,25,25,75);
+}
+QDockWidget{
+  titlebar-close-icon:url(:/themes/darkstyle/icon_close.png);
+  titlebar-normal-icon:url(:/themes/darkstyle/icon_restore.png);
+}
+QDockWidget::close-button,QDockWidget::float-button{
+  subcontrol-position:top right;
+  subcontrol-origin:margin;
+  position:absolute;
+  top:3px;
+  bottom:0px;
+  width:20px;
+  height:20px;
+}
+QDockWidget::close-button{
+  right:3px;
+}
+QDockWidget::float-button{
+  right:25px;
+}
+QGroupBox{
+  background-color:rgba(66,66,66,50%);
+  margin-top:27px;
+  border:1px solid rgba(25,25,25,127);
+  border-radius:4px;
+}
+QGroupBox::title{
+  subcontrol-origin:margin;
+  subcontrol-position:left top;
+  padding:4px 6px;
+  margin-left:3px;
+  background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
+  border:1px solid rgba(25,25,25,75);
+  border-bottom:2px solid rgb(127,127,127);
+  border-top-left-radius:4px;
+  border-top-right-radius:4px;
+}
+QTabWidget::pane{
+  background-color:rgba(66,66,66,50%);
+  border-top:1px solid rgba(25,25,25,50%);
+}
+QTabWidget::tab-bar{
+  left:3px;
+  top:1px;
+}
+QTabBar{
+  background-color:transparent;
+  qproperty-drawBase:0;
+  border-bottom:1px solid rgba(25,25,25,50%);
+}
+QTabBar::tab{
+  padding:4px 6px;
+  background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
+  border:1px solid rgba(25,25,25,75);
+  border-top-left-radius:4px;
+  border-top-right-radius:4px;
+}
+QTabBar::tab:selected,QTabBar::tab:hover{
+  background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(53,53,53,127),stop:1 rgba(66,66,66,50%));
+  border-bottom-color:rgba(66,66,66,75%);
+}
+QTabBar::tab:selected{
+  border-bottom:2px solid palette(highlight);
+}
+QTabBar::tab::selected:disabled{
+  border-bottom:2px solid rgb(127,127,127);
+}
+QTabBar::tab:!selected{
+  margin-top:2px;
+}
+QCheckBox::indicator{
+  width:18px;
+  height:18px;
+}
+QCheckBox::indicator:checked,QTreeView::indicator:checked,QTableView::indicator:checked,QGroupBox::indicator:checked{
+  image:url(:/themes/darkstyle/icon_checkbox_checked.png);
+}
+QCheckBox::indicator:checked:pressed,QTreeView::indicator:checked:pressed,QTableView::indicator:checked:pressed,QGroupBox::indicator:checked:pressed{
+  image:url(:/themes/darkstyle/icon_checkbox_checked_pressed.png);
+}
+QCheckBox::indicator:checked:disabled,QTreeView::indicator:checked:disabled,QTableView::indicator:checked:disabled,QGroupBox::indicator:checked:disabled{
+  image:url(:/themes/darkstyle/icon_checkbox_checked_disabled.png);
+}
+QCheckBox::indicator:unchecked,QTreeView::indicator:unchecked,QTableView::indicator:unchecked,QGroupBox::indicator:unchecked{
+  image:url(:/themes/darkstyle/icon_checkbox_unchecked.png);
+}
+QCheckBox::indicator:unchecked:pressed,QTreeView::indicator:unchecked:pressed,QTableView::indicator:unchecked:pressed,QGroupBox::indicator:unchecked:pressed{
+  image:url(:/themes/darkstyle/icon_checkbox_unchecked_pressed.png);
+}
+QCheckBox::indicator:unchecked:disabled,QTreeView::indicator:unchecked:disabled,QTableView::indicator:unchecked:disabled,QGroupBox::indicator:unchecked:disabled{
+  image:url(:/themes/darkstyle/icon_checkbox_unchecked_disabled.png);
+}
+QCheckBox::indicator:indeterminate,QTreeView::indicator:indeterminate,QTableView::indicator:indeterminate,QGroupBox::indicator:indeterminate{
+  image:url(:/themes/darkstyle/icon_checkbox_indeterminate.png);
+}
+QCheckBox::indicator:indeterminate:pressed,QTreeView::indicator:indeterminate:pressed,QTableView::indicator:indeterminate:pressed,QGroupBox::indicator:indeterminate:pressed{
+  image:url(:/themes/darkstyle/icon_checkbox_indeterminate_pressed.png);
+}
+QCheckBox::indicator:indeterminate:disabled,QTreeView::indicator:indeterminate:disabled,QTableView::indicator:indeterminate:disabled,QGroupBox::indicator:indeterminate:disabled{
+  image:url(:/themes/darkstyle/icon_checkbox_indeterminate_disabled.png);
+}
+QRadioButton::indicator{
+  width:18px;
+  height:18px;
+}
+QRadioButton::indicator:checked{
+  image:url(:/themes/darkstyle/icon_radiobutton_checked.png);
+}
+QRadioButton::indicator:checked:pressed{
+  image:url(:/themes/darkstyle/icon_radiobutton_checked_pressed.png);
+}
+QRadioButton::indicator:checked:disabled{
+  image:url(:/themes/darkstyle/icon_radiobutton_checked_disabled.png);
+}
+QRadioButton::indicator:unchecked{
+  image:url(:/themes/darkstyle/icon_radiobutton_unchecked.png);
+}
+QRadioButton::indicator:unchecked:pressed{
+  image:url(:/themes/darkstyle/icon_radiobutton_unchecked_pressed.png);
+}
+QRadioButton::indicator:unchecked:disabled{
+  image:url(:/themes/darkstyle/icon_radiobutton_unchecked_disabled.png);
+}
+QTreeView, QTableView{
+  alternate-background-color:palette(window);
+  background:palette(base);
+}
+QTreeView QHeaderView::section, QTableView QHeaderView::section{
+  background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
+  border-style:none;
+  border-bottom:1px solid palette(dark);
+  padding-left:5px;
+  padding-right:5px;
+}
+QTreeView::item:selected:disabled, QTableView::item:selected:disabled{
+  background:rgb(80,80,80);
+}
+QTreeView::branch{
+  background-color:palette(base);
+}
+QTreeView::branch:has-siblings:!adjoins-item{
+  border-image:url(:/themes/darkstyle/icon_vline.png) 0;
+}
+QTreeView::branch:has-siblings:adjoins-item{
+  border-image:url(:/themes/darkstyle/icon_branch_more.png) 0;
+}
+QTreeView::branch:!has-children:!has-siblings:adjoins-item{
+  border-image:url(:/themes/darkstyle/icon_branch_end.png) 0;
+}
+QTreeView::branch:has-children:!has-siblings:closed,
+QTreeView::branch:closed:has-children:has-siblings{
+  border-image:none;
+  image:url(:/themes/darkstyle/icon_branch_closed.png);
+}
+QTreeView::branch:open:has-children:!has-siblings,
+QTreeView::branch:open:has-children:has-siblings{
+  border-image:none;
+  image:url(:/themes/darkstyle/icon_branch_open.png);
+}
+QScrollBar:vertical{
+  background:palette(base);
+  border-top-right-radius:2px;
+  border-bottom-right-radius:2px;
+  width:16px;
+  margin:0px;
+}
+QScrollBar::handle:vertical{
+  background-color:palette(alternate-base);
+  border-radius:2px;
+  min-height:20px;
+  margin:2px 4px 2px 4px;
+}
+QScrollBar::handle:vertical:hover{
+  background-color:palette(highlight);
+}
+QScrollBar::add-line:vertical{
+  background:none;
+  height:0px;
+  subcontrol-position:right;
+  subcontrol-origin:margin;
+}
+QScrollBar::sub-line:vertical{
+  background:none;
+  height:0px;
+  subcontrol-position:left;
+  subcontrol-origin:margin;
+}
+QScrollBar:horizontal{
+  background:palette(base);
+  height:16px;
+  margin:0px;
+}
+QScrollBar::handle:horizontal{
+  background-color:palette(alternate-base);
+  border-radius:2px;
+  min-width:20px;
+  margin:4px 2px 4px 2px;
+}
+QScrollBar::handle:horizontal:hover{
+  background-color:palette(highlight);
+}
+QScrollBar::add-line:horizontal{
+  background:none;
+  width:0px;
+  subcontrol-position:bottom;
+  subcontrol-origin:margin;
+}
+QScrollBar::sub-line:horizontal{
+  background:none;
+  width:0px;
+  subcontrol-position:top;
+  subcontrol-origin:margin;
+}
+QSlider::handle:horizontal{
+  border-radius:4px;
+  border:1px solid rgba(25,25,25,255);
+  background-color:palette(alternate-base);
+  min-height:20px;
+  margin:0 -4px;
+}
+QSlider::handle:horizontal:hover{
+  background:palette(highlight);
+}
+QSlider::add-page:horizontal{
+  background:palette(base);
+}
+QSlider::sub-page:horizontal{
+  background:palette(highlight);
+}
+QSlider::sub-page:horizontal:disabled{
+  background:rgb(80,80,80);
+}
diff --git a/themes/darkstyle/icon_branch_closed.png b/themes/darkstyle/icon_branch_closed.png
new file mode 100644 (file)
index 0000000..fa785cc
Binary files /dev/null and b/themes/darkstyle/icon_branch_closed.png differ
diff --git a/themes/darkstyle/icon_branch_end.png b/themes/darkstyle/icon_branch_end.png
new file mode 100644 (file)
index 0000000..d90a04c
Binary files /dev/null and b/themes/darkstyle/icon_branch_end.png differ
diff --git a/themes/darkstyle/icon_branch_more.png b/themes/darkstyle/icon_branch_more.png
new file mode 100644 (file)
index 0000000..bdbe4ed
Binary files /dev/null and b/themes/darkstyle/icon_branch_more.png differ
diff --git a/themes/darkstyle/icon_branch_open.png b/themes/darkstyle/icon_branch_open.png
new file mode 100644 (file)
index 0000000..9dd05d6
Binary files /dev/null and b/themes/darkstyle/icon_branch_open.png differ
diff --git a/themes/darkstyle/icon_checkbox_checked.png b/themes/darkstyle/icon_checkbox_checked.png
new file mode 100644 (file)
index 0000000..fa22907
Binary files /dev/null and b/themes/darkstyle/icon_checkbox_checked.png differ
diff --git a/themes/darkstyle/icon_checkbox_checked_disabled.png b/themes/darkstyle/icon_checkbox_checked_disabled.png
new file mode 100644 (file)
index 0000000..441d0d9
Binary files /dev/null and b/themes/darkstyle/icon_checkbox_checked_disabled.png differ
diff --git a/themes/darkstyle/icon_checkbox_checked_pressed.png b/themes/darkstyle/icon_checkbox_checked_pressed.png
new file mode 100644 (file)
index 0000000..7b508c8
Binary files /dev/null and b/themes/darkstyle/icon_checkbox_checked_pressed.png differ
diff --git a/themes/darkstyle/icon_checkbox_indeterminate.png b/themes/darkstyle/icon_checkbox_indeterminate.png
new file mode 100644 (file)
index 0000000..87ebf23
Binary files /dev/null and b/themes/darkstyle/icon_checkbox_indeterminate.png differ
diff --git a/themes/darkstyle/icon_checkbox_indeterminate_disabled.png b/themes/darkstyle/icon_checkbox_indeterminate_disabled.png
new file mode 100644 (file)
index 0000000..ee7d112
Binary files /dev/null and b/themes/darkstyle/icon_checkbox_indeterminate_disabled.png differ
diff --git a/themes/darkstyle/icon_checkbox_indeterminate_pressed.png b/themes/darkstyle/icon_checkbox_indeterminate_pressed.png
new file mode 100644 (file)
index 0000000..562c482
Binary files /dev/null and b/themes/darkstyle/icon_checkbox_indeterminate_pressed.png differ
diff --git a/themes/darkstyle/icon_checkbox_unchecked.png b/themes/darkstyle/icon_checkbox_unchecked.png
new file mode 100644 (file)
index 0000000..c3c14dd
Binary files /dev/null and b/themes/darkstyle/icon_checkbox_unchecked.png differ
diff --git a/themes/darkstyle/icon_checkbox_unchecked_disabled.png b/themes/darkstyle/icon_checkbox_unchecked_disabled.png
new file mode 100644 (file)
index 0000000..3ac26d8
Binary files /dev/null and b/themes/darkstyle/icon_checkbox_unchecked_disabled.png differ
diff --git a/themes/darkstyle/icon_checkbox_unchecked_pressed.png b/themes/darkstyle/icon_checkbox_unchecked_pressed.png
new file mode 100644 (file)
index 0000000..c24130c
Binary files /dev/null and b/themes/darkstyle/icon_checkbox_unchecked_pressed.png differ
diff --git a/themes/darkstyle/icon_close.png b/themes/darkstyle/icon_close.png
new file mode 100644 (file)
index 0000000..ece7c28
Binary files /dev/null and b/themes/darkstyle/icon_close.png differ
diff --git a/themes/darkstyle/icon_radiobutton_checked.png b/themes/darkstyle/icon_radiobutton_checked.png
new file mode 100644 (file)
index 0000000..f747f49
Binary files /dev/null and b/themes/darkstyle/icon_radiobutton_checked.png differ
diff --git a/themes/darkstyle/icon_radiobutton_checked_disabled.png b/themes/darkstyle/icon_radiobutton_checked_disabled.png
new file mode 100644 (file)
index 0000000..fa554cb
Binary files /dev/null and b/themes/darkstyle/icon_radiobutton_checked_disabled.png differ
diff --git a/themes/darkstyle/icon_radiobutton_checked_pressed.png b/themes/darkstyle/icon_radiobutton_checked_pressed.png
new file mode 100644 (file)
index 0000000..7b4bb11
Binary files /dev/null and b/themes/darkstyle/icon_radiobutton_checked_pressed.png differ
diff --git a/themes/darkstyle/icon_radiobutton_unchecked.png b/themes/darkstyle/icon_radiobutton_unchecked.png
new file mode 100644 (file)
index 0000000..e74f040
Binary files /dev/null and b/themes/darkstyle/icon_radiobutton_unchecked.png differ
diff --git a/themes/darkstyle/icon_radiobutton_unchecked_disabled.png b/themes/darkstyle/icon_radiobutton_unchecked_disabled.png
new file mode 100644 (file)
index 0000000..87d1846
Binary files /dev/null and b/themes/darkstyle/icon_radiobutton_unchecked_disabled.png differ
diff --git a/themes/darkstyle/icon_radiobutton_unchecked_pressed.png b/themes/darkstyle/icon_radiobutton_unchecked_pressed.png
new file mode 100644 (file)
index 0000000..8f4d548
Binary files /dev/null and b/themes/darkstyle/icon_radiobutton_unchecked_pressed.png differ
diff --git a/themes/darkstyle/icon_restore.png b/themes/darkstyle/icon_restore.png
new file mode 100644 (file)
index 0000000..be29650
Binary files /dev/null and b/themes/darkstyle/icon_restore.png differ
diff --git a/themes/darkstyle/icon_undock.png b/themes/darkstyle/icon_undock.png
new file mode 100644 (file)
index 0000000..25e317e
Binary files /dev/null and b/themes/darkstyle/icon_undock.png differ
diff --git a/themes/darkstyle/icon_vline.png b/themes/darkstyle/icon_vline.png
new file mode 100644 (file)
index 0000000..14228c8
Binary files /dev/null and b/themes/darkstyle/icon_vline.png differ
diff --git a/themes/qdarkstyle/rc/Hmovetoolbar.png b/themes/qdarkstyle/rc/Hmovetoolbar.png
new file mode 100644 (file)
index 0000000..cead99e
Binary files /dev/null and b/themes/qdarkstyle/rc/Hmovetoolbar.png differ
diff --git a/themes/qdarkstyle/rc/Hsepartoolbar.png b/themes/qdarkstyle/rc/Hsepartoolbar.png
new file mode 100644 (file)
index 0000000..7f183c8
Binary files /dev/null and b/themes/qdarkstyle/rc/Hsepartoolbar.png differ
diff --git a/themes/qdarkstyle/rc/Vmovetoolbar.png b/themes/qdarkstyle/rc/Vmovetoolbar.png
new file mode 100644 (file)
index 0000000..512edce
Binary files /dev/null and b/themes/qdarkstyle/rc/Vmovetoolbar.png differ
diff --git a/themes/qdarkstyle/rc/Vsepartoolbar.png b/themes/qdarkstyle/rc/Vsepartoolbar.png
new file mode 100644 (file)
index 0000000..d9dc156
Binary files /dev/null and b/themes/qdarkstyle/rc/Vsepartoolbar.png differ
diff --git a/themes/qdarkstyle/rc/branch_closed-on.png b/themes/qdarkstyle/rc/branch_closed-on.png
new file mode 100644 (file)
index 0000000..d081e9b
Binary files /dev/null and b/themes/qdarkstyle/rc/branch_closed-on.png differ
diff --git a/themes/qdarkstyle/rc/branch_closed.png b/themes/qdarkstyle/rc/branch_closed.png
new file mode 100644 (file)
index 0000000..d652159
Binary files /dev/null and b/themes/qdarkstyle/rc/branch_closed.png differ
diff --git a/themes/qdarkstyle/rc/branch_open-on.png b/themes/qdarkstyle/rc/branch_open-on.png
new file mode 100644 (file)
index 0000000..ec372b2
Binary files /dev/null and b/themes/qdarkstyle/rc/branch_open-on.png differ
diff --git a/themes/qdarkstyle/rc/branch_open.png b/themes/qdarkstyle/rc/branch_open.png
new file mode 100644 (file)
index 0000000..66f8e1a
Binary files /dev/null and b/themes/qdarkstyle/rc/branch_open.png differ
diff --git a/themes/qdarkstyle/rc/checkbox_checked.png b/themes/qdarkstyle/rc/checkbox_checked.png
new file mode 100644 (file)
index 0000000..830cfee
Binary files /dev/null and b/themes/qdarkstyle/rc/checkbox_checked.png differ
diff --git a/themes/qdarkstyle/rc/checkbox_checked_disabled.png b/themes/qdarkstyle/rc/checkbox_checked_disabled.png
new file mode 100644 (file)
index 0000000..cb63cc2
Binary files /dev/null and b/themes/qdarkstyle/rc/checkbox_checked_disabled.png differ
diff --git a/themes/qdarkstyle/rc/checkbox_checked_focus.png b/themes/qdarkstyle/rc/checkbox_checked_focus.png
new file mode 100644 (file)
index 0000000..671be27
Binary files /dev/null and b/themes/qdarkstyle/rc/checkbox_checked_focus.png differ
diff --git a/themes/qdarkstyle/rc/checkbox_indeterminate.png b/themes/qdarkstyle/rc/checkbox_indeterminate.png
new file mode 100644 (file)
index 0000000..41024f7
Binary files /dev/null and b/themes/qdarkstyle/rc/checkbox_indeterminate.png differ
diff --git a/themes/qdarkstyle/rc/checkbox_indeterminate_disabled.png b/themes/qdarkstyle/rc/checkbox_indeterminate_disabled.png
new file mode 100644 (file)
index 0000000..abdc01d
Binary files /dev/null and b/themes/qdarkstyle/rc/checkbox_indeterminate_disabled.png differ
diff --git a/themes/qdarkstyle/rc/checkbox_indeterminate_focus.png b/themes/qdarkstyle/rc/checkbox_indeterminate_focus.png
new file mode 100644 (file)
index 0000000..415f9b6
Binary files /dev/null and b/themes/qdarkstyle/rc/checkbox_indeterminate_focus.png differ
diff --git a/themes/qdarkstyle/rc/checkbox_unchecked.png b/themes/qdarkstyle/rc/checkbox_unchecked.png
new file mode 100644 (file)
index 0000000..2159aca
Binary files /dev/null and b/themes/qdarkstyle/rc/checkbox_unchecked.png differ
diff --git a/themes/qdarkstyle/rc/checkbox_unchecked_disabled.png b/themes/qdarkstyle/rc/checkbox_unchecked_disabled.png
new file mode 100644 (file)
index 0000000..ade721e
Binary files /dev/null and b/themes/qdarkstyle/rc/checkbox_unchecked_disabled.png differ
diff --git a/themes/qdarkstyle/rc/checkbox_unchecked_focus.png b/themes/qdarkstyle/rc/checkbox_unchecked_focus.png
new file mode 100644 (file)
index 0000000..e4258cc
Binary files /dev/null and b/themes/qdarkstyle/rc/checkbox_unchecked_focus.png differ
diff --git a/themes/qdarkstyle/rc/close-hover.png b/themes/qdarkstyle/rc/close-hover.png
new file mode 100644 (file)
index 0000000..657943a
Binary files /dev/null and b/themes/qdarkstyle/rc/close-hover.png differ
diff --git a/themes/qdarkstyle/rc/close-pressed.png b/themes/qdarkstyle/rc/close-pressed.png
new file mode 100644 (file)
index 0000000..937d005
Binary files /dev/null and b/themes/qdarkstyle/rc/close-pressed.png differ
diff --git a/themes/qdarkstyle/rc/close.png b/themes/qdarkstyle/rc/close.png
new file mode 100644 (file)
index 0000000..bc0f576
Binary files /dev/null and b/themes/qdarkstyle/rc/close.png differ
diff --git a/themes/qdarkstyle/rc/down_arrow.png b/themes/qdarkstyle/rc/down_arrow.png
new file mode 100644 (file)
index 0000000..e271f7f
Binary files /dev/null and b/themes/qdarkstyle/rc/down_arrow.png differ
diff --git a/themes/qdarkstyle/rc/down_arrow_disabled.png b/themes/qdarkstyle/rc/down_arrow_disabled.png
new file mode 100644 (file)
index 0000000..5805d98
Binary files /dev/null and b/themes/qdarkstyle/rc/down_arrow_disabled.png differ
diff --git a/themes/qdarkstyle/rc/left_arrow.png b/themes/qdarkstyle/rc/left_arrow.png
new file mode 100644 (file)
index 0000000..f808d2d
Binary files /dev/null and b/themes/qdarkstyle/rc/left_arrow.png differ
diff --git a/themes/qdarkstyle/rc/left_arrow_disabled.png b/themes/qdarkstyle/rc/left_arrow_disabled.png
new file mode 100644 (file)
index 0000000..f5b9af8
Binary files /dev/null and b/themes/qdarkstyle/rc/left_arrow_disabled.png differ
diff --git a/themes/qdarkstyle/rc/radio_checked.png b/themes/qdarkstyle/rc/radio_checked.png
new file mode 100644 (file)
index 0000000..235e6b0
Binary files /dev/null and b/themes/qdarkstyle/rc/radio_checked.png differ
diff --git a/themes/qdarkstyle/rc/radio_checked_disabled.png b/themes/qdarkstyle/rc/radio_checked_disabled.png
new file mode 100644 (file)
index 0000000..bf0051e
Binary files /dev/null and b/themes/qdarkstyle/rc/radio_checked_disabled.png differ
diff --git a/themes/qdarkstyle/rc/radio_checked_focus.png b/themes/qdarkstyle/rc/radio_checked_focus.png
new file mode 100644 (file)
index 0000000..700c6b5
Binary files /dev/null and b/themes/qdarkstyle/rc/radio_checked_focus.png differ
diff --git a/themes/qdarkstyle/rc/radio_unchecked.png b/themes/qdarkstyle/rc/radio_unchecked.png
new file mode 100644 (file)
index 0000000..9a4def6
Binary files /dev/null and b/themes/qdarkstyle/rc/radio_unchecked.png differ
diff --git a/themes/qdarkstyle/rc/radio_unchecked_disabled.png b/themes/qdarkstyle/rc/radio_unchecked_disabled.png
new file mode 100644 (file)
index 0000000..6ece890
Binary files /dev/null and b/themes/qdarkstyle/rc/radio_unchecked_disabled.png differ
diff --git a/themes/qdarkstyle/rc/radio_unchecked_focus.png b/themes/qdarkstyle/rc/radio_unchecked_focus.png
new file mode 100644 (file)
index 0000000..564e022
Binary files /dev/null and b/themes/qdarkstyle/rc/radio_unchecked_focus.png differ
diff --git a/themes/qdarkstyle/rc/right_arrow.png b/themes/qdarkstyle/rc/right_arrow.png
new file mode 100644 (file)
index 0000000..9b0a4e6
Binary files /dev/null and b/themes/qdarkstyle/rc/right_arrow.png differ
diff --git a/themes/qdarkstyle/rc/right_arrow_disabled.png b/themes/qdarkstyle/rc/right_arrow_disabled.png
new file mode 100644 (file)
index 0000000..5c0bee4
Binary files /dev/null and b/themes/qdarkstyle/rc/right_arrow_disabled.png differ
diff --git a/themes/qdarkstyle/rc/sizegrip.png b/themes/qdarkstyle/rc/sizegrip.png
new file mode 100644 (file)
index 0000000..350583a
Binary files /dev/null and b/themes/qdarkstyle/rc/sizegrip.png differ
diff --git a/themes/qdarkstyle/rc/stylesheet-branch-end.png b/themes/qdarkstyle/rc/stylesheet-branch-end.png
new file mode 100644 (file)
index 0000000..cb5d3b5
Binary files /dev/null and b/themes/qdarkstyle/rc/stylesheet-branch-end.png differ
diff --git a/themes/qdarkstyle/rc/stylesheet-branch-more.png b/themes/qdarkstyle/rc/stylesheet-branch-more.png
new file mode 100644 (file)
index 0000000..6271140
Binary files /dev/null and b/themes/qdarkstyle/rc/stylesheet-branch-more.png differ
diff --git a/themes/qdarkstyle/rc/stylesheet-vline.png b/themes/qdarkstyle/rc/stylesheet-vline.png
new file mode 100644 (file)
index 0000000..87536cc
Binary files /dev/null and b/themes/qdarkstyle/rc/stylesheet-vline.png differ
diff --git a/themes/qdarkstyle/rc/transparent.png b/themes/qdarkstyle/rc/transparent.png
new file mode 100644 (file)
index 0000000..483df25
Binary files /dev/null and b/themes/qdarkstyle/rc/transparent.png differ
diff --git a/themes/qdarkstyle/rc/undock.png b/themes/qdarkstyle/rc/undock.png
new file mode 100644 (file)
index 0000000..88691d7
Binary files /dev/null and b/themes/qdarkstyle/rc/undock.png differ
diff --git a/themes/qdarkstyle/rc/up_arrow.png b/themes/qdarkstyle/rc/up_arrow.png
new file mode 100644 (file)
index 0000000..abcc724
Binary files /dev/null and b/themes/qdarkstyle/rc/up_arrow.png differ
diff --git a/themes/qdarkstyle/rc/up_arrow_disabled.png b/themes/qdarkstyle/rc/up_arrow_disabled.png
new file mode 100644 (file)
index 0000000..b9c8e3b
Binary files /dev/null and b/themes/qdarkstyle/rc/up_arrow_disabled.png differ
diff --git a/themes/qdarkstyle/style.qss b/themes/qdarkstyle/style.qss
new file mode 100644 (file)
index 0000000..026bc3a
--- /dev/null
@@ -0,0 +1,1252 @@
+QScrollArea {
+    background-color: transparent; /* Workaround for widget background on Windows, see https://stackoverflow.com/questions/25012224/qt-widget-background-differs-from-linux-to-windows/25088075 */
+}
+
+QToolTip {
+    border: 1px solid #76797C;
+    background-color: #5A7566;
+    color: white;
+    padding: 0px;                /*remove padding, for fix combobox tooltip.*/
+    opacity: 200;
+}
+
+QWidget {
+    color: #eff0f1;
+    background-color: #31363b;
+    selection-background-color: #3daee9;
+    selection-color: #eff0f1;
+    background-clip: border;
+    border-image: none;
+    border: 0px transparent black;
+    outline: 0;
+}
+
+QWidget:item:hover {
+    background-color: #18465d;
+    color: #eff0f1;
+}
+
+QWidget:item:selected {
+    background-color: #18465d;
+}
+
+QCheckBox {
+    spacing: 5px;
+    outline: none;
+    color: #eff0f1;
+    margin-bottom: 2px;
+}
+
+QCheckBox:disabled {
+    color: #76797C;
+}
+
+QCheckBox::indicator,
+QGroupBox::indicator {
+    width: 18px;
+    height: 18px;
+}
+
+QGroupBox::indicator {
+    margin-left: 2px;
+}
+
+QCheckBox::indicator:unchecked,
+QGroupBox::indicator:unchecked {
+    image: url(:/themes/qdarkstyle/rc/checkbox_unchecked.png);
+}
+
+QCheckBox::indicator:unchecked:hover,
+QCheckBox::indicator:unchecked:focus,
+QCheckBox::indicator:unchecked:pressed,
+QGroupBox::indicator:unchecked:hover,
+QGroupBox::indicator:unchecked:focus,
+QGroupBox::indicator:unchecked:pressed {
+    border: none;
+    image: url(:/themes/qdarkstyle/rc/checkbox_unchecked_focus.png);
+}
+
+QCheckBox::indicator:checked,
+QGroupBox::indicator:checked {
+    image: url(:/themes/qdarkstyle/rc/checkbox_checked.png);
+}
+
+QCheckBox::indicator:checked:hover,
+QCheckBox::indicator:checked:focus,
+QCheckBox::indicator:checked:pressed,
+QGroupBox::indicator:checked:hover,
+QGroupBox::indicator:checked:focus,
+QGroupBox::indicator:checked:pressed {
+    border: none;
+    image: url(:/themes/qdarkstyle/rc/checkbox_checked_focus.png);
+}
+
+QCheckBox::indicator:indeterminate {
+    image: url(:/themes/qdarkstyle/rc/checkbox_indeterminate.png);
+}
+
+QCheckBox::indicator:indeterminate:focus,
+QCheckBox::indicator:indeterminate:hover,
+QCheckBox::indicator:indeterminate:pressed {
+    image: url(:/themes/qdarkstyle/rc/checkbox_indeterminate_focus.png);
+}
+
+QCheckBox::indicator:checked:disabled,
+QGroupBox::indicator:checked:disabled {
+    image: url(:/themes/qdarkstyle/rc/checkbox_checked_disabled.png);
+}
+
+QCheckBox::indicator:unchecked:disabled,
+QGroupBox::indicator:unchecked:disabled {
+    image: url(:/themes/qdarkstyle/rc/checkbox_unchecked_disabled.png);
+}
+
+QRadioButton {
+    spacing: 5px;
+    outline: none;
+    color: #eff0f1;
+    margin-bottom: 2px;
+}
+
+QRadioButton:disabled {
+    color: #76797C;
+}
+
+QRadioButton::indicator {
+    width: 21px;
+    height: 21px;
+}
+
+QRadioButton::indicator:unchecked {
+    image: url(:/themes/qdarkstyle/rc/radio_unchecked.png);
+}
+
+QRadioButton::indicator:unchecked:hover,
+QRadioButton::indicator:unchecked:focus,
+QRadioButton::indicator:unchecked:pressed {
+    border: none;
+    outline: none;
+    image: url(:/themes/qdarkstyle/rc/radio_unchecked_focus.png);
+}
+
+QRadioButton::indicator:checked {
+    border: none;
+    outline: none;
+    image: url(:/themes/qdarkstyle/rc/radio_checked.png);
+}
+
+QRadioButton::indicator:checked:hover,
+QRadioButton::indicator:checked:focus,
+QRadioButton::indicator:checked:pressed {
+    border: none;
+    outline: none;
+    image: url(:/themes/qdarkstyle/rc/radio_checked_focus.png);
+}
+
+QRadioButton::indicator:checked:disabled {
+    outline: none;
+    image: url(:/themes/qdarkstyle/rc/radio_checked_disabled.png);
+}
+
+QRadioButton::indicator:unchecked:disabled {
+    image: url(:/themes/qdarkstyle/rc/radio_unchecked_disabled.png);
+}
+
+QMenuBar {
+    background-color: #31363b;
+    color: #eff0f1;
+}
+
+QMenuBar::item {
+    background: transparent;
+}
+
+QMenuBar::item:selected {
+    background: transparent;
+    border: 1px solid #76797C;
+}
+
+QMenuBar::item:pressed {
+    border: 1px solid #76797C;
+    background-color: #3daee9;
+    color: #eff0f1;
+    margin-bottom: -1px;
+    padding-bottom: 1px;
+}
+
+QMenu {
+    border: 1px solid #76797C;
+    color: #eff0f1;
+    margin: 2px;
+}
+
+QMenu::icon {
+    margin: 5px;
+}
+
+QMenu::item {
+    padding: 5px 30px 5px 30px;
+    border: 1px solid transparent;
+    /* reserve space for selection border */
+}
+
+QMenu::item:selected {
+    color: #eff0f1;
+}
+
+QMenu::separator {
+    height: 2px;
+    background: lightblue;
+    margin-left: 10px;
+    margin-right: 5px;
+}
+
+QMenu::indicator {
+    width: 18px;
+    height: 18px;
+}
+
+
+/* non-exclusive indicator = check box style indicator
+   (see QActionGroup::setExclusive) */
+
+QMenu::indicator:non-exclusive:unchecked {
+    image: url(:/themes/qdarkstyle/rc/checkbox_unchecked.png);
+}
+
+QMenu::indicator:non-exclusive:unchecked:selected {
+    image: url(:/themes/qdarkstyle/rc/checkbox_unchecked_disabled.png);
+}
+
+QMenu::indicator:non-exclusive:checked {
+    image: url(:/themes/qdarkstyle/rc/checkbox_checked.png);
+}
+
+QMenu::indicator:non-exclusive:checked:selected {
+    image: url(:/themes/qdarkstyle/rc/checkbox_checked_disabled.png);
+}
+
+
+/* exclusive indicator = radio button style indicator (see QActionGroup::setExclusive) */
+
+QMenu::indicator:exclusive:unchecked {
+    image: url(:/themes/qdarkstyle/rc/radio_unchecked.png);
+}
+
+QMenu::indicator:exclusive:unchecked:selected {
+    image: url(:/themes/qdarkstyle/rc/radio_unchecked_disabled.png);
+}
+
+QMenu::indicator:exclusive:checked {
+    image: url(:/themes/qdarkstyle/rc/radio_checked.png);
+}
+
+QMenu::indicator:exclusive:checked:selected {
+    image: url(:/themes/qdarkstyle/rc/radio_checked_disabled.png);
+}
+
+QMenu::right-arrow {
+    margin: 5px;
+    image: url(:/themes/qdarkstyle/rc/right_arrow.png)
+}
+
+QWidget:disabled {
+    color: #454545;
+    background-color: #31363b;
+}
+
+QAbstractItemView {
+    alternate-background-color: #31363b;
+    color: #eff0f1;
+    border: 1px solid #3A3939;
+    border-radius: 2px;
+}
+
+QWidget:focus,
+QMenuBar:focus {
+    border: 1px solid #3daee9;
+}
+
+QTabWidget:focus,
+QCheckBox:focus,
+QRadioButton:focus,
+QSlider:focus {
+    border: none;
+}
+
+QLineEdit {
+    background-color: #232629;
+    padding: 5px;
+    border-style: solid;
+    border: 1px solid #76797C;
+    border-radius: 2px;
+    color: #eff0f1;
+}
+
+QAbstractItemView QLineEdit {
+    padding: 0;
+}
+
+QGroupBox {
+    border: 1px solid #76797C;
+    border-radius: 2px;
+    margin-top: 20px;
+}
+
+QGroupBox::title {
+    subcontrol-origin: margin;
+    subcontrol-position: top center;
+    padding-left: 10px;
+    padding-right: 10px;
+    padding-top: 10px;
+}
+
+QAbstractScrollArea {
+    border-radius: 2px;
+    border: 1px solid #76797C;
+    background-color: transparent;
+}
+
+QScrollBar:horizontal {
+    height: 15px;
+    margin: 3px 15px 3px 15px;
+    border: 1px transparent #2A2929;
+    border-radius: 4px;
+    background-color: #2A2929;
+}
+
+QScrollBar::handle:horizontal {
+    background-color: #605F5F;
+    min-width: 5px;
+    border-radius: 4px;
+}
+
+QScrollBar::add-line:horizontal {
+    margin: 0px 3px 0px 3px;
+    border-image: url(:/themes/qdarkstyle/rc/right_arrow_disabled.png);
+    width: 10px;
+    height: 10px;
+    subcontrol-position: right;
+    subcontrol-origin: margin;
+}
+
+QScrollBar::sub-line:horizontal {
+    margin: 0px 3px 0px 3px;
+    border-image: url(:/themes/qdarkstyle/rc/left_arrow_disabled.png);
+    height: 10px;
+    width: 10px;
+    subcontrol-position: left;
+    subcontrol-origin: margin;
+}
+
+QScrollBar::add-line:horizontal:hover,
+QScrollBar::add-line:horizontal:on {
+    border-image: url(:/themes/qdarkstyle/rc/right_arrow.png);
+    height: 10px;
+    width: 10px;
+    subcontrol-position: right;
+    subcontrol-origin: margin;
+}
+
+QScrollBar::sub-line:horizontal:hover,
+QScrollBar::sub-line:horizontal:on {
+    border-image: url(:/themes/qdarkstyle/rc/left_arrow.png);
+    height: 10px;
+    width: 10px;
+    subcontrol-position: left;
+    subcontrol-origin: margin;
+}
+
+QScrollBar::up-arrow:horizontal,
+QScrollBar::down-arrow:horizontal {
+    background: none;
+}
+
+QScrollBar::add-page:horizontal,
+QScrollBar::sub-page:horizontal {
+    background: none;
+}
+
+QScrollBar:vertical {
+    background-color: #2A2929;
+    width: 15px;
+    margin: 15px 3px 15px 3px;
+    border: 1px transparent #2A2929;
+    border-radius: 4px;
+}
+
+QScrollBar::handle:vertical {
+    background-color: #605F5F;
+    min-height: 5px;
+    border-radius: 4px;
+}
+
+QScrollBar::sub-line:vertical {
+    margin: 3px 0px 3px 0px;
+    border-image: url(:/themes/qdarkstyle/rc/up_arrow_disabled.png);
+    height: 10px;
+    width: 10px;
+    subcontrol-position: top;
+    subcontrol-origin: margin;
+}
+
+QScrollBar::add-line:vertical {
+    margin: 3px 0px 3px 0px;
+    border-image: url(:/themes/qdarkstyle/rc/down_arrow_disabled.png);
+    height: 10px;
+    width: 10px;
+    subcontrol-position: bottom;
+    subcontrol-origin: margin;
+}
+
+QScrollBar::sub-line:vertical:hover,
+QScrollBar::sub-line:vertical:on {
+    border-image: url(:/themes/qdarkstyle/rc/up_arrow.png);
+    height: 10px;
+    width: 10px;
+    subcontrol-position: top;
+    subcontrol-origin: margin;
+}
+
+QScrollBar::add-line:vertical:hover,
+QScrollBar::add-line:vertical:on {
+    border-image: url(:/themes/qdarkstyle/rc/down_arrow.png);
+    height: 10px;
+    width: 10px;
+    subcontrol-position: bottom;
+    subcontrol-origin: margin;
+}
+
+QScrollBar::up-arrow:vertical,
+QScrollBar::down-arrow:vertical {
+    background: none;
+}
+
+QScrollBar::add-page:vertical,
+QScrollBar::sub-page:vertical {
+    background: none;
+}
+
+QTextEdit {
+    background-color: #232629;
+    color: #eff0f1;
+    border: 1px solid #76797C;
+}
+
+QPlainTextEdit {
+    background-color: #232629;
+    ;
+    color: #eff0f1;
+    border-radius: 2px;
+    border: 1px solid #76797C;
+}
+
+QHeaderView::section {
+    background-color: #76797C;
+    color: #eff0f1;
+    padding: 5px;
+    border: 1px solid #76797C;
+}
+
+QSizeGrip {
+    image: url(:/themes/qdarkstyle/rc/sizegrip.png);
+    width: 12px;
+    height: 12px;
+}
+
+QMainWindow::separator {
+    background-color: #31363b;
+    color: white;
+    padding-left: 4px;
+    spacing: 2px;
+    border: 1px dashed #76797C;
+}
+
+QMainWindow::separator:hover {
+    background-color: #787876;
+    color: white;
+    padding-left: 4px;
+    border: 1px solid #76797C;
+    spacing: 2px;
+}
+
+QMenu::separator {
+    height: 1px;
+    background-color: #76797C;
+    color: white;
+    padding-left: 4px;
+    margin-left: 10px;
+    margin-right: 5px;
+}
+
+QFrame {
+    border-radius: 2px;
+    border: 1px solid #76797C;
+}
+
+QFrame[frameShape="0"] {
+    border-radius: 2px;
+    border: 1px transparent #76797C;
+}
+
+QStackedWidget {
+    border: 1px transparent black;
+}
+
+QToolBar {
+    border: 1px transparent #393838;
+    background: 1px solid #31363b;
+    font-weight: bold;
+}
+
+QToolBar::handle:horizontal {
+    image: url(:/themes/qdarkstyle/rc/Hmovetoolbar.png);
+}
+
+QToolBar::handle:vertical {
+    image: url(:/themes/qdarkstyle/rc/Vmovetoolbar.png);
+}
+
+QToolBar::separator:horizontal {
+    image: url(:/themes/qdarkstyle/rc/Hsepartoolbar.png);
+}
+
+QToolBar::separator:vertical {
+    image: url(:/themes/qdarkstyle/rc/Vsepartoolbar.png);
+}
+
+QToolButton#qt_toolbar_ext_button {
+    background: #58595a
+}
+
+QPushButton {
+    color: #eff0f1;
+    background-color: #31363b;
+    border-width: 1px;
+    border-color: #76797C;
+    border-style: solid;
+    padding: 5px;
+    border-radius: 2px;
+    outline: none;
+}
+
+QPushButton:disabled {
+    background-color: #31363b;
+    border-width: 1px;
+    border-color: #454545;
+    border-style: solid;
+    padding-top: 5px;
+    padding-bottom: 5px;
+    padding-left: 10px;
+    padding-right: 10px;
+    border-radius: 2px;
+    color: #454545;
+}
+
+QPushButton:focus {
+    background-color: #3daee9;
+    color: white;
+}
+
+QPushButton:pressed {
+    background-color: #3daee9;
+    padding-top: -15px;
+    padding-bottom: -17px;
+}
+
+QComboBox {
+    selection-background-color: #3daee9;
+    border-style: solid;
+    border: 1px solid #76797C;
+    border-radius: 2px;
+    padding: 5px;
+    min-width: 75px;
+}
+
+QPushButton:checked {
+    background-color: #76797C;
+    border-color: #6A6969;
+}
+
+QComboBox:hover,
+QPushButton:hover,
+QAbstractSpinBox:hover,
+QLineEdit:hover,
+QTextEdit:hover,
+QPlainTextEdit:hover,
+QAbstractView:hover,
+QTreeView:hover {
+    border: 1px solid #3daee9;
+    color: #eff0f1;
+}
+
+QComboBox:on {
+    padding-top: 3px;
+    padding-left: 4px;
+    selection-background-color: #4a4a4a;
+}
+
+QComboBox QAbstractItemView {
+    background-color: #232629;
+    border-radius: 2px;
+    border: 1px solid #76797C;
+    selection-background-color: #18465d;
+}
+
+QComboBox::drop-down {
+    subcontrol-origin: padding;
+    subcontrol-position: top right;
+    width: 15px;
+    border-left-width: 0px;
+    border-left-color: darkgray;
+    border-left-style: solid;
+    border-top-right-radius: 3px;
+    border-bottom-right-radius: 3px;
+}
+
+QComboBox::down-arrow {
+    image: url(:/themes/qdarkstyle/rc/down_arrow_disabled.png);
+}
+
+QComboBox::down-arrow:on,
+QComboBox::down-arrow:hover,
+QComboBox::down-arrow:focus {
+    image: url(:/themes/qdarkstyle/rc/down_arrow.png);
+}
+
+QAbstractSpinBox {
+    padding: 5px;
+    border: 1px solid #76797C;
+    background-color: #232629;
+    color: #eff0f1;
+    border-radius: 2px;
+    min-width: 75px;
+}
+
+QAbstractSpinBox:up-button {
+    background-color: transparent;
+    subcontrol-origin: border;
+    subcontrol-position: center right;
+}
+
+QAbstractSpinBox:down-button {
+    background-color: transparent;
+    subcontrol-origin: border;
+    subcontrol-position: center left;
+}
+
+QAbstractSpinBox::up-arrow,
+QAbstractSpinBox::up-arrow:disabled,
+QAbstractSpinBox::up-arrow:off {
+    image: url(:/themes/qdarkstyle/rc/up_arrow_disabled.png);
+    width: 10px;
+    height: 10px;
+}
+
+QAbstractSpinBox::up-arrow:hover {
+    image: url(:/themes/qdarkstyle/rc/up_arrow.png);
+}
+
+QAbstractSpinBox::down-arrow,
+QAbstractSpinBox::down-arrow:disabled,
+QAbstractSpinBox::down-arrow:off {
+    image: url(:/themes/qdarkstyle/rc/down_arrow_disabled.png);
+    width: 10px;
+    height: 10px;
+}
+
+QAbstractSpinBox::down-arrow:hover {
+    image: url(:/themes/qdarkstyle/rc/down_arrow.png);
+}
+
+QLabel {
+    border: 0px solid black;
+}
+
+QTabWidget {
+    border: 0px transparent black;
+}
+
+QTabWidget::pane {
+    border: 1px solid #76797C;
+    padding: 5px;
+    margin: 0px;
+}
+
+QTabWidget::tab-bar {
+    /* left: 5px; move to the right by 5px */
+}
+
+QTabBar {
+    qproperty-drawBase: 0;
+    border-radius: 3px;
+}
+
+QTabBar:focus {
+    border: 0px transparent black;
+}
+
+QTabBar::close-button {
+    image: url(:/themes/qdarkstyle/rc/close.png);
+    background: transparent;
+}
+
+QTabBar::close-button:hover {
+    image: url(:/themes/qdarkstyle/rc/close-hover.png);
+    background: transparent;
+}
+
+QTabBar::close-button:pressed {
+    image: url(:/themes/qdarkstyle/rc/close-pressed.png);
+    background: transparent;
+}
+
+
+/* TOP TABS */
+
+QTabBar::tab:top {
+    color: #eff0f1;
+    border: 1px solid #76797C;
+    border-bottom: 1px transparent black;
+    background-color: #31363b;
+    padding: 5px;
+    min-width: 50px;
+    border-top-left-radius: 2px;
+    border-top-right-radius: 2px;
+}
+
+QTabBar::tab:top:selected {
+    color: #eff0f1;
+    background-color: #54575B;
+    border: 1px solid #76797C;
+    border-bottom: 2px solid #3daee9;
+    border-top-left-radius: 2px;
+    border-top-right-radius: 2px;
+}
+
+QTabBar::tab:top:!selected:hover {
+    background-color: #3daee9;
+}
+
+
+/* BOTTOM TABS */
+
+QTabBar::tab:bottom {
+    color: #eff0f1;
+    border: 1px solid #76797C;
+    border-top: 1px transparent black;
+    background-color: #31363b;
+    padding: 5px;
+    border-bottom-left-radius: 2px;
+    border-bottom-right-radius: 2px;
+    min-width: 50px;
+}
+
+QTabBar::tab:bottom:selected {
+    color: #eff0f1;
+    background-color: #54575B;
+    border: 1px solid #76797C;
+    border-top: 2px solid #3daee9;
+    border-bottom-left-radius: 2px;
+    border-bottom-right-radius: 2px;
+}
+
+QTabBar::tab:bottom:!selected:hover {
+    background-color: #3daee9;
+}
+
+
+/* LEFT TABS */
+
+QTabBar::tab:left {
+    color: #eff0f1;
+    border: 1px solid #76797C;
+    border-left: 1px transparent black;
+    background-color: #31363b;
+    padding: 5px;
+    border-top-right-radius: 2px;
+    border-bottom-right-radius: 2px;
+    min-height: 50px;
+}
+
+QTabBar::tab:left:selected {
+    color: #eff0f1;
+    background-color: #54575B;
+    border: 1px solid #76797C;
+    border-left: 2px solid #3daee9;
+    border-top-right-radius: 2px;
+    border-bottom-right-radius: 2px;
+}
+
+QTabBar::tab:left:!selected:hover {
+    background-color: #3daee9;
+}
+
+
+/* RIGHT TABS */
+
+QTabBar::tab:right {
+    color: #eff0f1;
+    border: 1px solid #76797C;
+    border-right: 1px transparent black;
+    background-color: #31363b;
+    padding: 5px;
+    border-top-left-radius: 2px;
+    border-bottom-left-radius: 2px;
+    min-height: 50px;
+}
+
+QTabBar::tab:right:selected {
+    color: #eff0f1;
+    background-color: #54575B;
+    border: 1px solid #76797C;
+    border-right: 2px solid #3daee9;
+    border-top-left-radius: 2px;
+    border-bottom-left-radius: 2px;
+}
+
+QTabBar::tab:right:!selected:hover {
+    background-color: #3daee9;
+}
+
+QTabBar QToolButton::right-arrow:enabled {
+    image: url(:/themes/qdarkstyle/rc/right_arrow.png);
+}
+
+QTabBar QToolButton::left-arrow:enabled {
+    image: url(:/themes/qdarkstyle/rc/left_arrow.png);
+}
+
+QTabBar QToolButton::right-arrow:disabled {
+    image: url(:/themes/qdarkstyle/rc/right_arrow_disabled.png);
+}
+
+QTabBar QToolButton::left-arrow:disabled {
+    image: url(:/themes/qdarkstyle/rc/left_arrow_disabled.png);
+}
+
+QDockWidget {
+    background: #31363b;
+    border: 1px solid #403F3F;
+    titlebar-close-icon: url(:/themes/qdarkstyle/rc/close.png);
+    titlebar-normal-icon: url(:/themes/qdarkstyle/rc/undock.png);
+}
+
+QDockWidget::close-button,
+QDockWidget::float-button {
+    border: 1px solid transparent;
+    border-radius: 2px;
+    background: transparent;
+}
+
+QDockWidget::close-button:hover,
+QDockWidget::float-button:hover {
+    background: rgba(255, 255, 255, 10);
+}
+
+QDockWidget::close-button:pressed,
+QDockWidget::float-button:pressed {
+    padding: 1px -1px -1px 1px;
+    background: rgba(255, 255, 255, 10);
+}
+
+QTreeView,
+QListView {
+    border: 1px solid #76797C;
+    background-color: #232629;
+}
+
+QTreeView:branch:selected,
+QTreeView:branch:hover {
+    background: url(:/themes/qdarkstyle/rc/transparent.png);
+}
+
+QTreeView::branch:has-siblings:!adjoins-item {
+    border-image: url(:/themes/qdarkstyle/rc/transparent.png);
+}
+
+QTreeView::branch:has-siblings:adjoins-item {
+    border-image: url(:/themes/qdarkstyle/rc/transparent.png);
+}
+
+QTreeView::branch:!has-children:!has-siblings:adjoins-item {
+    border-image: url(:/themes/qdarkstyle/rc/transparent.png);
+}
+
+QTreeView::branch:has-children:!has-siblings:closed,
+QTreeView::branch:closed:has-children:has-siblings {
+    image: url(:/themes/qdarkstyle/rc/branch_closed.png);
+}
+
+QTreeView::branch:open:has-children:!has-siblings,
+QTreeView::branch:open:has-children:has-siblings {
+    image: url(:/themes/qdarkstyle/rc/branch_open.png);
+}
+
+QTreeView::branch:has-children:!has-siblings:closed:hover,
+QTreeView::branch:closed:has-children:has-siblings:hover {
+    image: url(:/themes/qdarkstyle/rc/branch_closed-on.png);
+}
+
+QTreeView::branch:open:has-children:!has-siblings:hover,
+QTreeView::branch:open:has-children:has-siblings:hover {
+    image: url(:/themes/qdarkstyle/rc/branch_open-on.png);
+}
+
+QListView::item:!selected:hover,
+QTreeView::item:!selected:hover {
+    background: #18465d;
+    outline: 0;
+    color: #eff0f1
+}
+
+QListView::item:selected:hover,
+QTreeView::item:selected:hover {
+    background: #287399;
+    color: #eff0f1;
+}
+
+QTreeView::indicator:checked,
+QListView::indicator:checked {
+    image: url(:/themes/qdarkstyle/rc/checkbox_checked.png);
+}
+
+QTreeView::indicator:unchecked,
+QListView::indicator:unchecked {
+    image: url(:/themes/qdarkstyle/rc/checkbox_unchecked.png);
+}
+
+QTreeView::indicator:indeterminate,
+QListView::indicator:indeterminate {
+    image: url(:/themes/qdarkstyle/rc/checkbox_indeterminate.png);
+}
+
+QTreeView::indicator:checked:hover,
+QTreeView::indicator:checked:focus,
+QTreeView::indicator:checked:pressed,
+QListView::indicator:checked:hover,
+QListView::indicator:checked:focus,
+QListView::indicator:checked:pressed {
+    image: url(:/themes/qdarkstyle/rc/checkbox_checked_focus.png);
+}
+
+QTreeView::indicator:unchecked:hover,
+QTreeView::indicator:unchecked:focus,
+QTreeView::indicator:unchecked:pressed,
+QListView::indicator:unchecked:hover,
+QListView::indicator:unchecked:focus,
+QListView::indicator:unchecked:pressed {
+    image: url(:/themes/qdarkstyle/rc/checkbox_unchecked_focus.png);
+}
+
+QTreeView::indicator:indeterminate:hover,
+QTreeView::indicator:indeterminate:focus,
+QTreeView::indicator:indeterminate:pressed,
+QListView::indicator:indeterminate:hover,
+QListView::indicator:indeterminate:focus,
+QListView::indicator:indeterminate:pressed {
+    image: url(:/themes/qdarkstyle/rc/checkbox_indeterminate_focus.png);
+}
+
+QSlider::groove:horizontal {
+    border: 1px solid #565a5e;
+    height: 4px;
+    background: #565a5e;
+    margin: 0px;
+    border-radius: 2px;
+}
+
+QSlider::handle:horizontal {
+    background: #232629;
+    border: 1px solid #565a5e;
+    width: 16px;
+    height: 16px;
+    margin: -8px 0;
+    border-radius: 9px;
+}
+
+QSlider::sub-page:horizontal {
+    border: 1px solid #565a5e;
+    height: 4px;
+    background: #3daee9;
+    margin: 0px;
+    border-radius: 2px;
+}
+
+QSlider::groove:vertical {
+    border: 1px solid #565a5e;
+    width: 4px;
+    background: #565a5e;
+    margin: 0px;
+    border-radius: 3px;
+}
+
+QSlider::handle:vertical {
+    background: #232629;
+    border: 1px solid #565a5e;
+    width: 16px;
+    height: 16px;
+    margin: 0 -8px;
+    border-radius: 9px;
+}
+
+QSlider::sub-page:vertical {
+    border: 1px solid #565a5e;
+    width: 4px;
+    background: #3daee9;
+    margin: 0px;
+    border-radius: 3px;
+}
+
+QToolButton {
+    background-color: transparent;
+    border: 1px transparent #76797C;
+    border-radius: 2px;
+    margin: 3px;
+    padding: 5px;
+}
+
+QToolButton[popupMode="1"] {
+    /* only for MenuButtonPopup */
+    padding-right: 20px;
+    /* make way for the popup button */
+    border: 1px #76797C;
+    border-radius: 5px;
+}
+
+QToolButton[popupMode="2"] {
+    /* only for InstantPopup */
+    padding-right: 10px;
+    /* make way for the popup button */
+    border: 1px #76797C;
+}
+
+QToolButton:hover,
+QToolButton::menu-button:hover {
+    background-color: transparent;
+    border: 1px solid #3daee9;
+}
+
+QToolButton:checked,
+QToolButton:pressed,
+QToolButton::menu-button:pressed {
+    background-color: #3daee9;
+    border: 1px solid #3daee9;
+/*    padding: 5px; */
+}
+
+
+/* the subcontrol below is used only in the InstantPopup or DelayedPopup mode */
+
+QToolButton::menu-indicator {
+    image: url(:/themes/qdarkstyle/rc/down_arrow.png);
+    top: -7px;
+}
+
+
+/* the subcontrols below are used only in the MenuButtonPopup mode */
+
+QToolButton::menu-button {
+    border: 1px transparent #76797C;
+    border-top-right-radius: 6px;
+    border-bottom-right-radius: 6px;
+    /* 16px width + 4px for border = 20px allocated above */
+    width: 16px;
+    outline: none;
+}
+
+QToolButton::menu-arrow {
+    image: url(:/themes/qdarkstyle/rc/down_arrow.png);
+}
+
+QToolButton::menu-arrow:open {
+    border: 1px solid #76797C;
+}
+
+QPushButton::menu-indicator {
+    subcontrol-origin: padding;
+    subcontrol-position: bottom right;
+    bottom: 5px;
+}
+
+QTableView {
+    border: 1px solid #76797C;
+    gridline-color: #31363b;
+    background-color: #232629;
+}
+
+QTableView,
+QHeaderView {
+    border-radius: 0px;
+}
+
+QTableView::item:pressed,
+QListView::item:pressed,
+QTreeView::item:pressed {
+    background: #18465d;
+    color: #eff0f1;
+}
+
+QTableView::item:selected:active,
+QTreeView::item:selected:active,
+QListView::item:selected:active {
+    background: #287399;
+    color: #eff0f1;
+}
+
+QHeaderView {
+    background-color: #31363b;
+    border: 1px transparent;
+    border-radius: 0px;
+    margin: 0px;
+    padding: 0px;
+}
+
+QHeaderView::section {
+    background-color: #31363b;
+    color: #eff0f1;
+    padding: 5px;
+    border: 1px solid #76797C;
+    border-radius: 0px;
+    text-align: center;
+}
+
+QHeaderView::section::vertical::first,
+QHeaderView::section::vertical::only-one {
+    border-top: 1px solid #76797C;
+}
+
+QHeaderView::section::vertical {
+    border-top: transparent;
+}
+
+QHeaderView::section::horizontal::first,
+QHeaderView::section::horizontal::only-one {
+    border-left: 1px solid #76797C;
+}
+
+QHeaderView::section::horizontal {
+    border-left: transparent;
+}
+
+QHeaderView::section:checked {
+    color: white;
+    background-color: #334e5e;
+}
+
+
+/* style the sort indicator */
+
+QHeaderView::down-arrow {
+    image: url(:/themes/qdarkstyle/rc/down_arrow.png);
+}
+
+QHeaderView::up-arrow {
+    image: url(:/themes/qdarkstyle/rc/up_arrow.png);
+}
+
+QTableCornerButton::section {
+    background-color: #31363b;
+    border: 1px transparent #76797C;
+    border-radius: 0px;
+}
+
+QToolBox {
+    padding: 5px;
+    border: 1px transparent black;
+}
+
+QToolBox::tab {
+    color: #eff0f1;
+    background-color: #31363b;
+    border: 1px solid #76797C;
+    border-bottom: 1px transparent #31363b;
+    border-top-left-radius: 5px;
+    border-top-right-radius: 5px;
+}
+
+QToolBox::tab:selected {
+    /* italicize selected tabs */
+    font: italic;
+    background-color: #31363b;
+    border-color: #3daee9;
+}
+
+QStatusBar::item {
+    border: 0px transparent dark;
+}
+
+QFrame[height="3"],
+QFrame[width="3"] {
+    background-color: #76797C;
+}
+
+QSplitter::handle {
+    border: 1px dashed #76797C;
+}
+
+QSplitter::handle:hover {
+    background-color: #787876;
+    border: 1px solid #76797C;
+}
+
+QSplitter::handle:horizontal {
+    width: 1px;
+}
+
+QSplitter::handle:vertical {
+    height: 1px;
+}
+
+QProgressBar {
+    border: 1px solid #76797C;
+    border-radius: 5px;
+    text-align: center;
+}
+
+QProgressBar::chunk {
+    background-color: #05B8CC;
+}
+
+QDateEdit {
+    selection-background-color: #3daee9;
+    border-style: solid;
+    border: 1px solid #3375A3;
+    border-radius: 2px;
+    padding: 1px;
+    min-width: 75px;
+}
+
+QDateEdit:on {
+    padding-top: 3px;
+    padding-left: 4px;
+    selection-background-color: #4a4a4a;
+}
+
+QDateEdit QAbstractItemView {
+    background-color: #232629;
+    border-radius: 2px;
+    border: 1px solid #3375A3;
+    selection-background-color: #3daee9;
+}
+
+QDateEdit::drop-down {
+    subcontrol-origin: padding;
+    subcontrol-position: top right;
+    width: 15px;
+    border-left-width: 0px;
+    border-left-color: darkgray;
+    border-left-style: solid;
+    border-top-right-radius: 3px;
+    border-bottom-right-radius: 3px;
+}
+
+QDateEdit::down-arrow {
+    image: url(:/themes/qdarkstyle/rc/down_arrow_disabled.png);
+}
+
+QDateEdit::down-arrow:on,
+QDateEdit::down-arrow:hover,
+QDateEdit::down-arrow:focus {
+    image: url(:/themes/qdarkstyle/rc/down_arrow.png);
+}