# QtCreator cruft
/CMakeLists.txt.user*
*.cbp
+
+doxy
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)
FORCE)
endif()
+#===============================================================================
+#= Documentation
+#-------------------------------------------------------------------------------
+
+add_subdirectory(manual)
+
#===============================================================================
#= Dependencies
#-------------------------------------------------------------------------------
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)
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)
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)
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.
#-------------------------------------------------------------------------------
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})
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]*)?$")
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
pv/application.cpp
pv/devicemanager.cpp
pv/globalsettings.cpp
+ pv/logging.cpp
pv/mainwindow.cpp
pv/session.cpp
pv/storesession.cpp
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
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
# This list includes only QObject derived class headers.
set(pulseview_HEADERS
+ pv/logging.hpp
pv/globalsettings.hpp
pv/mainwindow.hpp
pv/session.hpp
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
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
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
)
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
add_definitions(-DENABLE_SIGNALS)
endif()
+if(ENABLE_STACKTRACE)
+ add_definitions(-DENABLE_STACKTRACE)
+endif()
+
#===============================================================================
#= Global Include Directories
#-------------------------------------------------------------------------------
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)
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()
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!
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
------------
- 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
$ 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
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
#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[] = {
[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;
[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;
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
}
Exec=pulseview
Icon=pulseview
Type=Application
-MimeType=application/vnd.sigrok.session
+MimeType=application/vnd.sigrok.session;
!define SHCNF_IDLIST 0
-# --- Functions ---------------------------------------------------------------
+# --- Functions/Macros --------------------------------------------------------
Function register_sr_files
${registerExtension} "$INSTDIR\pulseview.exe" ".sr" "sigrok session file"
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 ---------------------------------------------------------------
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 \
"$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" \
# 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"
# 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"
-.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"
.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
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
#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)
#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,
"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"
{
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);
{"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;
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
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':
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
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();
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
--- /dev/null
+##
+## 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 ()
--- /dev/null
+== 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".
+
--- /dev/null
+== 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.
--- /dev/null
+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
--- /dev/null
+/*! 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; }
--- /dev/null
+== 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
--- /dev/null
+== 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.
+
--- /dev/null
+== 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.
--- /dev/null
+[[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.
--- /dev/null
+== 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.
--- /dev/null
+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[]
--- /dev/null
+[[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.
<RCC>
- <qresource prefix="/" >
+ <qresource prefix="/">
<file>icons/add-decoder.svg</file>
<file>icons/application-exit.png</file>
<file>icons/channels.svg</file>
<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>
#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)
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;
}
}
#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
#include <cassert>
#include <QFormLayout>
+#include <QHBoxLayout>
+#include <QIcon>
#include <QLabel>
+#include <QPushButton>
#include <pv/prop/property.hpp>
}
}
-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);
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;
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
#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 {
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
#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;
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_);
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));
assert(decoder_);
decoder_->set_option(id, value.gobj());
- assert(decoder_stack_);
- decoder_stack_->begin_decode();
+ assert(decode_signal_);
+ decode_signal_->begin_decode();
}
} // namespace binding
namespace pv {
namespace data {
-class DecoderStack;
+class DecodeSignal;
namespace decode {
class Decoder;
}
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:
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_;
};
using boost::optional;
using std::function;
-using std::make_pair;
using std::pair;
using std::set;
using std::shared_ptr;
string name_str;
try {
name_str = key->description();
- } catch (Error e) {
+ } catch (Error& e) {
name_str = key->name();
}
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;
case SR_CONF_EXTERNAL_CLOCK:
case SR_CONF_RLE:
case SR_CONF_POWER_OFF:
+ case SR_CONF_AVERAGING:
bind_bool(name, "", get, set);
break;
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;
}
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)));
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
#ifndef PULSEVIEW_PV_BINDING_DEVICE_HPP
#define PULSEVIEW_PV_BINDING_DEVICE_HPP
+#include <functional>
+
#include <boost/optional.hpp>
#include <QObject>
namespace binding {
-class Device : public QObject, public Binding
+class Device : public Binding
{
Q_OBJECT
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_;
using boost::none;
-using std::make_pair;
using std::map;
using std::pair;
using std::shared_ptr;
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();
{
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));
}
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
segments_.begin(), segments_.end());
}
+uint32_t Analog::get_segment_count() const
+{
+ return (uint32_t)segments_.size();
+}
+
void Analog::clear()
{
segments_.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());
}
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
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_;
};
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)
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);
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
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,
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
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),
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++) {
*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
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
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;
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) {
return end_sample_;
}
-int Annotation::format() const
+Annotation::Class Annotation::ann_class() const
{
- return format_;
+ return ann_class_;
}
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
#ifndef PULSEVIEW_PV_VIEW_DECODE_ANNOTATION_HPP
#define PULSEVIEW_PV_VIEW_DECODE_ANNOTATION_HPP
-#include <stdint.h>
+#include <cstdint>
+#include <vector>
#include <QString>
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
#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 {
Decoder::Decoder(const srd_decoder *const dec) :
decoder_(dec),
shown_(true),
- initial_pins_(nullptr)
+ decoder_inst_(nullptr)
{
}
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_;
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);
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
#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;
namespace data {
+struct DecodeChannel;
class Logic;
class SignalBase;
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
{
}
-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)
{
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_) ||
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_;
};
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
#include <vector>
+#include <libsigrokdecode/libsigrokdecode.h>
+
#include "annotation.hpp"
using std::vector;
namespace data {
namespace decode {
+class Row;
+
class RowData
{
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_;
+++ /dev/null
-/*
- * 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
+++ /dev/null
-/*
- * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
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
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();
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());
}
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,
* 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"
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_));
}
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;
#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;
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);
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);
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);
// 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) {
// 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;
}
// 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);
}
}
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)
} 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);
}
}
// 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;
}
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
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;
}
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.
*/
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;
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;
#include <cstdlib>
#include <cstring>
+#include <QDebug>
+
+using std::bad_alloc;
using std::lock_guard;
using std::min;
using std::recursive_mutex;
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);
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_;
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_);
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)
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_;
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_;
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_;
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_);
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_);
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;
}
}
+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
#include <thread>
#include <vector>
+#include <QObject>
+
using std::recursive_mutex;
using std::vector;
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();
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_;
unsigned int unit_size_;
int iterator_count_;
bool mem_optimization_requested_;
+ bool is_complete_;
friend struct SegmentTest::SmallSize8Single;
friend struct SegmentTest::MediumSize8Single;
#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
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_)
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)
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;
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)));
+ }
}
}
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
{
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_)
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
#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;
namespace data {
class Analog;
+class AnalogSegment;
class DecoderStack;
class Logic;
+class LogicSegment;
class SignalData;
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);
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.
*/
*/
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.
*/
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
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
#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;
using std::shared_ptr;
using std::string;
using std::unique_ptr;
+using std::vector;
using Glib::VariantBase;
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
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)
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;
}
#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;
class Driver;
}
+using sigrok::ConfigKey;
+
namespace pv {
namespace devices {
class DeviceManager
{
public:
- DeviceManager(shared_ptr<sigrok::Context> context);
+ DeviceManager(shared_ptr<sigrok::Context> context,
+ std::string driver, bool do_scan);
~DeviceManager() = default;
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,
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
*/
#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;
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)
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()
public:
/**
- * Builds the full name. It only contains all the fields.
+ * Builds the full name. It contains all the fields.
*/
string full_name() const;
string display_name(const DeviceManager&) const;
protected:
- const string file_name_;
+ string file_name_;
};
} // namespace devices
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, " ");
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))
#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,
{
}
+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_)
else
session_ = context_->create_session();
+ if (!format_)
+ return;
+
input_ = format_->create_input(options_);
if (!input_)
// 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_);
void InputFile::run()
{
- char buffer[BufferSize];
+ if (!input_)
+ return;
if (!f) {
// Previous call to run() processed the entire file already
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;
#include "file.hpp"
+#include <QSettings>
+
using std::atomic;
using std::ifstream;
using std::map;
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();
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;
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);
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;
/**
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));
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(
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());
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);
}
QWidget *tcp_config_;
QLineEdit *tcp_host_;
QSpinBox *tcp_port_;
- QCheckBox *tcp_use_vxi_;
+ QComboBox *tcp_protocol_;
QPushButton scan_button_;
QListWidget device_list_;
#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);
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
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
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
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);
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)));
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);
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;
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>");
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);
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;
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)
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
#define PULSEVIEW_PV_SETTINGS_HPP
#include <QCheckBox>
+#include <QColor>
#include <QDialog>
#include <QListWidget>
+#include <QPlainTextEdit>
#include <QStackedWidget>
+#include <QLineEdit>
namespace pv {
namespace dialogs {
+class PageListWidget;
+
class Settings : public QDialog
{
Q_OBJECT
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
#include <cassert>
+#include <QDebug>
#include <QMessageBox>
#include "pv/session.hpp"
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());
#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)
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()
{
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
#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);
*/
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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
#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;
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();
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...
}
// 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;
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;
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());
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);
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);
// 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();
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()
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 =
}
}
+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"));
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();
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;
{
bool data_saved = true;
- for (auto entry : session_windows_)
+ for (auto& entry : session_windows_)
if (!entry.first->data_saved())
data_saved = false;
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);
}
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:
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());
}
// 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);
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);
}
// 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;
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()
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);
}
}
{
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
{
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
#include <QTabWidget>
#include <QToolButton>
+#include "globalsettings.hpp"
#include "session.hpp"
#include "views/viewbase.hpp"
#endif
}
-class MainWindow : public QMainWindow
+class MainWindow : public QMainWindow, public GlobalSettingsInterface
{
Q_OBJECT
~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,
void save_sessions();
void restore_sessions();
+ void on_setting_changed(const QString &key, const QVariant &value);
+
private:
void setup_ui();
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);
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);
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_;
#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;
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
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())
}
// 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*)),
{
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;
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);
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()));
{
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;
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
#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;
}
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:
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_;
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_;
};
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
pv::binding::Device& binding();
+ virtual void show();
+
private:
shared_ptr<sigrok::Device> device_;
#include <cassert>
#include <QCheckBox>
+#include <QDebug>
+
+#include <libsigrokcxx/libsigrokcxx.hpp>
#include "bool.hpp"
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)),
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_);
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)
QWidget* get_widget(QWidget *parent, bool auto_commit);
bool labeled_widget() const;
+ void update_widget();
void commit();
#include <cassert>
+#include <QDebug>
#include <QDoubleSpinBox>
+#include <libsigrokcxx/libsigrokcxx.hpp>
+
#include "double.hpp"
using boost::optional;
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_);
if (step_)
spin_box_->setSingleStep(*step_);
- spin_box_->setValue(value);
+ update_widget();
if (auto_commit)
connect(spin_box_, SIGNAL(valueChanged(double)),
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_);
virtual ~Double() = default;
QWidget* get_widget(QWidget *parent, bool auto_commit);
+ void update_widget();
void commit();
*/
#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;
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();
}
Q_DECLARE_METATYPE(Glib::VariantBase);
class QComboBox;
+class QLabel;
+class QSlider;
namespace pv {
namespace prop {
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
#include <cassert>
#include <cstdint>
+#include <QDebug>
#include <QSpinBox>
+#include <libsigrokcxx/libsigrokcxx.hpp>
+
#include "int.hpp"
using boost::optional;
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_)
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)
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.
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)),
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_);
virtual ~Int() = default;
QWidget* get_widget(QWidget *parent, bool auto_commit);
+ void update_widget();
void commit();
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;
const Getter getter_;
const Setter setter_;
-private:
+protected:
QString name_;
QString desc_;
};
#include <cassert>
+#include <QDebug>
#include <QLineEdit>
#include <QSpinBox>
+#include <libsigrokcxx/libsigrokcxx.hpp>
+
#include "string.hpp"
using std::string;
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&)),
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_);
String(QString name, QString desc, Getter getter, Setter setter);
QWidget* get_widget(QWidget *parent, bool auto_commit);
+ void update_widget();
void commit();
* 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"
#ifdef ENABLE_DECODE
#include <libsigrokdecode/libsigrokdecode.h>
+#include "data/decodesignal.hpp"
#endif
using std::bad_alloc;
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),
{
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 =
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()));
}
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");
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
}
}
- 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
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);
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_)));
}
}
// 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
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);
}
}
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();
}
device_->open();
} catch (const QString &e) {
device_.reset();
+ MainWindow::show_session_error(tr("Failed to open device"), e);
}
if (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,
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>(
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;
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());
}
}
// 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 =
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)
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;
{
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
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();
}
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();
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();
}
// 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());
} 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;
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;
}
try {
device_->run();
- } catch (Error e) {
+ } catch (Error& e) {
error_handler(e.what());
set_capture_state(Stopped);
return;
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();
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;
}
}
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;
}
}
- 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_) {
// 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);
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
// 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) {
void Session::data_feed_in(shared_ptr<sigrok::Device> device,
shared_ptr<Packet> packet)
{
- static bool frame_began = false;
-
(void)device;
assert(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();
}
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;
}
#ifndef PULSEVIEW_PV_SESSION_HPP
#define PULSEVIEW_PV_SESSION_HPP
+#include <functional>
#include <map>
#include <memory>
#include <mutex>
class InputFormat;
class Logic;
class Meta;
+class Option;
class OutputFormat;
class Packet;
class Session;
} // namespace sigrok
+using sigrok::Option;
+
namespace pv {
class DeviceManager;
namespace data {
class Analog;
class AnalogSegment;
+class DecodeSignal;
class Logic;
class LogicSegment;
class SignalBase;
Running
};
+ static shared_ptr<sigrok::Context> sr_context;
+
public:
Session(DeviceManager &device_manager, QString name);
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);
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:
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);
void feed_in_trigger();
void feed_in_frame_begin();
+ void feed_in_frame_end();
void feed_in_logic(shared_ptr<sigrok::Logic> logic);
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_;
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
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;
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 {
}
}
+ // 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();
{{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;
}
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},
}
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())
delete[] ldata;
}
- } catch (Error error) {
+ } catch (Error& error) {
error_ = tr("Error while saving: ") + error.what();
break;
}
#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>
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)),
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),
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..."));
// 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_);
return;
}
- 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_.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;
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];
} 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);
}
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;
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();
}
}
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;
}
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),
{
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);
// 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
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()),
{
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;
}
{
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;
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;
}
}
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);
{
#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
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;
}
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 {
// 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;
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()
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();
}
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);
QAction* action_save_selection_as() const;
QAction* action_connect() const;
- void session_error(const QString text, const QString info_text);
-
private:
void run_stop();
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)
{
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
#define PULSEVIEW_UTIL_HPP
#include <cmath>
+#include <string>
+#include <vector>
#ifndef Q_MOC_RUN
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <QMetaType>
#include <QString>
+using std::string;
+using std::vector;
+
namespace pv {
namespace util {
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
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
#include <QApplication>
#include <QCheckBox>
#include <QComboBox>
+#include <QDebug>
#include <QFormLayout>
#include <QGridLayout>
#include <QLabel>
#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;
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();
}
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)
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
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());
}
}
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();
(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)
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(),
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++) {
}
}
- 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_;
}
}
- 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_;
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;
{
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);
return;
p.setPen(QPen(Qt::NoPen));
- p.setBrush(base_->colour());
+ p.setBrush(base_->color());
QRectF *const rects = new QRectF[e.length];
QRectF *rect = rects;
for (uint64_t sample = 0; sample < e.length - 1; sample++) {
const float x = ((e.scale * sample + e.start) /
samples_per_pixel - pixels_offset) + left;
- const 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
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
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);
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;
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;
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);
// Paint the sampling points
if (show_sampling_points) {
- p.setPen(SamplingPointColour);
+ p.setPen(SamplingPointColor);
p.drawRects(sampling_points.data(), sampling_points.size());
}
}
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};
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)
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);
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
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_);
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);
// 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)
}
}
+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();
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)
#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;
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;
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;
*/
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.
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.
*/
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
#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)
{
}
Q_OBJECT
public:
- static const QColor FillColour;
+ static const QColor FillColor;
public:
/**
* 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;
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
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)
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)
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());
}
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(s1, s2);
}
-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
#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;
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:
/**
*/
CursorPair(View &view);
-public:
+ ~CursorPair();
+
/**
* Returns true if the item is visible and enabled.
*/
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;
/**
*/
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
#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"
#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) :
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
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) {
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++) {
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(
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();
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) =
// 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;
// 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) =
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;
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;
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);
}
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
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),
{
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);
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)
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;
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_)
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);
// 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);
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);
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,
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) {
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);
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();
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
#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>
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;
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,
bool enabled() const;
- const shared_ptr<pv::data::DecoderStack>& decoder() const;
-
shared_ptr<data::SignalBase> base() const;
/**
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,
* @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);
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);
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_;
int min_useful_label_width_;
QSignalMapper delete_mapper_, show_hide_mapper_;
+
+ QTimer delayed_trace_updater_;
};
} // namespace trace
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)
{
}
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();
return popup;
}
-QMenu* Flag::create_context_menu(QWidget *parent)
+QMenu* Flag::create_header_context_menu(QWidget *parent)
{
QMenu *const menu = new QMenu(parent);
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:
/**
pv::widgets::Popup* create_popup(QWidget *parent);
- QMenu* create_context_menu(QWidget *parent);
+ QMenu* create_header_context_menu(QWidget *parent);
void delete_pressed();
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_ &&
if (!r)
return;
- QMenu *menu = r->create_context_menu(this);
+ QMenu *menu = r->create_header_context_menu(this);
if (!menu)
menu = new QMenu(this);
menu->addAction(group);
}
- menu->exec(event->globalPos());
+ menu->popup(event->globalPos());
}
void Header::keyPressEvent(QKeyEvent *event)
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;
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
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;
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
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,
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),
{
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. */
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)
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
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);
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;
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;
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);
// 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,
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"),
{
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()) {
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_);
}
}
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;
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
#define PULSEVIEW_PV_VIEWS_TRACEVIEW_LOGICSIGNAL_HPP
#include <QCache>
+#include <QColor>
+#include <QDebug>
+#include <QSpinBox>
#include "signal.hpp"
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];
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.
*/
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;
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_;
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)
namespace views {
namespace trace {
-void RowItem::hover_point_changed()
+void RowItem::hover_point_changed(const QPoint &hp)
{
+ (void)hp;
}
} // namespace trace
Q_OBJECT
public:
- virtual void hover_point_changed();
+ virtual void hover_point_changed(const QPoint &hp);
};
} // namespace trace
#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;
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
{
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()),
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());
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_) {
tick_position_cache_ = calculate_tick_positions(
view_.tick_period(),
- view_.offset(),
+ view_.ruler_offset(),
view_.scale(),
width(),
+ view_.minor_tick_count(),
ffunc);
}
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) {
}
}
-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;
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);
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
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
/// 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;
/**
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.
*/
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.
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.
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
#include "view.hpp"
using std::shared_ptr;
-using std::make_shared;
namespace pv {
namespace views {
shared_ptr<data::SignalBase> channel) :
Trace(channel),
session_(session),
- scale_handle_(make_shared<SignalScaleHandle>(*this)),
- items_({scale_handle_}),
name_widget_(nullptr)
{
assert(base_);
void Signal::set_name(QString name)
{
- Trace::set_name(name);
+ base_->set_name(name);
if (name != name_widget_->currentText())
name_widget_->setEditText(name);
(void)settings;
}
-const ViewItemOwner::item_list& Signal::child_items() const
-{
- return items_;
-}
-
void Signal::paint_back(QPainter &p, ViewItemPaintParams &pp)
{
if (base_->enabled())
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();
#include <cstdint>
-#include "signalscalehandle.hpp"
+#include <pv/data/logicsegment.hpp>
+
#include "trace.hpp"
#include "viewitemowner.hpp"
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
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.
*/
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);
protected:
pv::Session &session_;
- const shared_ptr<SignalScaleHandle> scale_handle_;
- const item_list items_;
-
QComboBox *name_widget_;
};
+++ /dev/null
-/*
- * 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
+++ /dev/null
-/*
- * 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
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"));
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));
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();
}
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
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_;
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
#include <cstdint>
#include <QAction>
+#include <QSpinBox>
#include <QToolBar>
+#include <QToolButton>
#include <QWidget>
#include <pv/session.hpp>
+#include "trace.hpp"
+
namespace pv {
class MainWindow;
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();
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
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
+#include "signal.hpp"
#include "timeitem.hpp"
#include "view.hpp"
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
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
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),
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
}
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());
}
return;
const float x = get_x();
- p.setPen(colour_.darker());
+ p.setPen(color_.darker());
p.drawLine(QPointF(x, pp.top()), QPointF(x, pp.bottom()));
}
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);
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
/**
* 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:
/**
* 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.
void on_value_changed(const pv::util::Timestamp& value);
protected:
- const QColor &colour_;
+ const QColor &color_;
pv::util::Timestamp time_;
#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;
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
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;
}
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;
}
popup_ = new Popup(parent);
popup_->set_position(parent->mapToGlobal(
- point(parent->rect())), Popup::Right);
+ drag_point(parent->rect())), Popup::Right);
create_popup_form();
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));
{
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()
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)
}
}
-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);
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
#include "tracetreeitem.hpp"
+#include <pv/globalsettings.hpp>
#include "pv/data/signalbase.hpp"
using std::shared_ptr;
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:
/**
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.
*/
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);
*/
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.
*/
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();
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_;
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()
{
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[] = {
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));
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));
return false;
}
-QMenu* TraceGroup::create_context_menu(QWidget *parent)
+QMenu* TraceGroup::create_header_context_menu(QWidget *parent)
{
QMenu *const menu = new QMenu(parent);
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;
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());
static const int Padding;
static const int Width;
static const int LineThickness;
- static const QColor LineColour;
+ static const QColor LineColor;
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);
*/
int owner_visual_v_offset() const;
- void restack_items();
-
/**
* Returns the number of nested parents that this row item owner has.
*/
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
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
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
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
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());
}
* 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.
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;
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
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),
return true;
}
-bool TriggerMarker::is_draggable() const
+bool TriggerMarker::is_draggable(QPoint pos) const
{
+ (void)pos;
return false;
}
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)
if (!enabled())
return;
- QPen pen(Colour);
+ QPen pen(Color);
pen.setStyle(Qt::DashLine);
const float x = get_x();
#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:
/**
/**
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.
* 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
#include <QApplication>
#include <QEvent>
#include <QFontMetrics>
+#include <QMenu>
#include <QMouseEvent>
#include <QScrollBar>
#include <QVBoxLayout>
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;
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);
// 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)));
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);
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()
{
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
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;
}
#endif
+shared_ptr<Signal> View::get_signal_under_mouse_cursor() const
+{
+ return signal_under_mouse_cursor_;
+}
+
View* View::view()
{
return this;
return viewport_;
}
+const Ruler* View::ruler() const
+{
+ return ruler_;
+}
+
void View::save_settings(QSettings &settings) const
{
settings.setValue("scale", scale_);
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();
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);
}
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;
}
}
+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
return 0;
}
+uint32_t View::current_segment() const
+{
+ return current_segment_;
+}
+
pv::util::SIPrefix View::tick_prefix() const
{
return tick_prefix_;
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) {
}
}
+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);
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
{
// 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());
{
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;
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
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
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
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));
}
(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);
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()
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
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 auto& channel : group->channels()) {
+ for (auto& entry : signal_map) {
if (entry.first->channel() == channel) {
TraceTreeItemOwner *const o = (entry.second)->owner();
owner_list.push_back(o);
{
vector< shared_ptr<Trace> > filtered_traces;
- for (const auto &channel : channels) {
- for (auto entry : signal_map) {
+ for (const auto& channel : 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);
// 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
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
// 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();
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
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)
(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)
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
// 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)
// 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);
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();
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
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();
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_);
// 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) {
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_) {
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
#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;
namespace trace {
-class CursorHeader;
class DecodeTrace;
class Header;
class Ruler;
class Signal;
-class Trace;
class Viewport;
class TriggerMarker;
bool viewportEvent(QEvent *event);
};
-class View : public ViewBase, public TraceTreeItemOwner
+class View : public ViewBase, public TraceTreeItemOwner, public GlobalSettingsInterface
{
Q_OBJECT
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;
#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.
*/
const Viewport* viewport() const;
+ const Ruler* ruler() const;
+
virtual void save_settings(QSettings &settings) const;
virtual void restore_settings(QSettings &settings);
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.
*/
*/
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.
*/
*/
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.
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.
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();
/// 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;
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();
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);
private Q_SLOTS:
+ void on_signal_name_changed();
void on_splitter_moved();
void h_scroll_value_changed(int value);
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'
*/
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_;
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_;
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_;
// 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
{
}
+bool ViewItem::is_selectable(QPoint pos) const
+{
+ (void)pos;
+ return true;
+}
+
bool ViewItem::selected() const
{
return selected_;
selected_ = select;
}
-bool ViewItem::is_draggable() const
+bool ViewItem::is_draggable(QPoint pos) const
{
+ (void)pos;
return true;
}
void ViewItem::drag()
{
- if (is_draggable())
- drag_point_ = point(QRect());
+ drag_point_ = drag_point(QRect());
}
void ViewItem::drag_release()
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;
(void)pp;
}
-QColor ViewItem::select_text_colour(QColor background)
+QColor ViewItem::select_text_color(QColor background)
{
return (background.lightness() > 110) ? Qt::black : Qt::white;
}
#include <list>
#include <QPen>
+#include <QPoint>
#include "viewitempaintparams.hpp"
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.
*/
/**
* 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.
* 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.
*/
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);
namespace views {
namespace trace {
+const ViewItemOwner::item_list& ViewItemOwner::child_items() const
+{
+ return items_;
+}
+
ViewItemOwner::iterator ViewItemOwner::begin()
{
return iterator(this, items_.begin());
/**
* 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
rect_(rect),
scale_(scale),
offset_(offset),
- bg_colour_state_(false)
+ bg_color_state_(false)
{
assert(scale > 0.0);
}
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;
}
QRect rect_;
double scale_;
pv::util::Timestamp offset_;
- bool bg_colour_state_;
+ bool bg_color_state_;
};
} // namespace trace
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;
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
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(),
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);
}
#include <boost/optional.hpp>
+#include <QPoint>
#include <QTimer>
#include <QTouchEvent>
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.
*/
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.
*/
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)
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();
// 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);
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();
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
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();
}
{
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)
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() <
#include <memory>
+#include <QPoint>
#include <QWidget>
using std::shared_ptr;
* 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.
#include "pv/session.hpp"
#include "pv/util.hpp"
+#include "pv/data/segment.hpp"
using std::shared_ptr;
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)
{
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()));
delayed_view_updater_.setInterval(1000 / MaxViewAutoUpdateRate);
}
+void ViewBase::reset_view_state()
+{
+ ruler_shift_ = 0;
+ current_segment_ = 0;
+}
+
Session& ViewBase::session()
{
return session_;
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();
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
{
}
-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
(void)settings;
}
-void ViewBase::trigger_event(util::Timestamp location)
+void ViewBase::trigger_event(int segment_id, util::Timestamp location)
{
+ (void)segment_id;
(void)location;
}
{
}
+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;
{
}
+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())
#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;
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;
#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;
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:
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_;
};
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
+++ /dev/null
-/*
- * 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
+++ /dev/null
-/*
- * 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
+++ /dev/null
-/*
- * 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
+++ /dev/null
-/*
- * 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
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);
&mapper_, SLOT(map()));
}
}
- g_slist_free(l);
+ g_slist_free(li);
connect(&mapper_, SIGNAL(mapped(QObject*)),
this, SLOT(on_action(QObject*)));
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;
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);
bool eventFilter(QObject *obj, QEvent *event);
- void show();
+ virtual void show();
private:
bool space_for_arrow() const;
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()));
show_none();
}
+void SweepTimingWidget::allow_user_entered_values(bool value)
+{
+ list_.setEditable(value);
+}
+
void SweepTimingWidget::show_none()
{
value_type_ = None;
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
{
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
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);
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);
* 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>
static int sockets_[2];
};
-#endif // SIGNALHANDLER_HPP
+#endif // PULSEVIEW_PV_SIGNALHANDLER_HPP
##
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
${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
${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
${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
# 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
${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
${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
${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
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
${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
/* --- 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 ----//
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 ----//
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;
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;
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);
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
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);
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));
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);
}
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));
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);
}
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));
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()
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);
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);
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;
--- /dev/null
+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);
+}
--- /dev/null
+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);
+}