##
## Copyright (C) 2012 Joel Holdsworth <joel@airwebreathe.org.uk>
## Copyright (C) 2012-2013 Alexandru Gagniuc <mr.nuke.me@gmail.com>
+## Copyright (C) 2020 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
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_FLOW "Build with libsigrokflow" FALSE)
option(ENABLE_TESTS "Enable unit tests" FALSE)
option(STATIC_PKGDEPS_LIBS "Statically link to (pkg-config) libraries" FALSE)
list(APPEND PKGDEPS glib-2.0>=2.28.0)
list(APPEND PKGDEPS glibmm-2.4>=2.28.0)
+if(ENABLE_FLOW)
+ list(APPEND PKGDEPS gstreamermm-1.0>=1.8.0)
+ list(APPEND PKGDEPS libsigrokflow>=0.1.0)
+endif()
+
set(LIBSR_CXX_BINDING "libsigrokcxx>=0.5.1")
list(APPEND PKGDEPS "${LIBSR_CXX_BINDING}")
endif()
find_package(PkgConfig)
-pkg_check_modules(LIBSRCXX QUIET ${LIBSR_CXX_BINDING})
+pkg_check_modules(LIBSRCXX ${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()
set(CMAKE_AUTOMOC TRUE)
-find_package(Qt5 COMPONENTS Core Gui Widgets Svg REQUIRED)
+find_package(Qt5 5.3 COMPONENTS Core Gui LinguistTools Widgets Svg REQUIRED)
+
+message(STATUS "Qt version: ${Qt5_VERSION}")
if(WIN32)
# MXE workaround: Use pkg-config to find Qt5 libs.
# https://github.com/mxe/mxe/issues/1642
# Not required (and doesn't work) on MSYS2.
if(NOT DEFINED ENV{MSYSTEM})
- pkg_check_modules(QT5ALL REQUIRED Qt5Widgets Qt5Gui Qt5Svg)
+ pkg_check_modules(QT5ALL REQUIRED Qt5Widgets>=5.3 Qt5Gui>=5.3 Qt5Svg>=5.3)
endif()
endif()
pv/prop/int.cpp
pv/prop/property.cpp
pv/prop/string.cpp
+ pv/subwindows/subwindowbase.cpp
pv/toolbars/mainbar.cpp
pv/views/trace/analogsignal.cpp
pv/views/trace/cursor.cpp
pv/views/trace/header.cpp
pv/views/trace/marginwidget.cpp
pv/views/trace/logicsignal.cpp
- pv/views/trace/rowitem.cpp
pv/views/trace/ruler.cpp
pv/views/trace/signal.cpp
pv/views/trace/timeitem.cpp
pv/widgets/colorpopup.cpp
pv/widgets/devicetoolbutton.cpp
pv/widgets/exportmenu.cpp
+ pv/widgets/flowlayout.cpp
pv/widgets/importmenu.cpp
pv/widgets/popup.cpp
pv/widgets/popuptoolbutton.cpp
pv/prop/int.hpp
pv/prop/property.hpp
pv/prop/string.hpp
+ pv/subwindows/subwindowbase.hpp
pv/toolbars/mainbar.hpp
pv/views/trace/analogsignal.hpp
pv/views/trace/cursor.hpp
pv/views/trace/header.hpp
pv/views/trace/logicsignal.hpp
pv/views/trace/marginwidget.hpp
- pv/views/trace/rowitem.hpp
pv/views/trace/ruler.hpp
pv/views/trace/signal.hpp
pv/views/trace/timeitem.hpp
pv/widgets/colorpopup.hpp
pv/widgets/devicetoolbutton.hpp
pv/widgets/exportmenu.hpp
+ pv/widgets/flowlayout.hpp
pv/widgets/importmenu.hpp
pv/widgets/popup.hpp
pv/widgets/popuptoolbutton.hpp
pv/data/decode/decoder.cpp
pv/data/decode/row.cpp
pv/data/decode/rowdata.cpp
+ pv/subwindows/decoder_selector/item.cpp
+ pv/subwindows/decoder_selector/model.cpp
+ pv/subwindows/decoder_selector/subwindow.cpp
+ pv/views/decoder_binary/view.cpp
+ pv/views/decoder_binary/QHexView.cpp
pv/views/trace/decodetrace.cpp
pv/widgets/decodergroupbox.cpp
pv/widgets/decodermenu.cpp
list(APPEND pulseview_HEADERS
pv/data/decodesignal.hpp
+ pv/subwindows/decoder_selector/subwindow.hpp
+ pv/views/decoder_binary/view.hpp
+ pv/views/decoder_binary/QHexView.hpp
pv/views/trace/decodetrace.hpp
pv/widgets/decodergroupbox.hpp
pv/widgets/decodermenu.hpp
qt5_add_resources(pulseview_RESOURCES_RCC ${pulseview_RESOURCES})
+#===============================================================================
+#= Translations
+#-------------------------------------------------------------------------------
+
+file(GLOB TS_FILES ${CMAKE_SOURCE_DIR}/l10n/*.ts)
+set_property(SOURCE ${TS_FILES} PROPERTY OUTPUT_LOCATION ${CMAKE_BINARY_DIR}/l10n)
+if (NOT CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
+ configure_file("translations.qrc" "translations.qrc" COPYONLY)
+endif ()
+
+qt5_add_translation(QM_FILES ${TS_FILES})
+qt5_create_translation(QM_FILES ${pulseview_SOURCES} ${TS_FILES})
+
+qt5_add_resources(pulseview_RESOURCES_RCC ${CMAKE_BINARY_DIR}/translations.qrc)
+
#===============================================================================
#= Global Definitions
#-------------------------------------------------------------------------------
add_definitions(-std=c++11)
add_definitions(-DBOOST_MATH_DISABLE_FLOAT128=1)
+if(ENABLE_FLOW)
+ add_definitions(-DENABLE_FLOW)
+endif()
+
if(ENABLE_DECODE)
add_definitions(-DENABLE_DECODE)
endif()
list(APPEND PULSEVIEW_LINK_LIBS "-llog")
endif()
+set(INPUT_FILES_LIST ${pulseview_SOURCES} ${pulseview_RESOURCES_RCC} ${QM_FILES})
if(ANDROID)
- add_library(${PROJECT_NAME} SHARED ${pulseview_SOURCES} ${pulseview_RESOURCES_RCC})
+ add_library(${PROJECT_NAME} SHARED ${INPUT_FILES_LIST})
else()
- add_executable(${PROJECT_NAME} ${pulseview_SOURCES} ${pulseview_RESOURCES_RCC})
+ add_executable(${PROJECT_NAME} ${INPUT_FILES_LIST})
endif()
target_link_libraries(${PROJECT_NAME} ${PULSEVIEW_LINK_LIBS})
install(FILES icons/pulseview.svg DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/apps)
# Generate Windows installer script.
-configure_file(contrib/pulseview_cross.nsi.in contrib/pulseview_cross.nsi @ONLY)
+configure_file(contrib/pulseview_cross.nsi.in ${CMAKE_CURRENT_BINARY_DIR}/contrib/pulseview_cross.nsi @ONLY)
#===============================================================================
#= Packaging (handled by CPack)
- cmake >= 2.8.12
- libglib >= 2.28.0
- glibmm-2.4 (>= 2.28.0)
- - Qt5 (including the following components):
- - Qt5Core, Qt5Gui, Qt5Widgets, Qt5Svg
+ - Qt5 (>= 5.3), including the following components:
+ - Qt5Core, Qt5Gui, Qt5Widgets, Qt5Svg, Qt5LinguistTools
+ - Qt translation package (optional; needed at runtime, not build time)
- libboost >= 1.55 (including the following libs):
- libboost-system
- libboost-filesystem
(GPL), version 3 or later.
While some individual source code files are licensed under the GPLv2+, and
-some files are licensed under the GPLv3+, this doesn't change the fact that
-the program as a whole is licensed under the terms of the GPLv3+ (e.g. also
-due to the fact that it links against GPLv3+ libraries).
+some files are licensed under the GPLv3+ or MIT, this doesn't change the fact
+that the program as a whole is licensed under the terms of the GPLv3+ (e.g.
+also due to the fact that it links against GPLv3+ libraries).
Please see the individual source files for the full list of copyright holders.
Resource authors and licenses
-----------------------------
+icons/application-exit.png,
+icons/document-new.png,
+icons/document-open.png,
+icons/document-save-as.png,
+icons/edit-paste.svg,
+icons/help-browser.png,
+icons/media-playback-pause.png,
+icons/media-playback-start.png,
+icons/preferences-system.png,
+icons/search.svg,
+icons/window-new.png,
+icons/zoom-fit-best.png,
+icons/zoom-in.png,
+icons/zoom-out.png: Tango Icon Library
+ http://tango.freedesktop.org/Tango_Desktop_Project
+ License:
+ Public Domain
+
icons/information.svg: Bobarino
https://en.wikipedia.org/wiki/File:Information.svg
License:
MIT license
https://github.com/Jorgen-VikingGod/Qt-Frameless-Window-DarkStyle#licence
+QHexView:
+ https://github.com/virinext/QHexView
+ License:
+ MIT license
+ https://github.com/virinext/QHexView/blob/master/LICENSE
+
Mailing list
------------
<name>PulseView</name>
<summary>Logic analyzer, oscilloscope and MSO GUI</summary>
<description>
- <p>PulseView is a Qt based logic analyzer, oscilloscope and MSO GUI for sigrok.</p>
- <p>Features:</p>
+ <p>PulseView is a Qt based logic analyzer, oscilloscope and MSO GUI for sigrok supporting various features:</p>
<ul>
<li>Fast O(log N) signal rendering at all zoom levels</li>
<li>Protocol decoder support</li>
<li>Trace groups support</li>
</ul>
</description>
- <url type="homepage">http://sigrok.org/wiki/PulseView</url>
- <url type="bugtracker">http://sigrok.org/bugzilla/enter_bug.cgi?format=guided&product=PulseView</url>
- <url type="faq">http://sigrok.org/wiki/FAQ</url>
+ <url type="homepage">https://sigrok.org/wiki/PulseView</url>
+ <url type="bugtracker">https://sigrok.org/bugzilla/enter_bug.cgi?format=guided&product=PulseView</url>
+ <url type="faq">https://sigrok.org/wiki/FAQ</url>
<screenshots>
<screenshot type="default">
- <image>http://sigrok.org/wimg/e/ee/PulseView_I2C_DS1307_Decode.png</image>
+ <image>https://sigrok.org/wimg/e/ee/PulseView_I2C_DS1307_Decode.png</image>
</screenshot>
</screenshots>
<provides>
##
## This file is part of the PulseView project.
##
-## Copyright (C) 2013-2014 Uwe Hermann <uwe@hermann-uwe.de>
+## Copyright (C) 2013-2020 Uwe Hermann <uwe@hermann-uwe.de>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
# Python
File "${CROSS}/python34.dll"
File "${CROSS}/python34.zip"
+ File "${CROSS}/*.pyd"
SetOutPath "$INSTDIR\share"
Delete "$INSTDIR\zadig_xp.exe"
Delete "$INSTDIR\python34.dll"
Delete "$INSTDIR\python34.zip"
+ Delete "$INSTDIR\*.pyd"
# Delete all decoders and everything else in libsigrokdecode/.
# There could be *.pyc files or __pycache__ subdirs and so on.
-.TH PULSEVIEW 1 "March 30, 2018"
+.TH PULSEVIEW 1 "March 31, 2020"
.SH "NAME"
PulseView \- Qt-based LA/scope/MSO GUI for sigrok
.SH "SYNOPSIS"
.BR "\-I, \-\-input\-format " <format>
Specifies the format of the input file to be loaded.
.TP
+.BR "\-s, \-\-settings " <filename>
+Load PulseView session setup to use with the input file. The setup file must be
+in the "PulseView session setup" format (.pvs).
+.TP
.BR "\-c, \-\-clean"
Prevents the previously used sessions to be restored from settings storage.
This is useful if you want only a single session with the file given on the
.B "f"
Zoom-to-fit.
.TP
-.B "o"
-Zoom 1:1.
-.TP
.B "s"
Enable / disable sticky scrolling. When enabled, the right edge of the screen
always shows the most recently captured data.
.B "c"
Show / hide cursors.
.TP
+.B "d"
+Show / hide protocol decoder selector.
+.TP
.B "b"
Toggle between coloured trace backgrounds and alternating light/dark
gray trace backgrounds.
.B "SPACE"
Start / stop an acquisition.
.TP
-.B "Arrow keys"
-Scroll up/down/left/right.
+.B "Left/right arrow keys"
+Scroll left/right.
+.TP
+.B "+/-"
+Zoom in/out.
+.TP
+.B "Up/down arrow keys"
+Zoom in/out.
+.TP
+.B "Home/End"
+Jump to the start/end of the sample data.
+.TP
+.B "1/2"
+Attach left/right side of the cursors to the mouse.
.TP
.B "CTRL+o"
Open file.
.B "CTRL+u"
Ungroup the traces in the currently selected trace group.
.TP
-.B "CTRL++"
-Zoom in.
-.TP
-.B "CTRL+-"
-Zoom out.
+.B "CTRL+up/down arrow keys"
+Scroll down/up.
.TP
.B "CTRL+q"
Quit, i.e. shutdown PulseView (closing all session tabs).
--- /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:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg12360"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docbase="/home/jimmac/src/cvs/tango-icon-theme/scalable/actions"
+ sodipodi:docname="edit-paste.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <defs
+ id="defs12362">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 24 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="48 : 24 : 1"
+ inkscape:persp3d-origin="24 : 16 : 1"
+ id="perspective80" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5060"
+ id="radialGradient5031"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
+ cx="605.71429"
+ cy="486.64789"
+ fx="605.71429"
+ fy="486.64789"
+ r="117.14286" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient5060">
+ <stop
+ style="stop-color:black;stop-opacity:1;"
+ offset="0"
+ id="stop5062" />
+ <stop
+ style="stop-color:black;stop-opacity:0;"
+ offset="1"
+ id="stop5064" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5060"
+ id="radialGradient5029"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
+ cx="605.71429"
+ cy="486.64789"
+ fx="605.71429"
+ fy="486.64789"
+ r="117.14286" />
+ <linearGradient
+ id="linearGradient5048">
+ <stop
+ style="stop-color:black;stop-opacity:0;"
+ offset="0"
+ id="stop5050" />
+ <stop
+ id="stop5056"
+ offset="0.5"
+ style="stop-color:black;stop-opacity:1;" />
+ <stop
+ style="stop-color:black;stop-opacity:0;"
+ offset="1"
+ id="stop5052" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5048"
+ id="linearGradient5027"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
+ x1="302.85715"
+ y1="366.64789"
+ x2="302.85715"
+ y2="609.50507" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2259">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop2261" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop2263" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2251">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop2253" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop2255" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2239">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop2241" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop2243" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2224">
+ <stop
+ style="stop-color:#7c7c7c;stop-opacity:1;"
+ offset="0"
+ id="stop2226" />
+ <stop
+ style="stop-color:#b8b8b8;stop-opacity:1;"
+ offset="1"
+ id="stop2228" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2216">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop2218" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop2220" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient15234">
+ <stop
+ style="stop-color:#97978a;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop15236" />
+ <stop
+ id="stop15242"
+ offset="0.50000000"
+ style="stop-color:#c2c2b9;stop-opacity:1.0000000;" />
+ <stop
+ style="stop-color:#7d7d6f;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop15238" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient15218">
+ <stop
+ style="stop-color:#f0f0ef;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop15220" />
+ <stop
+ id="stop2269"
+ offset="0.59928656"
+ style="stop-color:#e8e8e8;stop-opacity:1;" />
+ <stop
+ id="stop2267"
+ offset="0.82758623"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ style="stop-color:#d8d8d3;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop15222" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient14484">
+ <stop
+ style="stop-color:#c68827;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop14486" />
+ <stop
+ style="stop-color:#89601f;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop14488" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient13746">
+ <stop
+ style="stop-color:#646459;stop-opacity:1;"
+ offset="0"
+ id="stop13748" />
+ <stop
+ style="stop-color:#646459;stop-opacity:0;"
+ offset="1"
+ id="stop13750" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient13746"
+ id="radialGradient13752"
+ cx="24.647722"
+ cy="45.272587"
+ fx="24.647722"
+ fy="45.272587"
+ r="21.011173"
+ gradientTransform="matrix(1.000000,0.000000,0.000000,0.110577,4.987330e-17,40.26648)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient14484"
+ id="linearGradient14490"
+ x1="6.1071744"
+ y1="10.451290"
+ x2="33.857143"
+ y2="37.879860"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient15218"
+ id="linearGradient15224"
+ x1="22.308331"
+ y1="18.992140"
+ x2="35.785294"
+ y2="39.498238"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.065698,0.000000,0.000000,0.987595,-1.564439,7.487332e-2)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient15234"
+ id="linearGradient15240"
+ x1="25.404572"
+ y1="3.8180194"
+ x2="25.464211"
+ y2="9.3233509"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.052632,0.000000,0.000000,1.000000,-1.789474,0.000000)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2216"
+ id="linearGradient2222"
+ x1="36.8125"
+ y1="39.15625"
+ x2="39.0625"
+ y2="42.0625"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2224"
+ id="linearGradient2230"
+ x1="35.996582"
+ y1="40.458221"
+ x2="33.664921"
+ y2="37.770721"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2239"
+ id="linearGradient2245"
+ x1="25.682829"
+ y1="12.172059"
+ x2="25.692169"
+ y2="-0.20294096"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2251"
+ id="linearGradient2257"
+ x1="33.396004"
+ y1="36.921333"
+ x2="34.170048"
+ y2="38.070381"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2259"
+ id="linearGradient2265"
+ x1="26.076092"
+ y1="26.696676"
+ x2="30.811172"
+ y2="42.007351"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient15234"
+ id="linearGradient2283"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.538743,0.000000,0.000000,0.511806,10.80080,-0.582640)"
+ x1="25.404572"
+ y1="3.8180194"
+ x2="25.404572"
+ y2="6.481061" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient15234"
+ id="linearGradient2287"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.005222,0.000000,0.000000,0.883928,-0.627923,0.843750)"
+ x1="25.404572"
+ y1="3.8180194"
+ x2="25.464211"
+ y2="9.3233509" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="0.15294118"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1"
+ inkscape:cx="-125.14425"
+ inkscape:cy="14.046835"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="872"
+ inkscape:window-height="818"
+ inkscape:window-x="385"
+ inkscape:window-y="30"
+ inkscape:showpageshadow="false" />
+ <metadata
+ id="metadata12365">
+ <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>Edit Paste</dc:title>
+ <dc:date>2005-10-10</dc:date>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Andreas Nilsson</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>edit</rdf:li>
+ <rdf:li>paste</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/publicdomain/" />
+ <dc:contributor>
+ <cc:Agent>
+ <dc:title>Jakub Steiner</dc:title>
+ </cc:Agent>
+ </dc:contributor>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/publicdomain/">
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#Reproduction" />
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#Distribution" />
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <g
+ style="display:inline"
+ id="g5022"
+ transform="matrix(2.325216e-2,0,0,1.485743e-2,44.80627,43.06039)">
+ <rect
+ y="-150.69685"
+ x="-1559.2523"
+ height="478.35718"
+ width="1339.6335"
+ id="rect4173"
+ style="opacity:0.40206185;color:black;fill:url(#linearGradient5027);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <path
+ sodipodi:nodetypes="cccc"
+ id="path5058"
+ d="M -219.61876,-150.68038 C -219.61876,-150.68038 -219.61876,327.65041 -219.61876,327.65041 C -76.744594,328.55086 125.78146,220.48075 125.78138,88.454235 C 125.78138,-43.572302 -33.655436,-150.68036 -219.61876,-150.68038 z "
+ style="opacity:0.40206185;color:black;fill:url(#radialGradient5029);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <path
+ style="opacity:0.40206185;color:black;fill:url(#radialGradient5031);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ d="M -1559.2523,-150.68038 C -1559.2523,-150.68038 -1559.2523,327.65041 -1559.2523,327.65041 C -1702.1265,328.55086 -1904.6525,220.48075 -1904.6525,88.454235 C -1904.6525,-43.572302 -1745.2157,-150.68036 -1559.2523,-150.68038 z "
+ id="path5018"
+ sodipodi:nodetypes="cccc" />
+ </g>
+ <rect
+ style="opacity:1.0000000;fill:url(#linearGradient14490);fill-opacity:1.0;fill-rule:evenodd;stroke:#714c16;stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dasharray:none;stroke-dashoffset:0.0000000;stroke-opacity:1"
+ id="rect12368"
+ width="39.035683"
+ height="41.045437"
+ x="4.4643173"
+ y="4.5000000"
+ rx="1.3879371"
+ ry="1.3879364" />
+ <rect
+ style="opacity:1;fill:url(#linearGradient15224);fill-opacity:1;fill-rule:evenodd;stroke:#888a85;stroke-width:1.00000024;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect12413"
+ width="30.951559"
+ height="35.976688"
+ x="8.5323219"
+ y="6.5295157"
+ rx="0.56650835"
+ ry="0.56650835" />
+ <rect
+ style="opacity:1.0000000;fill:#5c5c5c;fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.0000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dasharray:none;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000"
+ id="rect13756"
+ width="12.000000"
+ height="4.0000000"
+ x="18.000000"
+ y="0.0000000"
+ rx="0.98387533"
+ ry="0.98387533" />
+ <rect
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient2265);stroke-width:1.00000048;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect15244"
+ width="29.014147"
+ height="34.040764"
+ x="9.5171413"
+ y="7.4665856"
+ rx="0"
+ ry="0" />
+ <rect
+ style="opacity:1.0000000;fill:none;fill-opacity:1.0000000;fill-rule:evenodd;stroke:#c68827;stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dasharray:none;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000"
+ id="rect15974"
+ width="37.085655"
+ height="39.092987"
+ x="5.4393425"
+ y="5.4307775"
+ rx="0.47879848"
+ ry="0.47879848" />
+ <rect
+ ry="1.3879364"
+ rx="1.3879377"
+ y="4.4722719"
+ x="14.791488"
+ height="7"
+ width="18.947376"
+ id="rect2208"
+ style="opacity:0.10795455;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <rect
+ ry="1.3879364"
+ rx="1.3879377"
+ y="3.5000000"
+ x="14.526322"
+ height="7.0000000"
+ width="18.947376"
+ id="rect2285"
+ style="opacity:1.0000000;fill:url(#linearGradient15240);fill-opacity:1.0000000;fill-rule:evenodd;stroke:#5c5c5c;stroke-width:1.0000001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dasharray:none;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000" />
+ <rect
+ ry="0.32543635"
+ rx="0.32543635"
+ y="1.2086792"
+ x="19.151323"
+ height="3.5826404"
+ width="9.6973763"
+ id="rect2281"
+ style="opacity:1;fill:url(#linearGradient2283);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.00000012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <rect
+ style="opacity:1;fill:url(#linearGradient2287);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.00000012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect13754"
+ width="18.093992"
+ height="6.1875"
+ x="14.953014"
+ y="3.9375"
+ rx="1.0129364"
+ ry="1.0129364" />
+ <path
+ style="opacity:0.48863636;color:#000000;fill:url(#linearGradient2222);fill-opacity:1.0;fill-rule:evenodd;stroke:none;stroke-width:1.00000024;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ d="M 39.018306,36.25 L 39.0625,42.0625 L 30.5625,42.018306 L 39.018306,36.25 z "
+ id="path2212"
+ sodipodi:nodetypes="cccc" />
+ <path
+ style="opacity:1;color:#000000;fill:url(#linearGradient2230);fill-opacity:1.0;fill-rule:evenodd;stroke:#868a84;stroke-width:1.00000024;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ d="M 30.059082,42.086864 C 31.850223,42.254516 39.048809,37.717278 39.539922,33.698855 C 37.97666,36.121952 34.584971,35.667446 30.476213,35.826456 C 30.476213,35.826456 30.871582,41.586864 30.059082,42.086864 z "
+ id="path2210"
+ sodipodi:nodetypes="cccc" />
+ <path
+ style="opacity:0.31681817;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient2245);stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 19.46875 1.46875 C 19.466654 1.4708456 19.470414 1.4975336 19.46875 1.5 C 19.46758 1.502776 19.438116 1.4969757 19.4375 1.5 L 19.4375 4.375 C 19.4375 4.3814229 19.46641 4.4006981 19.46875 4.40625 C 19.471216 4.4079135 19.465974 4.4363298 19.46875 4.4375 L 15.9375 4.4375 C 15.91974 4.4375 15.892285 4.4357551 15.875 4.4375 C 15.840968 4.4426713 15.781454 4.4572762 15.75 4.46875 C 15.611832 4.5269964 15.482328 4.6677699 15.4375 4.8125 C 15.426991 4.8535348 15.4375 4.9243489 15.4375 4.96875 L 15.4375 9.125 C 15.4375 9.1427605 15.435755 9.1702147 15.4375 9.1875 C 15.442671 9.2215321 15.457276 9.2810456 15.46875 9.3125 C 15.478458 9.3355281 15.487176 9.3851004 15.5 9.40625 C 15.5046 9.41307 15.526336 9.4309205 15.53125 9.4375 C 15.552124 9.4628138 15.599686 9.5103764 15.625 9.53125 C 15.638159 9.5410789 15.6734 9.5539504 15.6875 9.5625 C 15.702038 9.5703781 15.734648 9.5872782 15.75 9.59375 C 15.781454 9.6052238 15.840968 9.6198287 15.875 9.625 C 15.892285 9.6267449 15.91974 9.625 15.9375 9.625 L 32.0625 9.625 C 32.08026 9.625 32.107715 9.6267449 32.125 9.625 C 32.159032 9.6198287 32.218546 9.6052238 32.25 9.59375 C 32.265352 9.5872782 32.297962 9.5703781 32.3125 9.5625 C 32.3266 9.5539504 32.361841 9.5410789 32.375 9.53125 C 32.400314 9.5103764 32.447876 9.4628138 32.46875 9.4375 C 32.473664 9.4309205 32.4954 9.41307 32.5 9.40625 C 32.512824 9.3851004 32.521542 9.3355281 32.53125 9.3125 C 32.542724 9.2810456 32.557329 9.2215321 32.5625 9.1875 C 32.564245 9.1702147 32.5625 9.1427605 32.5625 9.125 L 32.5625 4.96875 C 32.5625 4.9243489 32.573009 4.8535348 32.5625 4.8125 C 32.517672 4.6677698 32.388168 4.5269964 32.25 4.46875 C 32.218546 4.4572762 32.159032 4.4426713 32.125 4.4375 C 32.107715 4.4357551 32.08026 4.4375 32.0625 4.4375 L 28.53125 4.4375 C 28.534026 4.4363298 28.528784 4.4079135 28.53125 4.40625 C 28.533591 4.4006981 28.5625 4.3814229 28.5625 4.375 L 28.5625 1.5 C 28.561884 1.4969757 28.53242 1.502776 28.53125 1.5 C 28.529586 1.4975336 28.533346 1.4708456 28.53125 1.46875 C 28.528474 1.4675798 28.503024 1.4693657 28.5 1.46875 L 19.5 1.46875 C 19.496976 1.4693657 19.471526 1.4675798 19.46875 1.46875 z "
+ id="rect2232" />
+ <path
+ sodipodi:nodetypes="cccc"
+ id="path2247"
+ d="M 31.509519,40.68705 C 32.879298,40.003221 36.038783,38.086016 37.338164,36.205012 C 35.545641,36.581496 34.347243,36.794585 31.610576,36.900494 C 31.610576,36.900494 31.697137,39.91208 31.509519,40.68705 z "
+ style="opacity:0.36931818;color:#000000;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient2257);stroke-width:0.99999982;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <rect
+ style="opacity:0.17045455;color:#000000;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="rect2271"
+ width="21"
+ height="2"
+ x="14"
+ y="15" />
+ <rect
+ y="19"
+ x="14"
+ height="2"
+ width="20"
+ id="rect2273"
+ style="opacity:0.17045455;color:#000000;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <rect
+ style="opacity:0.17045455;color:#000000;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="rect2275"
+ width="18"
+ height="2"
+ x="14"
+ y="23" />
+ <rect
+ y="27"
+ x="14"
+ height="2"
+ width="21"
+ id="rect2277"
+ style="opacity:0.17045455;color:#000000;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <rect
+ style="opacity:0.17045455;color:#000000;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="rect2279"
+ width="13"
+ height="2"
+ x="14"
+ y="31" />
+ </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:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ sodipodi:docname="system-search.svg"
+ sodipodi:docbase="/home/tigert/cvs/freedesktop.org/tango-icon-theme/scalable/actions"
+ inkscape:version="0.46"
+ sodipodi:version="0.32"
+ id="svg11300"
+ height="48px"
+ width="48px"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <defs
+ id="defs3">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 24 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="48 : 24 : 1"
+ inkscape:persp3d-origin="24 : 16 : 1"
+ id="perspective47" />
+ <linearGradient
+ id="linearGradient2846">
+ <stop
+ id="stop2848"
+ offset="0.0000000"
+ style="stop-color:#8a8a8a;stop-opacity:1.0000000;" />
+ <stop
+ id="stop2850"
+ offset="1.0000000"
+ style="stop-color:#484848;stop-opacity:1.0000000;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2366">
+ <stop
+ id="stop2368"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.21904762;"
+ offset="0.50000000"
+ id="stop2374" />
+ <stop
+ id="stop2370"
+ offset="1.0000000"
+ style="stop-color:#ffffff;stop-opacity:1.0000000;" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient4487">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop4489" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop4491" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient4477">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4479" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop4481" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4467">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop4469" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.24761905;"
+ offset="1.0000000"
+ id="stop4471" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4454">
+ <stop
+ style="stop-color:#729fcf;stop-opacity:0.20784314;"
+ offset="0.0000000"
+ id="stop4456" />
+ <stop
+ style="stop-color:#729fcf;stop-opacity:0.67619050;"
+ offset="1.0000000"
+ id="stop4458" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4440">
+ <stop
+ style="stop-color:#7d7d7d;stop-opacity:1;"
+ offset="0"
+ id="stop4442" />
+ <stop
+ id="stop4448"
+ offset="0.50000000"
+ style="stop-color:#b1b1b1;stop-opacity:1.0000000;" />
+ <stop
+ style="stop-color:#686868;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop4444" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4440"
+ id="linearGradient4446"
+ x1="30.656250"
+ y1="34.000000"
+ x2="33.218750"
+ y2="31.062500"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.334593,0.000000,0.000000,1.291292,-6.973842,-7.460658)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4454"
+ id="radialGradient4460"
+ cx="18.240929"
+ cy="21.817987"
+ fx="18.240929"
+ fy="21.817987"
+ r="8.3085051"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4467"
+ id="radialGradient4473"
+ cx="15.414371"
+ cy="13.078408"
+ fx="15.414371"
+ fy="13.078408"
+ r="6.6562500"
+ gradientTransform="matrix(2.592963,-7.746900e-24,-5.714443e-24,2.252104,-25.05975,-18.94100)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4487"
+ id="radialGradient4493"
+ cx="24.130018"
+ cy="37.967922"
+ fx="24.130018"
+ fy="37.967922"
+ r="16.528622"
+ gradientTransform="matrix(1.000000,0.000000,0.000000,0.237968,3.152859e-15,28.93278)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="25.743469"
+ x2="17.500893"
+ y1="13.602121"
+ x1="18.292673"
+ id="linearGradient2372"
+ xlink:href="#linearGradient2366"
+ inkscape:collect="always" />
+ <radialGradient
+ r="16.528622"
+ fy="37.967922"
+ fx="24.130018"
+ cy="37.967922"
+ cx="24.130018"
+ gradientTransform="matrix(1.000000,0.000000,0.000000,0.237968,-2.471981e-16,28.93278)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient2842"
+ xlink:href="#linearGradient4477"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="30.557772"
+ x2="31.335964"
+ y1="26.580296"
+ x1="27.366341"
+ id="linearGradient2852"
+ xlink:href="#linearGradient2846"
+ inkscape:collect="always" />
+ </defs>
+ <sodipodi:namedview
+ stroke="#3465a4"
+ inkscape:window-y="94"
+ inkscape:window-x="239"
+ inkscape:window-height="754"
+ inkscape:window-width="691"
+ inkscape:showpageshadow="false"
+ inkscape:document-units="px"
+ inkscape:grid-bbox="true"
+ showgrid="false"
+ inkscape:current-layer="layer1"
+ inkscape:cy="23.07052"
+ inkscape:cx="9.502648"
+ inkscape:zoom="11.313708"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ borderopacity="0.25490196"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ fill="#729fcf" />
+ <metadata
+ id="metadata4">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Jakub Steiner</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:source>http://jimmac.musichall.cz</dc:source>
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/publicdomain/" />
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/publicdomain/">
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#Reproduction" />
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#Distribution" />
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:groupmode="layer"
+ inkscape:label="Layer 1"
+ id="layer1">
+ <g
+ id="g1772">
+ <path
+ sodipodi:type="arc"
+ style="opacity:0.17112298;color:#000000;fill:url(#radialGradient2842);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.000000;stroke-dasharray:none;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;visibility:visible;display:inline;overflow:visible;"
+ id="path4475"
+ sodipodi:cx="24.130018"
+ sodipodi:cy="37.967922"
+ sodipodi:rx="16.528622"
+ sodipodi:ry="3.9332814"
+ d="M 40.658640 37.967922 A 16.528622 3.9332814 0 1 1 7.6013966,37.967922 A 16.528622 3.9332814 0 1 1 40.658640 37.967922 z"
+ transform="matrix(1.446431,0.000000,0.000000,1.519990,-10.97453,-17.75168)" />
+ <path
+ sodipodi:nodetypes="csscccscccscczzzz"
+ id="path2844"
+ d="M 18.627569,3.1435548 C 10.488439,3.1435548 3.8827682,9.7492259 3.8827682,17.888356 C 3.8827682,26.027486 10.488439,32.633158 18.627569,32.633158 C 22.107124,32.633158 25.178570,31.248765 27.701292,29.230511 C 27.495915,30.237392 27.623257,31.265879 28.457436,31.990436 L 39.421520,41.517846 C 40.654936,42.589175 42.508982,42.448806 43.580310,41.215389 C 44.651638,39.981971 44.511269,38.127927 43.277853,37.056599 L 32.313769,27.529188 C 31.642242,26.945909 30.820891,26.773219 30.007531,26.886466 C 31.994231,24.374044 33.372370,21.337663 33.372370,17.888356 C 33.372370,9.7492259 26.766699,3.1435548 18.627569,3.1435548 z M 18.551954,4.3697381 C 26.191413,4.3697381 31.843729,9.1586886 31.843729,17.661513 C 31.843729,26.336626 26.027039,30.953288 18.551954,30.953288 C 11.249005,30.953288 5.2601806,25.475196 5.2601806,17.661513 C 5.2601806,9.6774061 11.084819,4.3697380 18.551954,4.3697381 z "
+ style="opacity:1.0000000;color:#000000;fill:#dcdcdc;fill-opacity:1.0000000;fill-rule:evenodd;stroke:url(#linearGradient2852);stroke-width:2.0000010;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.000000;stroke-dasharray:none;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;visibility:visible;display:inline;overflow:visible;" />
+ <path
+ style="opacity:1.0000000;color:#000000;fill:#dcdcdc;fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.0000004;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.000000;stroke-dasharray:none;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;visibility:visible;display:inline;overflow:visible;"
+ d="M 18.602905,3.0803551 C 10.437465,3.0803551 3.8104408,9.7073791 3.8104408,17.872819 C 3.8104408,26.038259 10.437465,32.665283 18.602905,32.665283 C 22.093708,32.665283 25.175082,31.276416 27.705960,29.251638 C 27.499919,30.261774 27.627672,31.293585 28.464547,32.020484 L 39.464073,41.578691 C 40.701476,42.653483 42.561515,42.512661 43.636306,41.275256 C 44.711097,40.037852 44.570274,38.177814 43.332871,37.103023 L 32.333346,27.544815 C 31.659648,26.959651 30.835642,26.786402 30.019653,26.900016 C 32.012775,24.379472 33.395369,21.333276 33.395369,17.872819 C 33.395369,9.7073791 26.768345,3.0803551 18.602905,3.0803551 z M 18.527046,6.2664243 C 24.808154,6.2664245 29.905864,11.364135 29.905864,17.645243 C 29.905864,23.926351 24.808154,29.024061 18.527046,29.024061 C 12.245938,29.024061 7.1482276,23.926351 7.1482276,17.645243 C 7.1482278,11.364135 12.245938,6.2664243 18.527046,6.2664243 z "
+ id="path4430" />
+ <path
+ style="opacity:1.0000000;color:#000000;fill:url(#linearGradient4446);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.000000;stroke-dasharray:none;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;visibility:visible;display:inline;overflow:visible;"
+ d="M 39.507004,41.577690 C 39.028332,39.304503 40.904334,36.766268 43.091057,36.789315 C 43.091057,36.789315 32.330690,27.531204 32.330690,27.531204 C 29.385899,27.474498 28.061188,29.803820 28.553876,32.131126 L 39.507004,41.577690 z "
+ id="path4438"
+ sodipodi:nodetypes="ccccc" />
+ <path
+ sodipodi:type="arc"
+ style="opacity:1.0000000;color:#000000;fill:none;fill-opacity:1.0000000;fill-rule:evenodd;stroke:url(#linearGradient2372);stroke-width:0.80273360;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.000000;stroke-dasharray:none;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;visibility:visible;display:inline;overflow:visible;"
+ id="path4450"
+ sodipodi:cx="17.500893"
+ sodipodi:cy="18.920233"
+ sodipodi:rx="11.048544"
+ sodipodi:ry="11.048544"
+ d="M 28.549437 18.920233 A 11.048544 11.048544 0 1 1 6.4523487,18.920233 A 11.048544 11.048544 0 1 1 28.549437 18.920233 z"
+ transform="matrix(1.245743,0.000000,0.000000,1.245743,-3.425346,-6.177033)" />
+ <path
+ transform="matrix(0.497764,0.000000,0.000000,0.609621,8.973526,15.61929)"
+ d="M 40.658640 37.967922 A 16.528622 3.9332814 0 1 1 7.6013966,37.967922 A 16.528622 3.9332814 0 1 1 40.658640 37.967922 z"
+ sodipodi:ry="3.9332814"
+ sodipodi:rx="16.528622"
+ sodipodi:cy="37.967922"
+ sodipodi:cx="24.130018"
+ id="path4485"
+ style="opacity:1.0000000;color:#000000;fill:url(#radialGradient4493);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.000000;stroke-dasharray:none;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;visibility:visible;display:inline;overflow:visible;"
+ sodipodi:type="arc" />
+ <rect
+ style="opacity:0.43315509;color:#000000;fill:none;fill-opacity:1.0000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.0000311;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.000000;stroke-dasharray:none;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;visibility:visible;display:inline;overflow:visible;"
+ id="rect4495"
+ width="19.048439"
+ height="4.4404783"
+ x="40.373337"
+ y="0.14086054"
+ rx="2.1366608"
+ ry="1.8879365"
+ transform="matrix(0.752986,0.658037,-0.648902,0.760872,0.000000,0.000000)" />
+ <path
+ sodipodi:type="arc"
+ style="color:#000000;fill:url(#radialGradient4460);fill-opacity:1.0000000;fill-rule:evenodd;stroke:#3063a3;stroke-width:0.71499395;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:10.000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;"
+ id="path4452"
+ sodipodi:cx="17.589281"
+ sodipodi:cy="18.478292"
+ sodipodi:rx="8.3085051"
+ sodipodi:ry="8.3085051"
+ d="M 25.897786 18.478292 A 8.3085051 8.3085051 0 1 1 9.2807760,18.478292 A 8.3085051 8.3085051 0 1 1 25.897786 18.478292 z"
+ transform="matrix(1.398614,0.000000,0.000000,1.398614,-6.224338,-8.298958)" />
+ <path
+ style="opacity:0.83422458;color:#000000;fill:url(#radialGradient4473);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.000000;stroke-dasharray:none;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;visibility:visible;display:inline;overflow:visible;"
+ d="M 18.156915,7.3966938 C 12.949325,7.3966938 8.7323681,11.613651 8.7323681,16.821241 C 8.7323681,18.325216 9.1526753,19.709014 9.7795400,20.971144 C 11.031920,21.432757 12.362297,21.746827 13.774307,21.746827 C 19.945262,21.746827 24.873589,16.885190 25.254413,10.809698 C 23.523449,8.7641668 21.044374,7.3966938 18.156915,7.3966938 z "
+ id="path4462" />
+ </g>
+ </g>
+</svg>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+ <name>Application</name>
+ <message>
+ <location filename="../pv/application.cpp" line="121"/>
+ <source>Some parts of the application may still use the previous language. Re-opening the affected windows or restarting the application will remedy this.</source>
+ <translation>Einige Teile der Anwendung verwenden vielleicht noch die vorherige Sprache. Sollte das der Fall sein, kann dies durch ein Schließen und neu Öffnen der betroffenen Fenster oder der Anwendung behoben werden.</translation>
+ </message>
+</context>
+<context>
+ <name>QApplication</name>
+ <message>
+ <source>Select a decoder to see its description here.</source>
+ <translation type="vanished">Wähle einen Dekoder, um dessen Beschreibung hier lesen zu können.</translation>
+ </message>
+ <message>
+ <source>Session %1</source>
+ <translation type="obsolete">Analysesitzung %1</translation>
+ </message>
+ <message>
+ <location filename="../pv/devices/device.cpp" line="70"/>
+ <source>Querying config key %1 is not allowed</source>
+ <translatorcomment>Internal message</translatorcomment>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../pv/devices/device.cpp" line="79"/>
+ <source>Querying config key %1 resulted in %2</source>
+ <translatorcomment>Internal message</translatorcomment>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../pv/devices/device.cpp" line="93"/>
+ <source>Unknown type supplied when attempting to query %1</source>
+ <translatorcomment>Internal message</translatorcomment>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../pv/devicemanager.cpp" line="274"/>
+ <source>Error when scanning device driver '%1': %2</source>
+ <translatorcomment>Internal message</translatorcomment>
+ <translation></translation>
+ </message>
+</context>
+<context>
+ <name>QHexView</name>
+ <message>
+ <location filename="../pv/views/decoder_binary/QHexView.cpp" line="288"/>
+ <source>No data available</source>
+ <translation>Keine Daten vorhanden</translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../pv/devicemanager.cpp" line="65"/>
+ <source>Cancel</source>
+ <translation>Abbrechen</translation>
+ </message>
+ <message>
+ <location filename="../pv/devicemanager.cpp" line="96"/>
+ <source>Scanning for devices that driver %1 can access...</source>
+ <translation>Suche nach Geräten, die von Treiber %1 angesprochen werden können...</translation>
+ </message>
+ <message>
+ <location filename="../main.cpp" line="113"/>
+ <source>Stack trace of previous crash:</source>
+ <translatorcomment>Internal message</translatorcomment>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../main.cpp" line="127"/>
+ <source>Don't show this message again</source>
+ <translation>Diese Meldung in Zukunft nicht mehr anzeigen</translation>
+ </message>
+ <message>
+ <location filename="../main.cpp" line="130"/>
+ <source>When %1 last crashed, it created a stack trace.
+A human-readable form has been saved to disk and was written to the log. You may access it from the settings dialog.</source>
+ <translatorcomment>Internal message</translatorcomment>
+ <translation></translation>
+ </message>
+</context>
+<context>
+ <name>SubWindow</name>
+ <message>
+ <source><p align='right'>Tags: %1</p></source>
+ <translation type="vanished"><p align='right'>Stichworte: %1</p></translation>
+ </message>
+</context>
+<context>
+ <name>pv::MainWindow</name>
+ <message>
+ <location filename="../pv/mainwindow.cpp" line="69"/>
+ <source>PulseView</source>
+ <translatorcomment>Name</translatorcomment>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../pv/mainwindow.cpp" line="276"/>
+ <source>Decoder Selector</source>
+ <translation>Protokolldekoder</translation>
+ </message>
+ <message>
+ <location filename="../pv/mainwindow.cpp" line="329"/>
+ <source>Session %1</source>
+ <translation>Analysesitzung %1</translation>
+ </message>
+ <message>
+ <location filename="../pv/mainwindow.cpp" line="511"/>
+ <source>Create New Session</source>
+ <translation>Neue Analysesitzung</translation>
+ </message>
+ <message>
+ <location filename="../pv/mainwindow.cpp" line="517"/>
+ <source>Start/Stop Acquisition</source>
+ <translation>Datenerfassung starten/stoppen</translation>
+ </message>
+ <message>
+ <location filename="../pv/mainwindow.cpp" line="525"/>
+ <source>Settings</source>
+ <translation>Einstellungen</translation>
+ </message>
+ <message>
+ <location filename="../pv/mainwindow.cpp" line="577"/>
+ <source>Reload</source>
+ <translation>Neu laden</translation>
+ </message>
+ <message>
+ <location filename="../pv/mainwindow.cpp" line="632"/>
+ <location filename="../pv/mainwindow.cpp" line="826"/>
+ <location filename="../pv/mainwindow.cpp" line="852"/>
+ <source>Confirmation</source>
+ <translation>Bestätigung</translation>
+ </message>
+ <message>
+ <location filename="../pv/mainwindow.cpp" line="633"/>
+ <source>There is unsaved data. Close anyway?</source>
+ <translation>Es gibt noch ungespeicherte Daten. Trotzdem beenden?</translation>
+ </message>
+ <message>
+ <location filename="../pv/mainwindow.cpp" line="577"/>
+ <location filename="../pv/mainwindow.cpp" line="580"/>
+ <source>Run</source>
+ <translation>Starten</translation>
+ </message>
+ <message>
+ <location filename="../pv/mainwindow.cpp" line="586"/>
+ <source>Stop</source>
+ <translation>Stoppen</translation>
+ </message>
+ <message>
+ <location filename="../pv/mainwindow.cpp" line="827"/>
+ <location filename="../pv/mainwindow.cpp" line="853"/>
+ <source>This session contains unsaved data. Close it anyway?</source>
+ <translation>Die Daten dieser Analysesitzung wurden nicht gespeichert. Trotzdem schließen?</translation>
+ </message>
+</context>
+<context>
+ <name>pv::Session</name>
+ <message>
+ <location filename="../pv/session.cpp" line="481"/>
+ <source>Failed to select device</source>
+ <translation>Fehler beim Auswählen des Gerätes</translation>
+ </message>
+ <message>
+ <location filename="../pv/session.cpp" line="530"/>
+ <source>Failed to open device</source>
+ <translation>Fehler beim Öffnen des Gerätes</translation>
+ </message>
+ <message>
+ <location filename="../pv/session.cpp" line="632"/>
+ <source>Error</source>
+ <translation>Fehler</translation>
+ </message>
+ <message>
+ <location filename="../pv/session.cpp" line="633"/>
+ <source>Unexpected input format: %s</source>
+ <translation>Unerwartetes Importformat: %s</translation>
+ </message>
+ <message>
+ <location filename="../pv/session.cpp" line="668"/>
+ <source>Failed to load %1</source>
+ <translation>Fehler beim Laden von %1</translation>
+ </message>
+ <message>
+ <location filename="../pv/session.cpp" line="703"/>
+ <source>No active device set, can't start acquisition.</source>
+ <translation>Kein Gerät aktiv, kann Datenerfassung nicht starten.</translation>
+ </message>
+ <message>
+ <location filename="../pv/session.cpp" line="716"/>
+ <source>No channels enabled.</source>
+ <translation>Keine aktiven Kanäle vorhanden.</translation>
+ </message>
+ <message>
+ <location filename="../pv/session.cpp" line="1171"/>
+ <source>Out of memory, acquisition stopped.</source>
+ <translation>Nicht genügend Arbeitsspeicher vorhanden, Datenerfassung wurde gestoppt.</translation>
+ </message>
+ <message>
+ <location filename="../pv/session.cpp" line="1377"/>
+ <source>Can't handle more than 64 logic channels.</source>
+ <translatorcomment>Internal message</translatorcomment>
+ <translation></translation>
+ </message>
+</context>
+<context>
+ <name>pv::StoreSession</name>
+ <message>
+ <location filename="../pv/storesession.cpp" line="114"/>
+ <source>Can't save logic channel without data.</source>
+ <translation>Kann Logikkanal nicht speichern, da er keine Daten beinhaltet.</translation>
+ </message>
+ <message>
+ <location filename="../pv/storesession.cpp" line="130"/>
+ <source>Can't save analog channel without data.</source>
+ <translation>Kann Analogkanal nicht speichern, da er keine Daten beinhaltet.</translation>
+ </message>
+ <message>
+ <location filename="../pv/storesession.cpp" line="142"/>
+ <source>No channels enabled.</source>
+ <translation>Keine Kanäle aktiviert.</translation>
+ </message>
+ <message>
+ <location filename="../pv/storesession.cpp" line="167"/>
+ <source>Can't save range without sample data.</source>
+ <translation>In dem gewählten Bereich befinden sich keine Daten zum Speichern.</translation>
+ </message>
+ <message>
+ <location filename="../pv/storesession.cpp" line="188"/>
+ <location filename="../pv/storesession.cpp" line="295"/>
+ <source>Error while saving: </source>
+ <translation>Fehler beim Speichern: </translation>
+ </message>
+</context>
+<context>
+ <name>pv::data::DecodeSignal</name>
+ <message>
+ <location filename="../pv/data/decodesignal.cpp" line="194"/>
+ <source>No decoders</source>
+ <translation>Keine Protokolldekoder</translation>
+ </message>
+ <message>
+ <location filename="../pv/data/decodesignal.cpp" line="201"/>
+ <source>There are no channels assigned to this decoder</source>
+ <translation>Dem Protokolldekoder sind keine Kanäle zugeordnet</translation>
+ </message>
+ <message>
+ <location filename="../pv/data/decodesignal.cpp" line="215"/>
+ <source>One or more required channels have not been specified</source>
+ <translation>Mindestens ein notwendiger Kanal wurde noch nicht zugeordnet</translation>
+ </message>
+ <message>
+ <location filename="../pv/data/decodesignal.cpp" line="234"/>
+ <source>No input data</source>
+ <translation>Keine Daten zum Auswerten vorhanden</translation>
+ </message>
+ <message>
+ <location filename="../pv/data/decodesignal.cpp" line="1160"/>
+ <source>Decoder reported an error</source>
+ <translation>Protokolldekoder meldet Fehler</translation>
+ </message>
+ <message>
+ <location filename="../pv/data/decodesignal.cpp" line="1296"/>
+ <source>Failed to create decoder instance</source>
+ <translation>Fehler beim Erzeugen des Protokolldekoders</translation>
+ </message>
+</context>
+<context>
+ <name>pv::data::SignalBase</name>
+ <message>
+ <location filename="../pv/data/signalbase.cpp" line="409"/>
+ <source>Signal average</source>
+ <translation>Durchschnittlicher Signalpegel</translation>
+ </message>
+ <message>
+ <location filename="../pv/data/signalbase.cpp" line="410"/>
+ <source>0.9V (for 1.8V CMOS)</source>
+ <translation>0.9V (für 1.8V CMOS)</translation>
+ </message>
+ <message>
+ <location filename="../pv/data/signalbase.cpp" line="411"/>
+ <source>1.8V (for 3.3V CMOS)</source>
+ <translation>1.8V (für 3.3V CMOS)</translation>
+ </message>
+ <message>
+ <location filename="../pv/data/signalbase.cpp" line="412"/>
+ <source>2.5V (for 5.0V CMOS)</source>
+ <translation>2.5V (für 5.0V CMOS)</translation>
+ </message>
+ <message>
+ <location filename="../pv/data/signalbase.cpp" line="413"/>
+ <source>1.5V (for TTL)</source>
+ <translation>1.5V (für TTL)</translation>
+ </message>
+ <message>
+ <location filename="../pv/data/signalbase.cpp" line="418"/>
+ <source>Signal average +/- 15%</source>
+ <translation>Durchschnittlicher Signalpegel +/- 15%</translation>
+ </message>
+ <message>
+ <location filename="../pv/data/signalbase.cpp" line="419"/>
+ <source>0.3V/1.2V (for 1.8V CMOS)</source>
+ <translation>0.3V/1.2V (für 1.8V CMOS)</translation>
+ </message>
+ <message>
+ <location filename="../pv/data/signalbase.cpp" line="420"/>
+ <source>0.7V/2.5V (for 3.3V CMOS)</source>
+ <translation>0.7V/2.5V (für 3.3V CMOS)</translation>
+ </message>
+ <message>
+ <location filename="../pv/data/signalbase.cpp" line="421"/>
+ <source>1.3V/3.7V (for 5.0V CMOS)</source>
+ <translation>1.3V/3.7V (für 5.0V CMOS)</translation>
+ </message>
+ <message>
+ <location filename="../pv/data/signalbase.cpp" line="422"/>
+ <source>0.8V/2.0V (for TTL)</source>
+ <translation>0.8V/2.0V (für TTL)</translation>
+ </message>
+</context>
+<context>
+ <name>pv::dialogs::Connect</name>
+ <message>
+ <location filename="../pv/dialogs/connect.cpp" line="58"/>
+ <source>&Scan for devices using driver above</source>
+ <translation>Nach Geräten &suchen, die der ausgewählte Treiber ansprechen kann</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/connect.cpp" line="63"/>
+ <source>Connect to Device</source>
+ <translation>Mit Gerät verbinden</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/connect.cpp" line="75"/>
+ <source>Step 1: Choose the driver</source>
+ <translation>Schritt 1: Treiber auswählen</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/connect.cpp" line="79"/>
+ <source>&USB</source>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/connect.cpp" line="80"/>
+ <source>Serial &Port</source>
+ <translation>Serielle Sch&nittstelle</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/connect.cpp" line="81"/>
+ <source>&TCP/IP</source>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/connect.cpp" line="116"/>
+ <source>Protocol:</source>
+ <translation>Protokoll:</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/connect.cpp" line="134"/>
+ <source>Step 2: Choose the interface</source>
+ <translation>Schritt 2: Schnittstelle auswählen</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/connect.cpp" line="140"/>
+ <source>Step 3: Scan for devices</source>
+ <translation>Schritt 3: Nach Geräten suchen</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/connect.cpp" line="146"/>
+ <source>Step 4: Select the device</source>
+ <translation>Schritt 4: Gerät auswählen</translation>
+ </message>
+</context>
+<context>
+ <name>pv::dialogs::Settings</name>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="134"/>
+ <location filename="../pv/dialogs/settings.cpp" line="213"/>
+ <source>General</source>
+ <translation>Allgemein</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="143"/>
+ <source>Views</source>
+ <translation>Ansichten</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="153"/>
+ <location filename="../pv/dialogs/settings.cpp" line="397"/>
+ <source>Decoders</source>
+ <translation>Protokolldekoder</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="163"/>
+ <source>About</source>
+ <translation>Programmdetails</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="172"/>
+ <source>Logging</source>
+ <translation>Programminterne Meldungen</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="236"/>
+ <source>User interface language</source>
+ <translation>Sprache der Benutzeroberfläche</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="247"/>
+ <source>User interface theme</source>
+ <translation>Design der Benutzeroberfläche</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="249"/>
+ <source>(You may need to restart PulseView for all UI elements to update)</source>
+ <translation>(Ein Neustart von PulseView kann notwendig sein, damit alle Bedienelemente das neue Design übernehmen)</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="255"/>
+ <source>System Default</source>
+ <translation>Standard</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="268"/>
+ <source>Qt widget style</source>
+ <translation>Qt-Anzeigestil</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="270"/>
+ <source>(Dark themes look best with the Fusion style)</source>
+ <translation>(Dunkle Designs sehen mit dem Fusion-Stil am besten aus)</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="277"/>
+ <source>Save session &setup along with .sr file</source>
+ <translation>Analyse&sitzungs-Konfiguration zusammen mit .sr-Dateien speichern</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="291"/>
+ <source>Trace View</source>
+ <translation>Signalansicht</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="299"/>
+ <source>Use colored trace &background</source>
+ <translation>Verwende &farbigen Kanalhintergrund</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="303"/>
+ <source>Constantly perform &zoom-to-fit during acquisition</source>
+ <translation>Ständig den &Zoom anpassen, während Daten aufgezeichnet werden</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="307"/>
+ <source>Perform a zoom-to-&fit when acquisition stops</source>
+ <translation>Den Zoom &anpassen, wenn die Datenerfassung stoppt</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="311"/>
+ <source>Show time zero at the trigger</source>
+ <translation>Den Triggerzeitpunkt automatisch als Nullpunkt festlegen</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="315"/>
+ <source>Always keep &newest samples at the right edge during capture</source>
+ <translation>Die neuesten Datenpunkte während der Aufzeichnung immer am rechten &Rand anzeigen</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="319"/>
+ <source>Show data &sampling points</source>
+ <translation>Daten&punkte visuell hervorheben</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="323"/>
+ <source>Fill high areas of logic signals</source>
+ <translation>High-Pegel von Logiksignalen hervorheben</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="330"/>
+ <source>Color to fill high areas of logic signals with</source>
+ <translation>Farbe für hervorgehobene High-Pegel</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="334"/>
+ <source>Show analog minor grid in addition to div grid</source>
+ <translation>Vertikale Unterteilungen nochmals unterteilen</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="338"/>
+ <source>Highlight mouse cursor using a vertical marker line</source>
+ <translation>Position des Mauscursors durch vertikalen Balken hervorheben</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="342"/>
+ <location filename="../pv/dialogs/settings.cpp" line="368"/>
+ <location filename="../pv/dialogs/settings.cpp" line="377"/>
+ <source> pixels</source>
+ <translation> Pixel</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="347"/>
+ <source>Maximum distance from edges before markers snap to them</source>
+ <translation>Abstand zu Signalflanken, bevor Markierer einrasten</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="354"/>
+ <source>Color to fill cursor area with</source>
+ <translation>Farbe für die Auswahl-Markierung</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="357"/>
+ <source>None</source>
+ <translation>Keine</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="358"/>
+ <source>Background</source>
+ <translation>Hintergrundfarbe</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="359"/>
+ <source>Dots</source>
+ <translation>Farbige Abtastpunkte</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="364"/>
+ <source>Conversion threshold display mode (analog traces only)</source>
+ <translation>Darstellung von Konvertierungsschwellen (nur für analoge Kanäle)</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="373"/>
+ <source>Default analog trace div height</source>
+ <translation>Standardgröße von analogen Kanälen</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="382"/>
+ <source>Default logic trace height</source>
+ <translation>Standardgröße von Logikkanälen</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="405"/>
+ <source>Allow configuration of &initial signal state</source>
+ <translation>&Initialzustände von Signalen konfigurierbar machen</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="409"/>
+ <source>Always show all &rows, even if no annotation is visible</source>
+ <translation>Immer alle &Reihen anzeigen, auch wenn hierfür keine dekodierten Werte vorliegen</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="417"/>
+ <source>Annotation export format</source>
+ <translation>Format für zu exportierende Dekodierwerte</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="418"/>
+ <source>%s = sample range; %d: decoder name; %r: row name; %c: class name</source>
+ <translation>%s = Start-/Endsample; %d: Dekodername; %r: Name der Reihe; %c: Klassenname</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="421"/>
+ <source>%1: longest annotation text; %a: all annotation texts; %q: use quotation marks</source>
+ <translation>%1: Längste Beschreibung des dekodierten Wertes; %a: Alle Beschreibungen des dekodierten Wertes; %q: Benutze Anführungszeichen</translation>
+ </message>
+ <message>
+ <source>%s = sample range; %d: decoder name; %r: row name; %q: use quotation marks</source>
+ <translation type="vanished">%s = Start-/Endsample; %d: Dekodername; %c Name der Kategorie; %q: Benutze Anführungszeichen</translation>
+ </message>
+ <message>
+ <source>%s = sample range; %d: decoder name; %c: row name; %q: use quotations marks</source>
+ <translation type="vanished">%s = Start-/Endsample; %d: Dekodername; %c Name der Kategorie; %q: Benutze Anführungszeichen</translation>
+ </message>
+ <message>
+ <source>%1: longest annotation text; %a: all annotation texts</source>
+ <translation type="vanished">%1: Längste Beschreibung des dekodierten Wertes; %a: Alle Beschreibungen des dekodierten Wertes</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="441"/>
+ <source>%1<br /><a href="http://%2">%2</a></source>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="442"/>
+ <source>GNU GPL, version 3 or later</source>
+ <translation>GNU GPL, Version 3 oder neuer</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="453"/>
+ <source>Versions, libraries and features:</source>
+ <translation>Versionen, Bibliotheken und Features:</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="460"/>
+ <source>Firmware search paths:</source>
+ <translation>Suchpfade für Firmware:</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="467"/>
+ <source>Protocol decoder search paths:</source>
+ <translation>Suchpfade für Protokolldekoder:</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="474"/>
+ <source>Supported hardware drivers:</source>
+ <translation>Unterstützte Hardwaretreiber:</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="481"/>
+ <source>Supported input formats:</source>
+ <translation>Unterstützte Importformate:</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="488"/>
+ <source>Supported output formats:</source>
+ <translation>Unterstützte Exportformate:</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="496"/>
+ <source>Supported protocol decoders:</source>
+ <translation>Unterstützte Protokolldekoder:</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="537"/>
+ <source>Log level:</source>
+ <translation>Log-Level:</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="542"/>
+ <source> lines</source>
+ <translation> Zeilen</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="551"/>
+ <source>Length of background buffer:</source>
+ <translation>Länge des Logpuffers:</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="557"/>
+ <source>&Save to File</source>
+ <translation>&Speichern</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="564"/>
+ <source>&Pop out</source>
+ <translation>&Abdocken</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="633"/>
+ <source>You selected a dark theme.
+Should I set the user-adjustable colors to better suit your choice?
+
+Please keep in mind that PulseView may need a restart to display correctly.</source>
+ <translation>Es wurde ein dunkles Design gewählt.
+Sollen die benutzerspezifischen Farben entsprechend angepasst werden, damit sie besser harmonieren?
+
+Bei einer Änderung benötigt PulseView eventuell einen Neustart, damit alles korrekt angezeigt wird.</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="639"/>
+ <source>You selected a bright theme.
+Should I set the user-adjustable colors to better suit your choice?
+
+Please keep in mind that PulseView may need a restart to display correctly.</source>
+ <translation>Es wurde ein helles Design gewählt.
+Sollen die benutzerspezifischen Farben entsprechend angepasst werden, damit sie besser harmonieren?
+
+Bei einer Änderung benötigt PulseView eventuell einen Neustart, damit alles korrekt angezeigt wird.</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="792"/>
+ <source>Save Log</source>
+ <translation>Log speichern</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="792"/>
+ <source>Log Files (*.txt *.log);;All Files (*)</source>
+ <translation>Logdateien (*.txt *.log);;Alle Dateien (*)</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="804"/>
+ <source>Success</source>
+ <translation>Erfolg</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="804"/>
+ <source>Log saved to %1.</source>
+ <translation>Log als %1 gespeichert.</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="814"/>
+ <source>Error</source>
+ <translation>Fehler</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="814"/>
+ <source>File %1 could not be written to.</source>
+ <translation>Konnte Datei %1 nicht speichern.</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/settings.cpp" line="828"/>
+ <source>%1 Log</source>
+ <translation></translation>
+ </message>
+</context>
+<context>
+ <name>pv::dialogs::StoreProgress</name>
+ <message>
+ <location filename="../pv/dialogs/storeprogress.cpp" line="44"/>
+ <source>Saving...</source>
+ <translation>Speichere...</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/storeprogress.cpp" line="44"/>
+ <source>Cancel</source>
+ <translation>Abbrechen</translation>
+ </message>
+ <message>
+ <location filename="../pv/dialogs/storeprogress.cpp" line="85"/>
+ <source>Failed to save session.</source>
+ <translation>Beim Speichern trat ein Fehler auf.</translation>
+ </message>
+</context>
+<context>
+ <name>pv::popups::Channels</name>
+ <message>
+ <location filename="../pv/popups/channels.cpp" line="62"/>
+ <location filename="../pv/popups/channels.cpp" line="63"/>
+ <location filename="../pv/popups/channels.cpp" line="273"/>
+ <location filename="../pv/popups/channels.cpp" line="300"/>
+ <source>All</source>
+ <translation>Alle</translation>
+ </message>
+ <message>
+ <location filename="../pv/popups/channels.cpp" line="64"/>
+ <location filename="../pv/popups/channels.cpp" line="65"/>
+ <source>Logic</source>
+ <translation>Logik</translation>
+ </message>
+ <message>
+ <location filename="../pv/popups/channels.cpp" line="66"/>
+ <location filename="../pv/popups/channels.cpp" line="67"/>
+ <source>Analog</source>
+ <translation>Analog</translation>
+ </message>
+ <message>
+ <location filename="../pv/popups/channels.cpp" line="68"/>
+ <source>Named</source>
+ <translation>Benamte</translation>
+ </message>
+ <message>
+ <location filename="../pv/popups/channels.cpp" line="69"/>
+ <source>Unnamed</source>
+ <translation>Unbenamte</translation>
+ </message>
+ <message>
+ <location filename="../pv/popups/channels.cpp" line="70"/>
+ <source>Changing</source>
+ <translation>Sich ändernde</translation>
+ </message>
+ <message>
+ <location filename="../pv/popups/channels.cpp" line="71"/>
+ <source>Non-changing</source>
+ <translation>Konstante</translation>
+ </message>
+ <message>
+ <location filename="../pv/popups/channels.cpp" line="141"/>
+ <source>Disable: </source>
+ <translation>Deaktivieren: </translation>
+ </message>
+ <message>
+ <location filename="../pv/popups/channels.cpp" line="149"/>
+ <source>Enable: </source>
+ <translation>Aktivieren: </translation>
+ </message>
+ <message>
+ <location filename="../pv/popups/channels.cpp" line="281"/>
+ <location filename="../pv/popups/channels.cpp" line="301"/>
+ <source>None</source>
+ <translation>Keine</translation>
+ </message>
+</context>
+<context>
+ <name>pv::prop::Bool</name>
+ <message>
+ <location filename="../pv/prop/bool.cpp" line="51"/>
+ <location filename="../pv/prop/bool.cpp" line="82"/>
+ <source>Querying config key %1 resulted in %2</source>
+ <translatorcomment>Internal message</translatorcomment>
+ <translation></translation>
+ </message>
+</context>
+<context>
+ <name>pv::prop::Double</name>
+ <message>
+ <location filename="../pv/prop/double.cpp" line="65"/>
+ <location filename="../pv/prop/double.cpp" line="96"/>
+ <source>Querying config key %1 resulted in %2</source>
+ <translatorcomment>Internal message</translatorcomment>
+ <translation></translation>
+ </message>
+</context>
+<context>
+ <name>pv::prop::Enum</name>
+ <message>
+ <location filename="../pv/prop/enum.cpp" line="113"/>
+ <location filename="../pv/prop/enum.cpp" line="176"/>
+ <source>Querying config key %1 resulted in %2</source>
+ <translatorcomment>Internal message</translatorcomment>
+ <translation></translation>
+ </message>
+</context>
+<context>
+ <name>pv::prop::Int</name>
+ <message>
+ <location filename="../pv/prop/int.cpp" line="65"/>
+ <location filename="../pv/prop/int.cpp" line="128"/>
+ <source>Querying config key %1 resulted in %2</source>
+ <translatorcomment>Internal message</translatorcomment>
+ <translation></translation>
+ </message>
+</context>
+<context>
+ <name>pv::prop::String</name>
+ <message>
+ <location filename="../pv/prop/string.cpp" line="59"/>
+ <location filename="../pv/prop/string.cpp" line="84"/>
+ <source>Querying config key %1 resulted in %2</source>
+ <translatorcomment>Internal message</translatorcomment>
+ <translation></translation>
+ </message>
+</context>
+<context>
+ <name>pv::subwindows::decoder_selector::DecoderCollectionModel</name>
+ <message>
+ <location filename="../pv/subwindows/decoder_selector/model.cpp" line="40"/>
+ <source>Decoder</source>
+ <translation>Dekoder</translation>
+ </message>
+ <message>
+ <location filename="../pv/subwindows/decoder_selector/model.cpp" line="41"/>
+ <source>Name</source>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../pv/subwindows/decoder_selector/model.cpp" line="42"/>
+ <source>ID</source>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../pv/subwindows/decoder_selector/model.cpp" line="49"/>
+ <source>All Decoders</source>
+ <translation>Alle Dekoder</translation>
+ </message>
+</context>
+<context>
+ <name>pv::subwindows::decoder_selector::SubWindow</name>
+ <message>
+ <location filename="../pv/subwindows/decoder_selector/subwindow.cpp" line="49"/>
+ <source>Select a decoder to see its description here.</source>
+ <translation>Wähle einen Dekoder, um dessen Beschreibung hier lesen zu können.</translation>
+ </message>
+ <message>
+ <location filename="../pv/subwindows/decoder_selector/subwindow.cpp" line="247"/>
+ <source>, %1</source>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../pv/subwindows/decoder_selector/subwindow.cpp" line="264"/>
+ <source><p align='right'>Tags: %1</p></source>
+ <translation><p align='right'>Stichworte: %1</p></translation>
+ </message>
+ <message>
+ <location filename="../pv/subwindows/decoder_selector/subwindow.cpp" line="311"/>
+ <source>Protocol decoder <b>%1</b> requires input type <b>%2</b> which several decoders provide.<br>Choose which one to use:<br></source>
+ <translation>Protokolldekoder <b>%1</b> benötigt Daten vom Typ <b>%2</b>, die von verschiedenen Protokolldekodern bereitgestellt werden. <br>Wähle, welcher benutzt werden soll:<br></translation>
+ </message>
+ <message>
+ <location filename="../pv/subwindows/decoder_selector/subwindow.cpp" line="319"/>
+ <source>Choose Decoder</source>
+ <translation>Wähle Protokolldekoder</translation>
+ </message>
+</context>
+<context>
+ <name>pv::toolbars::MainBar</name>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="120"/>
+ <source>New &View</source>
+ <translation>Neue &Ansicht</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="126"/>
+ <source>&Open...</source>
+ <translation>&Öffnen...</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="133"/>
+ <source>Restore Session Setu&p...</source>
+ <translation>&Konfiguration der Analysesitzung laden...</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="137"/>
+ <source>&Save As...</source>
+ <translation>&Speichern als...</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="144"/>
+ <source>Save Selected &Range As...</source>
+ <translation>Ausgewählten &Bereich speichern als...</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="151"/>
+ <source>Save Session Setu&p...</source>
+ <translation>&Konfiguration der Analysesitzung speichern...</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="157"/>
+ <source>&Export</source>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="163"/>
+ <source>&Import</source>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="167"/>
+ <source>&Connect to Device...</source>
+ <translation>Mit Gerät &verbinden...</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="228"/>
+ <source>Add protocol decoder</source>
+ <translation>Protokolldekoder hinzufügen</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="244"/>
+ <source>Configure Device</source>
+ <translation>Gerät konfigurieren</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="248"/>
+ <source>Configure Channels</source>
+ <translation>Kanäle konfigurieren</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="357"/>
+ <source>Failed to get sample rate list:</source>
+ <translation>Konnte Liste unterstützter Abtastraten nicht abfragen:</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="420"/>
+ <source>Failed to get sample rate:</source>
+ <translation>Konnte Abtastrate nicht abfragen:</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="461"/>
+ <source>Failed to get sample limit list:</source>
+ <translation>Konnte Liste der maximal erlaubten Abtastraten nicht abfragen:</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="551"/>
+ <source>Failed to configure samplerate:</source>
+ <translation>Konnte Abtastrate nicht einstellen:</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="578"/>
+ <source>Failed to configure sample count:</source>
+ <translation>Konnte Anzahl der Abtastpunkte nicht einstellen:</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="616"/>
+ <source>Missing Cursors</source>
+ <translation>Fehlende Auswahl</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="616"/>
+ <source>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).</source>
+ <translation>Du musst die Auswahl-Markierer setzen, bevor du die darin befindlichen Daten abspeichern kannst. Verwende hierzu bspw. den Knopf für die Auswahl-Markierer.</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="634"/>
+ <source>Invalid Range</source>
+ <translation>Auswahl ungültig</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="634"/>
+ <source>The cursors don't define a valid range of samples.</source>
+ <translation>Die Auswahl-Markierer geben keinen gültigen Datenbereich an.</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="646"/>
+ <source>%1 files </source>
+ <translation>%1-Dateien </translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="654"/>
+ <location filename="../pv/toolbars/mainbar.cpp" line="699"/>
+ <source>All Files</source>
+ <translation>Alle Dateien</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="658"/>
+ <location filename="../pv/toolbars/mainbar.cpp" line="817"/>
+ <source>Save File</source>
+ <translation>Speichern</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="670"/>
+ <source>Export %1</source>
+ <translation>%1 exportieren</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="696"/>
+ <source>%1 files</source>
+ <translation>%1-Dateien</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="707"/>
+ <source>Import File</source>
+ <translation>Dateiimport</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="716"/>
+ <source>Import %1</source>
+ <translation>%1 importieren</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="789"/>
+ <location filename="../pv/toolbars/mainbar.cpp" line="834"/>
+ <source>Open File</source>
+ <translation>Öffnen</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="789"/>
+ <source>sigrok Sessions (*.sr);;All Files (*)</source>
+ <translation>sigrok-Datenformat (*.sr);;Alle Dateien (*)</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="817"/>
+ <location filename="../pv/toolbars/mainbar.cpp" line="834"/>
+ <source>PulseView Session Setups (*.pvs);;All Files (*)</source>
+ <translation>Analysesitzungs-Konfigurationen (*.pvs);;Alle Dateien (*)</translation>
+ </message>
+ <message>
+ <location filename="../pv/toolbars/mainbar.cpp" line="895"/>
+ <source>Total sampling time: %1</source>
+ <translatorcomment>Internal message</translatorcomment>
+ <translation></translation>
+ </message>
+</context>
+<context>
+ <name>pv::views::decoder_binary::View</name>
+ <message>
+ <location filename="../pv/views/decoder_binary/view.cpp" line="83"/>
+ <source>Decoder:</source>
+ <translation>Dekoder:</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/decoder_binary/view.cpp" line="87"/>
+ <source>Show data as</source>
+ <translation>Zeige Daten als</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/decoder_binary/view.cpp" line="93"/>
+ <source>Hexdump</source>
+ <translation>Hex-Dump</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/decoder_binary/view.cpp" line="110"/>
+ <source>&Save...</source>
+ <translation>&Speichern...</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/decoder_binary/view.cpp" line="258"/>
+ <location filename="../pv/views/decoder_binary/view.cpp" line="298"/>
+ <source>Save Binary Data</source>
+ <translation>Binäre Daten speichern</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/decoder_binary/view.cpp" line="258"/>
+ <source>Binary Data Files (*.bin);;All Files (*)</source>
+ <translation>Binärdateien (*.bin);;Alle Dateien (*)</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/decoder_binary/view.cpp" line="277"/>
+ <location filename="../pv/views/decoder_binary/view.cpp" line="329"/>
+ <source>Error</source>
+ <translation>Fehler</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/decoder_binary/view.cpp" line="277"/>
+ <location filename="../pv/views/decoder_binary/view.cpp" line="329"/>
+ <source>File %1 could not be written to.</source>
+ <translation>Konnte Datei %1 nicht speichern.</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/decoder_binary/view.cpp" line="298"/>
+ <source>Hex Dumps (*.txt);;All Files (*)</source>
+ <translation>Hex-Dumps (*.txt);;Alle Dateien (*)</translation>
+ </message>
+</context>
+<context>
+ <name>pv::views::decoder_output::View</name>
+ <message>
+ <source>Decoder:</source>
+ <translation type="vanished">Dekoder:</translation>
+ </message>
+ <message>
+ <source>Show data as</source>
+ <translation type="vanished">Zeige Daten als</translation>
+ </message>
+ <message>
+ <source>Hexdump</source>
+ <translation type="vanished">Hex-Dump</translation>
+ </message>
+ <message>
+ <source>&Save...</source>
+ <translation type="vanished">&Speichern...</translation>
+ </message>
+ <message>
+ <source>Save Binary Data</source>
+ <translation type="vanished">Binäre Daten speichern</translation>
+ </message>
+ <message>
+ <source>Binary Data Files (*.bin);;All Files (*)</source>
+ <translation type="vanished">Binärdateien (*.bin);;Alle Dateien (*)</translation>
+ </message>
+ <message>
+ <source>Error</source>
+ <translation type="vanished">Fehler</translation>
+ </message>
+ <message>
+ <source>File %1 could not be written to.</source>
+ <translation type="vanished">Konnte Datei %1 nicht speichern.</translation>
+ </message>
+ <message>
+ <source>Hex Dumps (*.txt);;All Files (*)</source>
+ <translation type="vanished">Hex-Dumps (*.txt);;Alle Dateien (*)</translation>
+ </message>
+</context>
+<context>
+ <name>pv::views::trace::AnalogSignal</name>
+ <message>
+ <location filename="../pv/views/trace/analogsignal.cpp" line="988"/>
+ <source>Number of pos vertical divs</source>
+ <translation>Anzahl Unterteilungen im Positiven</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/analogsignal.cpp" line="995"/>
+ <source>Number of neg vertical divs</source>
+ <translation>Anzahl Unterteilungen im Negativen</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/analogsignal.cpp" line="1000"/>
+ <source> pixels</source>
+ <translation> Pixel</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/analogsignal.cpp" line="1004"/>
+ <source>Div height</source>
+ <translation>Höhe einer Unterteilung</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/analogsignal.cpp" line="1021"/>
+ <source>V/div</source>
+ <translation>V/div</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/analogsignal.cpp" line="1025"/>
+ <source>Vertical resolution</source>
+ <translation>Vertikale Auflösung</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/analogsignal.cpp" line="1034"/>
+ <source>Autoranging</source>
+ <translation>Automatische Skalierung</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/analogsignal.cpp" line="1039"/>
+ <source>none</source>
+ <translation>keine</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/analogsignal.cpp" line="1041"/>
+ <source>to logic via threshold</source>
+ <translation>zu Logik mittels Schwellwert</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/analogsignal.cpp" line="1043"/>
+ <source>to logic via schmitt-trigger</source>
+ <translation>zu Logik mittels Schmitt-Trigger</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/analogsignal.cpp" line="1049"/>
+ <source>Conversion</source>
+ <translation>Konvertierung</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/analogsignal.cpp" line="1058"/>
+ <source>Conversion threshold(s)</source>
+ <translation>Konvertierungs-Schwellwert(e)</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/analogsignal.cpp" line="1068"/>
+ <source>analog</source>
+ <translation>nur analog</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/analogsignal.cpp" line="1069"/>
+ <source>converted</source>
+ <translation>nur konvertiert</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/analogsignal.cpp" line="1070"/>
+ <source>analog+converted</source>
+ <translation>analog+konvertiert</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/analogsignal.cpp" line="1075"/>
+ <source>Show traces for</source>
+ <translation>Anzuzeigende Signale</translation>
+ </message>
+</context>
+<context>
+ <name>pv::views::trace::Cursor</name>
+ <message>
+ <location filename="../pv/views/trace/cursor.cpp" line="97"/>
+ <source>Disable snapping</source>
+ <translation>Einrasten deaktivieren</translation>
+ </message>
+</context>
+<context>
+ <name>pv::views::trace::CursorPair</name>
+ <message>
+ <location filename="../pv/views/trace/cursorpair.cpp" line="128"/>
+ <source>Display interval</source>
+ <translation>Intervall anzeigen</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/cursorpair.cpp" line="140"/>
+ <source>Display frequency</source>
+ <translation>Frequenz anzeigen</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/cursorpair.cpp" line="152"/>
+ <source>Display samples</source>
+ <translation>Samples anzeigen</translation>
+ </message>
+</context>
+<context>
+ <name>pv::views::trace::DecodeTrace</name>
+ <message>
+ <location filename="../pv/views/trace/decodetrace.cpp" line="448"/>
+ <source><p><i>No decoders in the stack</i></p></source>
+ <translation><p><i>Keine Protokolldekoder vorhanden</i></p></translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/decodetrace.cpp" line="459"/>
+ <source><i>* Required channels</i></source>
+ <translation><i>* Notwendige Kanäle</i></translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/decodetrace.cpp" line="463"/>
+ <source>Stack Decoder</source>
+ <translation>Protokolldekoder stapeln</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/decodetrace.cpp" line="464"/>
+ <source>Stack a higher-level decoder on top of this one</source>
+ <translation>Weiteren Protokolldekoder auf diesen stapeln</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/decodetrace.cpp" line="478"/>
+ <source>Delete</source>
+ <translation>Löschen</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/decodetrace.cpp" line="520"/>
+ <source>Resume decoding</source>
+ <translation>Dekodierung fortsetzen</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/decodetrace.cpp" line="527"/>
+ <source>Pause decoding</source>
+ <translation>Dekodierung anhalten</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/decodetrace.cpp" line="535"/>
+ <source>Copy annotation text to clipboard</source>
+ <translation>Dekodierten Wert in die Zwischenablage kopieren</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/decodetrace.cpp" line="544"/>
+ <source>Export all annotations</source>
+ <translation>Alle dekodierten Werte exportieren</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/decodetrace.cpp" line="551"/>
+ <source>Export all annotations for this row</source>
+ <translation>Alle dekodierten Werte dieser Zeile exportieren</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/decodetrace.cpp" line="560"/>
+ <source>Export all annotations, starting here</source>
+ <translation>Alle dekodierten Werte ab hier exportieren</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/decodetrace.cpp" line="567"/>
+ <source>Export annotations for this row, starting here</source>
+ <translation>Alle dekodierten Werte dieser Zeile ab hier exportieren</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/decodetrace.cpp" line="576"/>
+ <source>Export all annotations within cursor range</source>
+ <translation>Alle dekodierten Werte innerhalb des gewählten Bereiches exportieren</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/decodetrace.cpp" line="583"/>
+ <source>Export annotations for this row within cursor range</source>
+ <translation>Alle dekodierten Werte dieser Zeile innerhalb des gewählten Bereiches exportieren</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/decodetrace.cpp" line="1100"/>
+ <source>%1:
+%2</source>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/decodetrace.cpp" line="1144"/>
+ <source><b>%1</b> (%2) %3</source>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/decodetrace.cpp" line="1214"/>
+ <source>Export annotations</source>
+ <translation>Dekodierte Werte exportieren</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/decodetrace.cpp" line="1214"/>
+ <source>Text Files (*.txt);;All Files (*)</source>
+ <translation>Textdateien (*.txt);;Alle Dateien (*)</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/decodetrace.cpp" line="1279"/>
+ <source>Error</source>
+ <translation>Fehler</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/decodetrace.cpp" line="1279"/>
+ <source>File %1 could not be written to.</source>
+ <translation>Konnte Datei %1 nicht speichern.</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/decodetrace.cpp" line="1332"/>
+ <source>Show this row</source>
+ <translation>Diese Zeile anzeigen</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/decodetrace.cpp" line="1343"/>
+ <source>Show All</source>
+ <translation>Alle anzeigen</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/decodetrace.cpp" line="1351"/>
+ <source>Hide All</source>
+ <translation>Alle verstecken</translation>
+ </message>
+</context>
+<context>
+ <name>pv::views::trace::Flag</name>
+ <message>
+ <location filename="../pv/views/trace/flag.cpp" line="132"/>
+ <source>Text</source>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/flag.cpp" line="141"/>
+ <source>Delete</source>
+ <translation>Löschen</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/flag.cpp" line="146"/>
+ <source>Disable snapping</source>
+ <translation>Einrasten deaktivieren</translation>
+ </message>
+</context>
+<context>
+ <name>pv::views::trace::Header</name>
+ <message>
+ <location filename="../pv/views/trace/header.cpp" line="137"/>
+ <source>Group</source>
+ <translation>Gruppieren</translation>
+ </message>
+</context>
+<context>
+ <name>pv::views::trace::LogicSignal</name>
+ <message>
+ <location filename="../pv/views/trace/logicsignal.cpp" line="451"/>
+ <source>No trigger</source>
+ <translation>Kein Trigger</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/logicsignal.cpp" line="456"/>
+ <source>Trigger on rising edge</source>
+ <translation>Trigger auf steigende Flanke</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/logicsignal.cpp" line="461"/>
+ <source>Trigger on high level</source>
+ <translation>Trigger auf High-Pegel</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/logicsignal.cpp" line="466"/>
+ <source>Trigger on falling edge</source>
+ <translation>Trigger auf fallende Flanke</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/logicsignal.cpp" line="471"/>
+ <source>Trigger on low level</source>
+ <translation>Trigger auf Low-Pegel</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/logicsignal.cpp" line="476"/>
+ <source>Trigger on rising or falling edge</source>
+ <translation>Trigger auf steigende oder fallende Flanke</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/logicsignal.cpp" line="563"/>
+ <source> pixels</source>
+ <translation> Pixel</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/logicsignal.cpp" line="567"/>
+ <source>Trace height</source>
+ <translation>Kanalgröße</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/logicsignal.cpp" line="591"/>
+ <source>Trigger</source>
+ <translation>Trigger</translation>
+ </message>
+</context>
+<context>
+ <name>pv::views::trace::Ruler</name>
+ <message>
+ <location filename="../pv/views/trace/ruler.cpp" line="153"/>
+ <source>Create marker here</source>
+ <translation>Hier neue Markierung anlegen</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/ruler.cpp" line="157"/>
+ <source>Set as zero point</source>
+ <translation>Als Nullpunkt setzen</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/ruler.cpp" line="162"/>
+ <source>Reset zero point</source>
+ <translation>Nullpunkt zurücksetzen</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/ruler.cpp" line="175"/>
+ <source>Disable mouse hover marker</source>
+ <translation>Mauszeigerbalken deaktivieren</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/ruler.cpp" line="175"/>
+ <source>Enable mouse hover marker</source>
+ <translation>Mauszeigerbalken aktivieren</translation>
+ </message>
+</context>
+<context>
+ <name>pv::views::trace::Signal</name>
+ <message>
+ <location filename="../pv/views/trace/signal.cpp" line="148"/>
+ <source>Name</source>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/signal.cpp" line="159"/>
+ <source>Disable</source>
+ <translation>Deaktivieren</translation>
+ </message>
+</context>
+<context>
+ <name>pv::views::trace::StandardBar</name>
+ <message>
+ <location filename="../pv/views/trace/standardbar.cpp" line="54"/>
+ <source>Zoom &In</source>
+ <translation>H&ineinzoomen</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/standardbar.cpp" line="62"/>
+ <source>Zoom &Out</source>
+ <translation>Hera&uszoomen</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/standardbar.cpp" line="70"/>
+ <source>Zoom to &Fit</source>
+ <translation>&Passend zoomen</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/standardbar.cpp" line="82"/>
+ <source>Show &Cursors</source>
+ <translation>&Auswahl-Markierer anzeigen</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/standardbar.cpp" line="85"/>
+ <source>Display last segment only</source>
+ <translation>Nur letztes Segment anzeigen</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/standardbar.cpp" line="90"/>
+ <source>Display last complete segment only</source>
+ <translation>Nur letztes vollständiges Segment anzeigen</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/standardbar.cpp" line="95"/>
+ <source>Display a single segment</source>
+ <translation>Einzelnes Segment anzeigen</translation>
+ </message>
+</context>
+<context>
+ <name>pv::views::trace::TimeMarker</name>
+ <message>
+ <location filename="../pv/views/trace/timemarker.cpp" line="191"/>
+ <source>Time</source>
+ <translation>Zeit</translation>
+ </message>
+</context>
+<context>
+ <name>pv::views::trace::Trace</name>
+ <message>
+ <location filename="../pv/views/trace/trace.cpp" line="206"/>
+ <source>Create marker here</source>
+ <translation>Hier neue Markierung anlegen</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/trace.cpp" line="315"/>
+ <source>Color</source>
+ <translation>Farbe</translation>
+ </message>
+ <message>
+ <location filename="../pv/views/trace/trace.cpp" line="380"/>
+ <source>Name</source>
+ <translation></translation>
+ </message>
+</context>
+<context>
+ <name>pv::views::trace::TraceGroup</name>
+ <message>
+ <location filename="../pv/views/trace/tracegroup.cpp" line="140"/>
+ <source>Ungroup</source>
+ <translation>Trennen</translation>
+ </message>
+</context>
+<context>
+ <name>pv::widgets::DecoderGroupBox</name>
+ <message>
+ <location filename="../pv/widgets/decodergroupbox.cpp" line="48"/>
+ <source>Show/hide this decoder trace</source>
+ <translation>Dekoder anzeigen/verbergen</translation>
+ </message>
+ <message>
+ <location filename="../pv/widgets/decodergroupbox.cpp" line="58"/>
+ <source>Delete this decoder trace</source>
+ <translation>Protokolldekoder entfernen</translation>
+ </message>
+</context>
+<context>
+ <name>pv::widgets::DeviceToolButton</name>
+ <message>
+ <location filename="../pv/widgets/devicetoolbutton.cpp" line="75"/>
+ <location filename="../pv/widgets/devicetoolbutton.cpp" line="82"/>
+ <source><No Device></source>
+ <translation><Kein Gerät></translation>
+ </message>
+</context>
+<context>
+ <name>pv::widgets::ExportMenu</name>
+ <message>
+ <location filename="../pv/widgets/exportmenu.cpp" line="71"/>
+ <source>Export %1...</source>
+ <translation>%1 exportieren...</translation>
+ </message>
+</context>
+<context>
+ <name>pv::widgets::ImportMenu</name>
+ <message>
+ <location filename="../pv/widgets/importmenu.cpp" line="68"/>
+ <source>Import %1...</source>
+ <translation>%1 importieren...</translation>
+ </message>
+</context>
+</TS>
#include <getopt.h>
#include <vector>
+#ifdef ENABLE_FLOW
+#include <gstreamermm.h>
+#include <libsigrokflow/libsigrokflow.hpp>
+#endif
+
#include <libsigrokcxx/libsigrokcxx.hpp>
#include <QCheckBox>
" -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"
+ " -s, --settings Load PulseView session setup from file\n"
" -I, --input-format Input format\n"
" -c, --clean Don't restore previous sessions on startup\n"
"\n", PV_BIN_NAME);
{
int ret = 0;
shared_ptr<sigrok::Context> context;
- string open_file_format, driver;
+ string open_file_format, open_setup_file, driver;
vector<string> open_files;
bool restore_sessions = true;
bool do_scan = true;
bool show_version = false;
+#ifdef ENABLE_FLOW
+ // Initialise gstreamermm. Must be called before any other GLib stuff.
+ Gst::init();
+
+ // Initialize libsigrokflow. Must be called after Gst::init().
+ Srf::init();
+#endif
+
Application a(argc, argv);
#ifdef ANDROID
{"driver", required_argument, nullptr, 'd'},
{"dont-scan", no_argument, nullptr, 'D'},
{"input-file", required_argument, nullptr, 'i'},
+ {"settings", required_argument, nullptr, 's'},
{"input-format", required_argument, nullptr, 'I'},
{"clean", no_argument, nullptr, 'c'},
{"log-to-stdout", no_argument, nullptr, 's'},
};
const int c = getopt_long(argc, argv,
- "h?VDcl:d:i:I:", long_options, nullptr);
+ "h?VDcl:d:i:s:I:", long_options, nullptr);
if (c == -1)
break;
open_files.emplace_back(optarg);
break;
+ case 's':
+ open_setup_file = optarg;
+ break;
+
case 'I':
open_file_format = optarg;
break;
// Prepare the global settings since logging needs them early on
pv::GlobalSettings settings;
+ settings.add_change_handler(&a); // Only the application object can't register itself
settings.save_internal_defaults();
settings.set_defaults_where_needed();
+ settings.apply_language();
settings.apply_theme();
pv::logging.init();
w.add_default_session();
else
for (string& open_file : open_files)
- w.add_session_with_file(open_file, open_file_format);
+ w.add_session_with_file(open_file, open_file_format, open_setup_file);
#ifdef ENABLE_SIGNALS
if (SignalHandler::prepare_signals()) {
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.
+to use is listed, you can just select it here.
image::device_selector_dropdown.png[]
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.
+time scale area. The area between the two boundary lines shows the time distance,
+its inverse (i.e. the frequency) and/or the number of samples encompassed. If there's
+not enough space to see these, you can either zoom in until it shows, hover the mouse
+cursor over the label in the middle or right-click on the label to configure what
+you want to see. You can also move both boundaries at the same time by dragging said
+label.
image::pv_cursors_markers.png[]
You can click on its label and you'll have the option to change its name, or
drag it to reposition it.
+When you have multiple markers, you can have PulseView show you the time difference
+between the markers by hovering over one of them, like so:
+
+image::pv_marker_deltas.png[]
+
+This works on the cursor, too.
+
+Speaking of which - if you want to place or move the cursor ranges quickly, you
+can also press '1' and '2' on your keyboard to attach either side to your mouse
+cursor. They will stay put when you either press Esc or click with the left
+mouse button. This also works when the cursor isn't even showing, so using this
+method allows you to place the cursor quickly without having to enable it first.
+
[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.
+dashed blue line.
=== Special-Purpose Decoders
You can make use of them to examine various properties of the signals that are
of interest to you.
-Their names are:
+Among them are:
* Counter - counts pulses and/or groups of pulses (i.e. words)
* Guess bitrate - guesses the bitrate when using a serial protocol
=== Other Features
+==== Signal Label Area Resizing
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).
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.
+==== Multiple Views
+You can create multiple views by clicking on the "New View" button on the very
+left of the toolbar. These can be rearranged as you wish.
+
+==== Session Saving/Restoring
+When closing PulseView, it automatically saves the sessions you currently have
+open, including the signal configuration and any protocol decoders you might
+have added. The next time you start it again, it'll be restored to its
+previous state.
+
+This metadata is also saved with every .sr file you save so that the next time
+you open the .sr file, your signal configurations, views and decoders are
+restored. These metadata files have the ending .pvs (PulseView Setup) and can
+be edited in any text editor if you wish to change something manually.
+
+Additionally, you can save or load this metadata at any time using the
+save/load buttons.
pulseview -i data.csv -I csv:samplerate=3000000
+If you previously saved a PulseView session setup alongside your input file, PulseView will
+automatically load those settings so long as the setup file (.pvs) has the same base name
+as your input file.
+You can also manually specify a PulseView session setup file to load with -s / --settings.
+Example:
+
+ pulseview -s settings.pvs data.sr
+
The remaining parameters are mostly for debug purposes:
-V / --version Shows the release version
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.
+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.
+In this example, we added the I²C and DS1307 decoders separately. However, when opening
+the decoder selector window, you can also double-click on the DS1307 decoder and PulseView
+will try to auto-resolve the dependencies needed to use this decoder. In case there are
+ambiguities (e.g. when several different protocol decoders offer 'uart' output), it will
+ask you to choose which one to use.
-You can check the https://sigrok.org/wiki/Protocol_decoders[List of Protocol Decoders]
-to see which protocol decoders have been created already.
+For a list of available and planned protocol decoders, you can https://sigrok.org/wiki/Protocol_decoders[check the wiki].
=== Using Decoders on Analog Signals
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.
+=== Per-row Settings and Actions
+
+Sometimes, you don't want to see all protocol decoder rows or all of the annotation classes
+available in a row. To do so, simply click on the arrow or label of the row you want to
+customize.
+
+image::pv_class_selectors.png[]
+
+From that menu, you can either show/hide the entire row or choose the annotation classes
+you want to see. Everything is visible by default but if you want to focus on specific
+protocol messages or status annotations like warnings or errors, this should help.
+
+Also, if you are examining really long traces, disabling annotations for the most-often
+occuring class (e.g. bit annotations for SPI) then drawing performance will increase, too.
+
+=== Binary Decoder Output
+
+While all protocol decoders create visible annotations, some of them also create binary
+output data which isn't immediately visible at the moment. However, you can examine it
+by opening the Binary Decoder Output View as shown below.
+
+image::pv_binary_decoder_output_view.png[]
+
+Once opened, you need to select a decoder with binary output for it to show anything -
+among which are I2C, I2S, EEPROM24xx, SPI and UART. Having acquired some I2S data and
+using the I2S protocol decoder lets you have the sound data as raw .wav file data, for
+example:
+
+image::pv_binary_decoder_output_view_i2s.png[]
+
+Using the save icon at the top then lets you save this data either as a binary file
+(in this case creating a valid .wav file) or various types of hex dumps. If you want to
+only save a certain part of the binary data, simply select that part before saving.
+
+You may have noticed that the bytes are grouped by color somehow. The meaning behind
+this is that every chunk of bytes emitted by the protocol decoder receives one color,
+the next chunk another color and so on. As there are currently three colors, the cycle
+repeats. This makes it easier to visually organize the data that you see - in the case
+of the I2S decoder, the header has one color because it's sent out in one go and
+following that, every sample for left/right consists of 4 bytes with the same color
+since they're sent out one by one.
+
=== Troubleshooting
In case a protocol decoder doesn't provide the expected result, there are several things
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.
+optionally make use of it, others may not be able to interpret the input data since
+timing information may be an essential part of that 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.
+be 4-5 times the rate of the fastest activity in the protocol (e.g. its clock signal).
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
+than the one you set. 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.
+In that case, you can 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,
+debug the decoder (and let us know of the fix) or create 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].
on the left). You are shown several export methods to choose from, with the last one
being only available if the cursor is enabled.
+image::pv_ann_export_menu.png[]
+
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.
+image::pv_ann_export_format.png[]
+
+For example, the string "%s %d: %1" will generate this type of output for the DS1307
+RTC clock protocol decoder: "253-471 DS1307: Read date/time: Sunday, 10.03.2013 23:35:30"
+
=== Creating a Protocol Decoder
Protocol decoders are written in Python and can be created using nothing more than a
_[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
+wget -O sigrok-cross-linux "'https://sigrok.org/gitweb/?p=sigrok-util.git;a=blob_plain;f=cross-compile/linux/sigrok-cross-linux'"
chmod u+x sigrok-cross-linux
./sigrok-cross-linux
export LD_LIBRARY_PATH=~/sr/lib
[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
+wget -O 60-libsigrok.rules "'https://sigrok.org/gitweb/?p=libsigrok.git;a=blob_plain;f=contrib/60-libsigrok.rules'"
+wget -O 61-libsigrok-plugdev.rules "'https://sigrok.org/gitweb/?p=libsigrok.git;a=blob_plain;f=contrib/61-libsigrok-plugdev.rules'"
+wget -O 61-libsigrok-uaccess.rules "'https://sigrok.org/gitweb/?p=libsigrok.git;a=blob_plain;f=contrib/61-libsigrok-uaccess.rules'"
sudo udevadm control --reload-rules
--
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.
+Additionally, 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
<file>icons/document-new.png</file>
<file>icons/document-open.png</file>
<file>icons/document-save-as.png</file>
+ <file>icons/edit-paste.svg</file>
<file>icons/help-browser.png</file>
<file>icons/information.svg</file>
<file>icons/media-playback-pause.png</file>
<file>icons/settings-views.svg</file>
<file>icons/pulseview.png</file>
<file>icons/pulseview.svg</file>
+ <file>icons/search.svg</file>
<file>icons/status-green.svg</file>
<file>icons/status-grey.svg</file>
<file>icons/status-red.svg</file>
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
-#include "application.hpp"
-#include "config.h"
-
#include <iostream>
#include <typeinfo>
#include <QDebug>
+#include <QDir>
+#include <QLibraryInfo>
+#include <QMessageBox>
+#include <QWidget>
#include <boost/version.hpp>
#include <libsigrokdecode/libsigrokdecode.h>
#endif
+#include "application.hpp"
+#include "config.h"
+#include "globalsettings.hpp"
+
using std::cout;
using std::endl;
using std::exception;
setOrganizationDomain("sigrok.org");
}
+QStringList Application::get_languages()
+{
+ QStringList files = QDir(":/l10n/").entryList(QStringList("*.qm"), QDir::Files);
+
+ QStringList result;
+ result << "en"; // Add default language to the set
+
+ // Remove file extensions
+ for (const QString& file : files)
+ result << file.split(".").front();
+
+ result.sort(Qt::CaseInsensitive);
+
+ return result;
+}
+
+void Application::switch_language(const QString& language)
+{
+ removeTranslator(&app_translator_);
+ removeTranslator(&qt_translator_);
+ removeTranslator(&qtbase_translator_);
+
+ if ((language != "C") && (language != "en")) {
+ // Application translations
+ QString resource = ":/l10n/" + language +".qm";
+ if (app_translator_.load(resource))
+ installTranslator(&app_translator_);
+ else
+ qWarning() << "Translation resource" << resource << "not found";
+
+ // Qt translations
+ QString tr_path(QLibraryInfo::location(QLibraryInfo::TranslationsPath));
+
+ if (qt_translator_.load("qt_" + language, tr_path))
+ installTranslator(&qt_translator_);
+ else
+ qWarning() << "QT translations for" << language << "not found at" <<
+ tr_path << ", Qt translations package is probably missing";
+
+ // Qt base translations
+ if (qtbase_translator_.load("qtbase_" + language, tr_path))
+ installTranslator(&qtbase_translator_);
+ else
+ qWarning() << "QT base translations for" << language << "not found at" <<
+ tr_path << ", Qt translations package is probably missing";
+ }
+
+ if (!topLevelWidgets().empty()) {
+ // Force all windows to update
+ for (QWidget *widget : topLevelWidgets())
+ widget->update();
+
+ QMessageBox msg(topLevelWidgets().front());
+ msg.setText(tr("Some parts of the application may still " \
+ "use the previous language. Re-opening the affected windows or " \
+ "restarting the application will remedy this."));
+ msg.setStandardButtons(QMessageBox::Ok);
+ msg.setIcon(QMessageBox::Information);
+ msg.exec();
+ }
+}
+
+void Application::on_setting_changed(const QString &key, const QVariant &value)
+{
+ if (key == pv::GlobalSettings::Key_General_Language)
+ switch_language(value.toString());
+}
+
void Application::collect_version_info(shared_ptr<sigrok::Context> context)
{
// Library versions and features
#include <vector>
#include <QApplication>
+#include <QStringList>
+#include <QTranslator>
#include <libsigrokcxx/libsigrokcxx.hpp>
+#include "globalsettings.hpp"
+
using std::shared_ptr;
using std::pair;
using std::vector;
-class Application : public QApplication
+class Application : public QApplication, public pv::GlobalSettingsInterface
{
Q_OBJECT
public:
Application(int &argc, char* argv[]);
+ QStringList get_languages();
+ void switch_language(const QString& language);
+ void on_setting_changed(const QString &key, const QVariant &value);
+
void collect_version_info(shared_ptr<sigrok::Context> context);
void print_version_info();
vector< pair<QString, QString> > input_format_list_;
vector< pair<QString, QString> > output_format_list_;
vector< pair<QString, QString> > pd_list_;
+
+ QTranslator app_translator_, qt_translator_, qtbase_translator_;
};
#endif // PULSEVIEW_PV_APPLICATION_HPP
help_lbl = new QLabel(p->desc());
help_lbl->setVisible(false);
help_lbl->setWordWrap(true);
+ help_lbl->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
help_labels_[help_btn] = help_lbl;
}
{
assert(decoder_);
- const srd_decoder *const dec = decoder_->decoder();
+ const srd_decoder *const dec = decoder_->get_srd_decoder();
assert(dec);
for (GSList *l = dec->options; l; l = l->next) {
- const srd_decoder_option *const opt =
- (srd_decoder_option*)l->data;
+ const srd_decoder_option *const opt = (srd_decoder_option*)l->data;
const QString name = QString::fromUtf8(opt->desc);
if (iter != options.end())
val = (*iter).second;
else {
- assert(decoder_->decoder());
+ assert(decoder_->get_srd_decoder());
// Get the default value if not
- for (GSList *l = decoder_->decoder()->options; l; l = l->next) {
- const srd_decoder_option *const opt =
- (srd_decoder_option*)l->data;
+ for (GSList *l = decoder_->get_srd_decoder()->options; l; l = l->next) {
+ const srd_decoder_option *const opt = (srd_decoder_option*)l->data;
if (strcmp(opt->id, id) == 0) {
val = opt->def;
break;
#include <cassert>
#include <vector>
-#include "annotation.hpp"
+#include <pv/data/decode/annotation.hpp>
+#include <pv/data/decode/decoder.hpp>
using std::vector;
(const srd_proto_data_annotation*)pdata->data;
assert(pda);
- ann_class_ = (Class)(pda->ann_class);
+ ann_class_id_ = (Class)(pda->ann_class);
+
+ annotations_ = new vector<QString>();
const char *const *annotations = (char**)pda->ann_text;
while (*annotations) {
- annotations_.push_back(QString::fromUtf8(*annotations));
+ annotations_->push_back(QString::fromUtf8(*annotations));
annotations++;
}
+
+ annotations_->shrink_to_fit();
+}
+
+Annotation::Annotation(Annotation&& a) :
+ start_sample_(a.start_sample_),
+ end_sample_(a.end_sample_),
+ annotations_(a.annotations_),
+ row_(a.row_),
+ ann_class_id_(a.ann_class_id_)
+{
+ a.annotations_ = nullptr;
+}
+
+Annotation& Annotation::operator=(Annotation&& a)
+{
+ if (&a != this) {
+ if (annotations_)
+ delete annotations_;
+
+ start_sample_ = a.start_sample_;
+ end_sample_ = a.end_sample_;
+ annotations_ = a.annotations_;
+ row_ = a.row_;
+ ann_class_id_ = a.ann_class_id_;
+
+ a.annotations_ = nullptr;
+ }
+
+ return *this;
+}
+
+Annotation::~Annotation()
+{
+ if (annotations_)
+ delete annotations_;
}
uint64_t Annotation::start_sample() const
return end_sample_;
}
-Annotation::Class Annotation::ann_class() const
+Annotation::Class Annotation::ann_class_id() const
{
- return ann_class_;
+ return ann_class_id_;
+}
+
+const QString Annotation::ann_class_name() const
+{
+ const AnnotationClass* ann_class =
+ row_->decoder()->get_ann_class_by_id(ann_class_id_);
+
+ return QString(ann_class->name);
}
-const vector<QString>& Annotation::annotations() const
+const vector<QString>* Annotation::annotations() const
{
return annotations_;
}
public:
Annotation(const srd_proto_data *const pdata, const Row *row);
+ Annotation(Annotation&& a);
+ Annotation& operator=(Annotation&& a);
+ ~Annotation();
uint64_t start_sample() const;
uint64_t end_sample() const;
- Class ann_class() const;
- const vector<QString>& annotations() const;
+
+ Class ann_class_id() const;
+ const QString ann_class_name() 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_;
- Class ann_class_;
- vector<QString> annotations_;
+ vector<QString>* annotations_;
const Row *row_;
+ Class ann_class_id_;
};
} // namespace decode
#include <pv/data/signalbase.hpp>
#include <pv/data/decodesignal.hpp>
-using pv::data::DecodeChannel;
using std::map;
using std::string;
namespace decode {
Decoder::Decoder(const srd_decoder *const dec) :
- decoder_(dec),
- shown_(true),
+ srd_decoder_(dec),
+ visible_(true),
decoder_inst_(nullptr)
{
+ // Query the annotation output classes
+ uint32_t i = 0;
+ for (GSList *l = dec->annotations; l; l = l->next) {
+ char **ann_class = (char**)l->data;
+ char *name = ann_class[0];
+ char *desc = ann_class[1];
+ ann_classes_.push_back({i++, name, desc, nullptr, true}); // Visible by default
+ }
+
+ // Query the binary output classes
+ i = 0;
+ for (GSList *l = dec->binary; l; l = l->next) {
+ char **bin_class = (char**)l->data;
+ char *name = bin_class[0];
+ char *desc = bin_class[1];
+ bin_classes_.push_back({i++, name, desc});
+ }
+
+ // Query the annotation rows and reference them by the classes that use them
+ uint32_t row_count = 0;
+ for (const GSList *rl = srd_decoder_->annotation_rows; rl; rl = rl->next)
+ row_count++;
+ rows_.reserve(row_count);
+
+ i = 0;
+ for (const GSList *rl = srd_decoder_->annotation_rows; rl; rl = rl->next) {
+ const srd_decoder_annotation_row *const srd_row = (srd_decoder_annotation_row *)rl->data;
+ assert(srd_row);
+ rows_.emplace_back(i++, this, srd_row);
+
+ // FIXME PV can crash from .at() if a PD's ann classes are defined incorrectly
+ for (const GSList *cl = srd_row->ann_classes; cl; cl = cl->next)
+ ann_classes_.at((size_t)cl->data).row = &(rows_.back());
+ }
+
+ if (rows_.empty()) {
+ // Make sure there is a row for PDs without row declarations
+ rows_.emplace_back(0, this);
+
+ for (AnnotationClass& c : ann_classes_)
+ c.row = &(rows_.back());
+ }
}
Decoder::~Decoder()
g_variant_unref(option.second);
}
-const srd_decoder* Decoder::decoder() const
+const srd_decoder* Decoder::get_srd_decoder() const
+{
+ return srd_decoder_;
+}
+
+const char* Decoder::name() const
{
- return decoder_;
+ return srd_decoder_->name;
}
-bool Decoder::shown() const
+bool Decoder::visible() const
{
- return shown_;
+ return visible_;
}
-void Decoder::show(bool show)
+void Decoder::set_visible(bool visible)
{
- shown_ = show;
+ visible_ = visible;
}
const vector<DecodeChannel*>& Decoder::channels() const
if (decoder_inst_)
qDebug() << "WARNING: previous decoder instance" << decoder_inst_ << "exists";
- decoder_inst_ = srd_inst_new(session, decoder_->id, opt_hash);
+ decoder_inst_ = srd_inst_new(session, srd_decoder_->id, opt_hash);
g_hash_table_destroy(opt_hash);
if (!decoder_inst_)
decoder_inst_ = nullptr;
}
+vector<Row*> Decoder::get_rows()
+{
+ vector<Row*> result;
+
+ for (Row& row : rows_)
+ result.push_back(&row);
+
+ return result;
+}
+
+Row* Decoder::get_row_by_id(size_t id)
+{
+ if (id > rows_.size())
+ return nullptr;
+
+ return &(rows_[id]);
+}
+
+vector<const AnnotationClass*> Decoder::ann_classes() const
+{
+ vector<const AnnotationClass*> result;
+
+ for (const AnnotationClass& c : ann_classes_)
+ result.push_back(&c);
+
+ return result;
+}
+
+vector<AnnotationClass*> Decoder::ann_classes()
+{
+ vector<AnnotationClass*> result;
+
+ for (AnnotationClass& c : ann_classes_)
+ result.push_back(&c);
+
+ return result;
+}
+
+AnnotationClass* Decoder::get_ann_class_by_id(size_t id)
+{
+ if (id >= ann_classes_.size())
+ return nullptr;
+
+ return &(ann_classes_[id]);
+}
+
+const AnnotationClass* Decoder::get_ann_class_by_id(size_t id) const
+{
+ if (id >= ann_classes_.size())
+ return nullptr;
+
+ return &(ann_classes_[id]);
+}
+
+uint32_t Decoder::get_binary_class_count() const
+{
+ return bin_classes_.size();
+}
+
+const DecodeBinaryClassInfo* Decoder::get_binary_class(uint32_t id) const
+{
+ return &(bin_classes_.at(id));
+}
+
} // namespace decode
} // namespace data
} // namespace pv
#include <glib.h>
+#include <pv/data/signalbase.hpp>
+#include <pv/data/decode/row.hpp>
+
using std::map;
using std::string;
using std::vector;
namespace data {
-struct DecodeChannel;
class Logic;
class SignalBase;
namespace decode {
+class Decoder;
+
+struct AnnotationClass
+{
+ size_t id;
+ char* name;
+ char* description;
+ Row* row;
+ bool visible;
+};
+
+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<Decoder> decoder_;
+ const srd_channel *pdch_;
+};
+
+struct DecodeBinaryClassInfo
+{
+ uint32_t bin_class_id;
+ char* name;
+ char* description;
+};
+
+
class Decoder
{
public:
virtual ~Decoder();
- const srd_decoder* decoder() const;
+ const srd_decoder* get_srd_decoder() const;
- bool shown() const;
- void show(bool show = true);
+ const char* name() const;
- const vector<data::DecodeChannel*>& channels() const;
- void set_channels(vector<data::DecodeChannel*> channels);
+ bool visible() const;
+ void set_visible(bool visible);
+
+ const vector<DecodeChannel*>& channels() const;
+ void set_channels(vector<DecodeChannel*> channels);
const map<string, GVariant*>& options() const;
srd_decoder_inst* create_decoder_inst(srd_session *session);
void invalidate_decoder_inst();
+ vector<Row*> get_rows();
+ Row* get_row_by_id(size_t id);
+
+ vector<const AnnotationClass*> ann_classes() const;
+ vector<AnnotationClass*> ann_classes();
+ AnnotationClass* get_ann_class_by_id(size_t id);
+ const AnnotationClass* get_ann_class_by_id(size_t id) const;
+
+ uint32_t get_binary_class_count() const;
+ const DecodeBinaryClassInfo* get_binary_class(uint32_t id) const;
+
private:
- const srd_decoder *const decoder_;
+ const srd_decoder* const srd_decoder_;
- bool shown_;
+ bool visible_;
- vector<data::DecodeChannel*> channels_;
+ vector<DecodeChannel*> channels_;
+ vector<Row> rows_;
+ vector<AnnotationClass> ann_classes_;
+ vector<DecodeBinaryClassInfo> bin_classes_;
map<string, GVariant*> options_;
srd_decoder_inst *decoder_inst_;
};
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
+#include <cassert>
+
+#include "decoder.hpp"
#include "row.hpp"
#include <libsigrokdecode/libsigrokdecode.h>
namespace decode {
Row::Row() :
+ index_(0),
decoder_(nullptr),
- row_(nullptr)
+ srd_row_(nullptr),
+ visible_(true)
{
}
-Row::Row(int index, const srd_decoder *decoder, const srd_decoder_annotation_row *row) :
+Row::Row(uint32_t index, Decoder* decoder, const srd_decoder_annotation_row* srd_row) :
index_(index),
decoder_(decoder),
- row_(row)
+ srd_row_(srd_row),
+ visible_(true)
{
}
-const srd_decoder* Row::decoder() const
+const Decoder* Row::decoder() const
{
return decoder_;
}
-const srd_decoder_annotation_row* Row::row() const
+const srd_decoder_annotation_row* Row::get_srd_row() const
{
- return row_;
+ return srd_row_;
}
const QString Row::title() const
{
- if (decoder_ && decoder_->name && row_ && row_->desc)
+ if (decoder_ && decoder_->name() && srd_row_ && srd_row_->desc)
return QString("%1: %2")
- .arg(QString::fromUtf8(decoder_->name),
- QString::fromUtf8(row_->desc));
- if (decoder_ && decoder_->name)
- return QString::fromUtf8(decoder_->name);
- if (row_ && row_->desc)
- return QString::fromUtf8(row_->desc);
+ .arg(QString::fromUtf8(decoder_->name()),
+ QString::fromUtf8(srd_row_->desc));
+ if (decoder_ && decoder_->name())
+ return QString::fromUtf8(decoder_->name());
+ if (srd_row_ && srd_row_->desc)
+ return QString::fromUtf8(srd_row_->desc);
+
return QString();
}
-const QString Row::class_name() const
+const QString Row::description() const
{
- if (row_ && row_->desc)
- return QString::fromUtf8(row_->desc);
+ if (srd_row_ && srd_row_->desc)
+ return QString::fromUtf8(srd_row_->desc);
return QString();
}
-int Row::index() const
+vector<AnnotationClass*> Row::ann_classes() const
+{
+ assert(decoder_);
+
+ vector<AnnotationClass*> result;
+
+ if (!srd_row_) {
+ if (index_ == 0) {
+ // When operating as the fallback row, all annotation classes belong to it
+ return decoder_->ann_classes();
+ }
+ return result;
+ }
+
+ for (GSList *l = srd_row_->ann_classes; l; l = l->next) {
+ size_t class_id = (size_t)l->data;
+ result.push_back(decoder_->get_ann_class_by_id(class_id));
+ }
+
+ return result;
+}
+
+uint32_t Row::index() const
{
return index_;
}
-bool Row::operator<(const Row &other) const
+bool Row::visible() const
+{
+ return visible_;
+}
+
+void Row::set_visible(bool visible)
+{
+ visible_ = visible;
+}
+
+bool Row::has_hidden_classes() const
+{
+ for (const AnnotationClass* c : ann_classes())
+ if (!c->visible)
+ return true;
+
+ return false;
+}
+
+bool Row::operator<(const Row& other) const
{
return (decoder_ < other.decoder_) ||
- (decoder_ == other.decoder_ && row_ < other.row_);
+ (decoder_ == other.decoder_ && srd_row_ < other.srd_row_);
+}
+
+bool Row::operator==(const Row& other) const
+{
+ return ((decoder_ == other.decoder()) && (srd_row_ == other.srd_row_));
}
} // namespace decode
#include <vector>
-#include "annotation.hpp"
+#include <pv/data/decode/annotation.hpp>
+#include <pv/data/decode/decoder.hpp>
struct srd_decoder;
struct srd_decoder_annotation_row;
namespace data {
namespace decode {
+struct AnnotationClass;
+class Decoder;
+
class Row
{
public:
Row();
- Row(int index, const srd_decoder *decoder,
- const srd_decoder_annotation_row *row = nullptr);
+ Row(uint32_t index, Decoder* decoder,
+ const srd_decoder_annotation_row* srd_row = nullptr);
- const srd_decoder* decoder() const;
- const srd_decoder_annotation_row* row() const;
+ const Decoder* decoder() const;
+ const srd_decoder_annotation_row* get_srd_row() const;
const QString title() const;
- const QString class_name() const;
- int index() const;
+ const QString description() const;
+ vector<AnnotationClass*> ann_classes() const;
+ uint32_t index() const;
+
+ bool visible() const;
+ void set_visible(bool visible);
+
+ bool has_hidden_classes() const;
- bool operator<(const Row &other) const;
+ bool operator<(const Row& other) const;
+ bool operator==(const Row& other) const;
private:
- int index_;
- const srd_decoder *decoder_;
- const srd_decoder_annotation_row *row_;
+ uint32_t index_;
+ Decoder* decoder_;
+ const srd_decoder_annotation_row* srd_row_;
+ bool visible_;
};
} // namespace decode
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
-#include "rowdata.hpp"
+#include <cassert>
+
+#include <pv/data/decode/decoder.hpp>
+#include <pv/data/decode/row.hpp>
+#include <pv/data/decode/rowdata.hpp>
using std::vector;
namespace data {
namespace decode {
+RowData::RowData(Row* row) :
+ row_(row),
+ prev_ann_start_sample_(0)
+{
+ assert(row);
+}
+
uint64_t RowData::get_max_sample() const
{
if (annotations_.empty())
return annotations_.back().end_sample();
}
+uint64_t RowData::get_annotation_count() const
+{
+ return annotations_.size();
+}
+
void RowData::get_annotation_subset(
- vector<pv::data::decode::Annotation> &dest,
+ deque<const pv::data::decode::Annotation*> &dest,
uint64_t start_sample, uint64_t end_sample) const
{
- for (const auto& annotation : annotations_)
- if (annotation.end_sample() > start_sample &&
- annotation.start_sample() <= end_sample)
- dest.push_back(annotation);
+ // Determine whether we must apply per-class filtering or not
+ bool all_ann_classes_enabled = true;
+ bool all_ann_classes_disabled = true;
+
+ uint32_t max_ann_class_id = 0;
+ for (AnnotationClass* c : row_->ann_classes()) {
+ if (!c->visible)
+ all_ann_classes_enabled = false;
+ else
+ all_ann_classes_disabled = false;
+ if (c->id > max_ann_class_id)
+ max_ann_class_id = c->id;
+ }
+
+ if (all_ann_classes_enabled) {
+ // No filtering, send everyting out as-is
+ for (const auto& annotation : annotations_)
+ if ((annotation.end_sample() > start_sample) &&
+ (annotation.start_sample() <= end_sample))
+ dest.push_back(&annotation);
+ } else {
+ if (!all_ann_classes_disabled) {
+ // Filter out invisible annotation classes
+ vector<size_t> class_visible;
+ class_visible.resize(max_ann_class_id + 1, 0);
+ for (AnnotationClass* c : row_->ann_classes())
+ if (c->visible)
+ class_visible[c->id] = 1;
+
+ for (const auto& annotation : annotations_)
+ if ((class_visible[annotation.ann_class_id()]) &&
+ (annotation.end_sample() > start_sample) &&
+ (annotation.start_sample() <= end_sample))
+ dest.push_back(&annotation);
+ }
+ }
}
-void RowData::emplace_annotation(srd_proto_data *pdata, const Row *row)
+void RowData::emplace_annotation(srd_proto_data *pdata)
{
- annotations_.emplace_back(pdata, row);
+ // We insert the annotation in a way so that the annotation list
+ // is sorted by start sample. Otherwise, we'd have to sort when
+ // painting, which is expensive
+
+ if (pdata->start_sample < prev_ann_start_sample_) {
+ // Find location to insert the annotation at
+
+ auto it = annotations_.end();
+ do {
+ it--;
+ } while ((it->start_sample() > pdata->start_sample) && (it != annotations_.begin()));
+
+ // Allow inserting at the front
+ if (it != annotations_.begin())
+ it++;
+
+ annotations_.emplace(it, pdata, row_);
+ } else {
+ annotations_.emplace_back(pdata, row_);
+ prev_ann_start_sample_ = pdata->start_sample;
+ }
}
} // namespace decode
#include <libsigrokdecode/libsigrokdecode.h>
-#include "annotation.hpp"
+#include <pv/data/decode/annotation.hpp>
+using std::deque;
using std::vector;
namespace pv {
class RowData
{
public:
- RowData() = default;
+ RowData(Row* row);
-public:
uint64_t get_max_sample() const;
+ uint64_t get_annotation_count() const;
+
/**
* 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,
+ void get_annotation_subset(deque<const pv::data::decode::Annotation*> &dest,
uint64_t start_sample, uint64_t end_sample) const;
- void emplace_annotation(srd_proto_data *pdata, const Row *row);
+ void emplace_annotation(srd_proto_data *pdata);
private:
- vector<Annotation> annotations_;
+ deque<Annotation> annotations_;
+ Row* row_;
+ uint64_t prev_ann_start_sample_;
};
} // namespace decode
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
+#include <cstring>
#include <forward_list>
#include <limits>
#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;
+using pv::data::decode::AnnotationClass;
+using pv::data::decode::DecodeChannel;
namespace pv {
namespace data {
return stack_;
}
-void DecodeSignal::stack_decoder(const srd_decoder *decoder)
+void DecodeSignal::stack_decoder(const srd_decoder *decoder, bool restart_decode)
{
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 or the name is unchanged
+ const srd_decoder* prev_dec = stack_.empty() ? nullptr : stack_.back()->get_srd_decoder();
+ const QString prev_dec_name = prev_dec ? QString::fromUtf8(prev_dec->name) : QString();
- // Set name if this decoder is the first in the list
- if (stack_.size() == 1)
+ if ((stack_.empty()) || ((stack_.size() > 0) && (name() == prev_dec_name)))
set_name(QString::fromUtf8(decoder->name));
+ const shared_ptr<Decoder> dec = make_shared<Decoder>(decoder);
+ stack_.push_back(dec);
+
// 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();
+
+ decoder_stacked((void*)dec.get());
+
+ if (restart_decode)
+ begin_decode();
}
void DecodeSignal::remove_decoder(int index)
for (int i = 0; i < index; i++, iter++)
assert(iter != stack_.end());
+ decoder_removed(iter->get());
+
// Delete the element
stack_.erase(iter);
// Toggle decoder visibility
bool state = false;
if (dec) {
- state = !dec->shown();
- dec->show(state);
+ state = !dec->visible();
+ dec->set_visible(state);
}
return state;
void DecodeSignal::reset_decode(bool shutting_down)
{
+ resume_decode(); // Make sure the decode thread isn't blocked by pausing
+
if (stack_config_changed_ || shutting_down)
stop_srd_session();
else
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();
// 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_)
+ for (decode::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_)
+ for (const shared_ptr<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();
return error_message_;
}
-const vector<data::DecodeChannel> DecodeSignal::get_channels() const
+const vector<decode::DecodeChannel> DecodeSignal::get_channels() const
{
return channels_;
}
bool new_assignment = false;
// Try to auto-select channels that don't have signals assigned yet
- for (data::DecodeChannel& ch : channels_) {
+ for (decode::DecodeChannel& ch : channels_) {
// If a decoder is given, auto-assign only its channels
if (dec && (ch.decoder_ != dec))
continue;
void DecodeSignal::assign_signal(const uint16_t channel_id, const SignalBase *signal)
{
- for (data::DecodeChannel& ch : channels_)
+ for (decode::DecodeChannel& ch : channels_)
if (ch.id == channel_id) {
ch.assigned_signal = signal;
logic_mux_data_invalid_ = true;
{
// Count all channels that have a signal assigned to them
return count_if(channels_.begin(), channels_.end(),
- [](data::DecodeChannel ch) { return ch.assigned_signal; });
+ [](decode::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_)
+ for (decode::DecodeChannel& ch : channels_)
if (ch.id == channel_id)
ch.initial_pin_state = init_state;
int64_t count = std::numeric_limits<int64_t>::max();
bool no_signals_assigned = true;
- for (const data::DecodeChannel& ch : channels_)
+ for (const decode::DecodeChannel& ch : channels_)
if (ch.assigned_signal) {
no_signals_assigned = false;
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
- }
+ if (segment_id >= segments_.size())
+ return result;
+
+ if (include_processing)
+ result = segments_[segment_id].samples_decoded_incl;
+ else
+ result = segments_[segment_id].samples_decoded_excl;
return result;
}
-vector<Row> DecodeSignal::visible_rows() const
+vector<Row*> DecodeSignal::get_rows(bool visible_only)
{
- lock_guard<mutex> lock(output_mutex_);
-
- vector<Row> rows;
+ vector<Row*> rows;
- for (const shared_ptr<decode::Decoder>& dec : stack_) {
+ for (const shared_ptr<Decoder>& dec : stack_) {
assert(dec);
- if (!dec->shown())
+ if (visible_only && !dec->visible())
continue;
- const srd_decoder *const decc = dec->decoder();
- assert(dec->decoder());
+ for (Row* row : dec->get_rows())
+ rows.push_back(row);
+ }
- 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);
+ return rows;
+}
- // 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);
- }
+vector<const Row*> DecodeSignal::get_rows(bool visible_only) const
+{
+ vector<const Row*> rows;
+
+ for (const shared_ptr<Decoder>& dec : stack_) {
+ assert(dec);
+ if (visible_only && !dec->visible())
+ continue;
+
+ for (const Row* row : dec->get_rows())
+ rows.push_back(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 DecodeSignal::get_annotation_count(const Row* row, uint32_t segment_id) const
+{
+ if (segment_id >= segments_.size())
+ return 0;
+
+ const DecodeSegment* segment = &(segments_.at(segment_id));
+
+ auto row_it = segment->annotation_rows.find(row);
+
+ const RowData* rd;
+ if (row_it == segment->annotation_rows.end())
+ return 0;
+ else
+ rd = &(row_it->second);
+
+ return rd->get_annotation_count();
+}
+
+void DecodeSignal::get_annotation_subset(deque<const Annotation*> &dest,
+ const Row* row, uint32_t segment_id, uint64_t start_sample,
uint64_t end_sample) const
{
lock_guard<mutex> lock(output_mutex_);
+ if (segment_id >= segments_.size())
+ return;
+
+ const DecodeSegment* segment = &(segments_.at(segment_id));
+
+ auto row_it = segment->annotation_rows.find(row);
+
+ const RowData* rd;
+ if (row_it == segment->annotation_rows.end())
+ return;
+ else
+ rd = &(row_it->second);
+
+ rd->get_annotation_subset(dest, start_sample, end_sample);
+}
+
+void DecodeSignal::get_annotation_subset(deque<const Annotation*> &dest,
+ uint32_t segment_id, uint64_t start_sample, uint64_t end_sample) const
+{
+ for (const Row* row : get_rows())
+ get_annotation_subset(dest, row, segment_id, start_sample, end_sample);
+}
+
+uint32_t DecodeSignal::get_binary_data_chunk_count(uint32_t segment_id,
+ const Decoder* dec, uint32_t bin_class_id) const
+{
+ if (segments_.size() == 0)
+ return 0;
+
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);
+ for (const DecodeBinaryClass& bc : segment->binary_classes)
+ if ((bc.decoder == dec) && (bc.info->bin_class_id == bin_class_id))
+ return bc.chunks.size();
} catch (out_of_range&) {
// Do nothing
}
+
+ return 0;
}
-void DecodeSignal::get_annotation_subset(
- vector<pv::data::decode::Annotation> &dest,
- uint32_t segment_id, uint64_t start_sample, uint64_t end_sample) const
+void DecodeSignal::get_binary_data_chunk(uint32_t segment_id,
+ const Decoder* dec, uint32_t bin_class_id, uint32_t chunk_id,
+ const vector<uint8_t> **dest, uint64_t *size)
{
- // Note: We put all vectors and lists on the heap, not the stack
+ try {
+ const DecodeSegment *segment = &(segments_.at(segment_id));
- const vector<Row> rows = visible_rows();
+ for (const DecodeBinaryClass& bc : segment->binary_classes)
+ if ((bc.decoder == dec) && (bc.info->bin_class_id == bin_class_id)) {
+ if (dest) *dest = &(bc.chunks.at(chunk_id).data);
+ if (size) *size = bc.chunks.at(chunk_id).data.size();
+ return;
+ }
+ } catch (out_of_range&) {
+ // Do nothing
+ }
+}
- // Use forward_lists for faster merging
- forward_list<Annotation> *all_ann_list = new forward_list<Annotation>();
+void DecodeSignal::get_merged_binary_data_chunks_by_sample(uint32_t segment_id,
+ const Decoder* dec, uint32_t bin_class_id, uint64_t start_sample,
+ uint64_t end_sample, vector<uint8_t> *dest) const
+{
+ assert(dest != nullptr);
- for (const Row& row : rows) {
- vector<Annotation> *ann_vector = new vector<Annotation>();
- get_annotation_subset(*ann_vector, row, segment_id, start_sample, end_sample);
+ try {
+ const DecodeSegment *segment = &(segments_.at(segment_id));
- forward_list<Annotation> *ann_list =
- new forward_list<Annotation>(ann_vector->begin(), ann_vector->end());
- delete ann_vector;
+ const DecodeBinaryClass* bin_class = nullptr;
+ for (const DecodeBinaryClass& bc : segment->binary_classes)
+ if ((bc.decoder == dec) && (bc.info->bin_class_id == bin_class_id))
+ bin_class = &bc;
+
+ // Determine overall size before copying to resize dest vector only once
+ uint64_t size = 0;
+ uint64_t matches = 0;
+ for (const DecodeBinaryDataChunk& chunk : bin_class->chunks)
+ if ((chunk.sample >= start_sample) && (chunk.sample < end_sample)) {
+ size += chunk.data.size();
+ matches++;
+ }
+ dest->resize(size);
+
+ uint64_t offset = 0;
+ uint64_t matches2 = 0;
+ for (const DecodeBinaryDataChunk& chunk : bin_class->chunks)
+ if ((chunk.sample >= start_sample) && (chunk.sample < end_sample)) {
+ memcpy(dest->data() + offset, chunk.data.data(), chunk.data.size());
+ offset += chunk.data.size();
+ matches2++;
+
+ // Make sure we don't overwrite memory if the array grew in the meanwhile
+ if (matches2 == matches)
+ break;
+ }
+ } catch (out_of_range&) {
+ // Do nothing
+ }
+}
+
+void DecodeSignal::get_merged_binary_data_chunks_by_offset(uint32_t segment_id,
+ const Decoder* dec, uint32_t bin_class_id, uint64_t start, uint64_t end,
+ vector<uint8_t> *dest) const
+{
+ assert(dest != nullptr);
- all_ann_list->merge(*ann_list);
- delete ann_list;
+ try {
+ const DecodeSegment *segment = &(segments_.at(segment_id));
+
+ const DecodeBinaryClass* bin_class = nullptr;
+ for (const DecodeBinaryClass& bc : segment->binary_classes)
+ if ((bc.decoder == dec) && (bc.info->bin_class_id == bin_class_id))
+ bin_class = &bc;
+
+ // Determine overall size before copying to resize dest vector only once
+ uint64_t size = 0;
+ uint64_t offset = 0;
+ for (const DecodeBinaryDataChunk& chunk : bin_class->chunks) {
+ if (offset >= start)
+ size += chunk.data.size();
+ offset += chunk.data.size();
+ if (offset >= end)
+ break;
+ }
+ dest->resize(size);
+
+ offset = 0;
+ uint64_t dest_offset = 0;
+ for (const DecodeBinaryDataChunk& chunk : bin_class->chunks) {
+ if (offset >= start) {
+ memcpy(dest->data() + dest_offset, chunk.data.data(), chunk.data.size());
+ dest_offset += chunk.data.size();
+ }
+ offset += chunk.data.size();
+ if (offset >= end)
+ break;
+ }
+ } catch (out_of_range&) {
+ // Do nothing
}
+}
+
+const DecodeBinaryClass* DecodeSignal::get_binary_data_class(uint32_t segment_id,
+ const Decoder* dec, uint32_t bin_class_id) const
+{
+ try {
+ const DecodeSegment *segment = &(segments_.at(segment_id));
- move(all_ann_list->begin(), all_ann_list->end(), back_inserter(dest));
- delete all_ann_list;
+ for (const DecodeBinaryClass& bc : segment->binary_classes)
+ if ((bc.decoder == dec) && (bc.info->bin_class_id == bin_class_id))
+ return &bc;
+ } catch (out_of_range&) {
+ // Do nothing
+ }
+
+ return nullptr;
}
void DecodeSignal::save_settings(QSettings &settings) const
// Save decoder stack
int decoder_idx = 0;
- for (const shared_ptr<decode::Decoder>& decoder : stack_) {
+ for (const shared_ptr<Decoder>& decoder : stack_) {
settings.beginGroup("decoder" + QString::number(decoder_idx++));
- settings.setValue("id", decoder->decoder()->id);
+ settings.setValue("id", decoder->get_srd_decoder()->id);
+ settings.setValue("visible", decoder->visible());
// Save decoder options
const map<string, GVariant*>& options = decoder->options();
settings.setValue("options", (int)options.size());
- // Note: decode::Decoder::options() returns only the options
+ // Note: Decoder::options() returns only the options
// that differ from the default. See binding::Decoder::getter()
int i = 0;
for (auto& option : options) {
i++;
}
+ // Save row properties
+ i = 0;
+ for (const Row* row : decoder->get_rows()) {
+ settings.beginGroup("row" + QString::number(i));
+ settings.setValue("visible", row->visible());
+ settings.endGroup();
+ i++;
+ }
+
+ // Save class properties
+ i = 0;
+ for (const AnnotationClass* ann_class : decoder->ann_classes()) {
+ settings.beginGroup("ann_class" + QString::number(i));
+ settings.setValue("visible", ann_class->visible);
+ settings.endGroup();
+ i++;
+ }
+
settings.endGroup();
}
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; });
+ [&](decode::DecodeChannel ch) { return ch.id == channel_id; });
if (channel == channels_.end()) {
qDebug() << "ERROR: Gap in channel index:" << channel_id;
continue;
if (QString::fromUtf8(dec->id) == id) {
- shared_ptr<decode::Decoder> decoder =
- make_shared<decode::Decoder>(dec);
+ shared_ptr<Decoder> decoder = make_shared<Decoder>(dec);
stack_.push_back(decoder);
+ decoder->set_visible(settings.value("visible", true).toBool());
// Restore decoder options that differ from their default
int options = settings.value("options").toInt();
// Include the newly created decode channels in the channel lists
update_channel_list();
+
+ // Restore row properties
+ int i = 0;
+ for (Row* row : decoder->get_rows()) {
+ settings.beginGroup("row" + QString::number(i));
+ row->set_visible(settings.value("visible", true).toBool());
+ settings.endGroup();
+ i++;
+ }
+
+ // Restore class properties
+ i = 0;
+ for (AnnotationClass* ann_class : decoder->ann_classes()) {
+ settings.beginGroup("ann_class" + QString::number(i));
+ ann_class->visible = settings.value("visible", true).toBool();
+ settings.endGroup();
+ i++;
+ }
+
break;
}
}
// Restore channel mapping
unsigned int channels = settings.value("channels").toInt();
- const unordered_set< shared_ptr<data::SignalBase> > signalbases =
+ const vector< 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; });
+ [&](decode::DecodeChannel ch) { return ch.id == channel_id; });
if (channel == channels_.end()) {
qDebug() << "ERROR: Non-existant channel index:" << channel_id;
uint64_t count = std::numeric_limits<uint64_t>::max();
bool no_signals_assigned = true;
- for (const data::DecodeChannel& ch : channels_)
+ for (const decode::DecodeChannel& ch : channels_)
if (ch.assigned_signal) {
no_signals_assigned = false;
{
double samplerate = 0;
- for (const data::DecodeChannel& ch : channels_)
+ for (const decode::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())
return samplerate;
}
+Decoder* DecodeSignal::get_decoder_by_instance(const srd_decoder *const srd_dec)
+{
+ for (shared_ptr<Decoder>& d : stack_)
+ if (d->get_srd_decoder() == srd_dec)
+ return d.get();
+
+ return nullptr;
+}
+
void DecodeSignal::update_channel_list()
{
- vector<data::DecodeChannel> prev_channels = channels_;
+ vector<decode::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 srd_decoder* srd_dec = decoder->get_srd_decoder();
const GSList *l;
// Mandatory channels
- for (l = srd_d->channels; l; l = l->next) {
+ for (l = srd_dec->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)
+ for (decode::DecodeChannel& ch : prev_channels)
if (ch.pdch_ == pdch) {
ch.id = id++;
channels_.push_back(ch);
if (!ch_added) {
// Create new entry without a mapped signal
- data::DecodeChannel ch = {id++, 0, false, nullptr,
+ decode::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) {
+ for (l = srd_dec->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)
+ for (decode::DecodeChannel& ch : prev_channels)
if (ch.pdch_ == pdch) {
ch.id = id++;
channels_.push_back(ch);
if (!ch_added) {
// Create new entry without a mapped signal
- data::DecodeChannel ch = {id++, 0, true, nullptr,
+ decode::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);
} 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];
+ const decode::DecodeChannel& p_ch = prev_channels[i];
+ const decode::DecodeChannel& ch = channels_[i];
if ((p_ch.pdch_ != ch.pdch_) ||
(p_ch.assigned_signal != ch.assigned_signal)) {
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 (shared_ptr<Decoder> dec : stack_) {
+ vector<decode::DecodeChannel*> channel_list;
- for (data::DecodeChannel& ch : channels_)
+ for (decode::DecodeChannel& ch : channels_)
if (ch.decoder_ == dec)
channel_list.push_back(&ch);
// Channel bit IDs must be in sync with the channel's apperance in channels_
int id = 0;
- for (data::DecodeChannel& ch : channels_)
+ for (decode::DecodeChannel& ch : channels_)
if (ch.assigned_signal)
ch.bit_id = id++;
}
vector<uint8_t> signal_in_bytepos;
vector<uint8_t> signal_in_bitpos;
- for (data::DecodeChannel& ch : channels_)
+ for (decode::DecodeChannel& ch : channels_)
if (ch.assigned_signal) {
const shared_ptr<Logic> logic_data = ch.assigned_signal->logic_data();
if (samplerate)
srd_session_metadata_set(srd_session_, SRD_CONF_SAMPLERATE,
g_variant_new_uint64(samplerate));
- for (const shared_ptr<decode::Decoder>& dec : stack_)
+ for (const shared_ptr<Decoder>& dec : stack_)
dec->apply_all_options();
srd_session_start(srd_session_);
// Create the decoders
srd_decoder_inst *prev_di = nullptr;
- for (const shared_ptr<decode::Decoder>& dec : stack_) {
+ for (const shared_ptr<Decoder>& dec : stack_) {
srd_decoder_inst *const di = dec->create_decoder_inst(srd_session_);
if (!di) {
srd_pd_output_callback_add(srd_session_, SRD_OUTPUT_ANN,
DecodeSignal::annotation_callback, this);
+ srd_pd_output_callback_add(srd_session_, SRD_OUTPUT_BINARY,
+ DecodeSignal::binary_callback, this);
+
srd_session_start(srd_session_);
// We just recreated the srd session, so all stack changes are applied now
if (samplerate)
srd_session_metadata_set(srd_session_, SRD_CONF_SAMPLERATE,
g_variant_new_uint64(samplerate));
- for (const shared_ptr<decode::Decoder>& dec : stack_)
+ for (const shared_ptr<Decoder>& dec : stack_)
dec->apply_all_options();
}
}
srd_session_ = nullptr;
// Mark the decoder instances as non-existant since they were deleted
- for (const shared_ptr<decode::Decoder>& dec : stack_)
+ for (const shared_ptr<Decoder>& dec : stack_)
dec->invalidate_decoder_inst();
}
}
disconnect(this, SLOT(on_data_received()));
// Connect the currently used signals to our slot
- for (data::DecodeChannel& ch : channels_) {
+ for (decode::DecodeChannel& ch : channels_) {
if (!ch.assigned_signal)
continue;
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();
- }
+ for (const shared_ptr<Decoder>& dec : stack_)
+ for (Row* row : dec->get_rows())
+ segments_.back().annotation_rows.emplace(row, RowData(row));
+
+ // Prepare our binary output classes
+ for (const shared_ptr<Decoder>& dec : stack_) {
+ uint32_t n = dec->get_binary_class_count();
+
+ for (uint32_t i = 0; i < n; i++)
+ segments_.back().binary_classes.push_back(
+ {dec.get(), dec->get_binary_class(i), deque<DecodeBinaryDataChunk>()});
}
}
lock_guard<mutex> lock(ds->output_mutex_);
- // Find the row
+ // Get the decoder and the annotation data
assert(pdata->pdo);
assert(pdata->pdo->di);
- const srd_decoder *const decc = pdata->pdo->di->decoder;
- assert(decc);
+ const srd_decoder *const srd_dec = pdata->pdo->di->decoder;
+ assert(srd_dec);
- const srd_proto_data_annotation *const pda =
- (const srd_proto_data_annotation*)pdata->data;
+ 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));
+ // Find the row
+ Decoder* dec = ds->get_decoder_by_instance(srd_dec);
+ assert(dec);
+
+ AnnotationClass* ann_class = dec->get_ann_class_by_id(pda->ann_class);
+ if (!ann_class) {
+ qWarning() << "Decoder" << ds->display_name() << "wanted to add annotation" <<
+ "with class ID" << pda->ann_class << "but there are only" <<
+ dec->ann_classes().size() << "known classes";
+ return;
}
- if (row_iter == ds->segments_.at(ds->current_segment_id_).annotation_rows.end()) {
- qDebug() << "Unexpected annotation: decoder = " << decc <<
- ", format = " << format;
- assert(false);
+ const Row* row = ann_class->row;
+
+ if (!row)
+ row = dec->get_row_by_id(0);
+
+ // Add the annotation
+ ds->segments_[ds->current_segment_id_].annotation_rows.at(row).emplace_annotation(pdata);
+}
+
+void DecodeSignal::binary_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;
+
+ // Get the decoder and the binary data
+ assert(pdata->pdo);
+ assert(pdata->pdo->di);
+ const srd_decoder *const srd_dec = pdata->pdo->di->decoder;
+ assert(srd_dec);
+
+ const srd_proto_data_binary *const pdb = (const srd_proto_data_binary*)pdata->data;
+ assert(pdb);
+
+ // Find the matching DecodeBinaryClass
+ DecodeSegment* segment = &(ds->segments_.at(ds->current_segment_id_));
+
+ DecodeBinaryClass* bin_class = nullptr;
+ for (DecodeBinaryClass& bc : segment->binary_classes)
+ if ((bc.decoder->get_srd_decoder() == srd_dec) &&
+ (bc.info->bin_class_id == (uint32_t)pdb->bin_class))
+ bin_class = &bc;
+
+ if (!bin_class) {
+ qWarning() << "Could not find valid DecodeBinaryClass in segment" <<
+ ds->current_segment_id_ << "for binary class ID" << pdb->bin_class <<
+ ", segment only knows" << segment->binary_classes.size() << "classes";
return;
}
- // Add the annotation
- (*row_iter).second.emplace_annotation(pdata, &((*row_iter).first));
+ // Add the data chunk
+ bin_class->chunks.emplace_back();
+ DecodeBinaryDataChunk* chunk = &(bin_class->chunks.back());
+
+ chunk->sample = pdata->start_sample;
+ chunk->data.resize(pdb->size);
+ memcpy(chunk->data.data(), pdb->data, pdb->size);
+
+ Decoder* dec = ds->get_decoder_by_instance(srd_dec);
+
+ ds->new_binary_data(ds->current_segment_id_, (void*)dec, pdb->bin_class);
}
void DecodeSignal::on_capture_state_changed(int state)
#define PULSEVIEW_PV_DATA_DECODESIGNAL_HPP
#include <atomic>
+#include <deque>
#include <condition_variable>
#include <unordered_set>
#include <vector>
#include <libsigrokdecode/libsigrokdecode.h>
+#include <pv/data/decode/decoder.hpp>
#include <pv/data/decode/row.hpp>
#include <pv/data/decode/rowdata.hpp>
#include <pv/data/signalbase.hpp>
using std::atomic;
using std::condition_variable;
+using std::deque;
using std::map;
using std::mutex;
-using std::pair;
using std::vector;
using std::shared_ptr;
+using pv::data::decode::Annotation;
+using pv::data::decode::DecodeBinaryClassInfo;
+using pv::data::decode::DecodeChannel;
+using pv::data::decode::Decoder;
+using pv::data::decode::Row;
+using pv::data::decode::RowData;
+
namespace pv {
class Session;
namespace data {
-namespace decode {
-class Annotation;
-class Decoder;
-class Row;
-}
-
class Logic;
class LogicSegment;
class SignalBase;
class SignalData;
-struct DecodeChannel
+struct DecodeBinaryDataChunk
+{
+ vector<uint8_t> data;
+ uint64_t sample; ///< Number of the sample where this data was provided by the PD
+};
+
+struct DecodeBinaryClass
{
- 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_;
+ const Decoder* decoder;
+ const DecodeBinaryClassInfo* info;
+ deque<DecodeBinaryDataChunk> chunks;
};
struct DecodeSegment
{
- map<const decode::Row, decode::RowData> annotation_rows;
+ map<const Row*, RowData> annotation_rows;
pv::util::Timestamp start_time;
double samplerate;
int64_t samples_decoded_incl, samples_decoded_excl;
+ vector<DecodeBinaryClass> binary_classes;
};
class DecodeSignal : public SignalBase
virtual ~DecodeSignal();
bool is_decode_signal() const;
- const vector< shared_ptr<data::decode::Decoder> >& decoder_stack() const;
+ const vector< shared_ptr<Decoder> >& decoder_stack() const;
- void stack_decoder(const srd_decoder *decoder);
+ void stack_decoder(const srd_decoder *decoder, bool restart_decode=true);
void remove_decoder(int index);
bool toggle_decoder_visibility(int index);
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);
+ const vector<decode::DecodeChannel> get_channels() const;
+ void auto_assign_signals(const shared_ptr<Decoder> dec);
void assign_signal(const uint16_t channel_id, const SignalBase *signal);
int get_assigned_signal_count() const;
int64_t get_decoded_sample_count(uint32_t segment_id,
bool include_processing) const;
- vector<decode::Row> visible_rows() const;
+ vector<Row*> get_rows(bool visible_only=false);
+ vector<const Row*> get_rows(bool visible_only=false) const;
+
+ uint64_t get_annotation_count(const Row* row, uint32_t segment_id) 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;
+ void get_annotation_subset(deque<const Annotation*> &dest, const 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;
+ void get_annotation_subset(deque<const Annotation*> &dest, uint32_t segment_id,
+ uint64_t start_sample, uint64_t end_sample) const;
+
+ uint32_t get_binary_data_chunk_count(uint32_t segment_id,
+ const Decoder* dec, uint32_t bin_class_id) const;
+ void get_binary_data_chunk(uint32_t segment_id, const Decoder* dec,
+ uint32_t bin_class_id, uint32_t chunk_id, const vector<uint8_t> **dest,
+ uint64_t *size);
+ void get_merged_binary_data_chunks_by_sample(uint32_t segment_id,
+ const Decoder* dec, uint32_t bin_class_id,
+ uint64_t start_sample, uint64_t end_sample,
+ vector<uint8_t> *dest) const;
+ void get_merged_binary_data_chunks_by_offset(uint32_t segment_id,
+ const Decoder* dec, uint32_t bin_class_id,
+ uint64_t start, uint64_t end,
+ vector<uint8_t> *dest) const;
+ const DecodeBinaryClass* get_binary_data_class(uint32_t segment_id,
+ const Decoder* dec, uint32_t bin_class_id) const;
virtual void save_settings(QSettings &settings) const;
void set_error_message(QString msg);
uint32_t get_input_segment_count() const;
-
uint32_t get_input_samplerate(uint32_t segment_id) const;
+ Decoder* get_decoder_by_instance(const srd_decoder *const srd_dec);
+
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 create_decode_segment();
static void annotation_callback(srd_proto_data *pdata, void *decode_signal);
+ static void binary_callback(srd_proto_data *pdata, void *decode_signal);
Q_SIGNALS:
+ void decoder_stacked(void* decoder); ///< decoder is of type decode::Decoder*
+ void decoder_removed(void* decoder); ///< decoder is of type decode::Decoder*
void new_annotations(); // TODO Supply segment for which they belong to
+ void new_binary_data(unsigned int segment_id, void* decoder, unsigned int bin_class_id);
void decode_reset();
void decode_finished();
void channels_updated();
private:
pv::Session &session_;
- vector<data::DecodeChannel> channels_;
+ vector<decode::DecodeChannel> channels_;
struct srd_session *srd_session_;
uint32_t logic_mux_unit_size_;
bool logic_mux_data_invalid_;
- vector< shared_ptr<decode::Decoder> > stack_;
+ vector< shared_ptr<Decoder> > stack_;
bool stack_config_changed_;
- map<pair<const srd_decoder*, int>, decode::Row> class_rows_;
vector<DecodeSegment> segments_;
uint32_t current_segment_id_;
}
void LogicSegment::get_samples(int64_t start_sample,
- int64_t end_sample, uint8_t* dest) const
+ int64_t end_sample, uint8_t* dest) const
{
assert(start_sample >= 0);
assert(start_sample <= (int64_t)sample_count_);
QString SignalBase::display_name() const
{
- if (name() != internal_name_)
+ if ((name() != internal_name_) && (!internal_name_.isEmpty()))
return name() + " (" + internal_name_ + ")";
else
return name();
if (!driver_supported(entry.second))
continue;
- progress->setLabelText(QObject::tr("Scanning for %1...")
+ progress->setLabelText(QObject::tr("Scanning for devices that driver %1 can access...")
.arg(QString::fromStdString(entry.first)));
if (entry.first == user_name)
radiobtn_usb->setChecked(true);
+ serial_config_ = new QWidget();
+ QHBoxLayout *serial_config_layout = new QHBoxLayout(serial_config_);
+
serial_devices_.setEditable(true);
- serial_devices_.setEnabled(false);
+ serial_devices_.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+
+ serial_baudrate_.setEditable(true);
+ serial_baudrate_.addItem("");
+ serial_baudrate_.addItem("921600");
+ serial_baudrate_.addItem("115200");
+ serial_baudrate_.addItem("57600");
+ serial_baudrate_.addItem("19200");
+ serial_baudrate_.addItem("9600");
+
+ serial_config_layout->addWidget(&serial_devices_);
+ serial_config_layout->addWidget(&serial_baudrate_);
+ serial_config_layout->addWidget(new QLabel("baud"));
+ serial_config_->setEnabled(false);
tcp_config_ = new QWidget();
QHBoxLayout *tcp_config_layout = new QHBoxLayout(tcp_config_);
QVBoxLayout *vbox_if = new QVBoxLayout;
vbox_if->addWidget(radiobtn_usb);
vbox_if->addWidget(radiobtn_serial);
- vbox_if->addWidget(&serial_devices_);
+ vbox_if->addWidget(serial_config_);
vbox_if->addWidget(radiobtn_tcp);
vbox_if->addWidget(tcp_config_);
void Connect::serial_toggled(bool checked)
{
serial_devices_.setEnabled(checked);
+ serial_baudrate_.setEnabled(checked);
+ serial_config_->setEnabled(checked);
}
void Connect::tcp_toggled(bool checked)
map<const ConfigKey *, VariantBase> drvopts;
- if (serial_devices_.isEnabled()) {
+ if (serial_config_->isEnabled()) {
QString serial;
const int index = serial_devices_.currentIndex();
if (index >= 0 && index < serial_devices_.count() &&
serial = serial_devices_.itemData(index).toString();
else
serial = serial_devices_.currentText();
+
drvopts[ConfigKey::CONN] = Variant<ustring>::create(
serial.toUtf8().constData());
+
+ // Set baud rate if specified
+ if (serial_baudrate_.currentText().length() > 0)
+ drvopts[ConfigKey::SERIALCOMM] = Variant<ustring>::create(
+ QString("%1/8n1").arg(serial_baudrate_.currentText()).toUtf8().constData());
}
if (tcp_config_->isEnabled()) {
QComboBox drivers_;
- QComboBox serial_devices_;
+ QWidget *serial_config_;
+ QComboBox serial_devices_, serial_baudrate_;
QWidget *tcp_config_;
QLineEdit *tcp_host_;
QWidget *Settings::get_general_settings_form(QWidget *parent) const
{
GlobalSettings settings;
+ QCheckBox *cb;
QWidget *form = new QWidget(parent);
QVBoxLayout *form_layout = new QVBoxLayout(form);
QFormLayout *general_layout = new QFormLayout();
general_group->setLayout(general_layout);
+ // Generate language combobox
+ QComboBox *language_cb = new QComboBox();
+ Application* a = qobject_cast<Application*>(QApplication::instance());
+
+ QString current_language = settings.value(GlobalSettings::Key_General_Language).toString();
+ for (const QString& language : a->get_languages()) {
+ QLocale locale = QLocale(language);
+ QString desc = locale.languageToString(locale.language());
+ language_cb->addItem(desc, language);
+
+ if (language == current_language) {
+ int index = language_cb->findText(desc, Qt::MatchFixedString);
+ language_cb->setCurrentIndex(index);
+ }
+ }
+ connect(language_cb, SIGNAL(currentIndexChanged(const QString&)),
+ this, SLOT(on_general_language_changed(const QString&)));
+ general_layout->addRow(tr("User interface language"), language_cb);
+
+ // Theme combobox
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)));
+ this, SLOT(on_general_theme_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);
+ // Style combobox
QComboBox *style_cb = new QComboBox();
style_cb->addItem(tr("System Default"), "");
for (QString& s : QStyleFactory::keys())
if (current_style.isEmpty())
style_cb->setCurrentIndex(0);
else
- style_cb->setCurrentIndex(style_cb->findText(current_style, 0));
+ style_cb->setCurrentIndex(style_cb->findText(current_style, nullptr));
connect(style_cb, SIGNAL(currentIndexChanged(int)),
this, SLOT(on_general_style_changed(int)));
description_2->setAlignment(Qt::AlignRight);
general_layout->addRow(description_2);
+ // Misc
+ cb = create_checkbox(GlobalSettings::Key_General_SaveWithSetup,
+ SLOT(on_general_save_with_setup_changed(int)));
+ general_layout->addRow(tr("Save session &setup along with .sr file"), cb);
+
return form;
}
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);
+ trace_view_layout->addRow(tr("Maximum distance from edges before markers snap to them"), snap_distance_sb);
ColorButton* cursor_fill_cb = new ColorButton(parent);
cursor_fill_cb->set_color(QColor::fromRgba(
SLOT(on_dec_initialStateConfigurable_changed(int)));
decoder_layout->addRow(tr("Allow configuration of &initial signal state"), cb);
+ cb = create_checkbox(GlobalSettings::Key_Dec_AlwaysShowAllRows,
+ SLOT(on_dec_alwaysshowallrows_changed(int)));
+ decoder_layout->addRow(tr("Always show all &rows, even if no annotation is visible"), cb);
+
// Annotation export settings
ann_export_format_ = new QLineEdit();
ann_export_format_->setText(
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"));
+ QLabel *description_1 = new QLabel(tr("%s = sample range; %d: decoder name; %r: row name; %c: class name"));
description_1->setAlignment(Qt::AlignRight);
decoder_layout->addRow(description_1);
- QLabel *description_2 = new QLabel(tr("%1: longest annotation text; %a: all annotation texts"));
+ QLabel *description_2 = new QLabel(tr("%1: longest annotation text; %a: all annotation texts; %q: use quotation marks"));
description_2->setAlignment(Qt::AlignRight);
decoder_layout->addRow(description_2);
pages->setCurrentIndex(page_list->row(current));
}
-void Settings::on_general_theme_changed_changed(int state)
+void Settings::on_general_language_changed(const QString &text)
{
GlobalSettings settings;
- settings.setValue(GlobalSettings::Key_General_Theme, state);
+ Application* a = qobject_cast<Application*>(QApplication::instance());
+
+ for (const QString& language : a->get_languages()) {
+ QLocale locale = QLocale(language);
+ QString desc = locale.languageToString(locale.language());
+
+ if (text == desc)
+ settings.setValue(GlobalSettings::Key_General_Language, language);
+ }
+}
+
+void Settings::on_general_theme_changed(int value)
+{
+ GlobalSettings settings;
+ settings.setValue(GlobalSettings::Key_General_Theme, value);
settings.apply_theme();
QMessageBox msg(this);
}
}
-void Settings::on_general_style_changed(int state)
+void Settings::on_general_style_changed(int value)
{
GlobalSettings settings;
- if (state == 0)
+ if (value == 0)
settings.setValue(GlobalSettings::Key_General_Style, "");
else
settings.setValue(GlobalSettings::Key_General_Style,
- QStyleFactory::keys().at(state - 1));
+ QStyleFactory::keys().at(value - 1));
settings.apply_theme();
}
+void Settings::on_general_save_with_setup_changed(int state)
+{
+ GlobalSettings settings;
+ settings.setValue(GlobalSettings::Key_General_SaveWithSetup, state ? true : false);
+}
+
void Settings::on_view_zoomToFitDuringAcq_changed(int state)
{
GlobalSettings settings;
GlobalSettings settings;
settings.setValue(GlobalSettings::Key_Dec_ExportFormat, text);
}
+
+void Settings::on_dec_alwaysshowallrows_changed(int state)
+{
+ GlobalSettings settings;
+ settings.setValue(GlobalSettings::Key_Dec_AlwaysShowAllRows, state ? true : false);
+}
#endif
void Settings::on_log_logLevel_changed(int value)
if (out_stream.status() == QTextStream::Ok) {
QMessageBox msg(this);
- msg.setText(tr("Success"));
- msg.setInformativeText(tr("Log saved to %1.").arg(file_name));
+ msg.setText(tr("Success") + "\n\n" + tr("Log saved to %1.").arg(file_name));
msg.setStandardButtons(QMessageBox::Ok);
msg.setIcon(QMessageBox::Information);
msg.exec();
}
QMessageBox msg(this);
- msg.setText(tr("Error"));
- msg.setInformativeText(tr("File %1 could not be written to.").arg(file_name));
+ msg.setText(tr("Error") + "\n\n" + tr("File %1 could not be written to.").arg(file_name));
msg.setStandardButtons(QMessageBox::Ok);
msg.setIcon(QMessageBox::Warning);
msg.exec();
private Q_SLOTS:
void on_page_changed(QListWidgetItem *current, QListWidgetItem *previous);
- void on_general_theme_changed_changed(int state);
- void on_general_style_changed(int state);
+ void on_general_language_changed(const QString &text);
+ void on_general_theme_changed(int value);
+ void on_general_style_changed(int value);
+ void on_general_save_with_setup_changed(int state);
void on_view_zoomToFitDuringAcq_changed(int state);
void on_view_zoomToFitAfterAcq_changed(int state);
void on_view_triggerIsZero_changed(int state);
#ifdef ENABLE_DECODE
void on_dec_initialStateConfigurable_changed(int state);
void on_dec_exportFormat_changed(const QString &text);
+ void on_dec_alwaysshowallrows_changed(int state);
#endif
void on_log_logLevel_changed(int value);
void on_log_bufferSize_changed(int value);
qDebug() << "Error trying to save:" << session_.error();
QMessageBox msg(parentWidget());
- msg.setText(tr("Failed to save session."));
- msg.setInformativeText(session_.error());
+ msg.setText(tr("Failed to save session.") + "\n\n" + session_.error());
msg.setStandardButtons(QMessageBox::Ok);
msg.setIcon(QMessageBox::Warning);
msg.exec();
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
-#include "globalsettings.hpp"
+#include <boost/archive/text_iarchive.hpp>
+#include <boost/archive/text_oarchive.hpp>
+#include <boost/serialization/serialization.hpp>
#include <QApplication>
#include <QColor>
#include <QStyle>
#include <QtGlobal>
+#include "globalsettings.hpp"
+#include "application.hpp"
+
using std::map;
using std::pair;
using std::string;
+using std::stringstream;
using std::vector;
namespace pv {
{"DarkStyle", ":/themes/darkstyle/darkstyle.qss"}
};
+const QString GlobalSettings::Key_General_Language = "General_Language";
const QString GlobalSettings::Key_General_Theme = "General_Theme";
const QString GlobalSettings::Key_General_Style = "General_Style";
+const QString GlobalSettings::Key_General_SaveWithSetup = "General_SaveWithSetup";
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_ShowHoverMarker = "View_ShowHoverMarker";
const QString GlobalSettings::Key_View_SnapDistance = "View_SnapDistance";
const QString GlobalSettings::Key_View_CursorFillColor = "View_CursorFillColor";
+const QString GlobalSettings::Key_View_CursorShowFrequency = "View_CursorShowFrequency";
+const QString GlobalSettings::Key_View_CursorShowInterval = "View_CursorShowInterval";
+const QString GlobalSettings::Key_View_CursorShowSamples = "View_CursorShowSamples";
const QString GlobalSettings::Key_Dec_InitialStateConfigurable = "Dec_InitialStateConfigurable";
const QString GlobalSettings::Key_Dec_ExportFormat = "Dec_ExportFormat";
+const QString GlobalSettings::Key_Dec_AlwaysShowAllRows = "Dec_AlwaysShowAllRows";
const QString GlobalSettings::Key_Log_BufferSize = "Log_BufferSize";
const QString GlobalSettings::Key_Log_NotifyOfStacktrace = "Log_NotifyOfStacktrace";
vector<GlobalSettingsInterface*> GlobalSettings::callbacks_;
bool GlobalSettings::tracking_ = false;
+bool GlobalSettings::is_dark_theme_ = false;
map<QString, QVariant> GlobalSettings::tracked_changes_;
QString GlobalSettings::default_style_;
QPalette GlobalSettings::default_palette_;
GlobalSettings::GlobalSettings() :
- QSettings(),
- is_dark_theme_(false)
+ QSettings()
{
beginGroup("Settings");
}
void GlobalSettings::set_defaults_where_needed()
{
+ if (!contains(Key_General_Language)) {
+ // Determine and set default UI language
+ QString language = QLocale().uiLanguages().first(); // May return e.g. en-Latn-US // clazy:exclude=detaching-temporary
+ language = language.split("-").first();
+
+ setValue(Key_General_Language, language);
+ apply_language();
+ }
+
// Use no theme by default
if (!contains(Key_General_Theme))
setValue(Key_General_Theme, 0);
if (!contains(Key_General_Style))
setValue(Key_General_Style, "");
+ // Save setup with .sr files by default
+ if (!contains(Key_General_SaveWithSetup))
+ setValue(Key_General_SaveWithSetup, true);
+
// Enable zoom-to-fit after acquisition by default
if (!contains(Key_View_ZoomToFitAfterAcq))
setValue(Key_View_ZoomToFitAfterAcq, true);
if (!contains(Key_View_SnapDistance))
setValue(Key_View_SnapDistance, 15);
- if (!contains(Key_Dec_ExportFormat))
- setValue(Key_Dec_ExportFormat, "%s %d: %c: %1");
+ if (!contains(Key_View_CursorShowInterval))
+ setValue(Key_View_CursorShowInterval, true);
+
+ if (!contains(Key_View_CursorShowFrequency))
+ setValue(Key_View_CursorShowFrequency, true);
+
+ // %c was used for the row name in the past so we need to transition such users
+ if (!contains(Key_Dec_ExportFormat) ||
+ value(Key_Dec_ExportFormat).toString() == "%s %d: %c: %1")
+ setValue(Key_Dec_ExportFormat, "%s %d: %r: %1");
// Default to 500 lines of backlog
if (!contains(Key_Log_BufferSize))
QPixmapCache::clear();
}
+void GlobalSettings::apply_language()
+{
+ Application* a = qobject_cast<Application*>(QApplication::instance());
+ a->switch_language(value(Key_General_Language).toString());
+}
+
void GlobalSettings::add_change_handler(GlobalSettingsInterface *cb)
{
callbacks_.push_back(cb);
return ret_val;
}
+void GlobalSettings::store_timestamp(QSettings &settings, const char *name, const pv::util::Timestamp &ts)
+{
+ stringstream ss;
+ boost::archive::text_oarchive oa(ss);
+ oa << boost::serialization::make_nvp(name, ts);
+ settings.setValue(name, QString::fromStdString(ss.str()));
+}
+
+pv::util::Timestamp GlobalSettings::restore_timestamp(QSettings &settings, const char *name)
+{
+ util::Timestamp result;
+ stringstream ss;
+ ss << settings.value(name).toString().toStdString();
+
+ try {
+ boost::archive::text_iarchive ia(ss);
+ ia >> boost::serialization::make_nvp(name, result);
+ } catch (boost::archive::archive_exception&) {
+ qDebug() << "Could not restore setting" << name;
+ }
+
+ return result;
+}
+
} // namespace pv
#include <QString>
#include <QVariant>
+#include "util.hpp"
+
using std::map;
using std::pair;
using std::vector;
Q_OBJECT
public:
+ static const QString Key_General_Language;
static const QString Key_General_Theme;
static const QString Key_General_Style;
+ static const QString Key_General_SaveWithSetup;
static const QString Key_View_ZoomToFitDuringAcq;
static const QString Key_View_ZoomToFitAfterAcq;
static const QString Key_View_TriggerIsZeroTime;
static const QString Key_View_ShowHoverMarker;
static const QString Key_View_SnapDistance;
static const QString Key_View_CursorFillColor;
+ static const QString Key_View_CursorShowInterval;
+ static const QString Key_View_CursorShowFrequency;
+ static const QString Key_View_CursorShowSamples;
static const QString Key_Dec_InitialStateConfigurable;
static const QString Key_Dec_ExportFormat;
+ static const QString Key_Dec_AlwaysShowAllRows;
static const QString Key_Log_BufferSize;
static const QString Key_Log_NotifyOfStacktrace;
void set_bright_theme_default_colors();
void set_dark_theme_default_colors();
- bool current_theme_is_dark();
+ static bool current_theme_is_dark();
void apply_theme();
+ void apply_language();
+
static void add_change_handler(GlobalSettingsInterface *cb);
static void remove_change_handler(GlobalSettingsInterface *cb);
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);
+ static void store_timestamp(QSettings &settings, const char *name, const pv::util::Timestamp &ts);
+ static pv::util::Timestamp restore_timestamp(QSettings &settings, const char *name);
+
private:
static vector<GlobalSettingsInterface*> callbacks_;
static QString default_style_;
static QPalette default_palette_;
- bool is_dark_theme_;
+ static bool is_dark_theme_;
};
} // namespace pv
va_end(args2);
char *text = g_strdup_vprintf(format, args);
- logging.log(QString::fromUtf8(text), LogSource_srd);
+
+ QString s = QString::fromUtf8(text);
+ for (QString& substring : s.split("\n", QString::SkipEmptyParts))
+ logging.log(substring, LogSource_srd);
g_free(text);
return SR_OK;
#include "mainwindow.hpp"
+#include "application.hpp"
#include "devicemanager.hpp"
#include "devices/hardwaredevice.hpp"
#include "dialogs/settings.hpp"
#include "views/trace/view.hpp"
#include "views/trace/standardbar.hpp"
+#ifdef ENABLE_DECODE
+#include "subwindows/decoder_selector/subwindow.hpp"
+#include "views/decoder_binary/view.hpp"
+#endif
+
#include <libsigrokcxx/libsigrokcxx.hpp>
using std::dynamic_pointer_cast;
namespace pv {
-namespace view {
-class ViewItem;
-}
-
using toolbars::MainBar;
const QString MainWindow::WindowTitle = tr("PulseView");
QMainWindow(parent),
device_manager_(device_manager),
session_selector_(this),
- session_state_mapper_(this),
icon_red_(":/icons/status-red.svg"),
icon_green_(":/icons/status-green.svg"),
icon_grey_(":/icons/status-grey.svg")
{
- GlobalSettings::add_change_handler(this);
-
setup_ui();
restore_ui_settings();
}
MainWindow::~MainWindow()
{
- GlobalSettings::remove_change_handler(this);
+ // Make sure we no longer hold any shared pointers to widgets after the
+ // destructor finishes (goes for sessions and sub windows alike)
while (!sessions_.empty())
remove_session(sessions_.front());
+
+ sub_windows_.clear();
}
void MainWindow::show_session_error(const QString text, const QString info_text)
qDebug() << "Notifying user of session error:" << info_text;
QMessageBox msg;
- msg.setText(text);
- msg.setInformativeText(info_text);
+ msg.setText(text + "\n\n" + info_text);
msg.setStandardButtons(QMessageBox::Ok);
msg.setIcon(QMessageBox::Warning);
msg.exec();
return nullptr;
}
-shared_ptr<views::ViewBase> MainWindow::add_view(const QString &title,
- views::ViewType type, Session &session)
+shared_ptr<views::ViewBase> MainWindow::add_view(views::ViewType type,
+ Session &session)
{
GlobalSettings settings;
shared_ptr<views::ViewBase> v;
shared_ptr<MainBar> main_bar = session.main_bar();
+ // Only use the view type in the name if it's not the main view
+ QString title;
+ if (main_bar)
+ title = QString("%1 (%2)").arg(session.name(), views::ViewTypeNames[type]);
+ else
+ title = session.name();
+
QDockWidget* dock = new QDockWidget(title, main_window);
dock->setObjectName(title);
main_window->addDockWidget(Qt::TopDockWidgetArea, dock);
if (type == views::ViewTypeTrace)
// This view will be the main view if there's no main bar yet
- v = make_shared<views::trace::View>(session,
- (main_bar ? false : true), dock_main);
+ v = make_shared<views::trace::View>(session, (main_bar ? false : true), dock_main);
+#ifdef ENABLE_DECODE
+ if (type == views::ViewTypeDecoderBinary)
+ v = make_shared<views::decoder_binary::View>(session, false, dock_main);
+#endif
if (!v)
return nullptr;
views::trace::View *tv =
qobject_cast<views::trace::View*>(v.get());
- 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());
-
if (!main_bar) {
/* Initial view, create the main bar */
main_bar = make_shared<MainBar>(session, this, tv);
dock_main->addToolBar(main_bar.get());
session.set_main_bar(main_bar);
- connect(main_bar.get(), SIGNAL(new_view(Session*)),
- this, SLOT(on_new_view(Session*)));
+ connect(main_bar.get(), SIGNAL(new_view(Session*, int)),
+ this, SLOT(on_new_view(Session*, int)));
+ connect(main_bar.get(), SIGNAL(show_decoder_selector(Session*)),
+ this, SLOT(on_show_decoder_selector(Session*)));
main_bar->action_view_show_cursors()->setChecked(tv->cursors_shown());
}
}
+ v->setFocus();
+
return v;
}
}
}
+shared_ptr<subwindows::SubWindowBase> MainWindow::add_subwindow(
+ subwindows::SubWindowType type, Session &session)
+{
+ GlobalSettings settings;
+ shared_ptr<subwindows::SubWindowBase> w;
+
+ QMainWindow *main_window = nullptr;
+ for (auto& entry : session_windows_)
+ if (entry.first.get() == &session)
+ main_window = entry.second;
+
+ assert(main_window);
+
+ QString title = "";
+
+ switch (type) {
+#ifdef ENABLE_DECODE
+ case subwindows::SubWindowTypeDecoderSelector:
+ title = tr("Decoder Selector");
+ break;
+#endif
+ default:
+ break;
+ }
+
+ QDockWidget* dock = new QDockWidget(title, main_window);
+ dock->setObjectName(title);
+ main_window->addDockWidget(Qt::TopDockWidgetArea, dock);
+
+ // Insert a QMainWindow into the dock widget to allow for a tool bar
+ QMainWindow *dock_main = new QMainWindow(dock);
+ dock_main->setWindowFlags(Qt::Widget); // Remove Qt::Window flag
+
+#ifdef ENABLE_DECODE
+ if (type == subwindows::SubWindowTypeDecoderSelector)
+ w = make_shared<subwindows::decoder_selector::SubWindow>(session, dock_main);
+#endif
+
+ if (!w)
+ return nullptr;
+
+ sub_windows_[dock] = w;
+ dock_main->setCentralWidget(w.get());
+ dock->setWidget(dock_main);
+
+ dock->setContextMenuPolicy(Qt::PreventContextMenu);
+ dock->setFeatures(QDockWidget::DockWidgetMovable |
+ QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable);
+
+ QAbstractButton *close_btn =
+ dock->findChildren<QAbstractButton*> // clazy:exclude=detaching-temporary
+ ("qt_dockwidget_closebutton").front();
+
+ // Allow all subwindows to be closed via ESC.
+ close_btn->setShortcut(QKeySequence(Qt::Key_Escape));
+
+ connect(close_btn, SIGNAL(clicked(bool)),
+ this, SLOT(on_sub_window_close_clicked()));
+
+ if (w->has_toolbar())
+ dock_main->addToolBar(w->create_toolbar(dock_main));
+
+ if (w->minimum_width() > 0)
+ dock->setMinimumSize(w->minimum_width(), 0);
+
+ return w;
+}
+
shared_ptr<Session> MainWindow::add_session()
{
static int last_session_id = 1;
shared_ptr<Session> session = make_shared<Session>(device_manager_, name);
- connect(session.get(), SIGNAL(add_view(const QString&, views::ViewType, Session*)),
- this, SLOT(on_add_view(const QString&, views::ViewType, Session*)));
+ connect(session.get(), SIGNAL(add_view(views::ViewType, Session*)),
+ this, SLOT(on_add_view(views::ViewType, Session*)));
connect(session.get(), SIGNAL(name_changed()),
this, SLOT(on_session_name_changed()));
- session_state_mapper_.setMapping(session.get(), session.get());
+ connect(session.get(), SIGNAL(device_changed()),
+ this, SLOT(on_session_device_changed()));
connect(session.get(), SIGNAL(capture_state_changed(int)),
- &session_state_mapper_, SLOT(map()));
+ this, SLOT(on_session_capture_state_changed(int)));
sessions_.push_back(session);
window->setDockNestingEnabled(true);
- shared_ptr<views::ViewBase> main_view =
- add_view(name, views::ViewTypeTrace, *session);
+ add_view(views::ViewTypeTrace, *session);
return session;
}
}
void MainWindow::add_session_with_file(string open_file_name,
- string open_file_format)
+ string open_file_format, string open_setup_file_name)
{
shared_ptr<Session> session = add_session();
- session->load_init_file(open_file_name, open_file_format);
+ session->load_init_file(open_file_name, open_file_format, open_setup_file_name);
}
void MainWindow::add_default_session()
}
}
-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"));
icon.addFile(QString(":/icons/pulseview.png"));
setWindowIcon(icon);
+ // Set up keyboard shortcuts that affect all views at once
view_sticky_scrolling_shortcut_ = new QShortcut(QKeySequence(Qt::Key_S), this, SLOT(on_view_sticky_scrolling_shortcut()));
view_sticky_scrolling_shortcut_->setAutoRepeat(false);
this, SLOT(on_new_session_clicked()));
connect(run_stop_button_, SIGNAL(clicked(bool)),
this, SLOT(on_run_stop_clicked()));
- connect(&session_state_mapper_, SIGNAL(mapped(QObject*)),
- this, SLOT(on_capture_state_changed(QObject*)));
connect(settings_button_, SIGNAL(clicked(bool)),
this, SLOT(on_settings_clicked()));
this, SLOT(on_focus_changed()));
}
+void MainWindow::update_acq_button(Session *session)
+{
+ int state;
+ QString run_caption;
+
+ if (session) {
+ state = session->get_capture_state();
+ run_caption = session->using_file_device() ? tr("Reload") : tr("Run");
+ } else {
+ state = Session::Stopped;
+ run_caption = tr("Run");
+ }
+
+ const QIcon *icons[] = {&icon_grey_, &icon_red_, &icon_green_};
+ run_stop_button_->setIcon(*icons[state]);
+ run_stop_button_->setText((state == pv::Session::Stopped) ?
+ run_caption : tr("Stop"));
+}
+
void MainWindow::save_ui_settings()
{
QSettings settings;
return false;
}
-void MainWindow::on_add_view(const QString &title, views::ViewType type,
- Session *session)
+void MainWindow::on_add_view(views::ViewType type, Session *session)
{
// We get a pointer and need a reference
for (shared_ptr<Session>& s : sessions_)
if (s.get() == session)
- add_view(title, type, *s);
+ add_view(type, *s);
}
void MainWindow::on_focus_changed()
setWindowTitle(session->name() + " - " + WindowTitle);
// Update the state of the run/stop button, too
- on_capture_state_changed(session.get());
+ update_acq_button(session.get());
}
void MainWindow::on_new_session_clicked()
setWindowTitle(session->name() + " - " + WindowTitle);
}
-void MainWindow::on_capture_state_changed(QObject *obj)
+void MainWindow::on_session_device_changed()
{
- Session *caller = qobject_cast<Session*>(obj);
+ Session *session = qobject_cast<Session*>(QObject::sender());
+ assert(session);
// Ignore if caller is not the currently focused session
// unless there is only one session
- if ((sessions_.size() > 1) && (caller != last_focused_session_.get()))
+ if ((sessions_.size() > 1) && (session != last_focused_session_.get()))
return;
- int state = caller->get_capture_state();
+ update_acq_button(session);
+}
- const QIcon *icons[] = {&icon_grey_, &icon_red_, &icon_green_};
- run_stop_button_->setIcon(*icons[state]);
- run_stop_button_->setText((state == pv::Session::Stopped) ?
- tr("Run") : tr("Stop"));
+void MainWindow::on_session_capture_state_changed(int state)
+{
+ (void)state;
+
+ Session *session = qobject_cast<Session*>(QObject::sender());
+ assert(session);
+
+ // Ignore if caller is not the currently focused session
+ // unless there is only one session
+ if ((sessions_.size() > 1) && (session != last_focused_session_.get()))
+ return;
+
+ update_acq_button(session);
}
-void MainWindow::on_new_view(Session *session)
+void MainWindow::on_new_view(Session *session, int view_type)
{
// We get a pointer and need a reference
for (shared_ptr<Session>& s : sessions_)
if (s.get() == session)
- add_view(session->name(), views::ViewTypeTrace, *s);
+ add_view((views::ViewType)view_type, *s);
}
void MainWindow::on_view_close_clicked()
tr("This session contains unsaved data. Close it anyway?"),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes))
remove_session(session);
+
+ if (sessions_.empty())
+ update_acq_button(nullptr);
+}
+
+void MainWindow::on_show_decoder_selector(Session *session)
+{
+#ifdef ENABLE_DECODE
+ // Close dock widget if it's already showing and return
+ for (auto& entry : sub_windows_) {
+ QDockWidget* dock = entry.first;
+ shared_ptr<subwindows::SubWindowBase> decoder_selector =
+ dynamic_pointer_cast<subwindows::decoder_selector::SubWindow>(entry.second);
+
+ if (decoder_selector && (&decoder_selector->session() == session)) {
+ sub_windows_.erase(dock);
+ dock->close();
+ return;
+ }
+ }
+
+ // We get a pointer and need a reference
+ for (shared_ptr<Session>& s : sessions_)
+ if (s.get() == session)
+ add_subwindow(subwindows::SubWindowTypeDecoderSelector, *s);
+#endif
+}
+
+void MainWindow::on_sub_window_close_clicked()
+{
+ // Find the dock widget that contains the close button that was clicked
+ QObject *w = QObject::sender();
+ QDockWidget *dock = nullptr;
+
+ while (w) {
+ dock = qobject_cast<QDockWidget*>(w);
+ if (dock)
+ break;
+ w = w->parent();
+ }
+
+ sub_windows_.erase(dock);
+ dock->close();
+
+ // Restore focus to the last used main view
+ if (last_focused_session_)
+ last_focused_session_->main_view()->setFocus();
}
void MainWindow::on_view_colored_bg_shortcut()
settings.setValue(GlobalSettings::Key_View_ShowAnalogMinorGrid, !state);
}
-void MainWindow::on_settingViewColoredBg_changed(const QVariant new_value)
-{
- bool state = new_value.toBool();
-
- 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_colored_bg(state);
- }
-}
-
-void MainWindow::on_settingViewShowSamplingPoints_changed(const QVariant new_value)
-{
- bool state = new_value.toBool();
-
- 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_show_sampling_points(state);
- }
-}
-
-void MainWindow::on_settingViewShowAnalogMinorGrid_changed(const QVariant new_value)
-{
- bool state = new_value.toBool();
-
- 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_show_analog_minor_grid(state);
- }
-}
-
void MainWindow::on_close_current_tab()
{
int tab = session_selector_.currentIndex();
#include <QMainWindow>
#include <QShortcut>
-#include <QSignalMapper>
#include <QTabWidget>
#include <QToolButton>
-#include "globalsettings.hpp"
#include "session.hpp"
+#include "subwindows/subwindowbase.hpp"
#include "views/viewbase.hpp"
using std::list;
#endif
}
-class MainWindow : public QMainWindow, public GlobalSettingsInterface
+class MainWindow : public QMainWindow
{
Q_OBJECT
shared_ptr<views::ViewBase> get_active_view() const;
- shared_ptr<views::ViewBase> add_view(const QString &title,
- views::ViewType type, Session &session);
+ shared_ptr<views::ViewBase> add_view(views::ViewType type, Session &session);
void remove_view(shared_ptr<views::ViewBase> view);
+ shared_ptr<subwindows::SubWindowBase> add_subwindow(
+ subwindows::SubWindowType type, Session &session);
+
shared_ptr<Session> add_session();
void remove_session(shared_ptr<Session> session);
- void add_session_with_file(string open_file_name, string open_file_format);
+ void add_session_with_file(string open_file_name, string open_file_format,
+ string open_setup_file_name);
void add_default_session();
void save_sessions();
void restore_sessions();
- void on_setting_changed(const QString &key, const QVariant &value);
-
private:
void setup_ui();
+ void update_acq_button(Session *session);
void save_ui_settings();
void restore_ui_settings();
virtual bool restoreState(const QByteArray &state, int version = 0);
private Q_SLOTS:
- void on_add_view(const QString &title, views::ViewType type,
- Session *session);
+ void on_add_view(views::ViewType type, Session *session);
void on_focus_changed();
void on_focused_session_changed(shared_ptr<Session> session);
void on_settings_clicked();
void on_session_name_changed();
- void on_capture_state_changed(QObject *obj);
+ void on_session_device_changed();
+ void on_session_capture_state_changed(int state);
- void on_new_view(Session *session);
+ void on_new_view(Session *session, int view_type);
void on_view_close_clicked();
void on_tab_changed(int index);
void on_tab_close_requested(int index);
+ void on_show_decoder_selector(Session *session);
+ void on_sub_window_close_clicked();
+
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_settingViewColoredBg_changed(const QVariant new_value);
- void on_settingViewShowSamplingPoints_changed(const QVariant new_value);
- void on_settingViewShowAnalogMinorGrid_changed(const QVariant new_value);
-
void on_close_current_tab();
private:
shared_ptr<Session> last_focused_session_;
map< QDockWidget*, shared_ptr<views::ViewBase> > view_docks_;
+ map< QDockWidget*, shared_ptr<subwindows::SubWindowBase> > sub_windows_;
map< shared_ptr<Session>, QMainWindow*> session_windows_;
QWidget *static_tab_widget_;
QToolButton *new_session_button_, *run_stop_button_, *settings_button_;
QTabWidget session_selector_;
- QSignalMapper session_state_mapper_;
QIcon icon_red_;
QIcon icon_green_;
using std::shared_ptr;
using std::unordered_set;
using std::vector;
+using std::weak_ptr;
using pv::data::SignalBase;
using pv::data::Logic;
if (group)
binding = make_shared<Device>(group);
+ QHBoxLayout* group_layout = new QHBoxLayout();
+ layout_.addRow(group_layout);
+
// Create a title if the group is going to have any content
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_layout->addWidget(label);
group_label_map_[group] = label;
}
// Create the channel group grid
- QGridLayout *const channel_grid = create_channel_group_grid(sigs);
- layout_.addRow(channel_grid);
-
- // Create the channel group options
- if (binding) {
- binding->add_properties_to_form(&layout_, true);
- group_bindings_.push_back(binding);
- }
-}
-
-QGridLayout* Channels::create_channel_group_grid(
- const vector< shared_ptr<SignalBase> > sigs)
-{
int row = 0, col = 0;
QGridLayout *const grid = new QGridLayout();
+ vector<QCheckBox*> group_checkboxes, this_row;
for (const shared_ptr<SignalBase>& sig : sigs) {
assert(sig);
QCheckBox *const checkbox = new QCheckBox(sig->display_name());
check_box_mapper_.setMapping(checkbox, checkbox);
- connect(checkbox, SIGNAL(toggled(bool)),
- &check_box_mapper_, SLOT(map()));
+ connect(checkbox, SIGNAL(toggled(bool)), &check_box_mapper_, SLOT(map()));
grid->addWidget(checkbox, row, col);
+ group_checkboxes.push_back(checkbox);
+ this_row.push_back(checkbox);
+
check_box_signal_map_[checkbox] = sig;
- if (++col >= 8)
+ weak_ptr<SignalBase> weak_sig(sig);
+ connect(checkbox, &QCheckBox::toggled,
+ [weak_sig](bool state) {
+ auto sig = weak_sig.lock();
+ assert(sig);
+ sig->set_enabled(state);
+ });
+
+ if ((++col >= 8 || &sig == &sigs.back())) {
+ // Show buttons if there's more than one row
+ if (sigs.size() > 8) {
+ QPushButton *row_enable_button = new QPushButton(tr("All"), this);
+ grid->addWidget(row_enable_button, row, 8);
+ connect(row_enable_button, &QPushButton::clicked, row_enable_button,
+ [this_row]() {
+ for (QCheckBox *box : this_row)
+ box->setChecked(true);
+ });
+
+ QPushButton *row_disable_button = new QPushButton(tr("None"), this);
+ connect(row_disable_button, &QPushButton::clicked, row_disable_button,
+ [this_row]() {
+ for (QCheckBox *box : this_row)
+ box->setChecked(false);
+ });
+ grid->addWidget(row_disable_button, row, 9);
+ }
+
+ this_row.clear();
col = 0, row++;
+ }
+ }
+ layout_.addRow(grid);
+
+ if (sigs.size() > 1) {
+ // Create enable all/none buttons
+ QPushButton *btn_enable_all, *btn_disable_all;
+
+ btn_enable_all = new QPushButton(tr("All"));
+ btn_disable_all = new QPushButton(tr("None"));
+ group_layout->addWidget(btn_enable_all);
+ group_layout->addWidget(btn_disable_all);
+
+ connect(btn_enable_all, &QPushButton::clicked, btn_enable_all,
+ [group_checkboxes](){
+ for (QCheckBox *box: group_checkboxes)
+ box->setChecked(true);
+ });
+
+ connect(btn_disable_all, &QPushButton::clicked, btn_disable_all,
+ [group_checkboxes](){
+ for (QCheckBox *box: group_checkboxes)
+ box->setChecked(false);
+ });
}
- return grid;
+ // Create the channel group options
+ if (binding) {
+ binding->add_properties_to_form(&layout_, true);
+ group_bindings_.push_back(binding);
+ }
}
void Channels::showEvent(QShowEvent *event)
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);
-
void showEvent(QShowEvent *event);
private Q_SLOTS:
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
-#include <QDebug>
-#include <QFileInfo>
-
#include <cassert>
#include <memory>
#include <mutex>
#include <sys/stat.h>
+#include <QDebug>
+#include <QFileInfo>
+
#include "devicemanager.hpp"
#include "mainwindow.hpp"
#include "session.hpp"
+#include "util.hpp"
#include "data/analog.hpp"
#include "data/analogsegment.hpp"
#include <libsigrokcxx/libsigrokcxx.hpp>
+#ifdef ENABLE_FLOW
+#include <gstreamermm.h>
+#include <libsigrokflow/libsigrokflow.hpp>
+#endif
+
#ifdef ENABLE_DECODE
#include <libsigrokdecode/libsigrokdecode.h>
#include "data/decodesignal.hpp"
using std::dynamic_pointer_cast;
using std::find_if;
using std::function;
-using std::lock_guard;
using std::list;
+using std::lock_guard;
using std::make_pair;
using std::make_shared;
using std::map;
using std::runtime_error;
using std::shared_ptr;
using std::string;
+#ifdef ENABLE_FLOW
+using std::unique_lock;
+#endif
using std::unique_ptr;
-using std::unordered_set;
using std::vector;
using sigrok::Analog;
using Glib::VariantBase;
+#ifdef ENABLE_FLOW
+using Gst::Bus;
+using Gst::ElementFactory;
+using Gst::Pipeline;
+#endif
+
+using pv::util::Timestamp;
+using pv::views::trace::Signal;
+using pv::views::trace::AnalogSignal;
+using pv::views::trace::LogicSignal;
+
namespace pv {
shared_ptr<sigrok::Context> Session::sr_context;
name_changed();
}
-const list< shared_ptr<views::ViewBase> > Session::views() const
+const vector< shared_ptr<views::ViewBase> > Session::views() const
{
return views_;
}
return data_saved_;
}
+void Session::save_setup(QSettings &settings) const
+{
+ int i = 0;
+
+ // Save channels and decoders
+ for (const shared_ptr<data::SignalBase>& base : signalbases_) {
+#ifdef ENABLE_DECODE
+ if (base->is_decode_signal()) {
+ settings.beginGroup("decode_signal" + QString::number(i++));
+ base->save_settings(settings);
+ settings.endGroup();
+ } else
+#endif
+ {
+ settings.beginGroup(base->internal_name());
+ base->save_settings(settings);
+ settings.endGroup();
+ }
+ }
+
+ settings.setValue("decode_signals", i);
+
+ // Save view states and their signal settings
+ // Note: main_view must be saved as view0
+ i = 0;
+ settings.beginGroup("view" + QString::number(i++));
+ main_view_->save_settings(settings);
+ settings.endGroup();
+
+ for (const shared_ptr<views::ViewBase>& view : views_) {
+ if (view != main_view_) {
+ settings.beginGroup("view" + QString::number(i++));
+ settings.setValue("type", view->get_type());
+ view->save_settings(settings);
+ settings.endGroup();
+ }
+ }
+
+ settings.setValue("views", i);
+
+ int view_id = 0;
+ i = 0;
+ for (const shared_ptr<views::ViewBase>& vb : views_) {
+ shared_ptr<views::trace::View> tv = dynamic_pointer_cast<views::trace::View>(vb);
+ if (tv) {
+ for (const shared_ptr<views::trace::TimeItem>& time_item : tv->time_items()) {
+
+ const shared_ptr<views::trace::Flag> flag =
+ dynamic_pointer_cast<views::trace::Flag>(time_item);
+ if (flag) {
+ if (!flag->enabled())
+ continue;
+
+ settings.beginGroup("meta_obj" + QString::number(i++));
+ settings.setValue("type", "time_marker");
+ settings.setValue("assoc_view", view_id);
+ GlobalSettings::store_timestamp(settings, "time", flag->time());
+ settings.setValue("text", flag->get_text());
+ settings.endGroup();
+ }
+ }
+
+ if (tv->cursors_shown()) {
+ settings.beginGroup("meta_obj" + QString::number(i++));
+ settings.setValue("type", "selection");
+ settings.setValue("assoc_view", view_id);
+ const shared_ptr<views::trace::CursorPair> cp = tv->cursors();
+ GlobalSettings::store_timestamp(settings, "start_time", cp->first()->time());
+ GlobalSettings::store_timestamp(settings, "end_time", cp->second()->time());
+ settings.endGroup();
+ }
+ }
+
+ view_id++;
+ }
+
+ settings.setValue("meta_objs", i);
+}
+
void Session::save_settings(QSettings &settings) const
{
map<string, string> dev_info;
list<string> key_list;
- int decode_signals = 0, views = 0;
if (device_) {
shared_ptr<devices::HardwareDevice> hw_device =
settings.endGroup();
}
- // Save channels and decoders
- for (const shared_ptr<data::SignalBase>& base : signalbases_) {
+ save_setup(settings);
+ }
+}
+
+void Session::restore_setup(QSettings &settings)
+{
+ // Restore channels
+ for (shared_ptr<data::SignalBase> base : signalbases_) {
+ settings.beginGroup(base->internal_name());
+ base->restore_settings(settings);
+ settings.endGroup();
+ }
+
+ // Restore decoders
#ifdef ENABLE_DECODE
- if (base->is_decode_signal()) {
- settings.beginGroup("decode_signal" + QString::number(decode_signals++));
- base->save_settings(settings);
- settings.endGroup();
- } else
+ 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
- {
- settings.beginGroup(base->internal_name());
- base->save_settings(settings);
- settings.endGroup();
- }
- }
- settings.setValue("decode_signals", decode_signals);
+ // Restore views
+ int views = settings.value("views").toInt();
+
+ for (int i = 0; i < views; i++) {
+ settings.beginGroup("view" + QString::number(i));
+
+ if (i > 0) {
+ views::ViewType type = (views::ViewType)settings.value("type").toInt();
+ add_view(type, this);
+ views_.back()->restore_settings(settings);
+ } else
+ main_view_->restore_settings(settings);
- // Save view states and their signal settings
- // Note: main_view must be saved as view0
- settings.beginGroup("view" + QString::number(views++));
- main_view_->save_settings(settings);
settings.endGroup();
+ }
- for (const shared_ptr<views::ViewBase>& view : views_) {
- if (view != main_view_) {
- settings.beginGroup("view" + QString::number(views++));
- view->save_settings(settings);
- settings.endGroup();
- }
+ // Restore meta objects like markers and cursors
+ int meta_objs = settings.value("meta_objs").toInt();
+
+ for (int i = 0; i < meta_objs; i++) {
+ settings.beginGroup("meta_obj" + QString::number(i));
+
+ shared_ptr<views::ViewBase> vb;
+ shared_ptr<views::trace::View> tv;
+ if (settings.contains("assoc_view"))
+ vb = views_.at(settings.value("assoc_view").toInt());
+
+ if (vb)
+ tv = dynamic_pointer_cast<views::trace::View>(vb);
+
+ const QString type = settings.value("type").toString();
+
+ if ((type == "time_marker") && tv) {
+ Timestamp ts = GlobalSettings::restore_timestamp(settings, "time");
+ shared_ptr<views::trace::Flag> flag = tv->add_flag(ts);
+ flag->set_text(settings.value("text").toString());
}
- settings.setValue("views", views);
+ if ((type == "selection") && tv) {
+ Timestamp start = GlobalSettings::restore_timestamp(settings, "start_time");
+ Timestamp end = GlobalSettings::restore_timestamp(settings, "end_time");
+ tv->set_cursors(start, end);
+ tv->show_cursors();
+ }
+
+ settings.endGroup();
}
}
{
shared_ptr<devices::Device> device;
- QString device_type = settings.value("device_type").toString();
+ const QString device_type = settings.value("device_type").toString();
if (device_type == "hardware") {
map<string, string> dev_info;
if ((device_type == "sessionfile") || (device_type == "inputfile")) {
if (device_type == "sessionfile") {
settings.beginGroup("device");
- QString filename = settings.value("filename").toString();
+ const QString filename = settings.value("filename").toString();
settings.endGroup();
if (QFileInfo(filename).isReadable()) {
}
}
- if (device) {
- // Restore channels
- for (shared_ptr<data::SignalBase> base : signalbases_) {
- settings.beginGroup(base->internal_name());
- base->restore_settings(settings);
- settings.endGroup();
- }
-
- // Restore decoders
-#ifdef ENABLE_DECODE
- 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
-
- // Restore views
- int views = settings.value("views").toInt();
-
- for (int i = 0; i < views; i++) {
- settings.beginGroup("view" + QString::number(i));
-
- if (i > 0) {
- views::ViewType type = (views::ViewType)settings.value("type").toInt();
- add_view(name_, type, this);
- views_.back()->restore_settings(settings);
- } else
- main_view_->restore_settings(settings);
-
- settings.endGroup();
- }
- }
+ if (device)
+ restore_setup(settings);
}
void Session::select_device(shared_ptr<devices::Device> device)
set_device((iter == devices.end()) ? devices.front() : *iter);
}
+bool Session::using_file_device() const
+{
+ shared_ptr<devices::SessionFile> sessionfile_device =
+ dynamic_pointer_cast<devices::SessionFile>(device_);
+
+ shared_ptr<devices::InputFile> inputfile_device =
+ dynamic_pointer_cast<devices::InputFile>(device_);
+
+ return (sessionfile_device || inputfile_device);
+}
+
/**
* Convert generic options to data types that are specific to InputFormat.
*
return result;
}
-void Session::load_init_file(const string &file_name, const string &format)
+void Session::load_init_file(const string &file_name,
+ const string &format, const string &setup_file_name)
{
shared_ptr<InputFormat> input_format;
map<string, Glib::VariantBase> input_opts;
input_format->options());
}
- load_file(QString::fromStdString(file_name), input_format, input_opts);
+ load_file(QString::fromStdString(file_name), QString::fromStdString(setup_file_name),
+ input_format, input_opts);
}
-void Session::load_file(QString file_name,
- shared_ptr<sigrok::InputFormat> format,
- const map<string, Glib::VariantBase> &options)
+void Session::load_file(QString file_name, QString setup_file_name,
+ shared_ptr<sigrok::InputFormat> format, const map<string, Glib::VariantBase> &options)
{
const QString errorMessage(
QString("Failed to load file %1").arg(file_name));
device_manager_.context(),
file_name.toStdString())));
} catch (Error& e) {
- MainWindow::show_session_error(tr("Failed to load ") + file_name, e.what());
+ MainWindow::show_session_error(tr("Failed to load %1").arg(file_name), e.what());
set_default_device();
main_bar_->update_device_list();
return;
}
+ // Use the input file with .pvs extension if no setup file was given
+ if (setup_file_name.isEmpty()) {
+ setup_file_name = file_name;
+ setup_file_name.truncate(setup_file_name.lastIndexOf('.'));
+ setup_file_name.append(".pvs");
+ }
+
+ if (QFileInfo::exists(setup_file_name) && QFileInfo(setup_file_name).isReadable()) {
+ QSettings settings_storage(setup_file_name, QSettings::IniFormat);
+ restore_setup(settings_storage);
+ }
+
main_bar_->update_device_list();
start_capture([&, errorMessage](QString infoMessage) {
void Session::register_view(shared_ptr<views::ViewBase> view)
{
- if (views_.empty()) {
+ if (views_.empty())
main_view_ = view;
- }
views_.push_back(view);
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:
+ vector< shared_ptr<data::SignalBase> > view_signalbases = view->signalbases();
+
+ 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 if it doesn't have it yet
+ if (!sb_exists)
+ switch (signalbase->type()) {
+ case data::SignalBase::AnalogChannel:
+ case data::SignalBase::LogicChannel:
+ case data::SignalBase::MathChannel:
+ view->add_signalbase(signalbase);
+ break;
+ case data::SignalBase::DecodeChannel:
#ifdef ENABLE_DECODE
- trace_view->add_decode_signal(
- dynamic_pointer_cast<data::DecodeSignal>(signalbase));
+ view->add_decode_signal(dynamic_pointer_cast<data::DecodeSignal>(signalbase));
#endif
- break;
- case data::SignalBase::MathChannel:
- // TBD
- break;
- }
- }
+ break;
+ }
}
signals_changed();
void Session::deregister_view(shared_ptr<views::ViewBase> view)
{
- views_.remove_if([&](shared_ptr<views::ViewBase> v) { return v == view; });
+ views_.erase(std::remove_if(views_.begin(), views_.end(),
+ [&](shared_ptr<views::ViewBase> v) { return v == view; }),
+ views_.end());
if (views_.empty()) {
main_view_.reset();
return result;
}
-const unordered_set< shared_ptr<data::SignalBase> > Session::signalbases() const
+const vector< shared_ptr<data::SignalBase> > Session::signalbases() const
{
return signalbases_;
}
// Create the decode signal
signal = make_shared<data::DecodeSignal>(*this);
- signalbases_.insert(signal);
+ signalbases_.push_back(signal);
// Add the decode signal to all views
for (shared_ptr<views::ViewBase>& view : views_)
void Session::remove_decode_signal(shared_ptr<data::DecodeSignal> signal)
{
- signalbases_.erase(signal);
+ signalbases_.erase(std::remove_if(signalbases_.begin(), signalbases_.end(),
+ [&](shared_ptr<data::SignalBase> s) { return s == signal; }),
+ signalbases_.end());
for (shared_ptr<views::ViewBase>& view : views_)
view->remove_decode_signal(signal);
{
bool changed;
+ if (state == Running)
+ acq_time_.restart();
+ if (state == Stopped)
+ qDebug("Acquisition took %.2f s", acq_time_.elapsed() / 1000.);
+
{
lock_guard<mutex> lock(sampling_mutex_);
changed = capture_state_ != state;
qobject_cast<views::trace::View*>(viewbase.get());
if (trace_view) {
- unordered_set< shared_ptr<views::trace::Signal> >
- prev_sigs(trace_view->signals());
+ vector< shared_ptr<Signal> > prev_sigs(trace_view->signals());
trace_view->clear_signals();
for (auto channel : sr_dev->channels()) {
shared_ptr<data::SignalBase> signalbase;
- shared_ptr<views::trace::Signal> signal;
+ shared_ptr<Signal> signal;
// Find the channel in the old signals
const auto iter = find_if(
prev_sigs.cbegin(), prev_sigs.cend(),
- [&](const shared_ptr<views::trace::Signal> &s) {
+ [&](const shared_ptr<Signal> &s) {
return s->base()->channel() == channel;
});
if (iter != prev_sigs.end()) {
if (b->channel() == channel)
signalbase = b;
+ shared_ptr<Signal> signal;
+
switch(channel->type()->id()) {
case SR_CHANNEL_LOGIC:
if (!signalbase) {
signalbase = make_shared<data::SignalBase>(channel,
data::SignalBase::LogicChannel);
- signalbases_.insert(signalbase);
+ signalbases_.push_back(signalbase);
all_signal_data_.insert(logic_data_);
signalbase->set_data(logic_data_);
signalbase.get(), SLOT(on_capture_state_changed(int)));
}
- signal = shared_ptr<views::trace::Signal>(
- new views::trace::LogicSignal(*this,
- device_, signalbase));
- trace_view->add_signal(signal);
+ signal = shared_ptr<Signal>(new LogicSignal(*this, device_, signalbase));
break;
case SR_CHANNEL_ANALOG:
if (!signalbase) {
signalbase = make_shared<data::SignalBase>(channel,
data::SignalBase::AnalogChannel);
- signalbases_.insert(signalbase);
+ signalbases_.push_back(signalbase);
shared_ptr<data::Analog> data(new data::Analog());
all_signal_data_.insert(data);
signalbase.get(), SLOT(on_capture_state_changed(int)));
}
- signal = shared_ptr<views::trace::Signal>(
- new views::trace::AnalogSignal(
- *this, signalbase));
- trace_view->add_signal(signal);
+ signal = shared_ptr<Signal>(new AnalogSignal(*this, signalbase));
break;
}
assert(false);
break;
}
+
+ // New views take their signal settings from the main view
+ if (!viewbase->is_main_view()) {
+ shared_ptr<pv::views::trace::View> main_tv =
+ dynamic_pointer_cast<pv::views::trace::View>(main_view_);
+ shared_ptr<Signal> main_signal =
+ main_tv->get_signal_by_signalbase(signalbase);
+ signal->restore_settings(main_signal->save_settings());
+ }
+
+ trace_view->add_signal(signal);
}
}
}
{
assert(error_handler);
+#ifdef ENABLE_FLOW
+ pipeline_ = Pipeline::create();
+
+ source_ = ElementFactory::create_element("filesrc", "source");
+ sink_ = RefPtr<AppSink>::cast_dynamic(ElementFactory::create_element("appsink", "sink"));
+
+ pipeline_->add(source_)->add(sink_);
+ source_->link(sink_);
+
+ source_->set_property("location", Glib::ustring("/tmp/dummy_binary"));
+
+ sink_->set_property("emit-signals", TRUE);
+ sink_->signal_new_sample().connect(sigc::mem_fun(*this, &Session::on_gst_new_sample));
+
+ // Get the bus from the pipeline and add a bus watch to the default main context
+ RefPtr<Bus> bus = pipeline_->get_bus();
+ bus->add_watch(sigc::mem_fun(this, &Session::on_gst_bus_message));
+
+ // Start pipeline and Wait until it finished processing
+ pipeline_done_interrupt_ = false;
+ pipeline_->set_state(Gst::STATE_PLAYING);
+
+ unique_lock<mutex> pipeline_done_lock_(pipeline_done_mutex_);
+ pipeline_done_cond_.wait(pipeline_done_lock_);
+
+ // Let the pipeline free all resources
+ pipeline_->set_state(Gst::STATE_NULL);
+
+#else
if (!device_)
return;
error_handler(e.what());
set_capture_state(Stopped);
return;
+ } catch (QString& e) {
+ error_handler(e);
+ set_capture_state(Stopped);
+ return;
}
set_capture_state(Stopped);
// Confirm that SR_DF_END was received
if (cur_logic_segment_)
qDebug() << "WARNING: SR_DF_END was not received.";
+#endif
// Optimize memory usage
free_unused_memory();
segment_completed(segment_id);
}
+#ifdef ENABLE_FLOW
+bool Session::on_gst_bus_message(const Glib::RefPtr<Gst::Bus>& bus, const Glib::RefPtr<Gst::Message>& message)
+{
+ (void)bus;
+
+ if ((message->get_source() == pipeline_) && \
+ ((message->get_message_type() == Gst::MESSAGE_EOS)))
+ pipeline_done_cond_.notify_one();
+
+ // TODO Also evaluate MESSAGE_STREAM_STATUS to receive error notifications
+
+ return true;
+}
+
+Gst::FlowReturn Session::on_gst_new_sample()
+{
+ RefPtr<Gst::Sample> sample = sink_->pull_sample();
+ RefPtr<Gst::Buffer> buf = sample->get_buffer();
+
+ for (uint32_t block_id = 0; block_id < buf->n_memory(); block_id++) {
+ RefPtr<Gst::Memory> buf_mem = buf->get_memory(block_id);
+ Gst::MapInfo mapinfo;
+ buf_mem->map(mapinfo, Gst::MAP_READ);
+
+ shared_ptr<sigrok::Packet> logic_packet =
+ sr_context->create_logic_packet(mapinfo.get_data(), buf->get_size(), 1);
+
+ try {
+ feed_in_logic(dynamic_pointer_cast<sigrok::Logic>(logic_packet->payload()));
+ } catch (bad_alloc&) {
+ out_of_memory_ = true;
+ device_->stop();
+ buf_mem->unmap(mapinfo);
+ return Gst::FLOW_ERROR;
+ }
+
+ buf_mem->unmap(mapinfo);
+ }
+
+ return Gst::FLOW_OK;
+}
+#endif
+
void Session::feed_in_header()
{
// Nothing to do here for now
return;
}
+ if (logic->unit_size() > 8)
+ throw QString(tr("Can't handle more than 64 logic channels."));
+
if (!cur_samplerate_)
try {
cur_samplerate_ = device_->read_config<uint64_t>(ConfigKey::SAMPLERATE);
data_saved_ = true;
}
+#ifdef ENABLE_DECODE
+void Session::on_new_decoders_selected(vector<const srd_decoder*> decoders)
+{
+ assert(decoders.size() > 0);
+
+ shared_ptr<data::DecodeSignal> signal = add_decode_signal();
+
+ if (signal)
+ for (unsigned int i = 0; i < decoders.size(); i++) {
+ const srd_decoder* d = decoders[i];
+ signal->stack_decoder(d, !(i < decoders.size() - 1));
+ }
+}
+#endif
+
} // namespace pv
#ifndef PULSEVIEW_PV_SESSION_HPP
#define PULSEVIEW_PV_SESSION_HPP
+#ifdef ENABLE_FLOW
+#include <atomic>
+#include <condition_variable>
+#endif
+
#include <functional>
#include <map>
#include <memory>
#include <QObject>
#include <QSettings>
#include <QString>
+#include <QElapsedTimer>
+
+#ifdef ENABLE_FLOW
+#include <gstreamermm.h>
+#include <libsigrokflow/libsigrokflow.hpp>
+#endif
#include "util.hpp"
#include "views/viewbase.hpp"
+
using std::function;
-using std::list;
using std::map;
using std::mutex;
using std::recursive_mutex;
using std::string;
using std::unordered_set;
+#ifdef ENABLE_FLOW
+using Glib::RefPtr;
+using Gst::AppSink;
+using Gst::Element;
+using Gst::Pipeline;
+#endif
+
struct srd_decoder;
struct srd_channel;
void set_name(QString name);
- const list< shared_ptr<views::ViewBase> > views() const;
+ const vector< shared_ptr<views::ViewBase> > views() const;
shared_ptr<views::ViewBase> main_view() const;
*/
bool data_saved() const;
+ void save_setup(QSettings &settings) const;
+
void save_settings(QSettings &settings) const;
+ void restore_setup(QSettings &settings);
+
void restore_settings(QSettings &settings);
/**
void set_default_device();
- void load_init_file(const string &file_name, const string &format);
+ bool using_file_device() const;
+
+ void load_init_file(const string &file_name, const string &format,
+ const string &setup_file_name);
- void load_file(QString file_name,
+ void load_file(QString file_name, QString setup_file_name = QString(),
shared_ptr<sigrok::InputFormat> format = nullptr,
const map<string, Glib::VariantBase> &options =
map<string, Glib::VariantBase>());
bool has_view(shared_ptr<views::ViewBase> view);
- const unordered_set< shared_ptr<data::SignalBase> > signalbases() const;
+ const vector< shared_ptr<data::SignalBase> > signalbases() const;
bool all_segments_complete(uint32_t segment_id) const;
void signal_new_segment();
void signal_segment_completed();
+#ifdef ENABLE_FLOW
+ bool on_gst_bus_message(const Glib::RefPtr<Gst::Bus>& bus, const Glib::RefPtr<Gst::Message>& message);
+
+ Gst::FlowReturn on_gst_new_sample();
+#endif
+
void feed_in_header();
void feed_in_meta(shared_ptr<sigrok::Meta> meta);
void data_received();
- void add_view(const QString &title, views::ViewType type,
- Session *session);
+ void add_view(views::ViewType type, Session *session);
public Q_SLOTS:
void on_data_saved();
+#ifdef ENABLE_DECODE
+ void on_new_decoders_selected(vector<const srd_decoder*> decoders);
+#endif
+
private:
DeviceManager &device_manager_;
shared_ptr<devices::Device> device_;
QString default_name_, name_;
- list< shared_ptr<views::ViewBase> > views_;
+ vector< shared_ptr<views::ViewBase> > views_;
shared_ptr<pv::views::ViewBase> main_view_;
shared_ptr<pv::toolbars::MainBar> main_bar_;
mutable mutex sampling_mutex_; //!< Protects access to capture_state_.
capture_state capture_state_;
- unordered_set< shared_ptr<data::SignalBase> > signalbases_;
+ vector< shared_ptr<data::SignalBase> > signalbases_;
unordered_set< shared_ptr<data::SignalData> > all_signal_data_;
/// trigger_list_ contains pairs of <segment_id, timestamp> values.
bool out_of_memory_;
bool data_saved_;
bool frame_began_;
+
+ QElapsedTimer acq_time_;
+
+#ifdef ENABLE_FLOW
+ RefPtr<Pipeline> pipeline_;
+ RefPtr<Element> source_;
+ RefPtr<AppSink> sink_;
+
+ mutable mutex pipeline_done_mutex_;
+ mutable condition_variable pipeline_done_cond_;
+ atomic<bool> pipeline_done_interrupt_;
+#endif
};
} // namespace pv
#include "storesession.hpp"
+#include <QSettings>
+
#include <pv/data/analog.hpp>
#include <pv/data/analogsegment.hpp>
#include <pv/data/logic.hpp>
#include <pv/data/signalbase.hpp>
#include <pv/devicemanager.hpp>
#include <pv/devices/device.hpp>
+#include <pv/globalsettings.hpp>
#include <pv/session.hpp>
#include <libsigrokcxx/libsigrokcxx.hpp>
using std::pair;
using std::shared_ptr;
using std::string;
-using std::unordered_set;
using std::vector;
using Glib::VariantBase;
bool StoreSession::start()
{
- const unordered_set< shared_ptr<data::SignalBase> > sigs(session_.signalbases());
+ const vector< shared_ptr<data::SignalBase> > sigs(session_.signalbases());
shared_ptr<data::Segment> any_segment;
shared_ptr<data::LogicSegment> lsegment;
thread_ = std::thread(&StoreSession::store_proc, this,
achannel_list, asegment_list, lsegment);
+
+ // Save session setup if we're saving to srzip and the user wants it
+ GlobalSettings settings;
+ bool save_with_setup = settings.value(GlobalSettings::Key_General_SaveWithSetup).toBool();
+
+ if ((output_format_->name() == "srzip") && (save_with_setup)) {
+ QString setup_file_name = QString::fromStdString(file_name_);
+ setup_file_name.truncate(setup_file_name.lastIndexOf('.'));
+ setup_file_name.append(".pvs");
+
+ QSettings settings_storage(setup_file_name, QSettings::IniFormat);
+ session_.save_setup(settings_storage);
+ }
+
return true;
}
const unsigned int samples_per_block =
min(asamples_per_block, lsamples_per_block);
+ const auto context = session_.device_manager().context();
while (!interrupt_ && sample_count_) {
progress_updated();
min((uint64_t)samples_per_block, sample_count_);
try {
- const auto context = session_.device_manager().context();
-
for (unsigned int i = 0; i < achannel_list.size(); i++) {
shared_ptr<sigrok::Channel> achannel = (achannel_list.at(i))->channel();
shared_ptr<data::AnalogSegment> asegment = asegment_list.at(i);
units_stored_ = unit_count_ - (sample_count_ >> progress_scale);
}
+ auto dfend = context->create_end_packet();
+ const string ldata_str = output_->receive(dfend);
+ if (output_stream_.is_open())
+ output_stream_ << ldata_str;
+
// Zeroing the progress variables indicates completion
units_stored_ = unit_count_ = 0;
--- /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 "subwindow.hpp"
+
+using std::out_of_range;
+
+namespace pv {
+namespace subwindows {
+namespace decoder_selector {
+
+DecoderCollectionItem::DecoderCollectionItem(const vector<QVariant>& data,
+ shared_ptr<DecoderCollectionItem> parent) :
+ data_(data),
+ parent_(parent)
+{
+}
+
+void DecoderCollectionItem::appendSubItem(shared_ptr<DecoderCollectionItem> item)
+{
+ subItems_.push_back(item);
+}
+
+shared_ptr<DecoderCollectionItem> DecoderCollectionItem::subItem(int row) const
+{
+ try {
+ return subItems_.at(row);
+ } catch (out_of_range&) {
+ return nullptr;
+ }
+}
+
+shared_ptr<DecoderCollectionItem> DecoderCollectionItem::parent() const
+{
+ return parent_;
+}
+
+shared_ptr<DecoderCollectionItem> DecoderCollectionItem::findSubItem(
+ const QVariant& value, int column)
+{
+ for (shared_ptr<DecoderCollectionItem> item : subItems_)
+ if (item->data(column) == value)
+ return item;
+
+ return nullptr;
+}
+
+int DecoderCollectionItem::subItemCount() const
+{
+ return subItems_.size();
+}
+
+int DecoderCollectionItem::columnCount() const
+{
+ return data_.size();
+}
+
+int DecoderCollectionItem::row() const
+{
+ if (parent_)
+ for (size_t i = 0; i < parent_->subItems_.size(); i++)
+ if (parent_->subItems_.at(i).get() == const_cast<DecoderCollectionItem*>(this))
+ return i;
+
+ return 0;
+}
+
+QVariant DecoderCollectionItem::data(int column) const
+{
+ try {
+ return data_.at(column);
+ } catch (out_of_range&) {
+ return QVariant();
+ }
+}
+
+} // namespace decoder_selector
+} // namespace subwindows
+} // 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 <QString>
+
+#include "subwindow.hpp"
+
+#include <libsigrokdecode/libsigrokdecode.h>
+
+#define DECODERS_HAVE_TAGS \
+ ((SRD_PACKAGE_VERSION_MAJOR > 0) || \
+ (SRD_PACKAGE_VERSION_MAJOR == 0) && (SRD_PACKAGE_VERSION_MINOR > 5))
+
+using std::make_shared;
+
+namespace pv {
+namespace subwindows {
+namespace decoder_selector {
+
+DecoderCollectionModel::DecoderCollectionModel(QObject* parent) :
+ QAbstractItemModel(parent)
+{
+ vector<QVariant> header_data;
+ header_data.emplace_back(tr("Decoder")); // Column #0
+ header_data.emplace_back(tr("Name")); // Column #1
+ header_data.emplace_back(tr("ID")); // Column #2
+ root_ = make_shared<DecoderCollectionItem>(header_data);
+
+ // Note: the tag groups are sub-items of the root item
+
+ // Create "all decoders" group
+ vector<QVariant> item_data;
+ item_data.emplace_back(tr("All Decoders"));
+ // Add dummy entries to make the row count the same as the
+ // sub-item size, or else we can't query sub-item data
+ item_data.emplace_back();
+ item_data.emplace_back();
+ shared_ptr<DecoderCollectionItem> group_item_all =
+ make_shared<DecoderCollectionItem>(item_data, root_);
+ root_->appendSubItem(group_item_all);
+
+ for (GSList* li = (GSList*)srd_decoder_list(); li; li = li->next) {
+ const srd_decoder *const d = (srd_decoder*)li->data;
+ assert(d);
+
+ const QString id = QString::fromUtf8(d->id);
+ const QString name = QString::fromUtf8(d->name);
+ const QString long_name = QString::fromUtf8(d->longname);
+
+ // Add decoder to the "all decoders" group
+ item_data.clear();
+ item_data.emplace_back(name);
+ item_data.emplace_back(long_name);
+ item_data.emplace_back(id);
+ shared_ptr<DecoderCollectionItem> decoder_item_all =
+ make_shared<DecoderCollectionItem>(item_data, group_item_all);
+ group_item_all->appendSubItem(decoder_item_all);
+
+ // Add decoder to all relevant groups using the tag information
+#if DECODERS_HAVE_TAGS
+ for (GSList* ti = (GSList*)d->tags; ti; ti = ti->next) {
+ const QString tag = tr((char*)ti->data);
+ const QVariant tag_var = QVariant(tag);
+
+ // Find tag group and create it if it doesn't exist yet
+ shared_ptr<DecoderCollectionItem> group_item =
+ root_->findSubItem(tag_var, 0);
+
+ if (!group_item) {
+ item_data.clear();
+ item_data.emplace_back(tag);
+ // Add dummy entries to make the row count the same as the
+ // sub-item size, or else we can't query sub-item data
+ item_data.emplace_back();
+ item_data.emplace_back();
+ group_item = make_shared<DecoderCollectionItem>(item_data, root_);
+ root_->appendSubItem(group_item);
+ }
+
+ // Create decoder item
+ item_data.clear();
+ item_data.emplace_back(name);
+ item_data.emplace_back(long_name);
+ item_data.emplace_back(id);
+ shared_ptr<DecoderCollectionItem> decoder_item =
+ make_shared<DecoderCollectionItem>(item_data, group_item);
+
+ // Add decoder to tag group
+ group_item->appendSubItem(decoder_item);
+ }
+#endif
+ }
+}
+
+QVariant DecoderCollectionModel::data(const QModelIndex& index, int role) const
+{
+ if (!index.isValid())
+ return QVariant();
+
+ if (role == Qt::DisplayRole)
+ {
+ DecoderCollectionItem* item =
+ static_cast<DecoderCollectionItem*>(index.internalPointer());
+
+ return item->data(index.column());
+ }
+
+ if ((role == Qt::FontRole) && (index.parent().isValid()) && (index.column() == 0))
+ {
+ QFont font;
+ font.setItalic(true);
+ return QVariant(font);
+ }
+
+ return QVariant();
+}
+
+Qt::ItemFlags DecoderCollectionModel::flags(const QModelIndex& index) const
+{
+ if (!index.isValid())
+ return nullptr;
+
+ return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
+}
+
+QVariant DecoderCollectionModel::headerData(int section, Qt::Orientation orientation,
+ int role) const
+{
+ if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
+ return root_->data(section);
+
+ return QVariant();
+}
+
+QModelIndex DecoderCollectionModel::index(int row, int column,
+ const QModelIndex& parent_idx) const
+{
+ if (!hasIndex(row, column, parent_idx))
+ return QModelIndex();
+
+ DecoderCollectionItem* parent = root_.get();
+
+ if (parent_idx.isValid())
+ parent = static_cast<DecoderCollectionItem*>(parent_idx.internalPointer());
+
+ DecoderCollectionItem* subItem = parent->subItem(row).get();
+
+ return subItem ? createIndex(row, column, subItem) : QModelIndex();
+}
+
+QModelIndex DecoderCollectionModel::parent(const QModelIndex& index) const
+{
+ if (!index.isValid())
+ return QModelIndex();
+
+ DecoderCollectionItem* subItem =
+ static_cast<DecoderCollectionItem*>(index.internalPointer());
+
+ shared_ptr<DecoderCollectionItem> parent = subItem->parent();
+
+ return (parent == root_) ? QModelIndex() :
+ createIndex(parent->row(), 0, parent.get());
+}
+
+int DecoderCollectionModel::rowCount(const QModelIndex& parent_idx) const
+{
+ DecoderCollectionItem* parent = root_.get();
+
+ if (parent_idx.column() > 0)
+ return 0;
+
+ if (parent_idx.isValid())
+ parent = static_cast<DecoderCollectionItem*>(parent_idx.internalPointer());
+
+ return parent->subItemCount();
+}
+
+int DecoderCollectionModel::columnCount(const QModelIndex& parent_idx) const
+{
+ if (parent_idx.isValid())
+ return static_cast<DecoderCollectionItem*>(
+ parent_idx.internalPointer())->columnCount();
+ else
+ return root_->columnCount();
+}
+
+
+} // namespace decoder_selector
+} // namespace subwindows
+} // 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 <algorithm>
+
+#include <QApplication>
+#include <QDebug>
+#include <QFontMetrics>
+#include <QInputDialog>
+#include <QLabel>
+#include <QLineEdit>
+#include <QPushButton>
+#include <QScrollArea>
+#include <QVBoxLayout>
+
+#include "pv/session.hpp"
+#include "pv/subwindows/decoder_selector/subwindow.hpp"
+
+#include <libsigrokdecode/libsigrokdecode.h>
+#include "subwindow.hpp" // Required only for lupdate since above include isn't recognized
+
+#define DECODERS_HAVE_TAGS \
+ ((SRD_PACKAGE_VERSION_MAJOR > 0) || \
+ (SRD_PACKAGE_VERSION_MAJOR == 0) && (SRD_PACKAGE_VERSION_MINOR > 5))
+
+using std::reverse;
+
+namespace pv {
+namespace subwindows {
+namespace decoder_selector {
+
+const char *initial_notice =
+ QT_TRANSLATE_NOOP("pv::subwindows::decoder_selector::SubWindow",
+ "Select a decoder to see its description here."); // clazy:exclude=non-pod-global-static
+
+const int min_width_margin = 75;
+
+
+bool QCustomSortFilterProxyModel::filterAcceptsRow(int source_row,
+ const QModelIndex& source_parent) const
+{
+ // Search model recursively
+
+ if (QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent))
+ return true;
+
+ const QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
+
+ for (int i = 0; i < sourceModel()->rowCount(index); i++)
+ if (filterAcceptsRow(i, index))
+ return true;
+
+ return false;
+}
+
+
+void QCustomTreeView::currentChanged(const QModelIndex& current,
+ const QModelIndex& previous)
+{
+ QTreeView::currentChanged(current, previous);
+ currentChanged(current);
+}
+
+
+SubWindow::SubWindow(Session& session, QWidget* parent) :
+ SubWindowBase(session, parent),
+ splitter_(new QSplitter()),
+ tree_view_(new QCustomTreeView()),
+ info_box_(new QWidget()),
+ info_label_header_(new QLabel()),
+ info_label_body_(new QLabel()),
+ info_label_footer_(new QLabel()),
+ model_(new DecoderCollectionModel()),
+ sort_filter_model_(new QCustomSortFilterProxyModel())
+{
+ QVBoxLayout* root_layout = new QVBoxLayout(this);
+ root_layout->setContentsMargins(0, 0, 0, 0);
+ root_layout->addWidget(splitter_);
+
+ QWidget* upper_container = new QWidget();
+ QVBoxLayout* upper_layout = new QVBoxLayout(upper_container);
+ upper_layout->setContentsMargins(0, 5, 0, 0);
+ QLineEdit* filter = new QLineEdit();
+ upper_layout->addWidget(filter);
+ upper_layout->addWidget(tree_view_);
+
+ splitter_->setOrientation(Qt::Vertical);
+ splitter_->addWidget(upper_container);
+ splitter_->addWidget(info_box_);
+
+ const QIcon filter_icon(QIcon::fromTheme("search",
+ QIcon(":/icons/search.svg")));
+ filter->setClearButtonEnabled(true);
+ filter->addAction(filter_icon, QLineEdit::LeadingPosition);
+
+
+ sort_filter_model_->setSourceModel(model_);
+ sort_filter_model_->setSortCaseSensitivity(Qt::CaseInsensitive);
+ sort_filter_model_->setFilterCaseSensitivity(Qt::CaseInsensitive);
+ sort_filter_model_->setFilterKeyColumn(-1);
+
+ tree_view_->setModel(sort_filter_model_);
+ tree_view_->setRootIsDecorated(true);
+ tree_view_->setSortingEnabled(true);
+ tree_view_->sortByColumn(0, Qt::AscendingOrder);
+
+ // Hide the columns that hold the detailed item information
+ tree_view_->hideColumn(2); // ID
+
+ // Ensure that all decoder tag names are fully visible by default
+ tree_view_->resizeColumnToContents(0);
+
+ tree_view_->setIndentation(10);
+
+#if (!DECODERS_HAVE_TAGS)
+ tree_view_->expandAll();
+ tree_view_->setItemsExpandable(false);
+#endif
+
+ QScrollArea* info_label_body_container = new QScrollArea();
+ info_label_body_container->setWidget(info_label_body_);
+ info_label_body_container->setWidgetResizable(true);
+
+ info_box_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ QVBoxLayout* info_box_layout = new QVBoxLayout(info_box_);
+ info_box_layout->addWidget(info_label_header_);
+ info_box_layout->addWidget(info_label_body_container);
+ info_box_layout->addWidget(info_label_footer_);
+ info_box_layout->setAlignment(Qt::AlignTop);
+ Qt::TextInteractionFlags flags = Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard;
+ info_label_header_->setWordWrap(true);
+ info_label_header_->setTextInteractionFlags(flags);
+ info_label_body_->setWordWrap(true);
+ info_label_body_->setTextInteractionFlags(flags);
+ info_label_body_->setText(QString(tr(initial_notice)));
+ info_label_body_->setAlignment(Qt::AlignTop);
+ info_label_footer_->setWordWrap(true);
+ info_label_footer_->setTextInteractionFlags(flags);
+
+ connect(filter, SIGNAL(textChanged(const QString&)),
+ this, SLOT(on_filter_changed(const QString&)));
+ connect(filter, SIGNAL(returnPressed()),
+ this, SLOT(on_filter_return_pressed()));
+
+ connect(tree_view_, SIGNAL(currentChanged(const QModelIndex&)),
+ this, SLOT(on_item_changed(const QModelIndex&)));
+ connect(tree_view_, SIGNAL(activated(const QModelIndex&)),
+ this, SLOT(on_item_activated(const QModelIndex&)));
+
+ connect(this, SIGNAL(new_decoders_selected(vector<const srd_decoder*>)),
+ &session, SLOT(on_new_decoders_selected(vector<const srd_decoder*>)));
+
+ // Place the keyboard cursor in the filter QLineEdit initially
+ filter->setFocus();
+}
+
+bool SubWindow::has_toolbar() const
+{
+ return true;
+}
+
+QToolBar* SubWindow::create_toolbar(QWidget *parent) const
+{
+ QToolBar* toolbar = new QToolBar(parent);
+
+ return toolbar;
+}
+
+int SubWindow::minimum_width() const
+{
+ QFontMetrics m(info_label_body_->font());
+ const int label_width = m.width(QString(tr(initial_notice)));
+
+ return label_width + min_width_margin;
+}
+
+vector<const char*> SubWindow::get_decoder_inputs(const srd_decoder* d) const
+{
+ vector<const char*> ret_val;
+
+ for (GSList* li = d->inputs; li; li = li->next)
+ ret_val.push_back((const char*)li->data);
+
+ return ret_val;
+}
+
+vector<const srd_decoder*> SubWindow::get_decoders_providing(const char* output) const
+{
+ vector<const srd_decoder*> ret_val;
+
+ for (GSList* li = (GSList*)srd_decoder_list(); li; li = li->next) {
+ const srd_decoder* d = (srd_decoder*)li->data;
+ assert(d);
+
+ if (!d->outputs)
+ continue;
+
+ const int maxlen = 1024;
+
+ // TODO For now we ignore that d->outputs is actually a list
+ if (strncmp((char*)(d->outputs->data), output, maxlen) == 0)
+ ret_val.push_back(d);
+ }
+
+ return ret_val;
+}
+
+void SubWindow::on_item_changed(const QModelIndex& index)
+{
+ QString decoder_name, id, longname, desc, doc, tags;
+
+ // If the parent isn't valid, a category title was clicked
+ if (index.isValid() && index.parent().isValid()) {
+ QModelIndex id_index = index.model()->index(index.row(), 2, index.parent());
+ decoder_name = index.model()->data(id_index, Qt::DisplayRole).toString();
+
+ if (decoder_name.isEmpty())
+ return;
+
+ const srd_decoder* d = srd_decoder_get_by_id(decoder_name.toUtf8());
+
+ id = QString::fromUtf8(d->id);
+ longname = QString::fromUtf8(d->longname);
+ desc = QString::fromUtf8(d->desc);
+ doc = QString::fromUtf8(srd_decoder_doc_get(d)).trimmed();
+
+#if DECODERS_HAVE_TAGS
+ for (GSList* li = (GSList*)d->tags; li; li = li->next) {
+ QString s = (li == (GSList*)d->tags) ?
+ tr((char*)li->data) :
+ QString(tr(", %1")).arg(tr((char*)li->data));
+ tags.append(s);
+ }
+#endif
+ } else
+ doc = QString(tr(initial_notice));
+
+ if (!id.isEmpty())
+ info_label_header_->setText(
+ QString("<span style='font-size:large'><b>%1 (%2)</b></span><br><i>%3</i>")
+ .arg(longname, id, desc));
+ else
+ info_label_header_->clear();
+
+ info_label_body_->setText(doc);
+
+ if (!tags.isEmpty())
+ info_label_footer_->setText(tr("<p align='right'>Tags: %1</p>").arg(tags));
+ else
+ info_label_footer_->clear();
+}
+
+void SubWindow::on_item_activated(const QModelIndex& index)
+{
+ if (!index.isValid())
+ return;
+
+ QModelIndex id_index = index.model()->index(index.row(), 2, index.parent());
+ QString decoder_name = index.model()->data(id_index, Qt::DisplayRole).toString();
+
+ const srd_decoder* chosen_decoder = srd_decoder_get_by_id(decoder_name.toUtf8());
+ if (chosen_decoder == nullptr)
+ return;
+
+ vector<const srd_decoder*> decoders;
+ decoders.push_back(chosen_decoder);
+
+ // If the decoder only depends on logic inputs, we add it and are done
+ vector<const char*> inputs = get_decoder_inputs(decoders.front());
+ if (inputs.size() == 0) {
+ qWarning() << "Protocol decoder" << decoder_name << "cannot have 0 inputs!";
+ return;
+ }
+
+ if (strcmp(inputs.at(0), "logic") == 0) {
+ new_decoders_selected(decoders);
+ return;
+ }
+
+ // Check if we can automatically fulfill the stacking requirements
+ while (strcmp(inputs.at(0), "logic") != 0) {
+ vector<const srd_decoder*> prov_decoders = get_decoders_providing(inputs.at(0));
+
+ if (prov_decoders.size() == 0) {
+ // Emit warning and add the stack that we could gather so far
+ qWarning() << "Protocol decoder" << QString::fromUtf8(decoders.back()->id) \
+ << "has input that no other decoder provides:" << QString::fromUtf8(inputs.at(0));
+ break;
+ }
+
+ if (prov_decoders.size() == 1) {
+ decoders.push_back(prov_decoders.front());
+ } else {
+ // Let user decide which one to use
+ QString caption = QString(tr("Protocol decoder <b>%1</b> requires input type <b>%2</b> " \
+ "which several decoders provide.<br>Choose which one to use:<br>"))
+ .arg(QString::fromUtf8(decoders.back()->id), QString::fromUtf8(inputs.at(0)));
+
+ QStringList items;
+ for (const srd_decoder* d : prov_decoders)
+ items << QString::fromUtf8(d->id) + " (" + QString::fromUtf8(d->longname) + ")";
+ bool ok_clicked;
+ QString item = QInputDialog::getItem(this, tr("Choose Decoder"),
+ tr(caption.toUtf8()), items, 0, false, &ok_clicked);
+
+ if ((!ok_clicked) || (item.isEmpty()))
+ return;
+
+ QString d = item.section(' ', 0, 0);
+ decoders.push_back(srd_decoder_get_by_id(d.toUtf8()));
+ }
+
+ inputs = get_decoder_inputs(decoders.back());
+ }
+
+ // Reverse decoder list and add the stack
+ reverse(decoders.begin(), decoders.end());
+ new_decoders_selected(decoders);
+}
+
+void SubWindow::on_filter_changed(const QString& text)
+{
+ sort_filter_model_->setFilterFixedString(text);
+
+ // Expand the "All Decoders" category/tag if the user filtered
+ tree_view_->setExpanded(tree_view_->model()->index(0, 0), !text.isEmpty());
+}
+
+void SubWindow::on_filter_return_pressed()
+{
+ int num_visible_decoders = 0;
+ QModelIndex last_valid_index;
+
+ QModelIndex index = tree_view_->model()->index(0, 0);
+
+ while (index.isValid()) {
+ QModelIndex id_index = index.model()->index(index.row(), 2, index.parent());
+ QString decoder_name = index.model()->data(id_index, Qt::DisplayRole).toString();
+ if (!decoder_name.isEmpty()) {
+ last_valid_index = index;
+ num_visible_decoders++;
+ }
+ index = tree_view_->indexBelow(index);
+ }
+
+ // If only one decoder matches the filter, apply it when the user presses enter
+ if (num_visible_decoders == 1)
+ tree_view_->activated(last_valid_index);
+}
+
+} // namespace decoder_selector
+} // namespace subwindows
+} // 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_SUBWINDOWS_DECODERSELECTOR_SUBWINDOW_HPP
+#define PULSEVIEW_PV_SUBWINDOWS_DECODERSELECTOR_SUBWINDOW_HPP
+
+#include <vector>
+
+#include <QAbstractItemModel>
+#include <QLabel>
+#include <QSortFilterProxyModel>
+#include <QSplitter>
+#include <QTreeView>
+
+#include "pv/subwindows/subwindowbase.hpp"
+
+using std::shared_ptr;
+
+namespace pv {
+namespace subwindows {
+namespace decoder_selector {
+
+class DecoderCollectionItem
+{
+public:
+ DecoderCollectionItem(const vector<QVariant>& data,
+ shared_ptr<DecoderCollectionItem> parent = nullptr);
+
+ void appendSubItem(shared_ptr<DecoderCollectionItem> item);
+
+ shared_ptr<DecoderCollectionItem> subItem(int row) const;
+ shared_ptr<DecoderCollectionItem> parent() const;
+ shared_ptr<DecoderCollectionItem> findSubItem(const QVariant& value, int column);
+
+ int subItemCount() const;
+ int columnCount() const;
+ int row() const;
+ QVariant data(int column) const;
+
+private:
+ vector< shared_ptr<DecoderCollectionItem> > subItems_;
+ vector<QVariant> data_;
+ shared_ptr<DecoderCollectionItem> parent_;
+};
+
+
+class DecoderCollectionModel : public QAbstractItemModel
+{
+ Q_OBJECT
+
+public:
+ DecoderCollectionModel(QObject* parent = nullptr);
+
+ QVariant data(const QModelIndex& index, int role) const override;
+ Qt::ItemFlags flags(const QModelIndex& index) const override;
+
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role = Qt::DisplayRole) const override;
+ QModelIndex index(int row, int column,
+ const QModelIndex& parent_idx = QModelIndex()) const override;
+
+ QModelIndex parent(const QModelIndex& index) const override;
+
+ int rowCount(const QModelIndex& parent_idx = QModelIndex()) const override;
+ int columnCount(const QModelIndex& parent_idx = QModelIndex()) const override;
+
+private:
+ shared_ptr<DecoderCollectionItem> root_;
+};
+
+
+class QCustomSortFilterProxyModel : public QSortFilterProxyModel
+{
+protected:
+ bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const;
+};
+
+class QCustomTreeView : public QTreeView
+{
+ Q_OBJECT
+
+public:
+ void currentChanged(const QModelIndex& current, const QModelIndex& previous);
+
+Q_SIGNALS:
+ void currentChanged(const QModelIndex& current);
+};
+
+class SubWindow : public SubWindowBase
+{
+ Q_OBJECT
+
+public:
+ explicit SubWindow(Session &session, QWidget *parent = nullptr);
+
+ bool has_toolbar() const;
+ QToolBar* create_toolbar(QWidget *parent) const;
+
+ int minimum_width() const;
+
+ /**
+ * Returns a list of input types that a given protocol decoder requires
+ * ("logic", "uart", etc.)
+ */
+ vector<const char*> get_decoder_inputs(const srd_decoder* d) const;
+
+ /**
+ * Returns a list of protocol decoder IDs which provide a given output
+ * ("uart", "spi", etc.)
+ */
+ vector<const srd_decoder*> get_decoders_providing(const char* output) const;
+
+Q_SIGNALS:
+ void new_decoders_selected(vector<const srd_decoder*> decoders);
+
+public Q_SLOTS:
+ void on_item_changed(const QModelIndex& index);
+ void on_item_activated(const QModelIndex& index);
+
+ void on_filter_changed(const QString& text);
+ void on_filter_return_pressed();
+
+private:
+ QSplitter* splitter_;
+ QCustomTreeView* tree_view_;
+ QWidget* info_box_;
+ QLabel* info_label_header_;
+ QLabel* info_label_body_;
+ QLabel* info_label_footer_;
+ DecoderCollectionModel* model_;
+ QCustomSortFilterProxyModel* sort_filter_model_;
+};
+
+} // namespace decoder_selector
+} // namespace subwindows
+} // namespace pv
+
+#endif // PULSEVIEW_PV_SUBWINDOWS_DECODERSELECTOR_SUBWINDOW_HPP
+
--- /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/>.
+ */
+
+#ifdef ENABLE_DECODE
+#include <libsigrokdecode/libsigrokdecode.h>
+#endif
+
+#include <QWidget>
+
+#include "pv/session.hpp"
+#include "pv/subwindows/subwindowbase.hpp"
+
+using std::shared_ptr;
+
+namespace pv {
+namespace subwindows {
+
+SubWindowBase::SubWindowBase(Session &session, QWidget *parent) :
+ QWidget(parent),
+ session_(session)
+{
+ connect(&session_, SIGNAL(signals_changed()), this, SLOT(on_signals_changed()));
+}
+
+bool SubWindowBase::has_toolbar() const
+{
+ return false;
+}
+
+QToolBar* SubWindowBase::create_toolbar(QWidget *parent) const
+{
+ (void)parent;
+
+ return nullptr;
+}
+
+Session& SubWindowBase::session()
+{
+ return session_;
+}
+
+const Session& SubWindowBase::session() const
+{
+ return session_;
+}
+
+unordered_set< shared_ptr<data::SignalBase> > SubWindowBase::signalbases() const
+{
+ return signalbases_;
+}
+
+void SubWindowBase::clear_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(uint64_t, uint64_t, uint64_t)),
+ this, SLOT(on_samples_added(uint64_t, uint64_t, uint64_t)));
+ }
+
+ signalbases_.clear();
+}
+
+void SubWindowBase::add_signalbase(const shared_ptr<data::SignalBase> signalbase)
+{
+ signalbases_.insert(signalbase);
+}
+
+#ifdef ENABLE_DECODE
+void SubWindowBase::clear_decode_signals()
+{
+}
+
+void SubWindowBase::add_decode_signal(shared_ptr<data::DecodeSignal> signal)
+{
+ (void)signal;
+}
+
+void SubWindowBase::remove_decode_signal(shared_ptr<data::DecodeSignal> signal)
+{
+ (void)signal;
+}
+#endif
+
+int SubWindowBase::minimum_width() const
+{
+ return 0;
+}
+
+void SubWindowBase::on_signals_changed()
+{
+}
+
+} // namespace subwindows
+} // 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_SUBWINDOWBASE_HPP
+#define PULSEVIEW_PV_SUBWINDOWBASE_HPP
+
+#include <cstdint>
+#include <memory>
+#include <unordered_set>
+
+#include <QToolBar>
+#include <QWidget>
+
+#include <pv/data/signalbase.hpp>
+
+#ifdef ENABLE_DECODE
+#include <pv/data/decodesignal.hpp>
+#endif
+
+using std::shared_ptr;
+using std::unordered_set;
+
+namespace pv {
+
+class Session;
+
+namespace subwindows {
+
+enum SubWindowType {
+ SubWindowTypeDecoderSelector,
+};
+
+class SubWindowBase : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit SubWindowBase(Session &session, QWidget *parent = nullptr);
+
+ virtual bool has_toolbar() const;
+ virtual QToolBar* create_toolbar(QWidget *parent) const;
+
+ Session& session();
+ const Session& session() const;
+
+ /**
+ * Returns the signal bases contained in this view.
+ */
+ unordered_set< shared_ptr<data::SignalBase> > signalbases() const;
+
+ virtual void clear_signalbases();
+
+ virtual void add_signalbase(const shared_ptr<data::SignalBase> signalbase);
+
+#ifdef ENABLE_DECODE
+ virtual void clear_decode_signals();
+
+ virtual void add_decode_signal(shared_ptr<data::DecodeSignal> signal);
+
+ virtual void remove_decode_signal(shared_ptr<data::DecodeSignal> signal);
+#endif
+
+ virtual int minimum_width() const;
+
+public Q_SLOTS:
+ virtual void on_signals_changed();
+
+protected:
+ Session &session_;
+
+ unordered_set< shared_ptr<data::SignalBase> > signalbases_;
+};
+
+} // namespace subwindows
+} // namespace pv
+
+#endif // PULSEVIEW_PV_SUBWINDOWBASE_HPP
#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>
#include <pv/widgets/exportmenu.hpp>
#include <pv/widgets/importmenu.hpp>
#ifdef ENABLE_DECODE
-#include <pv/widgets/decodermenu.hpp>
+#include <pv/data/decodesignal.hpp>
#endif
#include <libsigrokcxx/libsigrokcxx.hpp>
action_open_(new QAction(this)),
action_save_as_(new QAction(this)),
action_save_selection_as_(new QAction(this)),
+ action_restore_setup_(new QAction(this)),
+ action_save_setup_(new QAction(this)),
action_connect_(new QAction(this)),
+ new_view_button_(new QToolButton()),
open_button_(new QToolButton()),
save_button_(new QToolButton()),
device_selector_(parent, session.device_manager(), action_connect_),
updating_sample_count_(false),
sample_count_supported_(false)
#ifdef ENABLE_DECODE
- , add_decoder_button_(new QToolButton()),
- menu_decoders_add_(new pv::widgets::DecoderMenu(this, true))
+ , add_decoder_button_(new QToolButton())
#endif
{
setObjectName(QString::fromUtf8("MainBar"));
connect(action_open_, SIGNAL(triggered(bool)),
this, SLOT(on_actionOpen_triggered()));
+ action_restore_setup_->setText(tr("Restore Session Setu&p..."));
+ connect(action_restore_setup_, SIGNAL(triggered(bool)),
+ this, SLOT(on_actionRestoreSetup_triggered()));
+
action_save_as_->setText(tr("&Save As..."));
action_save_as_->setIcon(QIcon::fromTheme("document-save-as",
QIcon(":/icons/document-save-as.png")));
connect(action_save_selection_as_, SIGNAL(triggered(bool)),
this, SLOT(on_actionSaveSelectionAs_triggered()));
+ action_save_setup_->setText(tr("Save Session Setu&p..."));
+ connect(action_save_setup_, SIGNAL(triggered(bool)),
+ this, SLOT(on_actionSaveSetup_triggered()));
+
widgets::ExportMenu *menu_file_export = new widgets::ExportMenu(this,
session.device_manager().context());
menu_file_export->setTitle(tr("&Export"));
connect(action_connect_, SIGNAL(triggered(bool)),
this, SLOT(on_actionConnect_triggered()));
+ // New view button
+ QMenu *menu_new_view = new QMenu();
+ connect(menu_new_view, SIGNAL(triggered(QAction*)),
+ this, SLOT(on_actionNewView_triggered(QAction*)));
+
+ for (int i = 0; i < views::ViewTypeCount; i++) {
+ QAction *const action = menu_new_view->addAction(tr(views::ViewTypeNames[i]));
+ action->setData(qVariantFromValue(i));
+ }
+
+ new_view_button_->setMenu(menu_new_view);
+ new_view_button_->setDefaultAction(action_new_view_);
+ new_view_button_->setPopupMode(QToolButton::MenuButtonPopup);
+
// Open button
+ vector<QAction*> open_actions;
+ open_actions.push_back(action_open_);
+ QAction* separator_o = new QAction(this);
+ separator_o->setSeparator(true);
+ open_actions.push_back(separator_o);
+ open_actions.push_back(action_restore_setup_);
+
widgets::ImportMenu *import_menu = new widgets::ImportMenu(this,
- session.device_manager().context(), action_open_);
+ session.device_manager().context(), open_actions);
connect(import_menu, SIGNAL(format_selected(shared_ptr<sigrok::InputFormat>)),
this, SLOT(import_file(shared_ptr<sigrok::InputFormat>)));
open_button_->setPopupMode(QToolButton::MenuButtonPopup);
// Save button
- vector<QAction*> open_actions;
- open_actions.push_back(action_save_as_);
- open_actions.push_back(action_save_selection_as_);
+ vector<QAction*> save_actions;
+ save_actions.push_back(action_save_as_);
+ save_actions.push_back(action_save_selection_as_);
+ QAction* separator_s = new QAction(this);
+ separator_s->setSeparator(true);
+ save_actions.push_back(separator_s);
+ save_actions.push_back(action_save_setup_);
widgets::ExportMenu *export_menu = new widgets::ExportMenu(this,
- session.device_manager().context(), open_actions);
+ session.device_manager().context(), save_actions);
connect(export_menu, SIGNAL(format_selected(shared_ptr<sigrok::OutputFormat>)),
this, SLOT(export_file(shared_ptr<sigrok::OutputFormat>)));
// Setup the decoder button
#ifdef ENABLE_DECODE
- menu_decoders_add_->setTitle(tr("&Add"));
- connect(menu_decoders_add_, SIGNAL(decoder_selected(srd_decoder*)),
- this, SLOT(add_decoder(srd_decoder*)));
-
add_decoder_button_->setIcon(QIcon(":/icons/add-decoder.svg"));
add_decoder_button_->setPopupMode(QToolButton::InstantPopup);
- add_decoder_button_->setMenu(menu_decoders_add_);
- add_decoder_button_->setToolTip(tr("Add low-level, non-stacked protocol decoder"));
+ add_decoder_button_->setToolTip(tr("Add protocol decoder"));
+ add_decoder_button_->setShortcut(QKeySequence(Qt::Key_D));
+
+ connect(add_decoder_button_, SIGNAL(clicked()),
+ this, SLOT(on_add_decoder_clicked()));
#endif
connect(&sample_count_, SIGNAL(value_changed()),
void MainBar::show_session_error(const QString text, const QString info_text)
{
QMessageBox msg(this);
- msg.setText(text);
- msg.setInformativeText(info_text);
+ msg.setText(text + "\n\n" + info_text);
msg.setStandardButtons(QMessageBox::Ok);
msg.setIcon(QMessageBox::Warning);
msg.exec();
}
-void MainBar::add_decoder(srd_decoder *decoder)
-{
-#ifdef ENABLE_DECODE
- assert(decoder);
- shared_ptr<data::DecodeSignal> signal = session_.add_decode_signal();
- if (signal)
- signal->stack_decoder(decoder);
-#else
- (void)decoder;
-#endif
-}
-
void MainBar::export_file(shared_ptr<OutputFormat> format, bool selection_only)
{
using pv::dialogs::StoreProgress;
options = dlg.options();
}
- session_.load_file(file_name, format, options);
+ session_.load_file(file_name, "", format, options);
const QString abs_path = QFileInfo(file_name).absolutePath();
settings.setValue(SettingOpenDirectory, abs_path);
commit_sample_rate();
}
-void MainBar::on_actionNewView_triggered()
+void MainBar::on_actionNewView_triggered(QAction* action)
{
- new_view(&session_);
+ if (action)
+ new_view(&session_, action->data().toInt());
+ else
+ // When the icon of the button is clicked, we create a trace view
+ new_view(&session_, views::ViewTypeTrace);
}
void MainBar::on_actionOpen_triggered()
export_file(session_.device_manager().context()->output_formats()["srzip"], true);
}
+void MainBar::on_actionSaveSetup_triggered()
+{
+ QSettings settings;
+ const QString dir = settings.value(SettingSaveDirectory).toString();
+
+ const QString file_name = QFileDialog::getSaveFileName(
+ this, tr("Save File"), dir, tr(
+ "PulseView Session Setups (*.pvs);;"
+ "All Files (*)"));
+
+ if (file_name.isEmpty())
+ return;
+
+ QSettings settings_storage(file_name, QSettings::IniFormat);
+ session_.save_setup(settings_storage);
+}
+
+void MainBar::on_actionRestoreSetup_triggered()
+{
+ QSettings settings;
+ const QString dir = settings.value(SettingSaveDirectory).toString();
+
+ const QString file_name = QFileDialog::getOpenFileName(
+ this, tr("Open File"), dir, tr(
+ "PulseView Session Setups (*.pvs);;"
+ "All Files (*)"));
+
+ if (file_name.isEmpty())
+ return;
+
+ QSettings settings_storage(file_name, QSettings::IniFormat);
+ session_.restore_setup(settings_storage);
+}
+
void MainBar::on_actionConnect_triggered()
{
// Stop any currently running capture session
update_device_list();
}
+void MainBar::on_add_decoder_clicked()
+{
+ show_decoder_selector(&session_);
+}
+
void MainBar::add_toolbar_widgets()
{
- addAction(action_new_view_);
+ addWidget(new_view_button_);
addSeparator();
addWidget(open_button_);
addWidget(save_button_);
QAction* action_open() const;
QAction* action_save_as() const;
QAction* action_save_selection_as() const;
+ QAction* action_restore_setup() const;
+ QAction* action_save_setup() const;
QAction* action_connect() const;
private:
void commit_sample_rate();
void commit_sample_count();
- QAction *const action_new_view_;
- QAction *const action_open_;
- QAction *const action_save_as_;
- QAction *const action_save_selection_as_;
- QAction *const action_connect_;
-
private Q_SLOTS:
void show_session_error(const QString text, const QString info_text);
- void add_decoder(srd_decoder *decoder);
-
void export_file(shared_ptr<sigrok::OutputFormat> format,
bool selection_only = false);
void import_file(shared_ptr<sigrok::InputFormat> format);
void on_config_changed();
- void on_actionNewView_triggered();
+ void on_actionNewView_triggered(QAction* action = nullptr);
void on_actionOpen_triggered();
void on_actionSaveAs_triggered();
void on_actionSaveSelectionAs_triggered();
+ void on_actionSaveSetup_triggered();
+ void on_actionRestoreSetup_triggered();
+
void on_actionConnect_triggered();
+ void on_add_decoder_clicked();
+
protected:
void add_toolbar_widgets();
bool eventFilter(QObject *watched, QEvent *event);
Q_SIGNALS:
- void new_view(Session *session);
+ void new_view(Session *session, int type);
+ void show_decoder_selector(Session *session);
private:
- QToolButton *open_button_, *save_button_;
+ QAction *const action_new_view_;
+ QAction *const action_open_;
+ QAction *const action_save_as_;
+ QAction *const action_save_selection_as_;
+ QAction *const action_restore_setup_;
+ QAction *const action_save_setup_;
+ QAction *const action_connect_;
+
+ QToolButton *new_view_button_, *open_button_, *save_button_;
pv::widgets::DeviceToolButton device_selector_;
#ifdef ENABLE_DECODE
QToolButton *add_decoder_button_;
- QMenu *const menu_decoders_add_;
#endif
};
QTextStream ts(&s);
if (sign && !v.is_zero())
ts << forcesign;
- ts << qSetRealNumberPrecision(precision) << (v * multiplier) << ' '
- << prefix << unit;
+ ts << qSetRealNumberPrecision(precision) << (v * multiplier);
+ ts << ' ' << prefix << unit;
return s;
}
exp -= 3;
}
}
+
+ const int prefix_order = -exponent(prefix);
+ precision = (prefix >= SIPrefix::none) ? max((int)(precision + prefix_order), 0) :
+ max((int)(precision - prefix_order), 0);
}
assert(prefix >= SIPrefix::yocto);
namespace util {
enum class TimeUnit {
+ None = 0,
Time = 1,
Samples = 2
};
--- /dev/null
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2015 Victor Anjin <virinext@gmail.com>
+ * Copyright (C) 2019 Soeren Apel <soeren@apelpie.net>
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015
+ *
+ * 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.
+ */
+
+#include <QApplication>
+#include <QClipboard>
+#include <QDebug>
+#include <QFont>
+#include <QKeyEvent>
+#include <QScrollBar>
+#include <QSize>
+#include <QPainter>
+#include <QPaintEvent>
+
+#include "QHexView.hpp"
+
+const unsigned int BYTES_PER_LINE = 16;
+const unsigned int HEXCHARS_IN_LINE = BYTES_PER_LINE * 3 - 1;
+const unsigned int GAP_ADR_HEX = 10;
+const unsigned int GAP_HEX_ASCII = 10;
+const unsigned int GAP_ASCII_SLIDER = 5;
+
+
+QHexView::QHexView(QWidget *parent):
+ QAbstractScrollArea(parent),
+ mode_(ChunkedDataMode),
+ data_(nullptr),
+ selectBegin_(0),
+ selectEnd_(0),
+ cursorPos_(0)
+{
+ setFont(QFont("Courier", 10));
+
+ charWidth_ = fontMetrics().boundingRect('X').width();
+ charHeight_ = fontMetrics().height();
+
+ // Determine X coordinates of the three sub-areas
+ posAddr_ = 0;
+ posHex_ = 10 * charWidth_ + GAP_ADR_HEX;
+ posAscii_ = posHex_ + HEXCHARS_IN_LINE * charWidth_ + GAP_HEX_ASCII;
+
+ setFocusPolicy(Qt::StrongFocus);
+
+ if (palette().color(QPalette::ButtonText).toHsv().value() > 127) {
+ // Color is bright
+ chunk_colors_.emplace_back(100, 149, 237); // QColorConstants::Svg::cornflowerblue
+ chunk_colors_.emplace_back(60, 179, 113); // QColorConstants::Svg::mediumseagreen
+ chunk_colors_.emplace_back(210, 180, 140); // QColorConstants::Svg::tan
+ } else {
+ // Color is dark
+ chunk_colors_.emplace_back(0, 0, 139); // QColorConstants::Svg::darkblue
+ chunk_colors_.emplace_back(34, 139, 34); // QColorConstants::Svg::forestgreen
+ chunk_colors_.emplace_back(160, 82, 45); // QColorConstants::Svg::sienna
+ }
+}
+
+void QHexView::set_mode(Mode m)
+{
+ mode_ = m;
+
+ // This is not expected to be set when data is showing,
+ // so we don't update the viewport here
+}
+
+void QHexView::set_data(const DecodeBinaryClass* data)
+{
+ data_ = data;
+
+ size_t size = 0;
+ if (data) {
+ size_t chunks = data_->chunks.size();
+ for (size_t i = 0; i < chunks; i++)
+ size += data_->chunks[i].data.size();
+ }
+ data_size_ = size;
+
+ viewport()->update();
+}
+
+unsigned int QHexView::get_bytes_per_line() const
+{
+ return BYTES_PER_LINE;
+}
+
+void QHexView::clear()
+{
+ verticalScrollBar()->setValue(0);
+ data_ = nullptr;
+ data_size_ = 0;
+
+ viewport()->update();
+}
+
+void QHexView::showFromOffset(size_t offset)
+{
+ if (data_ && (offset < data_size_)) {
+ setCursorPos(offset * 2);
+
+ int cursorY = cursorPos_ / (2 * BYTES_PER_LINE);
+ verticalScrollBar() -> setValue(cursorY);
+ }
+
+ viewport()->update();
+}
+
+QSizePolicy QHexView::sizePolicy() const
+{
+ return QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
+}
+
+pair<size_t, size_t> QHexView::get_selection() const
+{
+ size_t start = selectBegin_ / 2;
+ size_t end = selectEnd_ / 2;
+
+ if (start == end) {
+ // Nothing is currently selected
+ start = 0;
+ end = data_size_;
+ } if (end < data_size_)
+ end++;
+
+ return std::make_pair(start, end);
+}
+
+size_t QHexView::create_hex_line(size_t start, size_t end, QString* dest,
+ bool with_offset, bool with_ascii)
+{
+ dest->clear();
+
+ // Determine start address for the row
+ uint64_t row = start / BYTES_PER_LINE;
+ uint64_t offset = row * BYTES_PER_LINE;
+ end = std::min((uint64_t)end, offset + BYTES_PER_LINE);
+
+ if (with_offset)
+ dest->append(QString("%1 ").arg(row * BYTES_PER_LINE, 10, 16, QChar('0')).toUpper());
+
+ initialize_byte_iterator(offset);
+ for (size_t i = offset; i < offset + BYTES_PER_LINE; i++) {
+ uint8_t value = 0;
+
+ if (i < end)
+ value = get_next_byte();
+
+ if ((i < start) || (i >= end))
+ dest->append(" ");
+ else
+ dest->append(QString("%1 ").arg(value, 2, 16, QChar('0')).toUpper());
+ }
+
+ if (with_ascii) {
+ initialize_byte_iterator(offset);
+ for (size_t i = offset; i < end; i++) {
+ uint8_t value = get_next_byte();
+
+ if ((value < 0x20) || (value > 0x7E))
+ value = '.';
+
+ if (i < start)
+ dest->append(' ');
+ else
+ dest->append((char)value);
+ }
+ }
+
+ return end;
+}
+
+void QHexView::initialize_byte_iterator(size_t offset)
+{
+ current_chunk_id_ = 0;
+ current_chunk_offset_ = 0;
+ current_offset_ = offset;
+
+ size_t chunks = data_->chunks.size();
+ for (size_t i = 0; i < chunks; i++) {
+ size_t size = data_->chunks[i].data.size();
+
+ if (offset >= size) {
+ current_chunk_id_++;
+ offset -= size;
+ } else {
+ current_chunk_offset_ = offset;
+ break;
+ }
+ }
+
+ if (current_chunk_id_ < data_->chunks.size())
+ current_chunk_ = data_->chunks[current_chunk_id_];
+}
+
+uint8_t QHexView::get_next_byte(bool* is_next_chunk)
+{
+ if (is_next_chunk != nullptr)
+ *is_next_chunk = (current_chunk_offset_ == 0);
+
+ uint8_t v = 0;
+ if (current_chunk_offset_ < current_chunk_.data.size())
+ v = current_chunk_.data[current_chunk_offset_];
+
+ current_offset_++;
+ current_chunk_offset_++;
+
+ if (current_offset_ > data_size_) {
+ qWarning() << "QHexView::get_next_byte() overran binary data boundary:" <<
+ current_offset_ << "of" << data_size_ << "bytes";
+ return 0xEE;
+ }
+
+ if ((current_chunk_offset_ == current_chunk_.data.size()) && (current_offset_ < data_size_)) {
+ current_chunk_id_++;
+ current_chunk_offset_ = 0;
+ current_chunk_ = data_->chunks[current_chunk_id_];
+ }
+
+ return v;
+}
+
+QSize QHexView::getFullSize() const
+{
+ size_t width = posAscii_ + (BYTES_PER_LINE * charWidth_);
+
+ if (verticalScrollBar()->isEnabled())
+ width += GAP_ASCII_SLIDER + verticalScrollBar()->width();
+
+ if (!data_ || (data_size_ == 0))
+ return QSize(width, 0);
+
+ size_t height = data_size_ / BYTES_PER_LINE;
+
+ if (data_size_ % BYTES_PER_LINE)
+ height++;
+
+ height *= charHeight_;
+
+ return QSize(width, height);
+}
+
+void QHexView::paintEvent(QPaintEvent *event)
+{
+ QPainter painter(viewport());
+
+ // Calculate and update the widget and paint area sizes
+ QSize widgetSize = getFullSize();
+ setMinimumWidth(widgetSize.width());
+ setMaximumWidth(widgetSize.width());
+ QSize areaSize = viewport()->size() - QSize(0, charHeight_);
+
+ // Only show scrollbar if the content goes beyond the visible area
+ if (widgetSize.height() > areaSize.height()) {
+ verticalScrollBar()->setEnabled(true);
+ verticalScrollBar()->setPageStep(areaSize.height() / charHeight_);
+ verticalScrollBar()->setRange(0, ((widgetSize.height() - areaSize.height())) / charHeight_ + 1);
+ } else
+ verticalScrollBar()->setEnabled(false);
+
+ // Fill widget background
+ painter.fillRect(event->rect(), palette().color(QPalette::Base));
+
+ if (!data_ || (data_size_ == 0) || (data_->chunks.empty())) {
+ painter.setPen(palette().color(QPalette::Text));
+ QString s = tr("No data available");
+ int x = (areaSize.width() - fontMetrics().boundingRect(s).width()) / 2;
+ int y = areaSize.height() / 2;
+ painter.drawText(x, y, s);
+ return;
+ }
+
+ // Determine first/last line indices
+ size_t firstLineIdx = verticalScrollBar()->value();
+
+ size_t lastLineIdx = firstLineIdx + (areaSize.height() / charHeight_);
+ if (lastLineIdx > (data_size_ / BYTES_PER_LINE)) {
+ lastLineIdx = data_size_ / BYTES_PER_LINE;
+ if (data_size_ % BYTES_PER_LINE)
+ lastLineIdx++;
+ }
+
+ // Paint divider line between hex and ASCII areas
+ int line_x = posAscii_ - (GAP_HEX_ASCII / 2);
+ painter.setPen(palette().color(QPalette::Midlight));
+ painter.drawLine(line_x, event->rect().top(), line_x, height());
+
+ // Fill address area background
+ painter.fillRect(QRect(posAddr_, event->rect().top(),
+ posHex_ - (GAP_ADR_HEX / 2), height()), palette().color(QPalette::Window));
+ painter.fillRect(QRect(posAddr_, event->rect().top(),
+ posAscii_ - (GAP_HEX_ASCII / 2), charHeight_ + 2), palette().color(QPalette::Window));
+
+ // Paint address area
+ painter.setPen(palette().color(QPalette::ButtonText));
+
+ int yStart = 2 * charHeight_;
+ for (size_t lineIdx = firstLineIdx, y = yStart; lineIdx < lastLineIdx; lineIdx++) {
+
+ QString address = QString("%1").arg(lineIdx * 16, 10, 16, QChar('0')).toUpper();
+ painter.drawText(posAddr_, y, address);
+ y += charHeight_;
+ }
+
+ // Paint top row with hex offsets
+ painter.setPen(palette().color(QPalette::ButtonText));
+ for (int offset = 0; offset <= 0xF; offset++)
+ painter.drawText(posHex_ + (1 + offset * 3) * charWidth_,
+ charHeight_ - 3, QString::number(offset, 16).toUpper());
+
+ // Paint hex values
+ QBrush regular = palette().buttonText();
+ QBrush selected = palette().highlight();
+
+ bool multiple_chunks = (data_->chunks.size() > 1);
+ unsigned int chunk_color = 0;
+
+ initialize_byte_iterator(firstLineIdx * BYTES_PER_LINE);
+ yStart = 2 * charHeight_;
+ for (size_t lineIdx = firstLineIdx, y = yStart; lineIdx < lastLineIdx; lineIdx++) {
+
+ int x = posHex_;
+ for (size_t i = 0; (i < BYTES_PER_LINE) && (current_offset_ < data_size_); i++) {
+ size_t pos = (lineIdx * BYTES_PER_LINE + i) * 2;
+
+ // Fetch byte
+ bool is_next_chunk;
+ uint8_t byte_value = get_next_byte(&is_next_chunk);
+
+ if (is_next_chunk) {
+ chunk_color++;
+ if (chunk_color == chunk_colors_.size())
+ chunk_color = 0;
+ }
+
+ if ((pos >= selectBegin_) && (pos < selectEnd_)) {
+ painter.setBackgroundMode(Qt::OpaqueMode);
+ painter.setBackground(selected);
+ painter.setPen(palette().color(QPalette::HighlightedText));
+ } else {
+ painter.setBackground(regular);
+ painter.setBackgroundMode(Qt::TransparentMode);
+ if (!multiple_chunks)
+ painter.setPen(palette().color(QPalette::Text));
+ else
+ painter.setPen(chunk_colors_[chunk_color]);
+ }
+
+ // First nibble
+ QString val = QString::number((byte_value & 0xF0) >> 4, 16).toUpper();
+ painter.drawText(x, y, val);
+
+ // Second nibble
+ val = QString::number((byte_value & 0xF), 16).toUpper();
+ painter.drawText(x + charWidth_, y, val);
+
+ if ((pos >= selectBegin_) && (pos < selectEnd_ - 1) && (i < BYTES_PER_LINE - 1))
+ painter.drawText(x + 2 * charWidth_, y, QString(' '));
+
+ x += 3 * charWidth_;
+ }
+
+ y += charHeight_;
+ }
+
+ // Paint ASCII characters
+ initialize_byte_iterator(firstLineIdx * BYTES_PER_LINE);
+ yStart = 2 * charHeight_;
+ for (size_t lineIdx = firstLineIdx, y = yStart; lineIdx < lastLineIdx; lineIdx++) {
+
+ int x = posAscii_;
+ for (size_t i = 0; (i < BYTES_PER_LINE) && (current_offset_ < data_size_); i++) {
+ // Fetch byte
+ uint8_t ch = get_next_byte();
+
+ if ((ch < 0x20) || (ch > 0x7E))
+ ch = '.';
+
+ size_t pos = (lineIdx * BYTES_PER_LINE + i) * 2;
+ if ((pos >= selectBegin_) && (pos < selectEnd_)) {
+ painter.setBackgroundMode(Qt::OpaqueMode);
+ painter.setBackground(selected);
+ painter.setPen(palette().color(QPalette::HighlightedText));
+ } else {
+ painter.setBackgroundMode(Qt::TransparentMode);
+ painter.setBackground(regular);
+ painter.setPen(palette().color(QPalette::Text));
+ }
+
+ painter.drawText(x, y, QString(ch));
+ x += charWidth_;
+ }
+
+ y += charHeight_;
+ }
+
+ // Paint cursor
+ if (hasFocus()) {
+ int x = (cursorPos_ % (2 * BYTES_PER_LINE));
+ int y = cursorPos_ / (2 * BYTES_PER_LINE);
+ y -= firstLineIdx;
+ int cursorX = (((x / 2) * 3) + (x % 2)) * charWidth_ + posHex_;
+ int cursorY = charHeight_ + y * charHeight_ + 4;
+ painter.fillRect(cursorX, cursorY, 2, charHeight_, palette().color(QPalette::WindowText));
+ }
+}
+
+void QHexView::keyPressEvent(QKeyEvent *event)
+{
+ bool setVisible = false;
+
+ // Cursor movements
+ if (event->matches(QKeySequence::MoveToNextChar)) {
+ setCursorPos(cursorPos_ + 1);
+ resetSelection(cursorPos_);
+ setVisible = true;
+ }
+ if (event->matches(QKeySequence::MoveToPreviousChar)) {
+ setCursorPos(cursorPos_ - 1);
+ resetSelection(cursorPos_);
+ setVisible = true;
+ }
+
+ if (event->matches(QKeySequence::MoveToEndOfLine)) {
+ setCursorPos(cursorPos_ | ((BYTES_PER_LINE * 2) - 1));
+ resetSelection(cursorPos_);
+ setVisible = true;
+ }
+ if (event->matches(QKeySequence::MoveToStartOfLine)) {
+ setCursorPos(cursorPos_ | (cursorPos_ % (BYTES_PER_LINE * 2)));
+ resetSelection(cursorPos_);
+ setVisible = true;
+ }
+ if (event->matches(QKeySequence::MoveToPreviousLine)) {
+ setCursorPos(cursorPos_ - BYTES_PER_LINE * 2);
+ resetSelection(cursorPos_);
+ setVisible = true;
+ }
+ if (event->matches(QKeySequence::MoveToNextLine)) {
+ setCursorPos(cursorPos_ + BYTES_PER_LINE * 2);
+ resetSelection(cursorPos_);
+ setVisible = true;
+ }
+
+ if (event->matches(QKeySequence::MoveToNextPage)) {
+ setCursorPos(cursorPos_ + (viewport()->height() / charHeight_ - 1) * 2 * BYTES_PER_LINE);
+ resetSelection(cursorPos_);
+ setVisible = true;
+ }
+ if (event->matches(QKeySequence::MoveToPreviousPage)) {
+ setCursorPos(cursorPos_ - (viewport()->height() / charHeight_ - 1) * 2 * BYTES_PER_LINE);
+ resetSelection(cursorPos_);
+ setVisible = true;
+ }
+ if (event->matches(QKeySequence::MoveToEndOfDocument)) {
+ setCursorPos(data_size_ * 2);
+ resetSelection(cursorPos_);
+ setVisible = true;
+ }
+ if (event->matches(QKeySequence::MoveToStartOfDocument)) {
+ setCursorPos(0);
+ resetSelection(cursorPos_);
+ setVisible = true;
+ }
+
+ // Select commands
+ if (event->matches(QKeySequence::SelectAll)) {
+ resetSelection(0);
+ setSelection(2 * data_size_);
+ setVisible = true;
+ }
+ if (event->matches(QKeySequence::SelectNextChar)) {
+ int pos = cursorPos_ + 1;
+ setCursorPos(pos);
+ setSelection(pos);
+ setVisible = true;
+ }
+ if (event->matches(QKeySequence::SelectPreviousChar)) {
+ int pos = cursorPos_ - 1;
+ setSelection(pos);
+ setCursorPos(pos);
+ setVisible = true;
+ }
+ if (event->matches(QKeySequence::SelectEndOfLine)) {
+ int pos = cursorPos_ - (cursorPos_ % (2 * BYTES_PER_LINE)) + (2 * BYTES_PER_LINE);
+ setCursorPos(pos);
+ setSelection(pos);
+ setVisible = true;
+ }
+ if (event->matches(QKeySequence::SelectStartOfLine)) {
+ int pos = cursorPos_ - (cursorPos_ % (2 * BYTES_PER_LINE));
+ setCursorPos(pos);
+ setSelection(pos);
+ setVisible = true;
+ }
+ if (event->matches(QKeySequence::SelectPreviousLine)) {
+ int pos = cursorPos_ - (2 * BYTES_PER_LINE);
+ setCursorPos(pos);
+ setSelection(pos);
+ setVisible = true;
+ }
+ if (event->matches(QKeySequence::SelectNextLine)) {
+ int pos = cursorPos_ + (2 * BYTES_PER_LINE);
+ setCursorPos(pos);
+ setSelection(pos);
+ setVisible = true;
+ }
+
+ if (event->matches(QKeySequence::SelectNextPage)) {
+ int pos = cursorPos_ + (((viewport()->height() / charHeight_) - 1) * 2 * BYTES_PER_LINE);
+ setCursorPos(pos);
+ setSelection(pos);
+ setVisible = true;
+ }
+ if (event->matches(QKeySequence::SelectPreviousPage)) {
+ int pos = cursorPos_ - (((viewport()->height() / charHeight_) - 1) * 2 * BYTES_PER_LINE);
+ setCursorPos(pos);
+ setSelection(pos);
+ setVisible = true;
+ }
+ if (event->matches(QKeySequence::SelectEndOfDocument)) {
+ int pos = data_size_ * 2;
+ setCursorPos(pos);
+ setSelection(pos);
+ setVisible = true;
+ }
+ if (event->matches(QKeySequence::SelectStartOfDocument)) {
+ setCursorPos(0);
+ setSelection(0);
+ setVisible = true;
+ }
+
+ if (event->matches(QKeySequence::Copy) && (data_)) {
+ QString text;
+
+ initialize_byte_iterator(selectBegin_ / 2);
+
+ size_t selectedSize = (selectEnd_ - selectBegin_ + 1) / 2;
+ for (size_t i = 0; i < selectedSize; i++) {
+ uint8_t byte_value = get_next_byte();
+
+ QString s = QString::number((byte_value & 0xF0) >> 4, 16).toUpper() +
+ QString::number((byte_value & 0xF), 16).toUpper() + " ";
+ text += s;
+
+ if (i % BYTES_PER_LINE == (BYTES_PER_LINE - 1))
+ text += "\n";
+ }
+
+ QClipboard *clipboard = QApplication::clipboard();
+ clipboard->setText(text, QClipboard::Clipboard);
+ if (clipboard->supportsSelection())
+ clipboard->setText(text, QClipboard::Selection);
+ }
+
+ if (setVisible)
+ ensureVisible();
+
+ viewport()->update();
+}
+
+void QHexView::mouseMoveEvent(QMouseEvent *event)
+{
+ int actPos = cursorPosFromMousePos(event->pos());
+ setCursorPos(actPos);
+ setSelection(actPos);
+
+ viewport()->update();
+}
+
+void QHexView::mousePressEvent(QMouseEvent *event)
+{
+ int cPos = cursorPosFromMousePos(event->pos());
+
+ if ((QApplication::keyboardModifiers() & Qt::ShiftModifier) && (event->button() == Qt::LeftButton))
+ setSelection(cPos);
+ else
+ resetSelection(cPos);
+
+ setCursorPos(cPos);
+
+ viewport()->update();
+}
+
+size_t QHexView::cursorPosFromMousePos(const QPoint &position)
+{
+ size_t pos = -1;
+
+ if (((size_t)position.x() >= posHex_) &&
+ ((size_t)position.x() < (posHex_ + HEXCHARS_IN_LINE * charWidth_))) {
+
+ // Note: We add 1.5 character widths so that selection across
+ // byte gaps is smoother
+ size_t x = (position.x() + (1.5 * charWidth_ / 2) - posHex_) / charWidth_;
+
+ // Note: We allow only full bytes to be selected, not nibbles,
+ // so we round to the nearest byte gap
+ x = (2 * x + 1) / 3;
+
+ size_t firstLineIdx = verticalScrollBar()->value();
+ size_t y = ((position.y() / charHeight_) - 1) * 2 * BYTES_PER_LINE;
+ pos = x + y + firstLineIdx * BYTES_PER_LINE * 2;
+ }
+
+ size_t max_pos = data_size_ * 2;
+
+ return std::min(pos, max_pos);
+}
+
+void QHexView::resetSelection()
+{
+ selectBegin_ = selectInit_;
+ selectEnd_ = selectInit_;
+}
+
+void QHexView::resetSelection(int pos)
+{
+ if (pos < 0)
+ pos = 0;
+
+ selectInit_ = pos;
+ selectBegin_ = pos;
+ selectEnd_ = pos;
+}
+
+void QHexView::setSelection(int pos)
+{
+ if (pos < 0)
+ pos = 0;
+
+ if ((size_t)pos >= selectInit_) {
+ selectEnd_ = pos;
+ selectBegin_ = selectInit_;
+ } else {
+ selectBegin_ = pos;
+ selectEnd_ = selectInit_;
+ }
+}
+
+void QHexView::setCursorPos(int position)
+{
+ if (position < 0)
+ position = 0;
+
+ int max_pos = data_size_ * 2;
+
+ if (position > max_pos)
+ position = max_pos;
+
+ cursorPos_ = position;
+}
+
+void QHexView::ensureVisible()
+{
+ QSize areaSize = viewport()->size();
+
+ int firstLineIdx = verticalScrollBar()->value();
+ int lastLineIdx = firstLineIdx + areaSize.height() / charHeight_;
+
+ int cursorY = cursorPos_ / (2 * BYTES_PER_LINE);
+
+ if (cursorY < firstLineIdx)
+ verticalScrollBar()->setValue(cursorY);
+ else
+ if(cursorY >= lastLineIdx)
+ verticalScrollBar()->setValue(cursorY - areaSize.height() / charHeight_ + 1);
+}
--- /dev/null
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2015 Victor Anjin <virinext@gmail.com>
+ * Copyright (C) 2019 Soeren Apel <soeren@apelpie.net>
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015
+ *
+ * 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.
+ */
+
+#ifndef PULSEVIEW_PV_VIEWS_DECODERBINARY_QHEXVIEW_H
+#define PULSEVIEW_PV_VIEWS_DECODERBINARY_QHEXVIEW_H
+
+#include <QAbstractScrollArea>
+
+#include <pv/data/decodesignal.hpp>
+
+using std::pair;
+using std::size_t;
+using pv::data::DecodeBinaryClass;
+using pv::data::DecodeBinaryDataChunk;
+
+class QHexView: public QAbstractScrollArea
+{
+ Q_OBJECT
+
+public:
+ enum Mode {
+ ChunkedDataMode, ///< Displays all data chunks in succession
+ MemoryEmulationMode ///< Reconstructs memory contents from data chunks
+ };
+
+public:
+ QHexView(QWidget *parent = nullptr);
+
+ void set_mode(Mode m);
+ void set_data(const DecodeBinaryClass* data);
+ unsigned int get_bytes_per_line() const;
+
+ void clear();
+ void showFromOffset(size_t offset);
+ virtual QSizePolicy sizePolicy() const;
+
+ pair<size_t, size_t> get_selection() const;
+
+ size_t create_hex_line(size_t start, size_t end, QString* dest,
+ bool with_offset=false, bool with_ascii=false);
+
+protected:
+ void initialize_byte_iterator(size_t offset);
+ uint8_t get_next_byte(bool* is_next_chunk = nullptr);
+
+ void paintEvent(QPaintEvent *event);
+ void keyPressEvent(QKeyEvent *event);
+ void mouseMoveEvent(QMouseEvent *event);
+ void mousePressEvent(QMouseEvent *event);
+
+private:
+ QSize getFullSize() const;
+ void resetSelection();
+ void resetSelection(int pos);
+ void setSelection(int pos);
+ void ensureVisible();
+ void setCursorPos(int pos);
+ size_t cursorPosFromMousePos(const QPoint &position);
+
+private:
+ Mode mode_;
+ const DecodeBinaryClass* data_;
+ size_t data_size_;
+
+ size_t posAddr_, posHex_, posAscii_;
+ size_t charWidth_, charHeight_;
+ size_t selectBegin_, selectEnd_, selectInit_, cursorPos_;
+
+ size_t current_chunk_id_, current_chunk_offset_, current_offset_;
+ DecodeBinaryDataChunk current_chunk_; // Cache locally so that we're not messed up when the vector is re-allocating its data
+
+ vector<QColor> chunk_colors_;
+};
+
+#endif /* PULSEVIEW_PV_VIEWS_DECODERBINARY_QHEXVIEW_H */
--- /dev/null
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2019 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 <climits>
+
+#include <QByteArray>
+#include <QDebug>
+#include <QFileDialog>
+#include <QLabel>
+#include <QMenu>
+#include <QMessageBox>
+#include <QToolBar>
+#include <QVBoxLayout>
+
+#include <libsigrokdecode/libsigrokdecode.h>
+
+#include "view.hpp"
+#include "QHexView.hpp"
+
+#include "pv/globalsettings.hpp"
+#include "pv/session.hpp"
+#include "pv/util.hpp"
+#include "pv/data/decode/decoder.hpp"
+
+using pv::data::DecodeSignal;
+using pv::data::SignalBase;
+using pv::data::decode::Decoder;
+using pv::util::Timestamp;
+
+using std::shared_ptr;
+
+namespace pv {
+namespace views {
+namespace decoder_binary {
+
+const char* SaveTypeNames[SaveTypeCount] = {
+ "Binary",
+ "Hex Dump, plain",
+ "Hex Dump, with offset",
+ "Hex Dump, canonical"
+};
+
+
+View::View(Session &session, bool is_main_view, QMainWindow *parent) :
+ ViewBase(session, is_main_view, parent),
+
+ // Note: Place defaults in View::reset_view_state(), not here
+ parent_(parent),
+ decoder_selector_(new QComboBox()),
+ format_selector_(new QComboBox()),
+ class_selector_(new QComboBox()),
+ stacked_widget_(new QStackedWidget()),
+ hex_view_(new QHexView()),
+ save_button_(new QToolButton()),
+ save_action_(new QAction(this)),
+ signal_(nullptr)
+{
+ QVBoxLayout *root_layout = new QVBoxLayout(this);
+ root_layout->setContentsMargins(0, 0, 0, 0);
+
+ // Create toolbar
+ QToolBar* toolbar = new QToolBar();
+ toolbar->setContextMenuPolicy(Qt::PreventContextMenu);
+ parent->addToolBar(toolbar);
+
+ // Populate toolbar
+ toolbar->addWidget(new QLabel(tr("Decoder:")));
+ toolbar->addWidget(decoder_selector_);
+ toolbar->addWidget(class_selector_);
+ toolbar->addSeparator();
+ toolbar->addWidget(new QLabel(tr("Show data as")));
+ toolbar->addWidget(format_selector_);
+ toolbar->addSeparator();
+ toolbar->addWidget(save_button_);
+
+ // Add format types
+ format_selector_->addItem(tr("Hexdump"), qVariantFromValue(QString("text/hexdump")));
+
+ // Add widget stack
+ root_layout->addWidget(stacked_widget_);
+ stacked_widget_->addWidget(hex_view_);
+ stacked_widget_->setCurrentIndex(0);
+
+ connect(decoder_selector_, SIGNAL(currentIndexChanged(int)),
+ this, SLOT(on_selected_decoder_changed(int)));
+ connect(class_selector_, SIGNAL(currentIndexChanged(int)),
+ this, SLOT(on_selected_class_changed(int)));
+
+ // Configure widgets
+ decoder_selector_->setSizeAdjustPolicy(QComboBox::AdjustToContents);
+ class_selector_->setSizeAdjustPolicy(QComboBox::AdjustToContents);
+
+ // Configure actions
+ save_action_->setText(tr("&Save..."));
+ save_action_->setIcon(QIcon::fromTheme("document-save-as",
+ QIcon(":/icons/document-save-as.png")));
+ save_action_->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_S));
+ connect(save_action_, SIGNAL(triggered(bool)),
+ this, SLOT(on_actionSave_triggered()));
+
+ QMenu *save_menu = new QMenu();
+ connect(save_menu, SIGNAL(triggered(QAction*)),
+ this, SLOT(on_actionSave_triggered(QAction*)));
+
+ for (int i = 0; i < SaveTypeCount; i++) {
+ QAction *const action = save_menu->addAction(tr(SaveTypeNames[i]));
+ action->setData(qVariantFromValue(i));
+ }
+
+ save_button_->setMenu(save_menu);
+ save_button_->setDefaultAction(save_action_);
+ save_button_->setPopupMode(QToolButton::MenuButtonPopup);
+
+ parent->setSizePolicy(hex_view_->sizePolicy()); // TODO Must be updated when selected widget changes
+
+ reset_view_state();
+}
+
+ViewType View::get_type() const
+{
+ return ViewTypeDecoderBinary;
+}
+
+void View::reset_view_state()
+{
+ ViewBase::reset_view_state();
+
+ decoder_selector_->clear();
+ class_selector_->clear();
+ format_selector_->setCurrentIndex(0);
+ save_button_->setEnabled(false);
+
+ hex_view_->clear();
+}
+
+void View::clear_decode_signals()
+{
+ ViewBase::clear_decode_signals();
+
+ reset_data();
+ reset_view_state();
+}
+
+void View::add_decode_signal(shared_ptr<data::DecodeSignal> signal)
+{
+ ViewBase::add_decode_signal(signal);
+
+ connect(signal.get(), SIGNAL(name_changed(const QString&)),
+ this, SLOT(on_signal_name_changed(const QString&)));
+ connect(signal.get(), SIGNAL(decoder_stacked(void*)),
+ this, SLOT(on_decoder_stacked(void*)));
+ connect(signal.get(), SIGNAL(decoder_removed(void*)),
+ this, SLOT(on_decoder_removed(void*)));
+
+ // Add all decoders provided by this signal
+ auto stack = signal->decoder_stack();
+ if (stack.size() > 1) {
+ for (const shared_ptr<Decoder>& dec : stack)
+ // Only add the decoder if it has binary output
+ if (dec->get_binary_class_count() > 0) {
+ QString title = QString("%1 (%2)").arg(signal->name(), dec->name());
+ decoder_selector_->addItem(title, QVariant::fromValue((void*)dec.get()));
+ }
+ } else
+ if (!stack.empty()) {
+ shared_ptr<Decoder>& dec = stack.at(0);
+ if (dec->get_binary_class_count() > 0)
+ decoder_selector_->addItem(signal->name(), QVariant::fromValue((void*)dec.get()));
+ }
+}
+
+void View::remove_decode_signal(shared_ptr<data::DecodeSignal> signal)
+{
+ // Remove all decoders provided by this signal
+ for (const shared_ptr<Decoder>& dec : signal->decoder_stack()) {
+ int index = decoder_selector_->findData(QVariant::fromValue((void*)dec.get()));
+
+ if (index != -1)
+ decoder_selector_->removeItem(index);
+ }
+
+ ViewBase::remove_decode_signal(signal);
+
+ if (signal.get() == signal_) {
+ reset_data();
+ update_data();
+ reset_view_state();
+ }
+}
+
+void View::save_settings(QSettings &settings) const
+{
+ (void)settings;
+}
+
+void View::restore_settings(QSettings &settings)
+{
+ // Note: It is assumed that this function is only called once,
+ // immediately after restoring a previous session.
+ (void)settings;
+}
+
+void View::reset_data()
+{
+ signal_ = nullptr;
+ decoder_ = nullptr;
+ bin_class_id_ = 0;
+ binary_data_exists_ = false;
+
+ hex_view_->clear();
+}
+
+void View::update_data()
+{
+ if (!signal_)
+ return;
+
+ const DecodeBinaryClass* bin_class =
+ signal_->get_binary_data_class(current_segment_, decoder_, bin_class_id_);
+
+ hex_view_->set_data(bin_class);
+
+ if (!binary_data_exists_)
+ return;
+
+ if (!save_button_->isEnabled())
+ save_button_->setEnabled(true);
+}
+
+void View::save_data() const
+{
+ assert(decoder_);
+ assert(signal_);
+
+ if (!signal_)
+ return;
+
+ GlobalSettings settings;
+ const QString dir = settings.value("MainWindow/SaveDirectory").toString();
+
+ const QString file_name = QFileDialog::getSaveFileName(
+ parent_, tr("Save Binary Data"), dir, tr("Binary Data Files (*.bin);;All Files (*)"));
+
+ if (file_name.isEmpty())
+ return;
+
+ QFile file(file_name);
+ if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
+ pair<size_t, size_t> selection = hex_view_->get_selection();
+
+ vector<uint8_t> data;
+ data.resize(selection.second - selection.first + 1);
+
+ signal_->get_merged_binary_data_chunks_by_offset(current_segment_, decoder_,
+ bin_class_id_, selection.first, selection.second, &data);
+
+ int64_t bytes_written = file.write((const char*)data.data(), data.size());
+
+ if ((bytes_written == -1) || ((uint64_t)bytes_written != data.size())) {
+ QMessageBox msg(parent_);
+ msg.setText(tr("Error") + "\n\n" + tr("File %1 could not be written to.").arg(file_name));
+ msg.setStandardButtons(QMessageBox::Ok);
+ msg.setIcon(QMessageBox::Warning);
+ msg.exec();
+ return;
+ }
+ }
+}
+
+void View::save_data_as_hex_dump(bool with_offset, bool with_ascii) const
+{
+ assert(decoder_);
+ assert(signal_);
+
+ if (!signal_)
+ return;
+
+ GlobalSettings settings;
+ const QString dir = settings.value("MainWindow/SaveDirectory").toString();
+
+ const QString file_name = QFileDialog::getSaveFileName(
+ parent_, tr("Save Binary Data"), dir, tr("Hex Dumps (*.txt);;All Files (*)"));
+
+ if (file_name.isEmpty())
+ return;
+
+ QFile file(file_name);
+ if (file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
+ pair<size_t, size_t> selection = hex_view_->get_selection();
+
+ vector<uint8_t> data;
+ data.resize(selection.second - selection.first + 1);
+
+ signal_->get_merged_binary_data_chunks_by_offset(current_segment_, decoder_,
+ bin_class_id_, selection.first, selection.second, &data);
+
+ QTextStream out_stream(&file);
+
+ uint64_t offset = selection.first;
+ uint64_t n = hex_view_->get_bytes_per_line();
+ QString s;
+
+ while (offset < selection.second) {
+ size_t end = std::min((uint64_t)(selection.second), offset + n);
+ offset = hex_view_->create_hex_line(offset, end, &s, with_offset, with_ascii);
+ out_stream << s << endl;
+ }
+
+ out_stream << endl;
+
+ if (out_stream.status() != QTextStream::Ok) {
+ QMessageBox msg(parent_);
+ msg.setText(tr("Error") + "\n\n" + tr("File %1 could not be written to.").arg(file_name));
+ msg.setStandardButtons(QMessageBox::Ok);
+ msg.setIcon(QMessageBox::Warning);
+ msg.exec();
+ return;
+ }
+ }
+}
+
+void View::on_selected_decoder_changed(int index)
+{
+ if (signal_)
+ disconnect(signal_, SIGNAL(new_binary_data(unsigned int, void*, unsigned int)));
+
+ reset_data();
+
+ decoder_ = (Decoder*)decoder_selector_->itemData(index).value<void*>();
+
+ // Find the signal that contains the selected decoder
+ for (const shared_ptr<DecodeSignal>& ds : decode_signals_)
+ for (const shared_ptr<Decoder>& dec : ds->decoder_stack())
+ if (decoder_ == dec.get())
+ signal_ = ds.get();
+
+ class_selector_->clear();
+
+ if (signal_) {
+ // Populate binary class selector
+ uint32_t bin_classes = decoder_->get_binary_class_count();
+ for (uint32_t i = 0; i < bin_classes; i++) {
+ const data::decode::DecodeBinaryClassInfo* class_info = decoder_->get_binary_class(i);
+ class_selector_->addItem(class_info->description, QVariant::fromValue(i));
+ }
+
+ connect(signal_, SIGNAL(new_binary_data(unsigned int, void*, unsigned int)),
+ this, SLOT(on_new_binary_data(unsigned int, void*, unsigned int)));
+ }
+
+ update_data();
+}
+
+void View::on_selected_class_changed(int index)
+{
+ bin_class_id_ = class_selector_->itemData(index).value<uint32_t>();
+
+ binary_data_exists_ =
+ signal_->get_binary_data_chunk_count(current_segment_, decoder_, bin_class_id_);
+
+ update_data();
+}
+
+void View::on_signal_name_changed(const QString &name)
+{
+ (void)name;
+
+ SignalBase* sb = qobject_cast<SignalBase*>(QObject::sender());
+ assert(sb);
+
+ DecodeSignal* signal = dynamic_cast<DecodeSignal*>(sb);
+ assert(signal);
+
+ // Update all decoder entries provided by this signal
+ auto stack = signal->decoder_stack();
+ if (stack.size() > 1) {
+ for (const shared_ptr<Decoder>& dec : stack) {
+ QString title = QString("%1 (%2)").arg(signal->name(), dec->name());
+ int index = decoder_selector_->findData(QVariant::fromValue((void*)dec.get()));
+
+ if (index != -1)
+ decoder_selector_->setItemText(index, title);
+ }
+ } else
+ if (!stack.empty()) {
+ shared_ptr<Decoder>& dec = stack.at(0);
+ int index = decoder_selector_->findData(QVariant::fromValue((void*)dec.get()));
+
+ if (index != -1)
+ decoder_selector_->setItemText(index, signal->name());
+ }
+}
+
+void View::on_new_binary_data(unsigned int segment_id, void* decoder, unsigned int bin_class_id)
+{
+ if ((segment_id == current_segment_) && (decoder == decoder_) && (bin_class_id == bin_class_id_))
+ if (!delayed_view_updater_.isActive())
+ delayed_view_updater_.start();
+}
+
+void View::on_decoder_stacked(void* decoder)
+{
+ // TODO This doesn't change existing entries for the same signal - but it should as the naming scheme may change
+
+ Decoder* d = static_cast<Decoder*>(decoder);
+
+ // Only add the decoder if it has binary output
+ if (d->get_binary_class_count() == 0)
+ return;
+
+ // Find the signal that contains the selected decoder
+ DecodeSignal* signal = nullptr;
+
+ for (const shared_ptr<DecodeSignal>& ds : decode_signals_)
+ for (const shared_ptr<Decoder>& dec : ds->decoder_stack())
+ if (d == dec.get())
+ signal = ds.get();
+
+ assert(signal);
+
+ // Add the decoder to the list
+ QString title = QString("%1 (%2)").arg(signal->name(), d->name());
+ decoder_selector_->addItem(title, QVariant::fromValue((void*)d));
+}
+
+void View::on_decoder_removed(void* decoder)
+{
+ Decoder* d = static_cast<Decoder*>(decoder);
+
+ // Remove the decoder from the list
+ int index = decoder_selector_->findData(QVariant::fromValue((void*)d));
+
+ if (index != -1)
+ decoder_selector_->removeItem(index);
+}
+
+void View::on_actionSave_triggered(QAction* action)
+{
+ int save_type = SaveTypeBinary;
+ if (action)
+ save_type = action->data().toInt();
+
+ switch (save_type)
+ {
+ case SaveTypeBinary: save_data(); break;
+ case SaveTypeHexDumpPlain: save_data_as_hex_dump(false, false); break;
+ case SaveTypeHexDumpWithOffset: save_data_as_hex_dump(true, false); break;
+ case SaveTypeHexDumpComplete: save_data_as_hex_dump(true, true); break;
+ }
+}
+
+void View::perform_delayed_view_update()
+{
+ if (signal_ && !binary_data_exists_)
+ if (signal_->get_binary_data_chunk_count(current_segment_, decoder_, bin_class_id_))
+ binary_data_exists_ = true;
+
+ update_data();
+}
+
+
+} // namespace decoder_binary
+} // namespace views
+} // namespace pv
--- /dev/null
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2019 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_VIEWS_DECODERBINARY_VIEW_HPP
+#define PULSEVIEW_PV_VIEWS_DECODERBINARY_VIEW_HPP
+
+#include <QAction>
+#include <QComboBox>
+#include <QStackedWidget>
+#include <QToolButton>
+
+#include <pv/views/viewbase.hpp>
+#include <pv/data/decodesignal.hpp>
+
+#include "QHexView.hpp"
+
+namespace pv {
+
+class Session;
+
+namespace views {
+
+namespace decoder_binary {
+
+// When adding an entry here, don't forget to update SaveTypeNames as well
+enum SaveType {
+ SaveTypeBinary,
+ SaveTypeHexDumpPlain,
+ SaveTypeHexDumpWithOffset,
+ SaveTypeHexDumpComplete,
+ SaveTypeCount // Indicates how many save types there are, must always be last
+};
+
+extern const char* SaveTypeNames[SaveTypeCount];
+
+
+class View : public ViewBase
+{
+ Q_OBJECT
+
+public:
+ explicit View(Session &session, bool is_main_view=false, QMainWindow *parent = nullptr);
+
+ virtual ViewType get_type() const;
+
+ /**
+ * 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();
+
+ virtual void clear_decode_signals();
+ virtual void add_decode_signal(shared_ptr<data::DecodeSignal> signal);
+ virtual void remove_decode_signal(shared_ptr<data::DecodeSignal> signal);
+
+ virtual void save_settings(QSettings &settings) const;
+ virtual void restore_settings(QSettings &settings);
+
+private:
+ void reset_data();
+ void update_data();
+
+ void save_data() const;
+ void save_data_as_hex_dump(bool with_offset=false, bool with_ascii=false) const;
+
+private Q_SLOTS:
+ void on_selected_decoder_changed(int index);
+ void on_selected_class_changed(int index);
+ void on_signal_name_changed(const QString &name);
+ void on_new_binary_data(unsigned int segment_id, void* decoder, unsigned int bin_class_id);
+
+ void on_decoder_stacked(void* decoder);
+ void on_decoder_removed(void* decoder);
+
+ void on_actionSave_triggered(QAction* action = nullptr);
+
+ virtual void perform_delayed_view_update();
+
+private:
+ QWidget* parent_;
+
+ QComboBox *decoder_selector_, *format_selector_, *class_selector_;
+ QStackedWidget *stacked_widget_;
+ QHexView *hex_view_;
+
+ QToolButton* save_button_;
+ QAction* save_action_;
+
+ data::DecodeSignal *signal_;
+ const data::decode::Decoder *decoder_;
+ uint32_t bin_class_id_;
+ bool binary_data_exists_;
+};
+
+} // namespace decoder_binary
+} // namespace views
+} // namespace pv
+
+#endif // PULSEVIEW_PV_VIEWS_DECODERBINARY_VIEW_HPP
const float AnalogSignal::EnvelopeThreshold = 64.0f;
const int AnalogSignal::MaximumVDivs = 10;
-const int AnalogSignal::MinScaleIndex = -6;
-const int AnalogSignal::MaxScaleIndex = 7;
+const int AnalogSignal::MinScaleIndex = -6; // 0.01 units/div
+const int AnalogSignal::MaxScaleIndex = 10; // 1000 units/div
const int AnalogSignal::InfoTextMarginRight = 20;
const int AnalogSignal::InfoTextMarginBottom = 5;
pv::Session &session,
shared_ptr<data::SignalBase> base) :
Signal(session, base),
+ value_at_hover_pos_(std::numeric_limits<float>::quiet_NaN()),
scale_index_(4), // 20 per div
pos_vdivs_(1),
neg_vdivs_(1),
resolution_(0),
display_type_(DisplayBoth),
- autoranging_(true),
- value_at_hover_pos_(std::numeric_limits<float>::quiet_NaN())
+ autoranging_(true)
{
axis_pen_ = AxisPen;
return base_->analog_data();
}
-void AnalogSignal::save_settings(QSettings &settings) const
+std::map<QString, QVariant> AnalogSignal::save_settings() const
{
- settings.setValue("pos_vdivs", pos_vdivs_);
- settings.setValue("neg_vdivs", neg_vdivs_);
- settings.setValue("scale_index", scale_index_);
- settings.setValue("display_type", display_type_);
- settings.setValue("autoranging", autoranging_);
- settings.setValue("div_height", div_height_);
+ std::map<QString, QVariant> result;
+
+ result["pos_vdivs"] = pos_vdivs_;
+ result["neg_vdivs"] = neg_vdivs_;
+ result["scale_index"] = scale_index_;
+ result["display_type"] = display_type_;
+ result["autoranging"] = pos_vdivs_;
+ result["div_height"] = div_height_;
+
+ return result;
}
-void AnalogSignal::restore_settings(QSettings &settings)
+void AnalogSignal::restore_settings(std::map<QString, QVariant> settings)
{
- if (settings.contains("pos_vdivs"))
- pos_vdivs_ = settings.value("pos_vdivs").toInt();
+ auto entry = settings.find("pos_vdivs");
+ if (entry != settings.end())
+ pos_vdivs_ = settings["pos_vdivs"].toInt();
- if (settings.contains("neg_vdivs"))
- neg_vdivs_ = settings.value("neg_vdivs").toInt();
+ entry = settings.find("neg_vdivs");
+ if (entry != settings.end())
+ neg_vdivs_ = settings["neg_vdivs"].toInt();
- if (settings.contains("scale_index")) {
- scale_index_ = settings.value("scale_index").toInt();
+ entry = settings.find("scale_index");
+ if (entry != settings.end()) {
+ scale_index_ = settings["scale_index"].toInt();
update_scale();
}
- if (settings.contains("display_type"))
- display_type_ = (DisplayType)(settings.value("display_type").toInt());
+ entry = settings.find("display_type");
+ if (entry != settings.end())
+ display_type_ = (DisplayType)(settings["display_type"].toInt());
- if (settings.contains("autoranging"))
- autoranging_ = settings.value("autoranging").toBool();
+ entry = settings.find("autoranging");
+ if (entry != settings.end())
+ autoranging_ = settings["autoranging"].toBool();
- if (settings.contains("div_height")) {
+ entry = settings.find("div_height");
+ if (entry != settings.end()) {
const int old_height = div_height_;
- div_height_ = settings.value("div_height").toInt();
+ div_height_ = settings["div_height"].toInt();
if ((div_height_ != old_height) && owner_) {
// Call order is important, otherwise the lazy event handler won't work
// 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(format_value_si(value_at_hover_pos_, SIPrefix::unspecified, 2, "V", false))
.arg(resolution_);
} else
infotext = QString("%1 V/div").arg(resolution_);
void AnalogSignal::paint_grid(QPainter &p, int y, int left, int right)
{
+ bool was_antialiased = p.testRenderHint(QPainter::Antialiasing);
p.setRenderHint(QPainter::Antialiasing, false);
if (pos_vdivs_ > 0) {
}
}
- p.setRenderHint(QPainter::Antialiasing, true);
+ p.setRenderHint(QPainter::Antialiasing, was_antialiased);
}
void AnalogSignal::paint_trace(QPainter &p,
}
delete[] sample_block;
- p.drawPolyline(points, points_count);
+ // QPainter::drawPolyline() is slow, let's paint the lines ourselves
+ for (int64_t i = 1; i < points_count; i++)
+ p.drawLine(points[i - 1], points[i]);
if (show_sampling_points) {
if (paint_thr_dots) {
if (segments.empty())
return;
- static double prev_min = 0, prev_max = 0;
+ double signal_min_ = 0, signal_max_ = 0;
double min = 0, max = 0;
for (const shared_ptr<pv::data::AnalogSegment>& segment : segments) {
max = std::max(max, mm.second);
}
- if ((min == prev_min) && (max == prev_max) && !force_update)
+ if ((min == signal_min_) && (max == signal_max_) && !force_update)
return;
- prev_min = min;
- prev_max = max;
+ signal_min_ = min;
+ signal_max_ = max;
// If we're allowed to alter the div assignment...
if (!keep_divs) {
if (hp.x() <= 0) {
value_at_hover_pos_ = std::numeric_limits<float>::quiet_NaN();
} else {
- try {
+ if ((size_t)hp.x() < value_at_pixel_pos_.size())
value_at_hover_pos_ = value_at_pixel_pos_.at(hp.x());
- } catch (out_of_range&) {
+ else
value_at_hover_pos_ = std::numeric_limits<float>::quiet_NaN();
- }
}
}
shared_ptr<pv::data::SignalData> data() const;
- virtual void save_settings(QSettings &settings) const;
-
- virtual void restore_settings(QSettings &settings);
+ virtual std::map<QString, QVariant> save_settings() const;
+ virtual void restore_settings(std::map<QString, QVariant> settings);
/**
* Computes the vertical extents of the contents of this row item.
*display_type_cb_;
QSpinBox *pvdiv_sb_, *nvdiv_sb_, *div_height_sb_;
- float scale_;
- int scale_index_;
-
- int div_height_;
- int pos_vdivs_, neg_vdivs_; // divs per positive/negative side
- float resolution_; // e.g. 10 for 10 V/div
+ double signal_min_, signal_max_; // Min/max values of this signal's analog data
bool show_analog_minor_grid_;
QColor high_fill_color_;
bool show_sampling_points_, fill_high_areas_;
- DisplayType display_type_;
- bool autoranging_;
int conversion_threshold_disp_mode_;
vector<float> value_at_pixel_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
+
+ // ---------------------------------------------------------------------------
+ // Note: Make sure to update .. when adding a trace-configurable variable here
+ float scale_;
+ int scale_index_;
+
+ int div_height_;
+ int pos_vdivs_, neg_vdivs_; // divs per positive/negative side
+ float resolution_; // e.g. 10 for 10 V/div
+
+ DisplayType display_type_;
+ bool autoranging_;
};
} // namespace trace
#include <QApplication>
#include <QBrush>
+#include <QMenu>
#include <QPainter>
#include <QPointF>
#include <QRect>
const pv::util::Timestamp& diff = abs(time_ - other->time_);
return Ruler::format_time_with_distance(
- diff, time_, view_.tick_prefix(), view_.time_unit(), view_.tick_precision());
+ diff, view_.ruler()->get_ruler_time_from_absolute_time(time_),
+ view_.tick_prefix(), view_.time_unit(), view_.tick_precision());
}
QRectF Cursor::label_rect(const QRectF &rect) const
return QRectF(x - label_size.width(), top, label_size.width(), height);
}
+QMenu *Cursor::create_header_context_menu(QWidget *parent)
+{
+ QMenu *const menu = new QMenu(parent);
+
+ QAction *const snap_disable = new QAction(tr("Disable snapping"), this);
+ snap_disable->setCheckable(true);
+ snap_disable->setChecked(snapping_disabled_);
+ connect(snap_disable, &QAction::toggled, this, [=](bool checked){snapping_disabled_ = checked;});
+ menu->addAction(snap_disable);
+
+ return menu;
+}
+
shared_ptr<Cursor> Cursor::get_other_cursor() const
{
const shared_ptr<CursorPair> cursors(view_.cursors());
/**
* Returns true if the item is visible and enabled.
*/
- bool enabled() const;
+ virtual bool enabled() const override;
/**
* Gets the text to show in the marker.
*/
- QString get_text() const;
+ virtual QString get_text() const override;
/**
* Gets the marker label rectangle.
* @param rect The rectangle of the ruler client area.
* @return Returns the label rectangle.
*/
- QRectF label_rect(const QRectF &rect) const;
+ virtual QRectF label_rect(const QRectF &rect) const override;
+
+ virtual QMenu* create_header_context_menu(QWidget *parent) override;
private:
shared_ptr<Cursor> get_other_cursor() const;
#include <cassert>
#include <QColor>
+#include <QMenu>
#include <QToolTip>
#include "cursorpair.hpp"
CursorPair::CursorPair(View &view) :
TimeItem(view),
first_(new Cursor(view, 0.0)),
- second_(new Cursor(view, 1.0))
+ second_(new Cursor(view, 1.0)),
+ label_incomplete_(true)
{
GlobalSettings::add_change_handler(this);
GlobalSettings settings;
fill_color_ = QColor::fromRgba(settings.value(
GlobalSettings::Key_View_CursorFillColor).value<uint32_t>());
+ show_frequency_ = settings.value(
+ GlobalSettings::Key_View_CursorShowFrequency).value<bool>();
+ show_interval_ = settings.value(
+ GlobalSettings::Key_View_CursorShowInterval).value<bool>();
+ show_samples_ = settings.value(
+ GlobalSettings::Key_View_CursorShowSamples).value<bool>();
connect(&view_, SIGNAL(hover_point_changed(const QWidget*, QPoint)),
this, SLOT(on_hover_point_changed(const QWidget*, QPoint)));
second_->set_time(time + delta);
}
+const pv::util::Timestamp CursorPair::time() const
+{
+ return 0;
+}
+
float CursorPair::get_x() const
{
return (first_->get_x() + second_->get_x()) / 2.0f;
}
+const pv::util::Timestamp CursorPair::delta(const pv::util::Timestamp& other) const
+{
+ if (other < second_->time())
+ return other - first_->time();
+ else
+ return other - second_->time();
+}
+
QPoint CursorPair::drag_point(const QRect &rect) const
{
return first_->drag_point(rect);
return nullptr;
}
+QMenu *CursorPair::create_header_context_menu(QWidget *parent)
+{
+ QMenu *menu = new QMenu(parent);
+
+ QAction *displayIntervalAction = new QAction(tr("Display interval"), this);
+ displayIntervalAction->setCheckable(true);
+ displayIntervalAction->setChecked(show_interval_);
+ menu->addAction(displayIntervalAction);
+
+ connect(displayIntervalAction, &QAction::toggled, displayIntervalAction,
+ [=]{
+ GlobalSettings settings;
+ settings.setValue(GlobalSettings::Key_View_CursorShowInterval,
+ !settings.value(GlobalSettings::Key_View_CursorShowInterval).value<bool>());
+ });
+
+ QAction *displayFrequencyAction = new QAction(tr("Display frequency"), this);
+ displayFrequencyAction->setCheckable(true);
+ displayFrequencyAction->setChecked(show_frequency_);
+ menu->addAction(displayFrequencyAction);
+
+ connect(displayFrequencyAction, &QAction::toggled, displayFrequencyAction,
+ [=]{
+ GlobalSettings settings;
+ settings.setValue(GlobalSettings::Key_View_CursorShowFrequency,
+ !settings.value(GlobalSettings::Key_View_CursorShowFrequency).value<bool>());
+ });
+
+ QAction *displaySamplesAction = new QAction(tr("Display samples"), this);
+ displaySamplesAction->setCheckable(true);
+ displaySamplesAction->setChecked(show_samples_);
+ menu->addAction(displaySamplesAction);
+
+ connect(displaySamplesAction, &QAction::toggled, displaySamplesAction,
+ [=]{
+ GlobalSettings settings;
+ settings.setValue(GlobalSettings::Key_View_CursorShowSamples,
+ !settings.value(GlobalSettings::Key_View_CursorShowSamples).value<bool>());
+ });
+
+ return menu;
+}
+
QRectF CursorPair::label_rect(const QRectF &rect) const
{
const QSizeF label_size(text_size_ + LabelPadding * 2);
const QColor text_color = ViewItem::select_text_color(Cursor::FillColor);
p.setPen(text_color);
- 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;
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;
+ QString text = format_string(text_rect.width(),
+ [&p](const QString& s) -> double { return p.boundingRect(QRectF(), 0, s).width(); });
+
+ text_size_ = p.boundingRect(QRectF(), 0, text).size();
if (selected()) {
p.setBrush(Qt::transparent);
p.drawRect(l, pp.top(), r - l, pp.height());
}
-QString CursorPair::format_string()
+QString CursorPair::format_string(int max_width, std::function<double(const QString&)> query_size)
{
- const pv::util::SIPrefix prefix = view_.tick_prefix();
- const pv::util::Timestamp diff = abs(second_->time() - first_->time());
+ int time_precision = 12;
+ int freq_precision = 12;
+
+ QString s = format_string_sub(time_precision, freq_precision);
+
+ // Try full "{time} s / {freq} Hz" format
+ if ((max_width <= 0) || (query_size(s) <= max_width)) {
+ label_incomplete_ = false;
+ return s;
+ }
+
+ label_incomplete_ = true;
+
+ // Gradually reduce time precision to match frequency precision
+ while (time_precision > freq_precision) {
+ time_precision--;
- const QString s1 = Ruler::format_time_with_distance(
- 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);
+ s = format_string_sub(time_precision, freq_precision);
+ if (query_size(s) <= max_width)
+ return s;
+ }
+
+ // Gradually reduce both precisions down to zero
+ while (time_precision > 0) {
+ time_precision--;
+ freq_precision--;
- return QString("%1 / %2").arg(s1, s2);
+ s = format_string_sub(time_precision, freq_precision);
+ if (query_size(s) <= max_width)
+ return s;
+ }
+
+ // Try no trailing digits and drop the unit to at least display something
+ s = format_string_sub(0, 0, false);
+
+ if (query_size(s) <= max_width)
+ return s;
+
+ // Give up
+ return "...";
}
pair<float, float> CursorPair::get_cursor_offsets() const
{
if (key == GlobalSettings::Key_View_CursorFillColor)
fill_color_ = QColor::fromRgba(value.value<uint32_t>());
+
+ if (key == GlobalSettings::Key_View_CursorShowFrequency)
+ show_frequency_ = value.value<bool>();
+
+ if (key == GlobalSettings::Key_View_CursorShowInterval)
+ show_interval_ = value.value<bool>();
+
+ if (key == GlobalSettings::Key_View_CursorShowSamples)
+ show_samples_ = value.value<bool>();
}
void CursorPair::on_hover_point_changed(const QWidget* widget, const QPoint& hp)
QToolTip::hideText(); // TODO Will break other tooltips when there can be others
}
+QString CursorPair::format_string_sub(int time_precision, int freq_precision, bool show_unit)
+{
+ QString s = " ";
+
+ const pv::util::SIPrefix prefix = view_.tick_prefix();
+ const pv::util::Timestamp diff = abs(second_->time() - first_->time());
+
+ const QString time = Ruler::format_time_with_distance(
+ diff, diff, prefix, (show_unit ? view_.time_unit() : pv::util::TimeUnit::None),
+ time_precision, false);
+
+ // We can only show a frequency when there's a time base
+ if (view_.time_unit() == pv::util::TimeUnit::Time) {
+ int items = 0;
+
+ if (show_frequency_) {
+ const QString freq = util::format_value_si(
+ 1 / diff.convert_to<double>(), pv::util::SIPrefix::unspecified,
+ freq_precision, (show_unit ? "Hz" : nullptr), false);
+ s = QString("%1").arg(freq);
+ items++;
+ }
+
+ if (show_interval_) {
+ if (items > 0)
+ s = QString("%1 / %2").arg(s, time);
+ else
+ s = QString("%1").arg(time);
+ items++;
+ }
+
+ if (show_samples_) {
+ const QString samples = QString::number(
+ (diff * view_.session().get_samplerate()).convert_to<uint64_t>());
+ if (items > 0)
+ s = QString("%1 / %2").arg(s, samples);
+ else
+ s = QString("%1").arg(samples);
+ }
+ } else
+ // In this case, we return the number of samples, really
+ s = time;
+
+ return s;
+}
+
} // namespace trace
} // namespace views
} // namespace pv
*/
void set_time(const pv::util::Timestamp& time) override;
+ virtual const pv::util::Timestamp time() const override;
+
float get_x() const override;
+ virtual const pv::util::Timestamp delta(const pv::util::Timestamp& other) const override;
+
QPoint drag_point(const QRect &rect) const override;
pv::widgets::Popup* create_popup(QWidget *parent) override;
+ QMenu* create_header_context_menu(QWidget *parent) override;
+
QRectF label_rect(const QRectF &rect) const override;
/**
/**
* Constructs the string to display.
*/
- QString format_string();
+ QString format_string(int max_width = 0, std::function<double(const QString&)> query_size
+ = [](const QString& s) -> double { (void)s; return 0; });
pair<float, float> get_cursor_offsets() const;
public Q_SLOTS:
void on_hover_point_changed(const QWidget* widget, const QPoint &hp);
+private:
+ QString format_string_sub(int time_precision, int freq_precision, bool show_unit = true);
+
private:
shared_ptr<Cursor> first_, second_;
QColor fill_color_;
QSizeF text_size_;
QRectF label_area_;
bool label_incomplete_;
+ bool show_interval_, show_frequency_, show_samples_;
};
} // namespace trace
#include <QAction>
#include <QApplication>
+#include <QClipboard>
+#include <QCheckBox>
#include <QComboBox>
+#include <QDebug>
#include <QFileDialog>
#include <QFormLayout>
#include <QLabel>
#include <pv/data/logicsegment.hpp>
#include <pv/widgets/decodergroupbox.hpp>
#include <pv/widgets/decodermenu.hpp>
+#include <pv/widgets/flowlayout.hpp>
using std::abs;
+using std::find_if;
+using std::lock_guard;
using std::make_pair;
using std::max;
using std::min;
using std::numeric_limits;
-using std::out_of_range;
using std::pair;
using std::shared_ptr;
using std::tie;
using std::vector;
using pv::data::decode::Annotation;
+using pv::data::decode::AnnotationClass;
using pv::data::decode::Row;
-using pv::data::DecodeChannel;
+using pv::data::decode::DecodeChannel;
using pv::data::DecodeSignal;
namespace pv {
namespace views {
namespace trace {
-
#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 QColor DecodeTrace::ExpandMarkerWarnColor = QColor(0xFF, 0xA5, 0x00); // QColorConstants::Svg::orange
+const QColor DecodeTrace::ExpandMarkerHiddenColor = QColor(0x69, 0x69, 0x69); // QColorConstants::Svg::dimgray
+const uint8_t DecodeTrace::ExpansionAreaHeaderAlpha = 10 * 255 / 100;
+const uint8_t DecodeTrace::ExpansionAreaAlpha = 5 * 255 / 100;
-const int DecodeTrace::ArrowSize = 4;
+const int DecodeTrace::ArrowSize = 6;
const double DecodeTrace::EndCapWidth = 5;
-const int DecodeTrace::RowTitleMargin = 10;
+const int DecodeTrace::RowTitleMargin = 7;
const int DecodeTrace::DrawPadding = 100;
const int DecodeTrace::MaxTraceUpdateRate = 1; // No more than 1 Hz
+const int DecodeTrace::AnimationDurationInTicks = 7;
+const int DecodeTrace::HiddenRowHideDelay = 1000; // 1 second
+
+/**
+ * Helper function for forceUpdate()
+ */
+void invalidateLayout(QLayout* layout)
+{
+ // Recompute the given layout and all its child layouts recursively
+ for (int i = 0; i < layout->count(); i++) {
+ QLayoutItem *item = layout->itemAt(i);
+
+ if (item->layout())
+ invalidateLayout(item->layout());
+ else
+ item->invalidate();
+ }
+
+ layout->invalidate();
+ layout->activate();
+}
+
+void forceUpdate(QWidget* widget)
+{
+ // Update all child widgets recursively
+ for (QObject* child : widget->children())
+ if (child->isWidgetType())
+ forceUpdate((QWidget*)child);
+
+ // Invalidate the layout of the widget itself
+ if (widget->layout())
+ invalidateLayout(widget->layout());
+}
+
+
+ContainerWidget::ContainerWidget(QWidget *parent) :
+ QWidget(parent)
+{
+}
+
+void ContainerWidget::resizeEvent(QResizeEvent* event)
+{
+ QWidget::resizeEvent(event);
+
+ widgetResized(this);
+}
+
DecodeTrace::DecodeTrace(pv::Session &session,
shared_ptr<data::SignalBase> signalbase, int index) :
Trace(signalbase),
session_(session),
- row_height_(0),
- max_visible_rows_(0),
+ show_hidden_rows_(false),
delete_mapper_(this),
- show_hide_mapper_(this)
+ show_hide_mapper_(this),
+ row_show_hide_mapper_(this)
{
decode_signal_ = dynamic_pointer_cast<data::DecodeSignal>(base_);
+ GlobalSettings settings;
+ always_show_all_rows_ = settings.value(GlobalSettings::Key_Dec_AlwaysShowAllRows).toBool();
+
+ GlobalSettings::add_change_handler(this);
+
// 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
+ default_row_height_ = (ViewItemPaintParams::text_height() * 6) / 4;
+ annotation_height_ = (ViewItemPaintParams::text_height() * 5) / 4;
+
// 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
this, SLOT(on_delete_decoder(int)));
connect(&show_hide_mapper_, SIGNAL(mapped(int)),
this, SLOT(on_show_hide_decoder(int)));
+ connect(&row_show_hide_mapper_, SIGNAL(mapped(int)),
+ this, SLOT(on_show_hide_row(int)));
+ connect(&class_show_hide_mapper_, SIGNAL(mapped(QWidget*)),
+ this, SLOT(on_show_hide_class(QWidget*)));
connect(&delayed_trace_updater_, SIGNAL(timeout()),
this, SLOT(on_delayed_trace_update()));
delayed_trace_updater_.setSingleShot(true);
delayed_trace_updater_.setInterval(1000 / MaxTraceUpdateRate);
+
+ connect(&animation_timer_, SIGNAL(timeout()),
+ this, SLOT(on_animation_timer()));
+ animation_timer_.setInterval(1000 / 50);
+
+ connect(&delayed_hidden_row_hider_, SIGNAL(timeout()),
+ this, SLOT(on_hide_hidden_rows()));
+ delayed_hidden_row_hider_.setSingleShot(true);
+ delayed_hidden_row_hider_.setInterval(HiddenRowHideDelay);
+
+ default_marker_shape_ << QPoint(0, -ArrowSize);
+ default_marker_shape_ << QPoint(ArrowSize, 0);
+ default_marker_shape_ << QPoint(0, ArrowSize);
+}
+
+DecodeTrace::~DecodeTrace()
+{
+ GlobalSettings::remove_change_handler(this);
+
+ for (DecodeTraceRow& r : rows_) {
+ for (QCheckBox* cb : r.selectors)
+ delete cb;
+
+ delete r.selector_container;
+ delete r.header_container;
+ delete r.container;
+ }
}
bool DecodeTrace::enabled() const
return base_;
}
-pair<int, int> DecodeTrace::v_extents() const
+void DecodeTrace::set_owner(TraceTreeItemOwner *owner)
{
- const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
+ Trace::set_owner(owner);
+ // The owner is set in trace::View::signals_changed(), which is a slot.
+ // So after this trace was added to the view, we won't have an owner
+ // that we need to initialize in update_rows(). Once we do, we call it
+ // from on_decode_reset().
+ on_decode_reset();
+}
+
+pair<int, int> DecodeTrace::v_extents() const
+{
// Make an empty decode trace appear symmetrical
- const int row_count = max(1, max_visible_rows_);
+ if (visible_rows_ == 0)
+ return make_pair(-default_row_height_, default_row_height_);
+
+ unsigned int height = 0;
+ for (const DecodeTraceRow& r : rows_)
+ if (r.currently_visible)
+ height += r.height;
- return make_pair(-row_height, row_height * row_count);
+ return make_pair(-default_row_height_, height);
}
void DecodeTrace::paint_back(QPainter &p, ViewItemPaintParams &pp)
void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp)
{
- const int text_height = ViewItemPaintParams::text_height();
- row_height_ = (text_height * 6) / 4;
- const int annotation_height = (text_height * 5) / 4;
+ lock_guard<mutex> lock(row_modification_mutex_);
+ unsigned int visible_rows;
+
+#if DECODETRACE_SHOW_RENDER_TIME
+ render_time_.restart();
+#endif
// 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_view_sample_range(pp.left(), pp.right());
// Just because the view says we see a certain sample range it
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) {
- // Cache the row title widths
- int row_title_width;
- try {
- row_title_width = row_title_widths_.at(row);
- } catch (out_of_range&) {
- const int w = p.boundingRect(QRectF(), 0, row.title()).width() +
- RowTitleMargin;
- row_title_widths_[row] = w;
- row_title_width = w;
+ visible_rows = 0;
+ int y = get_visual_y();
+
+ for (DecodeTraceRow& r : rows_) {
+ // If the row is hidden, we don't want to fetch annotations
+ assert(r.decode_row);
+ assert(r.decode_row->decoder());
+ if ((!r.decode_row->decoder()->visible()) ||
+ ((!r.decode_row->visible() && (!show_hidden_rows_) && (!r.expanding) && (!r.expanded) && (!r.collapsing)))) {
+ r.currently_visible = false;
+ continue;
}
- vector<Annotation> annotations;
- decode_signal_->get_annotation_subset(annotations, row,
+ deque<const Annotation*> annotations;
+ decode_signal_->get_annotation_subset(annotations, r.decode_row,
current_segment_, sample_range.first, sample_range.second);
- if (!annotations.empty()) {
- draw_annotations(annotations, p, annotation_height, pp, y,
- get_row_color(row.index()), row_title_width);
- y += row_height_;
- visible_rows_.push_back(row);
+
+ // Show row if there are visible annotations, when user wants to see
+ // all rows that have annotations somewhere and this one is one of them
+ // or when the row has at least one hidden annotation class
+ r.currently_visible = !annotations.empty();
+ if (!r.currently_visible) {
+ size_t ann_count = decode_signal_->get_annotation_count(r.decode_row, current_segment_);
+ r.currently_visible = ((always_show_all_rows_ || r.has_hidden_classes) &&
+ (ann_count > 0)) || r.expanded;
+ }
+
+ if (r.currently_visible) {
+ draw_annotations(annotations, p, pp, y, r);
+ y += r.height;
+ visible_rows++;
}
}
- draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
+ draw_unresolved_period(p, pp.left(), pp.right());
- if ((int)visible_rows_.size() > max_visible_rows_) {
- max_visible_rows_ = (int)visible_rows_.size();
+ if (visible_rows != visible_rows_) {
+ visible_rows_ = visible_rows;
// Call order is important, otherwise the lazy event handler won't work
owner_->extents_changed(false, true);
const QString err = decode_signal_->error_message();
if (!err.isEmpty())
draw_error(p, err, pp);
+
+#if DECODETRACE_SHOW_RENDER_TIME
+ qDebug() << "Rendering" << base_->name() << "took" << render_time_.elapsed() << "ms";
+#endif
}
void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp)
{
- assert(row_height_);
+ unsigned int y = get_visual_y();
- for (size_t i = 0; i < visible_rows_.size(); i++) {
- const int y = i * row_height_ + get_visual_y();
+ update_expanded_rows();
+
+ for (const DecodeTraceRow& r : rows_) {
+ if (!r.currently_visible)
+ continue;
p.setPen(QPen(Qt::NoPen));
- p.setBrush(QApplication::palette().brush(QPalette::WindowText));
- if (i != 0) {
- const QPointF points[] = {
- QPointF(pp.left(), y - ArrowSize),
- QPointF(pp.left() + ArrowSize, y),
- QPointF(pp.left(), y + ArrowSize)
- };
- p.drawPolygon(points, countof(points));
- }
+ if (r.expand_marker_highlighted)
+ p.setBrush(QApplication::palette().brush(QPalette::Highlight));
+ else if (!r.decode_row->visible())
+ p.setBrush(ExpandMarkerHiddenColor);
+ else if (r.has_hidden_classes)
+ p.setBrush(ExpandMarkerWarnColor);
+ else
+ p.setBrush(QApplication::palette().brush(QPalette::WindowText));
- const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
- pp.right() - pp.left(), row_height_);
- const QString h(visible_rows_[i].title());
+ // Draw expansion marker
+ QPolygon marker(r.expand_marker_shape);
+ marker.translate(pp.left(), y);
+ p.drawPolygon(marker);
+
+ p.setBrush(QApplication::palette().brush(QPalette::WindowText));
+
+ const QRect text_rect(pp.left() + ArrowSize * 2, y - r.height / 2,
+ pp.right() - pp.left(), r.height);
+ const QString h(r.decode_row->title());
const int f = Qt::AlignLeft | Qt::AlignVCenter |
Qt::TextDontClip;
for (int dx = -1; dx <= 1; dx++)
for (int dy = -1; dy <= 1; dy++)
if (dx != 0 && dy != 0)
- p.drawText(r.translated(dx, dy), f, h);
+ p.drawText(text_rect.translated(dx, dy), f, h);
// Draw the text
- p.setPen(QApplication::palette().color(QPalette::WindowText));
- p.drawText(r, f, h);
+ if (!r.decode_row->visible())
+ p.setPen(ExpandMarkerHiddenColor);
+ else
+ p.setPen(QApplication::palette().color(QPalette::WindowText));
+
+ p.drawText(text_rect, f, h);
+
+ y += r.height;
}
if (show_hover_marker_)
paint_hover_marker(p);
}
-void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
+void DecodeTrace::update_stack_button()
{
- using pv::data::decode::Decoder;
+ const vector< shared_ptr<Decoder> > &stack = decode_signal_->decoder_stack();
+
+ // Only show decoders in the menu that can be stacked onto the last one in the stack
+ if (!stack.empty()) {
+ const srd_decoder* d = stack.back()->get_srd_decoder();
+
+ if (d->outputs) {
+ pv::widgets::DecoderMenu *const decoder_menu =
+ new pv::widgets::DecoderMenu(stack_button_, (const char*)(d->outputs->data));
+ connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
+ this, SLOT(on_stack_decoder(srd_decoder*)));
+
+ decoder_menu->setStyleSheet("QMenu { menu-scrollable: 1; }");
+
+ stack_button_->setMenu(decoder_menu);
+ stack_button_->show();
+ return;
+ }
+ }
+
+ // No decoders available for stacking
+ stack_button_->setMenu(nullptr);
+ stack_button_->hide();
+}
+void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
+{
assert(form);
// Add the standard options
}
// Add stacking button
- pv::widgets::DecoderMenu *const decoder_menu =
- new pv::widgets::DecoderMenu(parent);
- connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
- this, SLOT(on_stack_decoder(srd_decoder*)));
-
- QPushButton *const stack_button =
- new QPushButton(tr("Stack Decoder"), parent);
- stack_button->setMenu(decoder_menu);
- stack_button->setToolTip(tr("Stack a higher-level decoder on top of this one"));
+ stack_button_ = new QPushButton(tr("Stack Decoder"), parent);
+ stack_button_->setToolTip(tr("Stack a higher-level decoder on top of this one"));
+ update_stack_button();
QHBoxLayout *stack_button_box = new QHBoxLayout;
- stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
+ stack_button_box->addWidget(stack_button_, 0, Qt::AlignRight);
form->addRow(stack_button_box);
}
menu->addSeparator();
}
- try {
- selected_row_ = &visible_rows_[get_row_at_point(click_pos)];
- } catch (out_of_range&) {
- selected_row_ = nullptr;
- }
+ selected_row_ = nullptr;
+ const DecodeTraceRow* r = get_row_at_point(click_pos);
+ if (r)
+ selected_row_ = r->decode_row;
+
+ const View *const view = owner_->view();
+ assert(view);
+ QPoint pos = view->viewport()->mapFrom(parent, click_pos);
// 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);
+ const pair<uint64_t, uint64_t> sample_range = get_view_sample_range(pos.x(), pos.x() + 1);
selected_sample_range_ = make_pair(sample_range.first, numeric_limits<uint64_t>::max());
if (decode_signal_->is_paused()) {
menu->addAction(pause);
}
+ QAction *const copy_annotation_to_clipboard =
+ new QAction(tr("Copy annotation text to clipboard"), this);
+ copy_annotation_to_clipboard->setIcon(QIcon::fromTheme("edit-paste",
+ QIcon(":/icons/edit-paste.svg")));
+ connect(copy_annotation_to_clipboard, SIGNAL(triggered()), this, SLOT(on_copy_annotation_to_clipboard()));
+ menu->addAction(copy_annotation_to_clipboard);
+
menu->addSeparator();
QAction *const export_all_rows =
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,
- QColor row_color, int row_title_width)
+void DecodeTrace::delete_pressed()
+{
+ on_delete();
+}
+
+void DecodeTrace::hover_point_changed(const QPoint &hp)
+{
+ Trace::hover_point_changed(hp);
+
+ assert(owner_);
+
+ DecodeTraceRow* hover_row = get_row_at_point(hp);
+
+ // Row expansion marker handling
+ for (DecodeTraceRow& r : rows_)
+ r.expand_marker_highlighted = false;
+
+ if (hover_row) {
+ int row_y = get_row_y(hover_row);
+ if ((hp.x() > 0) && (hp.x() < (int)(ArrowSize + 3 + hover_row->title_width)) &&
+ (hp.y() > (int)(row_y - ArrowSize)) && (hp.y() < (int)(row_y + ArrowSize))) {
+
+ hover_row->expand_marker_highlighted = true;
+ show_hidden_rows_ = true;
+ delayed_hidden_row_hider_.start();
+ }
+ }
+
+ // Tooltip handling
+ if (hp.x() > 0) {
+ QString ann = get_annotation_at_point(hp);
+
+ if (!ann.isEmpty()) {
+ QFontMetrics m(QToolTip::font());
+ const QRect text_size = m.boundingRect(QRect(), 0, ann);
+
+ // This is OS-specific and unfortunately we can't query it, so
+ // use an approximation to at least try to minimize the error.
+ const int padding = default_row_height_ + 8;
+
+ // Make sure the tool tip doesn't overlap with the mouse cursor.
+ // 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.
+ QPoint p = hp;
+ p.setX(hp.x() - (text_size.width() / 2) - padding);
+
+ p.setY(get_row_y(hover_row) - default_row_height_ -
+ text_size.height() - padding);
+
+ const View *const view = owner_->view();
+ assert(view);
+ QToolTip::showText(view->viewport()->mapToGlobal(p), ann);
+
+ } else
+ QToolTip::hideText();
+
+ } else
+ QToolTip::hideText();
+}
+
+void DecodeTrace::mouse_left_press_event(const QMouseEvent* event)
{
- using namespace pv::data::decode;
+ // Update container widths which depend on the scrollarea's current width
+ update_expanded_rows();
+
+ // Handle row expansion marker
+ for (DecodeTraceRow& r : rows_) {
+ if (!r.expand_marker_highlighted)
+ continue;
+
+ unsigned int y = get_row_y(&r);
+ if ((event->x() > 0) && (event->x() <= (int)(ArrowSize + 3 + r.title_width)) &&
+ (event->y() > (int)(y - (default_row_height_ / 2))) &&
+ (event->y() <= (int)(y + (default_row_height_ / 2)))) {
+
+ if (r.expanded) {
+ r.collapsing = true;
+ r.expanded = false;
+ r.anim_shape = ArrowSize;
+ } else {
+ r.expanding = true;
+ r.anim_shape = 0;
+
+ // Force geometry update of the widget container to get
+ // an up-to-date height (which also depends on the width)
+ forceUpdate(r.container);
+
+ r.container->setVisible(true);
+ r.expanded_height = 2 * default_row_height_ + r.container->sizeHint().height();
+ }
+
+ r.animation_step = 0;
+ r.anim_height = r.height;
+
+ animation_timer_.start();
+ }
+ }
+}
+void DecodeTrace::draw_annotations(deque<const Annotation*>& annotations,
+ QPainter &p, const ViewItemPaintParams &pp, int y, const DecodeTraceRow& row)
+{
Annotation::Class block_class = 0;
bool block_class_uniform = true;
qreal block_start = 0;
int block_ann_count = 0;
- const Annotation *prev_ann;
+ const Annotation* prev_ann;
qreal prev_end = INT_MIN;
qreal a_end;
tie(pixels_offset, samples_per_pixel) =
get_pixels_offset_samples_per_pixel();
- // Sort the annotations by start sample so that decoders
- // can't confuse us by creating annotations out of order
- stable_sort(annotations.begin(), annotations.end(),
- [](const Annotation &a, const Annotation &b) {
- return a.start_sample() < b.start_sample(); });
-
// Gather all annotations that form a visual "block" and draw them as such
- for (const Annotation &a : annotations) {
+ for (const Annotation* a : annotations) {
- const qreal abs_a_start = a.start_sample() / samples_per_pixel;
- const qreal abs_a_end = a.end_sample() / samples_per_pixel;
+ const qreal abs_a_start = a->start_sample() / samples_per_pixel;
+ const qreal abs_a_end = a->end_sample() / samples_per_pixel;
const qreal a_start = abs_a_start - pixels_offset;
a_end = abs_a_end - pixels_offset;
// Annotation wider than the threshold for a useful label width?
if (a_width >= min_useful_label_width_) {
- for (const QString &ann_text : a.annotations()) {
+ for (const QString &ann_text : *(a->annotations())) {
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) {
if ((abs(delta) > 1) || a_is_separate) {
// Block was broken, draw annotations that form the current block
if (block_ann_count == 1)
- draw_annotation(*prev_ann, p, h, pp, y, row_color,
- row_title_width);
+ draw_annotation(prev_ann, p, pp, y, row);
else if (block_ann_count > 0)
draw_annotation_block(block_start, prev_end, block_class,
- block_class_uniform, p, h, y, row_color);
+ block_class_uniform, p, y, row);
block_ann_count = 0;
}
if (a_is_separate) {
- draw_annotation(a, p, h, pp, y, row_color, row_title_width);
+ draw_annotation(a, p, pp, y, row);
// Next annotation must start a new block. delta will be > 1
// because we set prev_end to INT_MIN but that's okay since
// block_ann_count will be 0 and nothing will be drawn
block_ann_count = 0;
} else {
prev_end = a_end;
- prev_ann = &a;
+ prev_ann = a;
if (block_ann_count == 0) {
block_start = a_start;
- block_class = a.ann_class();
+ block_class = a->ann_class_id();
block_class_uniform = true;
} else
- if (a.ann_class() != block_class)
+ if (a->ann_class_id() != block_class)
block_class_uniform = false;
block_ann_count++;
}
if (block_ann_count == 1)
- draw_annotation(*prev_ann, p, h, pp, y, row_color, row_title_width);
+ draw_annotation(prev_ann, p, pp, y, row);
else if (block_ann_count > 0)
draw_annotation_block(block_start, prev_end, block_class,
- block_class_uniform, p, h, y, row_color);
+ block_class_uniform, p, y, row);
}
-void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
- QPainter &p, int h, const ViewItemPaintParams &pp, int y,
- QColor row_color, int row_title_width) const
+void DecodeTrace::draw_annotation(const Annotation* a, QPainter &p,
+ const ViewItemPaintParams &pp, int y, const DecodeTraceRow& row) const
{
double samples_per_pixel, pixels_offset;
tie(pixels_offset, samples_per_pixel) =
get_pixels_offset_samples_per_pixel();
- const double start = a.start_sample() / samples_per_pixel -
- pixels_offset;
- const double end = a.end_sample() / samples_per_pixel - pixels_offset;
+ const double start = a->start_sample() / samples_per_pixel - pixels_offset;
+ const double end = a->end_sample() / samples_per_pixel - pixels_offset;
- QColor color = get_annotation_color(row_color, a.ann_class());
- p.setPen(color.darker());
- p.setBrush(color);
+ p.setPen(row.ann_class_dark_color.at(a->ann_class_id()));
+ p.setBrush(row.ann_class_color.at(a->ann_class_id()));
- if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
+ if ((start > (pp.right() + DrawPadding)) || (end < (pp.left() - DrawPadding)))
return;
- if (a.start_sample() == a.end_sample())
- draw_instant(a, p, h, start, y);
+ if (a->start_sample() == a->end_sample())
+ draw_instant(a, p, start, y);
else
- draw_range(a, p, h, start, end, y, pp, row_title_width);
+ draw_range(a, p, start, end, y, pp, row.title_width);
}
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
+ Annotation::Class ann_class, bool use_ann_format, QPainter &p, int y,
+ const DecodeTraceRow& row) const
{
- const double top = y + .5 - h / 2;
- const double bottom = y + .5 + h / 2;
-
- const QRectF rect(start, top, end - start, bottom - top);
- const int r = h / 4;
-
- p.setPen(QPen(Qt::NoPen));
- p.setBrush(Qt::white);
- p.drawRoundedRect(rect, r, r);
+ const double top = y + .5 - annotation_height_ / 2;
+ const double bottom = y + .5 + annotation_height_ / 2;
+ const double width = end - start;
// 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));
+ p.setPen(row.ann_class_dark_color.at(ann_class));
+ p.setBrush(QBrush(row.ann_class_color.at(ann_class), Qt::Dense4Pattern));
} else {
- p.setPen(Qt::gray);
+ p.setPen(QColor(Qt::darkGray));
p.setBrush(QBrush(Qt::gray, Qt::Dense4Pattern));
}
- p.drawRoundedRect(rect, r, r);
+ if (width <= 1)
+ p.drawLine(QPointF(start, top), QPointF(start, bottom));
+ else {
+ const QRectF rect(start, top, width, bottom - top);
+ const int r = annotation_height_ / 4;
+ p.drawRoundedRect(rect, r, r);
+ }
}
-void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
- int h, qreal x, int y) const
+void DecodeTrace::draw_instant(const Annotation* a, QPainter &p, qreal x, int y) const
{
- const QString text = a.annotations().empty() ?
- QString() : a.annotations().back();
+ const QString text = a->annotations()->empty() ?
+ QString() : a->annotations()->back();
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);
+ 0.0) + annotation_height_;
+ const QRectF rect(x - w / 2, y - annotation_height_ / 2, w, annotation_height_);
- p.drawRoundedRect(rect, h / 2, h / 2);
+ p.drawRoundedRect(rect, annotation_height_ / 2, annotation_height_ / 2);
p.setPen(Qt::black);
p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
}
-void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
- int h, qreal start, qreal end, int y, const ViewItemPaintParams &pp,
+void DecodeTrace::draw_range(const Annotation* a, QPainter &p,
+ qreal start, qreal end, int y, const ViewItemPaintParams &pp,
int row_title_width) const
{
- const qreal top = y + .5 - h / 2;
- const qreal bottom = y + .5 + h / 2;
- const vector<QString> annotations = a.annotations();
+ const qreal top = y + .5 - annotation_height_ / 2;
+ const qreal bottom = y + .5 + annotation_height_ / 2;
+ const vector<QString>* annotations = a->annotations();
// If the two ends are within 1 pixel, draw a vertical line
if (start + 1.0 > end) {
p.drawConvexPolygon(pts, countof(pts));
- if (annotations.empty())
+ if (annotations->empty())
return;
const int ann_start = start + cap_width;
const int ann_end = end - cap_width;
- const int real_start = max(ann_start, pp.left() + row_title_width);
+ const int real_start = max(ann_start, pp.left() + ArrowSize + row_title_width);
const int real_end = min(ann_end, pp.right());
const int real_width = real_end - real_start;
- QRectF rect(real_start, y - h / 2, real_width, h);
+ QRectF rect(real_start, y - annotation_height_ / 2, real_width, annotation_height_);
if (rect.width() <= 4)
return;
QString best_annotation;
int best_width = 0;
- for (const QString &a : annotations) {
- const int w = p.boundingRect(QRectF(), 0, a).width();
+ for (const QString &s : *annotations) {
+ const int w = p.boundingRect(QRectF(), 0, s).width();
if (w <= rect.width() && w > best_width)
- best_annotation = a, best_width = w;
+ best_annotation = s, best_width = w;
}
if (best_annotation.isEmpty())
- best_annotation = annotations.back();
+ best_annotation = annotations->back();
// If not ellide the last in the list
p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
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 left, int right) const
{
- using namespace pv::data;
- using pv::data::decode::Decoder;
-
double samples_per_pixel, pixels_offset;
const int64_t sample_count = decode_signal_->get_working_sample_count(current_segment_);
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 - (annotation_height_ / 2) - 0.5,
+ end - start, annotation_height_);
p.setPen(QPen(Qt::NoPen));
p.setBrush(Qt::white);
return color;
}
-int DecodeTrace::get_row_at_point(const QPoint &point)
+unsigned int DecodeTrace::get_row_y(const DecodeTraceRow* row) const
{
- if (!row_height_)
- return -1;
-
- const int y = (point.y() - get_visual_y() + row_height_ / 2);
+ assert(row);
- /* Integer divison of (x-1)/x would yield 0, so we check for this. */
- if (y < 0)
- return -1;
+ unsigned int y = get_visual_y();
- const int row = y / row_height_;
+ for (const DecodeTraceRow& r : rows_) {
+ if (!r.currently_visible)
+ continue;
- if (row >= (int)visible_rows_.size())
- return -1;
+ if (row->decode_row == r.decode_row)
+ break;
+ else
+ y += r.height;
+ }
- return row;
+ return y;
}
-const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
+DecodeTraceRow* DecodeTrace::get_row_at_point(const QPoint &point)
{
- using namespace pv::data::decode;
-
- if (!enabled())
- return QString();
+ int y = get_visual_y() - (default_row_height_ / 2);
- const pair<uint64_t, uint64_t> sample_range =
- get_view_sample_range(point.x(), point.x() + 1);
- const int row = get_row_at_point(point);
- if (row < 0)
- return QString();
+ for (DecodeTraceRow& r : rows_) {
+ if (!r.currently_visible)
+ continue;
- vector<Annotation> annotations;
+ if ((point.y() >= y) && (point.y() < (int)(y + r.height)))
+ return &r;
- decode_signal_->get_annotation_subset(annotations, visible_rows_[row],
- current_segment_, sample_range.first, sample_range.second);
+ y += r.height;
+ }
- return (annotations.empty()) ?
- QString() : annotations[0].annotations().front();
+ return nullptr;
}
-void DecodeTrace::hover_point_changed(const QPoint &hp)
+const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
{
- Trace::hover_point_changed(hp);
-
- assert(owner_);
-
- const View *const view = owner_->view();
- assert(view);
-
- if (hp.x() == 0) {
- QToolTip::hideText();
- return;
- }
-
- QString ann = get_annotation_at_point(hp);
-
- assert(view);
-
- if (!row_height_ || ann.isEmpty()) {
- QToolTip::hideText();
- return;
- }
+ if (!enabled())
+ return QString();
- const int hover_row = get_row_at_point(hp);
+ const pair<uint64_t, uint64_t> sample_range =
+ get_view_sample_range(point.x(), point.x() + 1);
+ const DecodeTraceRow* r = get_row_at_point(point);
- QFontMetrics m(QToolTip::font());
- const QRect text_size = m.boundingRect(QRect(), 0, ann);
+ if (!r)
+ return QString();
- // This is OS-specific and unfortunately we can't query it, so
- // use an approximation to at least try to minimize the error.
- const int padding = 8;
+ if (point.y() > (int)(get_row_y(r) + (annotation_height_ / 2)))
+ return QString();
- // Make sure the tool tip doesn't overlap with the mouse cursor.
- // 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.
- QPoint p = hp;
- p.setX(hp.x() - (text_size.width() / 2) - padding);
+ deque<const Annotation*> annotations;
- p.setY(get_visual_y() - (row_height_ / 2) +
- (hover_row * row_height_) -
- row_height_ - text_size.height() - padding);
+ decode_signal_->get_annotation_subset(annotations, r->decode_row,
+ current_segment_, sample_range.first, sample_range.second);
- QToolTip::showText(view->viewport()->mapToGlobal(p), ann);
+ return (annotations.empty()) ?
+ QString() : annotations[0]->annotations()->front();
}
-void DecodeTrace::create_decoder_form(int index,
- shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
- QFormLayout *form)
+void DecodeTrace::create_decoder_form(int index, shared_ptr<Decoder> &dec,
+ QWidget *parent, QFormLayout *form)
{
GlobalSettings settings;
assert(dec);
- const srd_decoder *const decoder = dec->decoder();
+ const srd_decoder *const decoder = dec->get_srd_decoder();
assert(decoder);
const bool decoder_deletable = index > 0;
tr("%1:\n%2").arg(QString::fromUtf8(decoder->longname),
QString::fromUtf8(decoder->desc)),
nullptr, decoder_deletable);
- group->set_decoder_visible(dec->shown());
+ group->set_decoder_visible(dec->visible());
if (decoder_deletable) {
delete_mapper_.setMapping(group, index);
return selector;
}
-void DecodeTrace::export_annotations(vector<Annotation> *annotations) const
+void DecodeTrace::export_annotations(deque<const Annotation*>& annotations) const
{
- using namespace pv::data::decode;
-
GlobalSettings settings;
const QString dir = settings.value("MainWindow/SaveDirectory").toString();
const QString quote = format.contains("%q") ? "\"" : "";
format = format.remove("%q");
+ const bool has_sample_range = format.contains("%s");
+ const bool has_row_name = format.contains("%r");
+ const bool has_dec_name = format.contains("%d");
+ const bool has_class_name = format.contains("%c");
+ const bool has_first_ann_text = format.contains("%1");
+ const bool has_all_ann_text = format.contains("%a");
+
QFile file(file_name);
if (file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
QTextStream out_stream(&file);
- for (Annotation &ann : *annotations) {
- const QString sample_range = QString("%1-%2") \
- .arg(QString::number(ann.start_sample()), QString::number(ann.end_sample()));
+ for (const Annotation* ann : annotations) {
+ QString out_text = format;
+
+ if (has_sample_range) {
+ const QString sample_range = QString("%1-%2") \
+ .arg(QString::number(ann->start_sample()), QString::number(ann->end_sample()));
+ out_text = out_text.replace("%s", sample_range);
+ }
- const QString class_name = quote + ann.row()->class_name() + quote;
+ if (has_dec_name)
+ out_text = out_text.replace("%d",
+ quote + QString::fromUtf8(ann->row()->decoder()->name()) + quote);
- QString all_ann_text;
- for (const QString &s : ann.annotations())
- all_ann_text = all_ann_text + quote + s + quote + ",";
- all_ann_text.chop(1);
+ if (has_row_name) {
+ const QString row_name = quote + ann->row()->description() + quote;
+ out_text = out_text.replace("%r", row_name);
+ }
- const QString first_ann_text = quote + ann.annotations().front() + quote;
+ if (has_class_name) {
+ const QString class_name = quote + ann->ann_class_name() + quote;
+ out_text = out_text.replace("%c", class_name);
+ }
+
+ if (has_first_ann_text) {
+ const QString first_ann_text = quote + ann->annotations()->front() + quote;
+ out_text = out_text.replace("%1", first_ann_text);
+ }
+
+ if (has_all_ann_text) {
+ QString all_ann_text;
+ for (const QString &s : *(ann->annotations()))
+ all_ann_text = all_ann_text + quote + s + quote + ",";
+ all_ann_text.chop(1);
+
+ out_text = out_text.replace("%a", all_ann_text);
+ }
- 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';
}
}
QMessageBox msg(owner_->view());
- msg.setText(tr("Error"));
- msg.setInformativeText(tr("File %1 could not be written to.").arg(file_name));
+ msg.setText(tr("Error") + "\n\n" + tr("File %1 could not be written to.").arg(file_name));
msg.setStandardButtons(QMessageBox::Ok);
msg.setIcon(QMessageBox::Warning);
msg.exec();
}
+void DecodeTrace::initialize_row_widgets(DecodeTraceRow* r, unsigned int row_id)
+{
+ // Set colors and fixed widths
+ QFontMetrics m(QApplication::font());
+
+ QPalette header_palette = owner_->view()->palette();
+ QPalette selector_palette = owner_->view()->palette();
+
+ if (GlobalSettings::current_theme_is_dark()) {
+ header_palette.setColor(QPalette::Background,
+ QColor(255, 255, 255, ExpansionAreaHeaderAlpha));
+ selector_palette.setColor(QPalette::Background,
+ QColor(255, 255, 255, ExpansionAreaAlpha));
+ } else {
+ header_palette.setColor(QPalette::Background,
+ QColor(0, 0, 0, ExpansionAreaHeaderAlpha));
+ selector_palette.setColor(QPalette::Background,
+ QColor(0, 0, 0, ExpansionAreaAlpha));
+ }
+
+ const int w = m.boundingRect(r->decode_row->title()).width() + RowTitleMargin;
+ r->title_width = w;
+
+ // Set up top-level container
+ connect(r->container, SIGNAL(widgetResized(QWidget*)),
+ this, SLOT(on_row_container_resized(QWidget*)));
+
+ QVBoxLayout* vlayout = new QVBoxLayout();
+ r->container->setLayout(vlayout);
+
+ // Add header container
+ vlayout->addWidget(r->header_container);
+ vlayout->setContentsMargins(0, 0, 0, 0);
+ vlayout->setSpacing(0);
+ QHBoxLayout* header_container_layout = new QHBoxLayout();
+ r->header_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ r->header_container->setMinimumSize(0, default_row_height_);
+ r->header_container->setLayout(header_container_layout);
+ r->header_container->layout()->setContentsMargins(10, 2, 10, 2);
+
+ r->header_container->setAutoFillBackground(true);
+ r->header_container->setPalette(header_palette);
+
+ // Add widgets inside the header container
+ QCheckBox* cb = new QCheckBox();
+ r->row_visibility_checkbox = cb;
+ header_container_layout->addWidget(cb);
+ cb->setText(tr("Show this row"));
+ cb->setChecked(r->decode_row->visible());
+
+ row_show_hide_mapper_.setMapping(cb, row_id);
+ connect(cb, SIGNAL(stateChanged(int)),
+ &row_show_hide_mapper_, SLOT(map()));
+
+ QPushButton* btn = new QPushButton();
+ header_container_layout->addWidget(btn);
+ btn->setFlat(true);
+ btn->setStyleSheet(":hover { background-color: palette(button); color: palette(button-text); border:0; }");
+ btn->setText(tr("Show All"));
+ btn->setProperty("decode_trace_row_ptr", QVariant::fromValue((void*)r));
+ connect(btn, SIGNAL(clicked(bool)), this, SLOT(on_show_all_classes()));
+
+ btn = new QPushButton();
+ header_container_layout->addWidget(btn);
+ btn->setFlat(true);
+ btn->setStyleSheet(":hover { background-color: palette(button); color: palette(button-text); border:0; }");
+ btn->setText(tr("Hide All"));
+ btn->setProperty("decode_trace_row_ptr", QVariant::fromValue((void*)r));
+ connect(btn, SIGNAL(clicked(bool)), this, SLOT(on_hide_all_classes()));
+
+ header_container_layout->addStretch(); // To left-align the header widgets
+
+ // Add selector container
+ vlayout->addWidget(r->selector_container);
+ r->selector_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ r->selector_container->setLayout(new FlowLayout(r->selector_container));
+
+ r->selector_container->setAutoFillBackground(true);
+ r->selector_container->setPalette(selector_palette);
+
+ // Add all classes that can be toggled
+ vector<AnnotationClass*> ann_classes = r->decode_row->ann_classes();
+
+ for (const AnnotationClass* ann_class : ann_classes) {
+ cb = new QCheckBox();
+ cb->setText(tr(ann_class->description));
+ cb->setChecked(ann_class->visible);
+
+ int dim = ViewItemPaintParams::text_height() - 2;
+ QPixmap pixmap(dim, dim);
+ pixmap.fill(r->ann_class_color[ann_class->id]);
+ cb->setIcon(pixmap);
+
+ r->selector_container->layout()->addWidget(cb);
+ r->selectors.push_back(cb);
+
+ cb->setProperty("ann_class_ptr", QVariant::fromValue((void*)ann_class));
+ cb->setProperty("decode_trace_row_ptr", QVariant::fromValue((void*)r));
+
+ class_show_hide_mapper_.setMapping(cb, cb);
+ connect(cb, SIGNAL(stateChanged(int)),
+ &class_show_hide_mapper_, SLOT(map()));
+ }
+}
+
+void DecodeTrace::update_rows()
+{
+ if (!owner_)
+ return;
+
+ lock_guard<mutex> lock(row_modification_mutex_);
+
+ for (DecodeTraceRow& r : rows_)
+ r.exists = false;
+
+ unsigned int row_id = 0;
+ for (Row* decode_row : decode_signal_->get_rows()) {
+ // Find row in our list
+ auto r_it = find_if(rows_.begin(), rows_.end(),
+ [&](DecodeTraceRow& r){ return r.decode_row == decode_row; });
+
+ DecodeTraceRow* r = nullptr;
+ if (r_it == rows_.end()) {
+ // Row doesn't exist yet, create and append it
+ DecodeTraceRow nr;
+ nr.decode_row = decode_row;
+ nr.height = default_row_height_;
+ nr.expanded_height = default_row_height_;
+ nr.currently_visible = false;
+ nr.has_hidden_classes = decode_row->has_hidden_classes();
+ nr.expand_marker_highlighted = false;
+ nr.expanding = false;
+ nr.expanded = false;
+ nr.collapsing = false;
+ nr.expand_marker_shape = default_marker_shape_;
+ nr.container = new ContainerWidget(owner_->view()->scrollarea());
+ nr.header_container = new QWidget(nr.container);
+ nr.selector_container = new QWidget(nr.container);
+
+ nr.row_color = get_row_color(decode_row->index());
+
+ vector<AnnotationClass*> ann_classes = decode_row->ann_classes();
+ for (const AnnotationClass* ann_class : ann_classes) {
+ nr.ann_class_color[ann_class->id] =
+ get_annotation_color(nr.row_color, ann_class->id);
+ nr.ann_class_dark_color[ann_class->id] =
+ nr.ann_class_color[ann_class->id].darker();
+ }
+
+ rows_.push_back(nr);
+ r = &rows_.back();
+ initialize_row_widgets(r, row_id);
+ } else
+ r = &(*r_it);
+
+ r->exists = true;
+ row_id++;
+ }
+
+ // If there's only one row, it must not be hidden or else it can't be un-hidden
+ if (row_id == 1)
+ rows_.front().row_visibility_checkbox->setEnabled(false);
+
+ // Remove any rows that no longer exist, obeying that iterators are invalidated
+ bool any_exists;
+ do {
+ any_exists = false;
+
+ for (unsigned int i = 0; i < rows_.size(); i++)
+ if (!rows_[i].exists) {
+ delete rows_[i].row_visibility_checkbox;
+
+ for (QCheckBox* cb : rows_[i].selectors)
+ delete cb;
+
+ delete rows_[i].selector_container;
+ delete rows_[i].header_container;
+ delete rows_[i].container;
+
+ rows_.erase(rows_.begin() + i);
+ any_exists = true;
+ break;
+ }
+ } while (any_exists);
+}
+
+void DecodeTrace::set_row_expanded(DecodeTraceRow* r)
+{
+ r->height = r->expanded_height;
+ r->expanding = false;
+ r->expanded = true;
+
+ // For details on this, see on_animation_timer()
+ r->expand_marker_shape.setPoint(0, 0, 0);
+ r->expand_marker_shape.setPoint(1, ArrowSize, ArrowSize);
+ r->expand_marker_shape.setPoint(2, 2*ArrowSize, 0);
+
+ r->container->resize(owner_->view()->viewport()->width() - r->container->pos().x(),
+ r->height - 2 * default_row_height_);
+}
+
+void DecodeTrace::set_row_collapsed(DecodeTraceRow* r)
+{
+ r->height = default_row_height_;
+ r->collapsing = false;
+ r->expanded = false;
+ r->expand_marker_shape = default_marker_shape_;
+ r->container->setVisible(false);
+
+ r->container->resize(owner_->view()->viewport()->width() - r->container->pos().x(),
+ r->height - 2 * default_row_height_);
+}
+
+void DecodeTrace::update_expanded_rows()
+{
+ for (DecodeTraceRow& r : rows_) {
+ if (r.expanding || r.expanded)
+ r.expanded_height = 2 * default_row_height_ + r.container->sizeHint().height();
+
+ if (r.expanded)
+ r.height = r.expanded_height;
+
+ int x = 2 * ArrowSize;
+ int y = get_row_y(&r) + default_row_height_;
+ // Only update the position if it actually changes
+ if ((x != r.container->pos().x()) || (y != r.container->pos().y()))
+ r.container->move(x, y);
+
+ int w = owner_->view()->viewport()->width() - x;
+ int h = r.height - 2 * default_row_height_;
+ // Only update the dimension if they actually change
+ if ((w != r.container->sizeHint().width()) || (h != r.container->sizeHint().height()))
+ r.container->resize(w, h);
+ }
+}
+
+void DecodeTrace::on_setting_changed(const QString &key, const QVariant &value)
+{
+ Trace::on_setting_changed(key, value);
+
+ if (key == GlobalSettings::Key_Dec_AlwaysShowAllRows)
+ always_show_all_rows_ = value.toBool();
+}
+
void DecodeTrace::on_new_annotations()
{
if (!delayed_trace_updater_.isActive())
void DecodeTrace::on_decode_reset()
{
- visible_rows_.clear();
- max_visible_rows_ = 0;
+ update_rows();
if (owner_)
owner_->row_item_appearance_changed(false, true);
decode_signal_->pause_decode();
}
-void DecodeTrace::delete_pressed()
-{
- on_delete();
-}
-
void DecodeTrace::on_delete()
{
session_.remove_decode_signal(decode_signal_);
void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
{
decode_signal_->stack_decoder(decoder);
+ update_rows();
create_popup_form();
}
void DecodeTrace::on_delete_decoder(int index)
{
decode_signal_->remove_decoder(index);
+ update_rows();
- // 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();
}
assert(index < (int)decoder_forms_.size());
decoder_forms_[index]->set_decoder_visible(state);
- if (!state) {
- // Force re-calculation of the trace height, see paint_mid()
- max_visible_rows_ = 0;
+ if (!state)
owner_->extents_changed(false, true);
- }
- if (owner_)
- owner_->row_item_appearance_changed(false, true);
+ owner_->row_item_appearance_changed(false, true);
+}
+
+void DecodeTrace::on_show_hide_row(int row_id)
+{
+ if (row_id >= (int)rows_.size())
+ return;
+
+ rows_[row_id].decode_row->set_visible(!rows_[row_id].decode_row->visible());
+
+ if (!rows_[row_id].decode_row->visible())
+ set_row_collapsed(&rows_[row_id]);
+
+ owner_->extents_changed(false, true);
+ owner_->row_item_appearance_changed(false, true);
+}
+
+void DecodeTrace::on_show_hide_class(QWidget* sender)
+{
+ void* ann_class_ptr = sender->property("ann_class_ptr").value<void*>();
+ assert(ann_class_ptr);
+ AnnotationClass* ann_class = (AnnotationClass*)ann_class_ptr;
+
+ ann_class->visible = !ann_class->visible;
+
+ void* row_ptr = sender->property("decode_trace_row_ptr").value<void*>();
+ assert(row_ptr);
+ DecodeTraceRow* row = (DecodeTraceRow*)row_ptr;
+
+ row->has_hidden_classes = row->decode_row->has_hidden_classes();
+
+ owner_->row_item_appearance_changed(false, true);
+}
+
+void DecodeTrace::on_show_all_classes()
+{
+ void* row_ptr = QObject::sender()->property("decode_trace_row_ptr").value<void*>();
+ assert(row_ptr);
+ DecodeTraceRow* row = (DecodeTraceRow*)row_ptr;
+
+ for (QCheckBox* cb : row->selectors)
+ cb->setChecked(true);
+
+ row->has_hidden_classes = false;
+
+ owner_->row_item_appearance_changed(false, true);
+}
+
+void DecodeTrace::on_hide_all_classes()
+{
+ void* row_ptr = QObject::sender()->property("decode_trace_row_ptr").value<void*>();
+ assert(row_ptr);
+ DecodeTraceRow* row = (DecodeTraceRow*)row_ptr;
+
+ for (QCheckBox* cb : row->selectors)
+ cb->setChecked(false);
+
+ row->has_hidden_classes = true;
+
+ owner_->row_item_appearance_changed(false, true);
+}
+
+void DecodeTrace::on_row_container_resized(QWidget* sender)
+{
+ sender->update();
+
+ owner_->extents_changed(false, true);
+ owner_->row_item_appearance_changed(false, true);
+}
+
+void DecodeTrace::on_copy_annotation_to_clipboard()
+{
+ if (!selected_row_)
+ return;
+
+ deque<const Annotation*> annotations;
+
+ decode_signal_->get_annotation_subset(annotations, selected_row_,
+ current_segment_, selected_sample_range_.first, selected_sample_range_.first);
+
+ if (annotations.empty())
+ return;
+
+ QClipboard *clipboard = QApplication::clipboard();
+ clipboard->setText(annotations.front()->annotations()->front(), QClipboard::Clipboard);
+
+ if (clipboard->supportsSelection())
+ clipboard->setText(annotations.front()->annotations()->front(), QClipboard::Selection);
}
void DecodeTrace::on_export_row()
void DecodeTrace::on_export_row_from_here()
{
- using namespace pv::data::decode;
-
if (!selected_row_)
return;
- vector<Annotation> *annotations = new vector<Annotation>();
+ deque<const Annotation*> annotations;
- decode_signal_->get_annotation_subset(*annotations, *selected_row_,
+ decode_signal_->get_annotation_subset(annotations, selected_row_,
current_segment_, selected_sample_range_.first, selected_sample_range_.second);
- if (annotations->empty())
+ 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>();
+ deque<const Annotation*> annotations;
- decode_signal_->get_annotation_subset(*annotations, current_segment_,
+ decode_signal_->get_annotation_subset(annotations, current_segment_,
selected_sample_range_.first, selected_sample_range_.second);
- if (!annotations->empty())
+ if (!annotations.empty())
export_annotations(annotations);
+}
+
+void DecodeTrace::on_animation_timer()
+{
+ bool animation_finished = true;
+
+ for (DecodeTraceRow& r : rows_) {
+ if (!(r.expanding || r.collapsing))
+ continue;
+
+ unsigned int height_delta = r.expanded_height - default_row_height_;
+
+ if (r.expanding) {
+ if (r.height < r.expanded_height) {
+ r.anim_height += height_delta / (float)AnimationDurationInTicks;
+ r.height = min((int)r.anim_height, (int)r.expanded_height);
+ r.anim_shape += ArrowSize / (float)AnimationDurationInTicks;
+ animation_finished = false;
+ } else
+ set_row_expanded(&r);
+ }
+
+ if (r.collapsing) {
+ if (r.height > default_row_height_) {
+ r.anim_height -= height_delta / (float)AnimationDurationInTicks;
+ r.height = max((int)r.anim_height, (int)0);
+ r.anim_shape -= ArrowSize / (float)AnimationDurationInTicks;
+ animation_finished = false;
+ } else
+ set_row_collapsed(&r);
+ }
- delete annotations;
+ // The expansion marker shape switches between
+ // 0/-A, A/0, 0/A (default state; anim_shape=0) and
+ // 0/ 0, A/A, 2A/0 (expanded state; anim_shape=ArrowSize)
+
+ r.expand_marker_shape.setPoint(0, 0, -ArrowSize + r.anim_shape);
+ r.expand_marker_shape.setPoint(1, ArrowSize, r.anim_shape);
+ r.expand_marker_shape.setPoint(2, 2*r.anim_shape, ArrowSize - r.anim_shape);
+ }
+
+ if (animation_finished)
+ animation_timer_.stop();
+
+ owner_->extents_changed(false, true);
+ owner_->row_item_appearance_changed(false, true);
+}
+
+void DecodeTrace::on_hide_hidden_rows()
+{
+ // Make all hidden traces invisible again unless the user is hovering over a row name
+ bool any_highlighted = false;
+
+ for (DecodeTraceRow& r : rows_)
+ if (r.expand_marker_highlighted)
+ any_highlighted = true;
+
+ if (!any_highlighted) {
+ show_hidden_rows_ = false;
+
+ owner_->extents_changed(false, true);
+ owner_->row_item_appearance_changed(false, true);
+ }
}
} // namespace trace
#ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_DECODETRACE_HPP
#define PULSEVIEW_PV_VIEWS_TRACEVIEW_DECODETRACE_HPP
+#include <config.h>
#include "trace.hpp"
#include <list>
#include <QColor>
#include <QComboBox>
+#include <QCheckBox>
+#include <QElapsedTimer>
+#include <QPolygon>
+#include <QPushButton>
#include <QSignalMapper>
#include <QTimer>
#include <pv/binding/decoder.hpp>
+#include <pv/data/decode/decoder.hpp>
#include <pv/data/decode/annotation.hpp>
#include <pv/data/decode/row.hpp>
#include <pv/data/signalbase.hpp>
+#define DECODETRACE_SHOW_RENDER_TIME 0
+
+using std::deque;
using std::list;
using std::map;
+using std::mutex;
using std::pair;
using std::shared_ptr;
using std::vector;
+using pv::data::SignalBase;
+using pv::data::decode::Annotation;
+using pv::data::decode::Decoder;
+using pv::data::decode::Row;
+
struct srd_channel;
struct srd_decoder;
namespace decode {
class Decoder;
+class Row;
}
} // namespace data
namespace views {
namespace trace {
+class ContainerWidget;
+
+struct DecodeTraceRow {
+ // When adding a field, make sure it's initialized properly in
+ // DecodeTrace::update_rows()
+
+ Row* decode_row;
+ unsigned int height, expanded_height, title_width, animation_step;
+ bool exists, currently_visible, has_hidden_classes;
+ bool expand_marker_highlighted, expanding, expanded, collapsing;
+ QPolygon expand_marker_shape;
+ float anim_height, anim_shape;
+
+ ContainerWidget* container;
+ QWidget* header_container;
+ QWidget* selector_container;
+ QCheckBox* row_visibility_checkbox;
+ vector<QCheckBox*> selectors;
+
+ QColor row_color;
+ map<uint32_t, QColor> ann_class_color;
+ map<uint32_t, QColor> ann_class_dark_color;
+};
+
+class ContainerWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ ContainerWidget(QWidget *parent = nullptr);
+
+ virtual void resizeEvent(QResizeEvent* event);
+
+Q_SIGNALS:
+ void widgetResized(QWidget* sender);
+};
+
class DecodeTrace : public Trace
{
Q_OBJECT
private:
static const QColor ErrorBgColor;
static const QColor NoDecodeColor;
+ static const QColor ExpandMarkerWarnColor;
+ static const QColor ExpandMarkerHiddenColor;
+ static const uint8_t ExpansionAreaHeaderAlpha;
+ static const uint8_t ExpansionAreaAlpha;
static const int ArrowSize;
static const double EndCapWidth;
static const int DrawPadding;
static const int MaxTraceUpdateRate;
+ static const int AnimationDurationInTicks;
+ static const int HiddenRowHideDelay;
public:
- DecodeTrace(pv::Session &session, shared_ptr<data::SignalBase> signalbase,
+ DecodeTrace(pv::Session &session, shared_ptr<SignalBase> signalbase,
int index);
+ ~DecodeTrace();
+
bool enabled() const;
- shared_ptr<data::SignalBase> base() const;
+ shared_ptr<SignalBase> base() const;
+
+ /**
+ * Sets the owner this trace in the view trace hierachy.
+ * @param The new owner of the trace.
+ */
+ virtual void set_owner(TraceTreeItemOwner *owner);
/**
* Computes the vertical extents of the contents of this row item.
virtual QMenu* create_view_context_menu(QWidget *parent, QPoint &click_pos);
- void delete_pressed();
+ virtual void delete_pressed();
+
+ virtual void hover_point_changed(const QPoint &hp);
+
+ virtual void mouse_left_press_event(const QMouseEvent* event);
private:
- void draw_annotations(vector<pv::data::decode::Annotation> annotations,
- QPainter &p, int h, const ViewItemPaintParams &pp, int y,
- QColor row_color, int row_title_width);
+ void draw_annotations(deque<const Annotation*>& annotations, QPainter &p,
+ const ViewItemPaintParams &pp, int y, const DecodeTraceRow& row);
- void draw_annotation(const pv::data::decode::Annotation &a, QPainter &p,
- int h, const ViewItemPaintParams &pp, int y,
- QColor row_color, int row_title_width) const;
+ void draw_annotation(const Annotation* a, QPainter &p,
+ const ViewItemPaintParams &pp, int y, const DecodeTraceRow& row) 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_annotation_block(qreal start, qreal end, Annotation::Class ann_class,
+ bool use_ann_format, QPainter &p, int y, const DecodeTraceRow& row) const;
- void draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
- int h, qreal x, int y) const;
+ void draw_instant(const Annotation* a, QPainter &p, qreal x, int y) const;
- void draw_range(const pv::data::decode::Annotation &a, QPainter &p,
- int h, qreal start, qreal end, int y, const ViewItemPaintParams &pp,
- int row_title_width) const;
+ void draw_range(const Annotation* a, QPainter &p, qreal start, qreal end,
+ int y, const ViewItemPaintParams &pp, int row_title_width) const;
- void draw_error(QPainter &p, const QString &message,
- const ViewItemPaintParams &pp);
+ void draw_error(QPainter &p, const QString &message, const ViewItemPaintParams &pp);
- void draw_unresolved_period(QPainter &p, int h, int left,
- int right) const;
+ void draw_unresolved_period(QPainter &p, int left, int right) const;
pair<double, double> get_pixels_offset_samples_per_pixel() 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);
+ unsigned int get_row_y(const DecodeTraceRow* row) const;
+
+ DecodeTraceRow* get_row_at_point(const QPoint &point);
const QString get_annotation_at_point(const QPoint &point);
- void create_decoder_form(int index,
- shared_ptr<pv::data::decode::Decoder> &dec,
+ void update_stack_button();
+
+ void create_decoder_form(int index, shared_ptr<Decoder> &dec,
QWidget *parent, QFormLayout *form);
QComboBox* create_channel_selector(QWidget *parent,
- const data::DecodeChannel *ch);
+ const data::decode::DecodeChannel *ch);
QComboBox* create_channel_selector_init_state(QWidget *parent,
- const data::DecodeChannel *ch);
+ const data::decode::DecodeChannel *ch);
- void export_annotations(vector<data::decode::Annotation> *annotations) const;
+ void export_annotations(deque<const Annotation*>& annotations) const;
-public:
- virtual void hover_point_changed(const QPoint &hp);
+ void initialize_row_widgets(DecodeTraceRow* r, unsigned int row_id);
+ void update_rows();
+
+ /**
+ * Sets row r to expanded state without forcing an update of the view
+ */
+ void set_row_expanded(DecodeTraceRow* r);
+
+ /**
+ * Sets row r to collapsed state without forcing an update of the view
+ */
+ void set_row_collapsed(DecodeTraceRow* r);
+
+ void update_expanded_rows();
private Q_SLOTS:
+ void on_setting_changed(const QString &key, const QVariant &value);
+
void on_new_annotations();
void on_delayed_trace_update();
void on_decode_reset();
void on_delete_decoder(int index);
void on_show_hide_decoder(int index);
+ void on_show_hide_row(int row_id);
+ void on_show_hide_class(QWidget* sender);
+ void on_show_all_classes();
+ void on_hide_all_classes();
+ void on_row_container_resized(QWidget* sender);
+
+ void on_copy_annotation_to_clipboard();
void on_export_row();
void on_export_all_rows();
void on_export_row_from_here();
void on_export_all_rows_from_here();
+ void on_animation_timer();
+ void on_hide_hidden_rows();
+
private:
pv::Session &session_;
shared_ptr<data::DecodeSignal> decode_signal_;
- vector<data::decode::Row> visible_rows_;
+ deque<DecodeTraceRow> rows_;
+ mutable mutex row_modification_mutex_;
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_;
- data::decode::Row *selected_row_;
+ const Row* selected_row_;
pair<uint64_t, uint64_t> selected_sample_range_;
vector<pv::widgets::DecoderGroupBox*> decoder_forms_;
+ QPushButton* stack_button_;
- map<data::decode::Row, int> row_title_widths_;
- int row_height_, max_visible_rows_;
+ unsigned int default_row_height_, annotation_height_;
+ unsigned int visible_rows_;
int min_useful_label_width_;
+ bool always_show_all_rows_, show_hidden_rows_;
QSignalMapper delete_mapper_, show_hide_mapper_;
+ QSignalMapper row_show_hide_mapper_, class_show_hide_mapper_;
+
+ QTimer delayed_trace_updater_, animation_timer_, delayed_hidden_row_hider_;
+
+ QPolygon default_marker_shape_;
- QTimer delayed_trace_updater_;
+#if DECODETRACE_SHOW_RENDER_TIME
+ QElapsedTimer render_time_;
+#endif
};
} // namespace trace
#include "timemarker.hpp"
#include "view.hpp"
+#include "ruler.hpp"
#include <QColor>
#include <QFormLayout>
#include <QLineEdit>
#include <QMenu>
+#include <QApplication>
#include <libsigrokcxx/libsigrokcxx.hpp>
QString Flag::get_text() const
{
- return text_;
+ QString s;
+
+ const shared_ptr<TimeItem> ref_item = view_.ruler()->get_reference_item();
+
+ if (!ref_item || (ref_item.get() == this))
+ s = text_;
+ else
+ s = Ruler::format_time_with_distance(
+ ref_item->time(), ref_item->delta(time_),
+ view_.tick_prefix(), view_.time_unit(), view_.tick_precision());
+
+ return s;
+}
+
+void Flag::set_text(const QString &text)
+{
+ text_ = text;
+ view_.time_item_appearance_changed(true, false);
+}
+
+QRectF Flag::label_rect(const QRectF &rect) const
+{
+ QRectF r;
+
+ const shared_ptr<TimeItem> ref_item = view_.ruler()->get_reference_item();
+
+ if (!ref_item || (ref_item.get() == this)) {
+ r = TimeMarker::label_rect(rect);
+ } else {
+ // TODO: Remove code duplication between here and cursor.cpp
+ const float x = get_x();
+
+ QFontMetrics m(QApplication::font());
+ QSize text_size = m.boundingRect(get_text()).size();
+
+ const QSizeF label_size(
+ text_size.width() + LabelPadding.width() * 2,
+ text_size.height() + LabelPadding.height() * 2);
+
+ const float height = label_size.height();
+ const float top =
+ rect.height() - label_size.height() - TimeMarker::ArrowSize - 0.5f;
+
+ const pv::util::Timestamp& delta = ref_item->delta(time_);
+
+ if (delta >= 0)
+ r = QRectF(x, top, label_size.width(), height);
+ else
+ r = QRectF(x - label_size.width(), top, label_size.width(), height);
+ }
+
+ return r;
}
pv::widgets::Popup* Flag::create_popup(QWidget *parent)
connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
menu->addAction(del);
+ QAction *const snap_disable = new QAction(tr("Disable snapping"), this);
+ snap_disable->setCheckable(true);
+ snap_disable->setChecked(snapping_disabled_);
+ connect(snap_disable, &QAction::toggled, this, [=](bool checked){snapping_disabled_ = checked;});
+ menu->addAction(snap_disable);
+
return menu;
}
void Flag::on_text_changed(const QString &text)
{
- text_ = text;
- view_.time_item_appearance_changed(true, false);
+ set_text(text);
}
} // namespace trace
/**
* Returns true if the item is visible and enabled.
*/
- bool enabled() const;
+ virtual bool enabled() const override;
/**
* Gets the text to show in the marker.
*/
- QString get_text() const;
+ virtual QString get_text() const override;
- pv::widgets::Popup* create_popup(QWidget *parent);
+ /**
+ * Sets the text to show in the marker.
+ */
+ virtual void set_text(const QString &text) override;
+
+ virtual pv::widgets::Popup* create_popup(QWidget *parent) override;
+
+ virtual QMenu* create_header_context_menu(QWidget *parent) override;
- QMenu* create_header_context_menu(QWidget *parent);
+ virtual void delete_pressed() override;
- void delete_pressed();
+ QRectF label_rect(const QRectF &rect) const override;
private Q_SLOTS:
void on_delete();
{
const QRect rect(0, 0, width(), height());
- vector< shared_ptr<RowItem> > items(view_.list_by_type<RowItem>());
+ vector< shared_ptr<ViewItem> > items(view_.list_by_type<ViewItem>());
stable_sort(items.begin(), items.end(),
- [](const shared_ptr<RowItem> &a, const shared_ptr<RowItem> &b) {
+ [](const shared_ptr<ViewItem> &a, const shared_ptr<ViewItem> &b) {
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<ViewItem>& r : items) {
assert(r);
const bool highlight = !item_dragging_ &&
return base_->logic_data();
}
-void LogicSignal::save_settings(QSettings &settings) const
+std::map<QString, QVariant> LogicSignal::save_settings() const
{
- settings.setValue("trace_height", signal_height_);
+ std::map<QString, QVariant> result;
+
+ result["trace_height"] = signal_height_;
+
+ return result;
}
-void LogicSignal::restore_settings(QSettings &settings)
+void LogicSignal::restore_settings(std::map<QString, QVariant> settings)
{
- if (settings.contains("trace_height")) {
+ auto entry = settings.find("trace_height");
+ if (entry != settings.end()) {
const int old_height = signal_height_;
- signal_height_ = settings.value("trace_height").toInt();
+ signal_height_ = settings["trace_height"].toInt();
if ((signal_height_ != old_height) && owner_) {
// Call order is important, otherwise the lazy event handler won't work
shared_ptr<pv::data::Logic> logic_data() const;
- virtual void save_settings(QSettings &settings) const;
- virtual void restore_settings(QSettings &settings);
+ virtual std::map<QString, QVariant> save_settings() const;
+ virtual void restore_settings(std::map<QString, QVariant> settings);
/**
* Computes the vertical extents of the contents of this row item.
void MarginWidget::show_popup(const shared_ptr<ViewItem> &item)
{
pv::widgets::Popup *const p = item->create_popup(this);
+
+ connect(p, SIGNAL(closed()), this, SLOT(on_popup_closed()));
+
if (p)
p->show();
}
if (i->selected())
i->delete_pressed();
}
+
+ ViewWidget::keyPressEvent(event);
}
+void MarginWidget::on_popup_closed()
+{
+ bool cursor_above_widget = rect().contains(mapFromGlobal(QCursor::pos()));
+
+ if (!cursor_above_widget)
+ mouse_point_ = QPoint(INT_MIN, INT_MIN);
+
+ update();
+}
+
+
} // namespace trace
} // namespace views
} // namespace pv
*/
void show_popup(const shared_ptr<ViewItem> &item);
-protected:
+protected Q_SLOTS:
virtual void contextMenuEvent(QContextMenuEvent *event);
virtual void keyPressEvent(QKeyEvent *event);
+
+ virtual void on_popup_closed();
};
} // namespace trace
+++ /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 "rowitem.hpp"
-
-namespace pv {
-namespace views {
-namespace trace {
-
-void RowItem::hover_point_changed(const QPoint &hp)
-{
- (void)hp;
-}
-
-} // namespace trace
-} // namespace views
-} // 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_VIEWS_TRACEVIEW_ROWITEM_HPP
-#define PULSEVIEW_PV_VIEWS_TRACEVIEW_ROWITEM_HPP
-
-#include "viewitem.hpp"
-
-namespace pv {
-namespace views {
-namespace trace {
-
-class RowItem : public ViewItem
-{
- Q_OBJECT
-
-public:
- virtual void hover_point_changed(const QPoint &hp);
-};
-
-} // namespace trace
-} // namespace views
-} // namespace pv
-
-#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_ROWITEM_HPP
#include <extdef.h>
-#include <QApplication>
#include <QFontMetrics>
#include <QMenu>
#include <QMouseEvent>
if (unit == pv::util::TimeUnit::Samples)
return pv::util::format_time_si_adjusted(t, prefix, precision, "sa", sign);
+ QString unit_string;
+ if (unit == pv::util::TimeUnit::Time)
+ unit_string = "s";
+ // Note: In case of pv::util::TimeUnit::None, unit_string remains empty
+
// View zoomed way out -> low precision (0), big distance (>=60s)
// -> DD:HH:MM
if ((precision == 0) && (distance >= limit))
// View zoomed way in -> high precision (>3), low step size (<1s)
// -> HH:MM:SS.mmm... or xxxx (si unit) if less than limit seconds
if (abs(t) < limit)
- return pv::util::format_time_si_adjusted(t, prefix, precision, "s", sign);
+ return pv::util::format_time_si_adjusted(t, prefix, precision, unit_string, sign);
else
return pv::util::format_time_minutes(t, precision, sign);
}
-pv::util::Timestamp Ruler::get_time_from_x_pos(uint32_t x) const
+pv::util::Timestamp Ruler::get_absolute_time_from_x_pos(uint32_t x) const
+{
+ return view_.offset() + ((double)x + 0.5) * view_.scale();
+}
+
+pv::util::Timestamp Ruler::get_ruler_time_from_x_pos(uint32_t x) const
{
return view_.ruler_offset() + ((double)x + 0.5) * view_.scale();
}
+pv::util::Timestamp Ruler::get_ruler_time_from_absolute_time(const pv::util::Timestamp& abs_time) const
+{
+ return abs_time + view_.zero_offset();
+}
+
+pv::util::Timestamp Ruler::get_absolute_time_from_ruler_time(const pv::util::Timestamp& ruler_time) const
+{
+ return ruler_time - view_.zero_offset();
+}
+
void Ruler::contextMenuEvent(QContextMenuEvent *event)
{
MarginWidget::contextMenuEvent(event);
connect(set_zero_position, SIGNAL(triggered()), this, SLOT(on_setZeroPosition()));
menu->addAction(set_zero_position);
+ if (view_.zero_offset().convert_to<double>() != 0) {
+ QAction *const reset_zero_position = new QAction(tr("Reset zero point"), this);
+ connect(reset_zero_position, SIGNAL(triggered()), this, SLOT(on_resetZeroPosition()));
+ menu->addAction(reset_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);
time_items.begin(), time_items.end());
}
+void Ruler::item_hover(const shared_ptr<ViewItem> &item, QPoint pos)
+{
+ (void)pos;
+
+ hover_item_ = dynamic_pointer_cast<TimeItem>(item);
+}
+
+shared_ptr<TimeItem> Ruler::get_reference_item() const
+{
+ // Note: time() returns 0 if item returns no valid time
+
+ if (mouse_modifiers_ & Qt::ShiftModifier)
+ return nullptr;
+
+ if (hover_item_ && (hover_item_->time() != 0))
+ return hover_item_;
+
+ shared_ptr<TimeItem> ref_item;
+ const vector< shared_ptr<TimeItem> > items(view_.time_items());
+
+ for (auto i = items.rbegin(); i != items.rend(); i++) {
+ if ((*i)->enabled() && (*i)->selected()) {
+ if (!ref_item)
+ ref_item = *i;
+ else {
+ // Return nothing if multiple items are selected
+ ref_item.reset();
+ break;
+ }
+ }
+ }
+
+ if (ref_item && (ref_item->time() == 0))
+ ref_item.reset();
+
+ return ref_item;
+}
+
shared_ptr<ViewItem> Ruler::get_mouse_over_item(const QPoint &pt)
{
const vector< shared_ptr<TimeItem> > items(view_.time_items());
+
for (auto i = items.rbegin(); i != items.rend(); i++)
if ((*i)->enabled() && (*i)->label_rect(rect()).contains(pt))
return *i;
+
return nullptr;
}
void Ruler::mouseDoubleClickEvent(QMouseEvent *event)
{
- view_.add_flag(get_time_from_x_pos(event->x()));
+ hover_item_ = view_.add_flag(get_absolute_time_from_x_pos(event->x()));
}
void Ruler::paintEvent(QPaintEvent*)
void Ruler::on_createMarker()
{
- view_.add_flag(get_time_from_x_pos(mouse_down_point_.x()));
+ hover_item_ = view_.add_flag(get_absolute_time_from_x_pos(mouse_down_point_.x()));
}
void Ruler::on_setZeroPosition()
{
- view_.set_zero_position(get_time_from_x_pos(mouse_down_point_.x()));
+ view_.set_zero_position(get_absolute_time_from_x_pos(mouse_down_point_.x()));
+}
+
+void Ruler::on_resetZeroPosition()
+{
+ view_.reset_zero_position();
}
void Ruler::on_toggleHoverMarker()
unsigned precision = 0,
bool sign = true);
- pv::util::Timestamp get_time_from_x_pos(uint32_t x) const;
+ pv::util::Timestamp get_absolute_time_from_x_pos(uint32_t x) const;
+ pv::util::Timestamp get_ruler_time_from_x_pos(uint32_t x) const;
+
+ pv::util::Timestamp get_ruler_time_from_absolute_time(const pv::util::Timestamp& abs_time) const;
+ pv::util::Timestamp get_absolute_time_from_ruler_time(const pv::util::Timestamp& ruler_time) const;
+
+ shared_ptr<TimeItem> get_reference_item() const;
protected:
virtual void contextMenuEvent(QContextMenuEvent *event) override;
void resizeEvent(QResizeEvent*) override;
+ virtual void item_hover(const shared_ptr<ViewItem> &item, QPoint pos) override;
private:
/**
void on_createMarker();
void on_setZeroPosition();
+ void on_resetZeroPosition();
void on_toggleHoverMarker();
private:
*/
boost::optional<TickPositions> tick_position_cache_;
+ shared_ptr<TimeItem> hover_item_;
+
uint32_t context_menu_x_pos_;
};
void Signal::save_settings(QSettings &settings) const
{
- (void)settings;
+ std::map<QString, QVariant> settings_map = save_settings();
+
+ for (auto& entry : settings_map)
+ settings.setValue(entry.first, entry.second);
+}
+
+std::map<QString, QVariant> Signal::save_settings() const
+{
+ return std::map<QString, QVariant>();
}
void Signal::restore_settings(QSettings &settings)
+{
+ std::map<QString, QVariant> settings_map;
+
+ QStringList keys = settings.allKeys();
+ for (int i = 0; i < keys.size(); i++)
+ settings_map[keys.at(i)] = settings.value(keys.at(i));
+
+ restore_settings(settings_map);
+}
+
+void Signal::restore_settings(std::map<QString, QVariant> settings)
{
(void)settings;
}
+
void Signal::paint_back(QPainter &p, ViewItemPaintParams &pp)
{
if (base_->enabled())
#include <memory>
#include <QComboBox>
+#include <QString>
+#include <QVariant>
#include <QWidgetAction>
#include <cstdint>
shared_ptr<data::SignalBase> base() const;
virtual void save_settings(QSettings &settings) const;
+ virtual std::map<QString, QVariant> save_settings() const;
virtual void restore_settings(QSettings &settings);
+ virtual void restore_settings(std::map<QString, QVariant> settings);
void paint_back(QPainter &p, ViewItemPaintParams &pp);
const bool show = action_view_show_cursors_->isChecked();
if (show)
- view_->centre_cursors();
+ view_->center_cursors();
view_->show_cursors(show);
}
void TimeItem::drag_by(const QPoint &delta)
{
- 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
+ if (snapping_disabled_) {
set_time(view_.offset() + (drag_point_.x() + delta.x() - 0.5) *
view_.scale());
+ } else {
+ 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());
+ }
+}
+
+const pv::util::Timestamp TimeItem::delta(const pv::util::Timestamp& other) const
+{
+ return other - time();
+}
+
+
+bool TimeItem::is_snapping_disabled() const
+{
+ return snapping_disabled_;
}
} // namespace trace
*/
TimeItem(View &view);
+ bool snapping_disabled_ = false;
+
public:
/**
* Sets the time of the marker.
*/
virtual void set_time(const pv::util::Timestamp& time) = 0;
+ /**
+ * Returns the time this time item is set to.
+ * @return 0 in case there is no valid time (e.g. for a cursor pair)
+ */
+ virtual const pv::util::Timestamp time() const = 0;
+
virtual float get_x() const = 0;
+ virtual const pv::util::Timestamp delta(const pv::util::Timestamp& other) const;
+
/**
* 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);
+ bool is_snapping_disabled() const;
+
protected:
View &view_;
};
#include "timemarker.hpp"
#include "pv/widgets/timestampspinbox.hpp"
+#include "ruler.hpp"
#include "view.hpp"
#include <QApplication>
color_(color),
time_(time),
value_action_(nullptr),
- value_widget_(nullptr),
- updating_value_widget_(false)
+ value_widget_(nullptr)
{
}
-const pv::util::Timestamp& TimeMarker::time() const
+const pv::util::Timestamp TimeMarker::time() const
{
return time_;
}
time_ = time;
if (value_widget_) {
- updating_value_widget_ = true;
- value_widget_->setValue(time);
- updating_value_widget_ = false;
+ QSignalBlocker blocker(value_widget_);
+ value_widget_->setValue(view_.ruler()->get_ruler_time_from_absolute_time(time));
}
view_.time_item_appearance_changed(true, true);
return QRectF(x - h / 2.0f, pp.top(), h, pp.height());
}
+void TimeMarker::set_text(const QString &text)
+{
+ (void)text;
+}
+
void TimeMarker::paint_label(QPainter &p, const QRect &rect, bool hover)
{
if (!enabled())
popup->setLayout(form);
value_widget_ = new pv::widgets::TimestampSpinBox(parent);
- value_widget_->setValue(time_);
+ value_widget_->setValue(view_.ruler()->get_ruler_time_from_absolute_time(time_));
connect(value_widget_, SIGNAL(valueChanged(const pv::util::Timestamp&)),
this, SLOT(on_value_changed(const pv::util::Timestamp&)));
void TimeMarker::on_value_changed(const pv::util::Timestamp& value)
{
- if (!updating_value_widget_)
- set_time(value);
+ set_time(view_.ruler()->get_absolute_time_from_ruler_time(value));
}
} // namespace trace
/**
* Gets the time of the marker.
*/
- const pv::util::Timestamp& time() const;
+ virtual const pv::util::Timestamp time() const override;
/**
* Sets the time of the marker.
*/
virtual QString get_text() const = 0;
+ /**
+ * Sets the text to show in the marker.
+ */
+ virtual void set_text(const QString &text);
+
/**
* Paints the marker's label to the ruler.
* @param p The painter to draw with.
QWidgetAction *value_action_;
pv::widgets::TimestampSpinBox *value_widget_;
- bool updating_value_widget_;
};
} // namespace trace
void Trace::paint_axis(QPainter &p, ViewItemPaintParams &pp, int y)
{
+ bool was_antialiased = p.testRenderHint(QPainter::Antialiasing);
p.setRenderHint(QPainter::Antialiasing, false);
p.setPen(axis_pen_);
p.drawLine(QPointF(pp.left(), y), QPointF(pp.right(), y));
- p.setRenderHint(QPainter::Antialiasing, true);
+ p.setRenderHint(QPainter::Antialiasing, was_antialiased);
}
void Trace::add_color_option(QWidget *parent, QFormLayout *form)
const pair<int, int> extents = v_extents();
+ bool was_antialiased = p.testRenderHint(QPainter::Antialiasing);
p.setRenderHint(QPainter::Antialiasing, false);
p.drawLine(x, get_visual_y() + extents.first,
x, get_visual_y() + extents.second);
- p.setRenderHint(QPainter::Antialiasing, true);
+ p.setRenderHint(QPainter::Antialiasing, was_antialiased);
}
void Trace::create_popup_form()
// handled, leaving the parent popup_ time to handle the change.
if (popup_form_) {
QWidget *suicidal = new QWidget();
- suicidal->setLayout(popup_form_);
+ suicidal->setLayout(popup_->layout());
suicidal->deleteLater();
}
// Repopulate the popup
- popup_form_ = new QFormLayout(popup_);
- popup_->setLayout(popup_form_);
+ widgets::QWidthAdjustingScrollArea* scrollarea = new widgets::QWidthAdjustingScrollArea();
+ QWidget* scrollarea_content = new QWidget(scrollarea);
+
+ scrollarea->setWidget(scrollarea_content);
+ scrollarea->setWidgetResizable(true);
+ scrollarea->setContentsMargins(0, 0, 0, 0);
+ scrollarea->setFrameShape(QFrame::NoFrame);
+ scrollarea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ scrollarea_content->setContentsMargins(0, 0, 0, 0);
+
+ popup_->setLayout(new QVBoxLayout());
+ popup_->layout()->addWidget(scrollarea);
+ popup_->layout()->setContentsMargins(0, 0, 0, 0);
+
+ popup_form_ = new QFormLayout(scrollarea_content);
+ popup_form_->setSizeConstraint(QLayout::SetMinAndMaxSize);
+
populate_popup_form(popup_, popup_form_);
}
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()));
+ view->add_flag(ruler->get_absolute_time_from_x_pos(p.x()));
}
} // namespace trace
#include <QPropertyAnimation>
-#include "rowitem.hpp"
+#include "viewitem.hpp"
using std::enable_shared_from_this;
using std::pair;
class TraceTreeItemOwner;
-class TraceTreeItem : public RowItem,
+class TraceTreeItem : public ViewItem,
public enable_shared_from_this<TraceTreeItem>
{
Q_OBJECT
* Sets the owner this trace in the view trace hierachy.
* @param The new owner of the trace.
*/
- void set_owner(TraceTreeItemOwner *owner);
+ virtual void set_owner(TraceTreeItemOwner *owner);
/**
* Gets the visual y-offset of the axis.
{
vector<shared_ptr<TraceTreeItem>> items(trace_tree_child_items());
- // Sort by the centre line of the extents
+ // Sort by the center 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();
view_.time_item_appearance_changed(true, true);
}
+const pv::util::Timestamp TriggerMarker::time() const
+{
+ return time_;
+}
+
float TriggerMarker::get_x() const
{
return ((time_ - view_.offset()) / view_.scale()).convert_to<float>();
*/
void set_time(const pv::util::Timestamp& time) override;
+ virtual const pv::util::Timestamp time() const override;
+
float get_x() const override;
/**
#include <iterator>
#include <unordered_set>
-#include <boost/archive/text_iarchive.hpp>
-#include <boost/archive/text_oarchive.hpp>
-#include <boost/serialization/serialization.hpp>
-
#include <QApplication>
+#include <QDebug>
#include <QEvent>
#include <QFontMetrics>
#include <QMenu>
using std::set;
using std::set_difference;
using std::shared_ptr;
-using std::stringstream;
using std::unordered_map;
using std::unordered_set;
using std::vector;
const int View::MaxScrollValue = INT_MAX / 2;
-const int View::ScaleUnits[3] = {1, 2, 5};
+/* Area at the top and bottom of the view that can't be scrolled out of sight */
+const int View::ViewScrollMargin = 50;
+const int View::ScaleUnits[3] = {1, 2, 5};
CustomScrollArea::CustomScrollArea(QWidget *parent) :
QAbstractScrollArea(parent)
}
}
-View::View(Session &session, bool is_main_view, QWidget *parent) :
+View::View(Session &session, bool is_main_view, QMainWindow *parent) :
ViewBase(session, is_main_view, parent),
// Note: Place defaults in View::reset_view_state(), not here
splitter_(new QSplitter()),
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()
+ sticky_scrolling_(false), // Default setting is set in MainWindow::setup_ui()
+ scroll_needs_defaults_(true)
{
QVBoxLayout *root_layout = new QVBoxLayout(this);
root_layout->setContentsMargins(0, 0, 0, 0);
connect(&lazy_event_handler_, SIGNAL(timeout()),
this, SLOT(process_sticky_events()));
lazy_event_handler_.setSingleShot(true);
+ lazy_event_handler_.setInterval(1000 / ViewBase::MaxViewAutoUpdateRate);
+
+ // Set up local keyboard shortcuts
+ zoom_in_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Plus), this,
+ SLOT(on_zoom_in_shortcut_triggered()), nullptr, Qt::WidgetWithChildrenShortcut);
+ zoom_in_shortcut_->setAutoRepeat(false);
+
+ zoom_out_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Minus), this,
+ SLOT(on_zoom_out_shortcut_triggered()), nullptr, Qt::WidgetWithChildrenShortcut);
+ zoom_out_shortcut_->setAutoRepeat(false);
+
+ zoom_in_shortcut_2_ = new QShortcut(QKeySequence(Qt::Key_Up), this,
+ SLOT(on_zoom_in_shortcut_triggered()), nullptr, Qt::WidgetWithChildrenShortcut);
+ zoom_out_shortcut_2_ = new QShortcut(QKeySequence(Qt::Key_Down), this,
+ SLOT(on_zoom_out_shortcut_triggered()), nullptr, Qt::WidgetWithChildrenShortcut);
+
+ home_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Home), this,
+ SLOT(on_scroll_to_start_shortcut_triggered()), nullptr, Qt::WidgetWithChildrenShortcut);
+ home_shortcut_->setAutoRepeat(false);
+
+ end_shortcut_ = new QShortcut(QKeySequence(Qt::Key_End), this,
+ SLOT(on_scroll_to_end_shortcut_triggered()), nullptr, Qt::WidgetWithChildrenShortcut);
+ end_shortcut_->setAutoRepeat(false);
+
+ grab_ruler_left_shortcut_ = new QShortcut(QKeySequence(Qt::Key_1), this,
+ nullptr, nullptr, Qt::WidgetWithChildrenShortcut);
+ connect(grab_ruler_left_shortcut_, &QShortcut::activated,
+ this, [=]{on_grab_ruler(1);});
+ grab_ruler_left_shortcut_->setAutoRepeat(false);
+
+ grab_ruler_right_shortcut_ = new QShortcut(QKeySequence(Qt::Key_2), this,
+ nullptr, nullptr, Qt::WidgetWithChildrenShortcut);
+ connect(grab_ruler_right_shortcut_, &QShortcut::activated,
+ this, [=]{on_grab_ruler(2);});
+ grab_ruler_right_shortcut_->setAutoRepeat(false);
+
+ cancel_grab_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Escape), this,
+ nullptr, nullptr, Qt::WidgetWithChildrenShortcut);
+ connect(cancel_grab_shortcut_, &QShortcut::activated,
+ this, [=]{grabbed_widget_ = nullptr;});
+ cancel_grab_shortcut_->setAutoRepeat(false);
// Trigger the initial event manually. The default device has signals
// which were created before this object came into being
signals_changed();
- // make sure the transparent widgets are on the top
+ // Make sure the transparent widgets are on the top
ruler_->raise();
header_->raise();
GlobalSettings::remove_change_handler(this);
}
+ViewType View::get_type() const
+{
+ return ViewTypeTrace;
+}
+
void View::reset_view_state()
{
ViewBase::reset_view_state();
scale_ = 1e-3;
offset_ = 0;
ruler_offset_ = 0;
+ zero_offset_ = 0;
+ custom_zero_offset_set_ = false;
updating_scroll_ = false;
settings_restored_ = false;
always_zoom_to_fit_ = false;
next_flag_text_ = 'A';
trigger_markers_.clear();
hover_widget_ = nullptr;
+ grabbed_widget_ = nullptr;
hover_point_ = QPoint(-1, -1);
scroll_needs_defaults_ = true;
saved_v_offset_ = 0;
return session_;
}
-unordered_set< shared_ptr<Signal> > View::signals() const
+vector< shared_ptr<Signal> > View::signals() const
{
return signals_;
}
+shared_ptr<Signal> View::get_signal_by_signalbase(shared_ptr<data::SignalBase> base) const
+{
+ shared_ptr<Signal> ret_val;
+
+ for (const shared_ptr<Signal>& s : signals_)
+ if (s->base() == base) {
+ ret_val = s;
+ break;
+ }
+
+ return ret_val;
+}
+
void View::clear_signals()
{
- ViewBase::clear_signalbases();
+ ViewBase::clear_signals();
signals_.clear();
}
void View::add_signal(const shared_ptr<Signal> signal)
{
ViewBase::add_signalbase(signal->base());
- signals_.insert(signal);
+ signals_.push_back(signal);
signal->set_segment_display_mode(segment_display_mode_);
signal->set_current_segment(current_segment_);
#ifdef ENABLE_DECODE
void View::clear_decode_signals()
{
+ ViewBase::clear_decode_signals();
decode_traces_.clear();
}
void View::add_decode_signal(shared_ptr<data::DecodeSignal> signal)
{
+ ViewBase::add_decode_signal(signal);
+
shared_ptr<DecodeTrace> d(
new DecodeTrace(session_, signal, decode_traces_.size()));
decode_traces_.push_back(d);
signals_changed();
return;
}
+
+ ViewBase::remove_decode_signal(signal);
}
#endif
return viewport_;
}
+QAbstractScrollArea* View::scrollarea() const
+{
+ return scrollarea_;
+}
+
const Ruler* View::ruler() const
{
return ruler_;
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("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()));
- }
+ GlobalSettings::store_timestamp(settings, "offset", offset_);
+
+ if (custom_zero_offset_set_)
+ GlobalSettings::store_timestamp(settings, "zero_offset", -zero_offset_);
+ else
+ settings.remove("zero_offset");
for (const shared_ptr<Signal>& signal : signals_) {
settings.beginGroup(signal->base()->internal_name());
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();
-
- 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";
- }
+ // This also updates ruler_offset_
+ set_offset(GlobalSettings::restore_timestamp(settings, "offset"));
}
+ if (settings.contains("zero_offset"))
+ set_zero_position(GlobalSettings::restore_timestamp(settings, "zero_offset"));
+
if (settings.contains("splitter_state"))
splitter_->restoreState(settings.value("splitter_state").toByteArray());
{
if ((offset_ != offset) || force_update) {
offset_ = offset;
- ruler_offset_ = offset_ + ruler_shift_;
+ ruler_offset_ = offset_ + zero_offset_;
offset_changed();
}
}
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_));
+ zero_offset_ = -position;
+ custom_zero_offset_set_ = true;
// Force an immediate update of the offsets
set_offset(offset_, true);
void View::reset_zero_position()
{
- ruler_shift_ = 0;
+ zero_offset_ = 0;
+
+ // 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) {
+ vector<util::Timestamp> triggers = session_.get_triggers(current_segment_);
+
+ if (triggers.size() > 0)
+ zero_offset_ = triggers.front();
+ }
+
+ custom_zero_offset_set_ = false;
// Force an immediate update of the offsets
set_offset(offset_, true);
ruler_->update();
}
+pv::util::Timestamp View::zero_offset() const
+{
+ return zero_offset_;
+}
+
int View::owner_visual_v_offset() const
{
return -scrollarea_->verticalScrollBar()->sliderPosition();
viewport_->update();
}
+void View::set_h_offset(int offset)
+{
+ scrollarea_->horizontalScrollBar()->setSliderPosition(offset);
+ header_->update();
+ viewport_->update();
+}
+
+int View::get_h_scrollbar_maximum() const
+{
+ return scrollarea_->horizontalScrollBar()->maximum();
+}
+
unsigned int View::depth() const
{
return 0;
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());
+ if (!custom_zero_offset_set_)
+ reset_zero_position();
viewport_->update();
viewport_->update();
}
-set< shared_ptr<SignalData> > View::get_visible_data() const
+vector< shared_ptr<SignalData> > View::get_visible_data() const
{
// Make a set of all the visible data objects
- set< shared_ptr<SignalData> > visible_data;
+ vector< shared_ptr<SignalData> > visible_data;
for (const shared_ptr<Signal>& sig : signals_)
if (sig->enabled())
- visible_data.insert(sig->data());
+ visible_data.push_back(sig->data());
return visible_data;
}
pair<Timestamp, Timestamp> View::get_time_extents() const
{
boost::optional<Timestamp> left_time, right_time;
- const set< shared_ptr<SignalData> > visible_data = get_visible_data();
- for (const shared_ptr<SignalData>& d : visible_data) {
+
+ vector< shared_ptr<SignalData> > data;
+ if (signals_.size() == 0)
+ return make_pair(0, 0);
+
+ data.push_back(signals_.front()->data());
+
+ for (const shared_ptr<SignalData>& d : data) {
const vector< shared_ptr<Segment> > segments = d->segments();
for (const shared_ptr<Segment>& s : segments) {
double samplerate = s->samplerate();
return make_pair(*left_time, *right_time);
}
-void View::enable_show_sampling_points(bool state)
-{
- (void)state;
-
- viewport_->update();
-}
-
-void View::enable_show_analog_minor_grid(bool state)
-{
- (void)state;
-
- viewport_->update();
-}
-
-void View::enable_colored_bg(bool state)
-{
- colored_bg_ = state;
- viewport_->update();
-}
-
bool View::colored_bg() const
{
return colored_bg_;
void View::show_cursors(bool show)
{
- show_cursors_ = show;
- cursor_state_changed(show);
+ if (show_cursors_ != show) {
+ show_cursors_ = show;
+
+ cursor_state_changed(show);
+ ruler_->update();
+ viewport_->update();
+ }
+}
+
+void View::set_cursors(pv::util::Timestamp& first, pv::util::Timestamp& second)
+{
+ assert(cursors_);
+
+ cursors_->first()->set_time(first);
+ cursors_->second()->set_time(second);
+
ruler_->update();
viewport_->update();
}
-void View::centre_cursors()
+void View::center_cursors()
{
- 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);
+ assert(cursors_);
- ruler_->update();
- viewport_->update();
- }
+ 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 cursors_;
}
-void View::add_flag(const Timestamp& time)
+shared_ptr<Flag> View::add_flag(const Timestamp& time)
{
- flags_.push_back(make_shared<Flag>(*this, time,
- QString("%1").arg(next_flag_text_)));
+ shared_ptr<Flag> flag =
+ make_shared<Flag>(*this, time, QString("%1").arg(next_flag_text_));
+ flags_.push_back(flag);
next_flag_text_ = (next_flag_text_ >= 'Z') ? 'A' :
(next_flag_text_ + 1);
time_item_appearance_changed(true, true);
+ return flag;
}
void View::remove_flag(shared_ptr<Flag> flag)
void View::on_setting_changed(const QString &key, const QVariant &value)
{
+ GlobalSettings settings;
+
+ if (key == GlobalSettings::Key_View_ColoredBG) {
+ colored_bg_ = settings.value(GlobalSettings::Key_View_ColoredBG).toBool();
+ viewport_->update();
+ }
+
+ if ((key == GlobalSettings::Key_View_ShowSamplingPoints) ||
+ (key == GlobalSettings::Key_View_ShowAnalogMinorGrid))
+ viewport_->update();
+
if (key == GlobalSettings::Key_View_TriggerIsZeroTime)
on_settingViewTriggerIsZeroTime_changed(value);
- if (key == GlobalSettings::Key_View_SnapDistance) {
- GlobalSettings settings;
+ if (key == GlobalSettings::Key_View_SnapDistance)
snap_distance_ = settings.value(GlobalSettings::Key_View_SnapDistance).toInt();
- }
}
void View::trigger_event(int segment_id, util::Timestamp location)
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);
+ if (!custom_zero_offset_set_)
+ reset_zero_position();
trigger_markers_.push_back(make_shared<TriggerMarker>(*this, location));
}
const pair<int, int> extents = v_extents();
// Don't change the scrollbar range if there are no traces
- if (extents.first != extents.second)
- vscrollbar->setRange(extents.first - areaSize.height(),
- extents.second);
+ if (extents.first != extents.second) {
+ int top_margin = ViewScrollMargin;
+ int btm_margin = ViewScrollMargin;
+
+ vscrollbar->setRange(extents.first - areaSize.height() + top_margin,
+ extents.second - btm_margin);
+ }
if (scroll_needs_defaults_) {
set_scroll_default();
bool View::eventFilter(QObject *object, QEvent *event)
{
const QEvent::Type type = event->type();
+
if (type == QEvent::MouseMove) {
if (object)
update_hover_point();
+ if (grabbed_widget_) {
+ int64_t nearest = get_nearest_level_change(hover_point_);
+ pv::util::Timestamp mouse_time = offset_ + hover_point_.x() * scale_;
+
+ if (nearest == -1) {
+ grabbed_widget_->set_time(mouse_time);
+ } else {
+ grabbed_widget_->set_time(nearest / get_signal_under_mouse_cursor()->base()->get_samplerate());
+ }
+ }
+
+ } else if (type == QEvent::MouseButtonPress) {
+ grabbed_widget_ = nullptr;
+
+ const QMouseEvent *const mouse_event = (QMouseEvent*)event;
+ if ((object == viewport_) && (mouse_event->button() & Qt::LeftButton)) {
+ // Send event to 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->mouse_left_press_event(mouse_event);
+ }
} else if (type == QEvent::Leave) {
hover_point_ = QPoint(-1, -1);
update_hover_point();
(horz ? TraceTreeItemHExtentsChanged : 0) |
(vert ? TraceTreeItemVExtentsChanged : 0);
- lazy_event_handler_.stop();
- lazy_event_handler_.start();
+ if (!lazy_event_handler_.isActive())
+ lazy_event_handler_.start();
}
void View::on_signal_name_changed()
resize_header_to_fit();
}
+void View::on_zoom_in_shortcut_triggered()
+{
+ zoom(1);
+}
+
+void View::on_zoom_out_shortcut_triggered()
+{
+ zoom(-1);
+}
+
+void View::on_scroll_to_start_shortcut_triggered()
+{
+ set_h_offset(0);
+}
+
+void View::on_scroll_to_end_shortcut_triggered()
+{
+ set_h_offset(get_h_scrollbar_maximum());
+}
+
void View::h_scroll_value_changed(int value)
{
if (updating_scroll_)
viewport_->update();
}
+void View::on_grab_ruler(int ruler_id)
+{
+ if (!cursors_shown()) {
+ center_cursors();
+ show_cursors();
+ }
+
+ // Release the grabbed widget if its trigger hotkey was pressed twice
+ if (ruler_id == 1)
+ grabbed_widget_ = (grabbed_widget_ == cursors_->first().get()) ?
+ nullptr : cursors_->first().get();
+ else
+ grabbed_widget_ = (grabbed_widget_ == cursors_->second().get()) ?
+ nullptr : cursors_->second().get();
+
+ if (grabbed_widget_)
+ grabbed_widget_->set_time(ruler_->get_absolute_time_from_x_pos(
+ mapFromGlobal(QCursor::pos()).x() - header_width()));
+}
+
void View::signals_changed()
{
using sigrok::Channel;
set_time_unit(util::TimeUnit::Samples);
trigger_markers_.clear();
+ if (!custom_zero_offset_set_)
+ set_zero_position(0);
scale_at_acq_start_ = scale_;
offset_at_acq_start_ = offset_;
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
+ (void)new_value;
+
+ if (!custom_zero_offset_set_)
reset_zero_position();
}
#include <vector>
#include <QAbstractScrollArea>
+#include <QShortcut>
#include <QSizeF>
#include <QSplitter>
using std::list;
using std::unordered_map;
-using std::unordered_set;
using std::set;
using std::shared_ptr;
using std::vector;
static const pv::util::Timestamp MinScale;
static const int MaxScrollValue;
+ static const int ViewScrollMargin;
static const int ScaleUnits[3];
public:
- explicit View(Session &session, bool is_main_view=false, QWidget *parent = nullptr);
+ explicit View(Session &session, bool is_main_view=false, QMainWindow *parent = nullptr);
~View();
+ virtual ViewType get_type() const;
+
/**
* 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;
+ Session& session(); // This method is needed for TraceTreeItemOwner, not ViewBase
+ const Session& session() const; // This method is needed for TraceTreeItemOwner, not ViewBase
/**
* Returns the signals contained in this view.
*/
- unordered_set< shared_ptr<Signal> > signals() const;
+ vector< shared_ptr<Signal> > signals() const;
+
+ shared_ptr<Signal> get_signal_by_signalbase(shared_ptr<data::SignalBase> base) const;
virtual void clear_signals();
virtual const View* view() const;
Viewport* viewport();
-
const Viewport* viewport() const;
+ QAbstractScrollArea* scrollarea() const;
+
const Ruler* ruler() const;
virtual void save_settings(QSettings &settings) const;
void reset_zero_position();
+ pv::util::Timestamp zero_offset() const;
+
/**
* Returns the vertical scroll offset.
*/
*/
void set_v_offset(int offset);
+ /**
+ * Sets the visual h-offset.
+ */
+ void set_h_offset(int offset);
+
+ /**
+ * Gets the length of the horizontal scrollbar.
+ */
+ int get_h_scrollbar_maximum() const;
+
/**
* Returns the SI prefix to apply to the graticule time markings.
*/
*/
void set_scale_offset(double scale, const pv::util::Timestamp& offset);
- set< shared_ptr<pv::data::SignalData> > get_visible_data() const;
+ vector< shared_ptr<pv::data::SignalData> > get_visible_data() const;
pair<pv::util::Timestamp, pv::util::Timestamp> get_time_extents() const;
- /**
- * Enables or disables colored trace backgrounds. If they're not
- * colored then they will use alternating colors.
- */
- void enable_colored_bg(bool state);
-
/**
* Returns true if the trace background should be drawn with a colored background.
*/
bool colored_bg() const;
/**
- * Enable or disable showing sampling points.
- */
- void enable_show_sampling_points(bool state);
-
- /**
- * Enable or disable showing the analog minor grid.
- */
- void enable_show_analog_minor_grid(bool state);
-
- /**
- * Returns true if cursors are displayed. false otherwise.
+ * Returns true if cursors are displayed, false otherwise.
*/
bool cursors_shown() const;
*/
void show_cursors(bool show = true);
+ /**
+ * Sets the cursors to the given offsets.
+ * You still have to call show_cursors() separately.
+ */
+ void set_cursors(pv::util::Timestamp& first, pv::util::Timestamp& second);
+
/**
* Moves the cursors to a convenient position in the view.
+ * You still have to call show_cursors() separately.
*/
- void centre_cursors();
+ void center_cursors();
/**
* Returns a reference to the pair of cursors.
/**
* Adds a new flag at a specified time.
*/
- void add_flag(const pv::util::Timestamp& time);
+ shared_ptr<Flag> add_flag(const pv::util::Timestamp& time);
/**
* Removes a flag from the list.
void extents_changed(bool horz, bool vert);
private Q_SLOTS:
-
void on_signal_name_changed();
void on_splitter_moved();
+ void on_zoom_in_shortcut_triggered();
+ void on_zoom_out_shortcut_triggered();
+ void on_scroll_to_start_shortcut_triggered();
+ void on_scroll_to_end_shortcut_triggered();
+
void h_scroll_value_changed(int value);
void v_scroll_value_changed();
+ void on_grab_ruler(int ruler_id);
+
void signals_changed();
void capture_state_updated(int state);
Header *header_;
QSplitter *splitter_;
- unordered_set< shared_ptr<Signal> > signals_;
+ QShortcut *zoom_in_shortcut_, *zoom_in_shortcut_2_;
+ QShortcut *zoom_out_shortcut_, *zoom_out_shortcut_2_;
+ QShortcut *home_shortcut_, *end_shortcut_;
+ QShortcut *grab_ruler_left_shortcut_, *grab_ruler_right_shortcut_;
+ QShortcut *cancel_grab_shortcut_;
+
+ vector< shared_ptr<Signal> > signals_;
#ifdef ENABLE_DECODE
vector< shared_ptr<DecodeTrace> > decode_traces_;
pv::util::Timestamp offset_;
/// The ruler version of the time offset in seconds.
pv::util::Timestamp ruler_offset_;
+ /// The offset of the zero point in seconds.
+ pv::util::Timestamp zero_offset_;
+ /// Shows whether the user set a custom zero offset that we should keep
+ bool custom_zero_offset_set_;
bool updating_scroll_;
bool settings_restored_;
vector< shared_ptr<TriggerMarker> > trigger_markers_;
QWidget* hover_widget_;
+ TimeMarker* grabbed_widget_;
QPoint hover_point_;
shared_ptr<Signal> signal_under_mouse_cursor_;
uint16_t snap_distance_;
return (background.lightness() > 110) ? Qt::black : Qt::white;
}
+void ViewItem::hover_point_changed(const QPoint &hp)
+{
+ (void)hp;
+}
+
+void ViewItem::mouse_left_press_event(const QMouseEvent* event)
+{
+ (void)event;
+}
+
} // namespace trace
} // namespace views
} // namespace pv
#include <list>
+#include <QMouseEvent>
#include <QPen>
#include <QPoint>
virtual void delete_pressed();
+ virtual void hover_point_changed(const QPoint &hp);
+
+ /**
+ * Handles left mouse button press events.
+ * @param event the mouse event that triggered this handler.
+ */
+ virtual void mouse_left_press_event(const QMouseEvent* event);
+
protected:
static QPen highlight_pen();
#include <pv/session.hpp>
#include <QMouseEvent>
+#include <QScreen>
+#include <QWindow>
#include <QDebug>
void Viewport::item_hover(const shared_ptr<ViewItem> &item, QPoint pos)
{
if (item && item->is_draggable(pos))
- setCursor(dynamic_pointer_cast<RowItem>(item) ?
- Qt::SizeVerCursor : Qt::SizeHorCursor);
+ setCursor(dynamic_pointer_cast<ViewItem>(item) ?
+ Qt::SizeHorCursor : Qt::SizeVerCursor);
else
unsetCursor();
}
pinch_zoom_active_ = false;
return false;
}
+ if (event->device()->type() == QTouchDevice::TouchPad) {
+ return false;
+ }
const QTouchEvent::TouchPoint &touchPoint0 = touchPoints.first();
const QTouchEvent::TouchPoint &touchPoint1 = touchPoints.last();
&ViewItem::paint_back, &ViewItem::paint_mid,
&ViewItem::paint_fore, nullptr};
- vector< shared_ptr<RowItem> > row_items(view_.list_by_type<RowItem>());
+ vector< shared_ptr<ViewItem> > row_items(view_.list_by_type<ViewItem>());
assert(none_of(row_items.begin(), row_items.end(),
- [](const shared_ptr<RowItem> &r) { return !r; }));
+ [](const shared_ptr<ViewItem> &r) { return !r; }));
stable_sort(row_items.begin(), row_items.end(),
- [](const shared_ptr<RowItem> &a, const shared_ptr<RowItem> &b) {
+ [](const shared_ptr<ViewItem> &a, const shared_ptr<ViewItem> &b) {
return a->drag_point(QRect()).y() < b->drag_point(QRect()).y(); });
const vector< shared_ptr<TimeItem> > time_items(view_.time_items());
[](const shared_ptr<TimeItem> &t) { return !t; }));
QPainter p(this);
- p.setRenderHint(QPainter::Antialiasing);
+
+ // Disable antialiasing for high-DPI displays
+ bool use_antialiasing =
+ window()->windowHandle()->screen()->devicePixelRatio() < 2.0;
+ p.setRenderHint(QPainter::Antialiasing, use_antialiasing);
for (LayerPaintFunc *paint_func = layer_paint_funcs;
*paint_func; paint_func++) {
(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<ViewItem>& r : row_items)
(r.get()->*(*paint_func))(p, row_pp);
}
bool item_dragged = false;
// Drag the row items
- const vector< shared_ptr<RowItem> > row_items(
- view_.list_by_type<RowItem>());
- for (const shared_ptr<RowItem>& r : row_items)
+ const vector< shared_ptr<ViewItem> > row_items(
+ view_.list_by_type<ViewItem>());
+ for (const shared_ptr<ViewItem>& r : row_items)
if (r->dragging()) {
r->drag_by(delta);
assert(event);
if (event->button() & Qt::LeftButton) {
+ if (event->modifiers() & Qt::ShiftModifier)
+ view_.show_cursors(false);
+
mouse_down_point_ = event->pos();
+ mouse_down_offset_ = view_.offset() + event->pos().x() * view_.scale();
mouse_down_item_ = get_mouse_over_item(event->pos());
mouse_left_press_event(event);
}
mouse_down_item_ = nullptr;
}
+void ViewWidget::keyReleaseEvent(QKeyEvent *event)
+{
+ // Update mouse_modifiers_ also if modifiers change, but pointer doesn't move
+ if ((mouse_point_.x() >= 0) && (mouse_point_.y() >= 0)) // mouse is inside
+ mouse_modifiers_ = event->modifiers();
+ update();
+}
+
+void ViewWidget::keyPressEvent(QKeyEvent *event)
+{
+ // Update mouse_modifiers_ also if modifiers change, but pointer doesn't move
+ if ((mouse_point_.x() >= 0) && (mouse_point_.y() >= 0)) // mouse is inside
+ mouse_modifiers_ = event->modifiers();
+ update();
+}
+
void ViewWidget::mouseMoveEvent(QMouseEvent *event)
{
assert(event);
mouse_point_ = event->pos();
+ mouse_modifiers_ = event->modifiers();
if (!event->buttons())
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() <
- QApplication::startDragDistance())
- return;
- if (!accept_drag())
- return;
+ if (event->buttons() & Qt::LeftButton) {
+ if (event->modifiers() & Qt::ShiftModifier) {
+ // Cursor drag
+ pv::util::Timestamp current_offset = view_.offset() + event->pos().x() * view_.scale();
- item_dragging_ = true;
- }
+ const int drag_distance = qAbs(current_offset.convert_to<double>() -
+ mouse_down_offset_.convert_to<double>()) / view_.scale();
+
+ if (drag_distance > QApplication::startDragDistance()) {
+ view_.show_cursors(true);
+ view_.set_cursors(mouse_down_offset_, current_offset);
+ } else
+ view_.show_cursors(false);
- // Do the drag
- drag_items(event->pos() - mouse_down_point_);
+ } else {
+ if (!item_dragging_) {
+ if ((event->pos() - mouse_down_point_).manhattanLength() <
+ QApplication::startDragDistance())
+ return;
+
+ if (!accept_drag())
+ return;
+
+ item_dragging_ = true;
+ }
+
+ // Do the drag
+ drag_items(event->pos() - mouse_down_point_);
+ }
}
+
+ // Force a repaint of the widget to update highlighted parts
+ update();
}
void ViewWidget::leaveEvent(QEvent*)
{
- mouse_point_ = QPoint(-1, -1);
+ bool cursor_above_widget = rect().contains(mapFromGlobal(QCursor::pos()));
+
+ // We receive leaveEvent also when the widget loses focus even when
+ // the mouse cursor hasn't moved at all - e.g. when the popup shows.
+ // However, we don't want to reset mouse_position_ when the mouse is
+ // still above this widget as doing so would break the context menu
+ if (!cursor_above_widget)
+ mouse_point_ = QPoint(INT_MIN, INT_MIN);
+
+ mouse_modifiers_ = Qt::NoModifier;
+ item_hover(nullptr, QPoint());
+
update();
}
#include <QPoint>
#include <QWidget>
+#include <pv/util.hpp>
+
using std::shared_ptr;
using std::vector;
void mouseReleaseEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
+ void keyPressEvent(QKeyEvent *event);
+ void keyReleaseEvent(QKeyEvent *event);
+
void leaveEvent(QEvent *event);
public Q_SLOTS:
pv::views::trace::View &view_;
QPoint mouse_point_;
QPoint mouse_down_point_;
+ pv::util::Timestamp mouse_down_offset_;
shared_ptr<ViewItem> mouse_down_item_;
+
+ /// Keyboard modifiers that were active when mouse was last moved or clicked
+ Qt::KeyboardModifiers mouse_modifiers_;
+
bool item_dragging_;
};
namespace pv {
namespace views {
+const char* ViewTypeNames[ViewTypeCount] = {
+ "Trace View",
+#ifdef ENABLE_DECODE
+ "Binary Decoder Output View"
+#endif
+};
+
const int ViewBase::MaxViewAutoUpdateRate = 25; // No more than 25 Hz
-ViewBase::ViewBase(Session &session, bool is_main_view, QWidget *parent) :
+ViewBase::ViewBase(Session &session, bool is_main_view, QMainWindow *parent) :
// Note: Place defaults in ViewBase::reset_view_state(), not here
+ QWidget(parent),
session_(session),
is_main_view_(is_main_view)
{
delayed_view_updater_.setInterval(1000 / MaxViewAutoUpdateRate);
}
+bool ViewBase::is_main_view() const
+{
+ return is_main_view_;
+}
+
void ViewBase::reset_view_state()
{
- ruler_shift_ = 0;
current_segment_ = 0;
}
void ViewBase::clear_signals()
{
+ clear_signalbases();
}
-unordered_set< shared_ptr<data::SignalBase> > ViewBase::signalbases() const
+vector< shared_ptr<data::SignalBase> > ViewBase::signalbases() const
{
return signalbases_;
}
void ViewBase::add_signalbase(const shared_ptr<data::SignalBase> signalbase)
{
- signalbases_.insert(signalbase);
+ signalbases_.push_back(signalbase);
connect(signalbase.get(), SIGNAL(samples_cleared()),
this, SLOT(on_data_updated()));
#ifdef ENABLE_DECODE
void ViewBase::clear_decode_signals()
{
+ decode_signals_.clear();
}
void ViewBase::add_decode_signal(shared_ptr<data::DecodeSignal> signal)
{
- (void)signal;
+ decode_signals_.push_back(signal);
}
void ViewBase::remove_decode_signal(shared_ptr<data::DecodeSignal> signal)
{
- (void)signal;
+ decode_signals_.erase(std::remove_if(
+ decode_signals_.begin(), decode_signals_.end(),
+ [&](shared_ptr<data::DecodeSignal> s) { return s == signal; }),
+ decode_signals_.end());
}
#endif
#include <unordered_set>
#include <vector>
+#include <QMainWindow>
#include <QTimer>
#include <QWidget>
#endif
using std::shared_ptr;
-using std::unordered_set;
+using std::vector;
namespace pv {
namespace views {
+// When adding an entry here, don't forget to update ViewTypeNames as well
enum ViewType {
ViewTypeTrace,
- ViewTypeTabularDecode
+#ifdef ENABLE_DECODE
+ ViewTypeDecoderBinary,
+#endif
+ ViewTypeCount // Indicates how many view types there are, must always be last
};
+extern const char* ViewTypeNames[ViewTypeCount];
+
class ViewBase : public QWidget
{
Q_OBJECT
-private:
+public:
static const int MaxViewAutoUpdateRate;
public:
- explicit ViewBase(Session &session, bool is_main_view = false, QWidget *parent = nullptr);
+ explicit ViewBase(Session &session, bool is_main_view = false, QMainWindow *parent = nullptr);
+
+ virtual ViewType get_type() const = 0;
+ bool is_main_view() const;
/**
* Resets the view to its default state after construction. It does however
/**
* Returns the signal bases contained in this view.
*/
- unordered_set< shared_ptr<data::SignalBase> > signalbases() const;
+ vector< shared_ptr<data::SignalBase> > signalbases() const;
virtual void clear_signalbases();
const bool is_main_view_;
- util::Timestamp ruler_shift_;
util::TimeUnit time_unit_;
- unordered_set< shared_ptr<data::SignalBase> > signalbases_;
+ vector< shared_ptr<data::SignalBase> > signalbases_;
+#ifdef ENABLE_DECODE
+ vector< shared_ptr<data::DecodeSignal> > decode_signals_;
+#endif
/// The ID of the currently displayed segment
uint32_t current_segment_;
namespace pv {
namespace widgets {
-DecoderMenu::DecoderMenu(QWidget *parent, bool first_level_decoder) :
+DecoderMenu::DecoderMenu(QWidget *parent, const char* input, bool first_level_decoder) :
QMenu(parent),
mapper_(this)
{
- GSList *li = g_slist_sort(g_slist_copy(
- (GSList*)srd_decoder_list()), decoder_name_cmp);
+ GSList *li = g_slist_sort(g_slist_copy((GSList*)srd_decoder_list()), decoder_name_cmp);
+
for (GSList *l = li; l; l = l->next) {
const srd_decoder *const d = (srd_decoder*)l->data;
assert(d);
const bool have_channels = (d->channels || d->opt_channels) != 0;
- if (first_level_decoder == have_channels) {
- QAction *const action =
- addAction(QString::fromUtf8(d->name));
- action->setData(qVariantFromValue(l->data));
- mapper_.setMapping(action, action);
- connect(action, SIGNAL(triggered()),
- &mapper_, SLOT(map()));
+ if (first_level_decoder != have_channels)
+ continue;
+
+ if (!first_level_decoder) {
+ // Dismiss all non-stacked decoders unless we're looking for first-level decoders
+ if (!d->inputs)
+ continue;
+
+ // TODO For now we ignore that d->inputs is actually a list
+ if (strncmp((char*)(d->inputs->data), input, 1024) != 0)
+ continue;
}
+
+ QAction *const action = addAction(QString::fromUtf8(d->name));
+ action->setData(qVariantFromValue(l->data));
+ mapper_.setMapping(action, action);
+ connect(action, SIGNAL(triggered()), &mapper_, SLOT(map()));
}
g_slist_free(li);
- connect(&mapper_, SIGNAL(mapped(QObject*)),
- this, SLOT(on_action(QObject*)));
+ connect(&mapper_, SIGNAL(mapped(QObject*)), this, SLOT(on_action(QObject*)));
}
int DecoderMenu::decoder_name_cmp(const void *a, const void *b)
{
- return strcmp(((const srd_decoder*)a)->name,
- ((const srd_decoder*)b)->name);
+ return strcmp(((const srd_decoder*)a)->name, ((const srd_decoder*)b)->name);
}
void DecoderMenu::on_action(QObject *action)
{
assert(action);
- srd_decoder *const dec =
- (srd_decoder*)((QAction*)action)->data().value<void*>();
+
+ srd_decoder *const dec = (srd_decoder*)((QAction*)action)->data().value<void*>();
assert(dec);
decoder_selected(dec);
Q_OBJECT;
public:
- DecoderMenu(QWidget *parent, bool first_level_decoder = false);
+ DecoderMenu(QWidget *parent, const char* input, bool first_level_decoder = false);
private:
static int decoder_name_cmp(const void *a, const void *b);
--- /dev/null
+/****************************************************************************
+ **
+ ** Copyright (C) 2015 The Qt Company Ltd.
+ ** Contact: http://www.qt.io/licensing/
+ **
+ ** This file is part of the examples of the Qt Toolkit.
+ **
+ ** $QT_BEGIN_LICENSE:BSD$
+ ** You may use this file under the terms of the BSD license as follows:
+ **
+ ** "Redistribution and use in source and binary forms, with or without
+ ** modification, are permitted provided that the following conditions are
+ ** met:
+ ** * Redistributions of source code must retain the above copyright
+ ** notice, this list of conditions and the following disclaimer.
+ ** * Redistributions in binary form must reproduce the above copyright
+ ** notice, this list of conditions and the following disclaimer in
+ ** the documentation and/or other materials provided with the
+ ** distribution.
+ ** * Neither the name of The Qt Company Ltd nor the names of its
+ ** contributors may be used to endorse or promote products derived
+ ** from this software without specific prior written permission.
+ **
+ **
+ ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+ **
+ ** $QT_END_LICENSE$
+ **
+ ****************************************************************************/
+
+#include <QWidget>
+
+#include "flowlayout.hpp"
+
+FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing) :
+ QLayout(parent),
+ m_parent(parent),
+ m_hSpace(hSpacing),
+ m_vSpace(vSpacing)
+{
+ setContentsMargins(margin, margin, margin, margin);
+}
+
+FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing) :
+ m_parent(nullptr),
+ m_hSpace(hSpacing),
+ m_vSpace(vSpacing)
+{
+ setContentsMargins(margin, margin, margin, margin);
+}
+
+FlowLayout::~FlowLayout()
+{
+ QLayoutItem *item;
+ while ((item = takeAt(0)))
+ delete item;
+}
+
+void FlowLayout::addItem(QLayoutItem *item)
+{
+ itemList.append(item);
+}
+
+int FlowLayout::horizontalSpacing() const
+{
+ if (m_hSpace >= 0)
+ return m_hSpace;
+ else
+ return smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
+}
+
+int FlowLayout::verticalSpacing() const
+{
+ if (m_vSpace >= 0)
+ return m_vSpace;
+ else
+ return smartSpacing(QStyle::PM_LayoutVerticalSpacing);
+}
+
+int FlowLayout::count() const
+{
+ return itemList.size();
+}
+
+QLayoutItem *FlowLayout::itemAt(int index) const
+{
+ return itemList.value(index);
+}
+
+QLayoutItem *FlowLayout::takeAt(int index)
+{
+ if ((index >= 0) && (index < itemList.size()))
+ return itemList.takeAt(index);
+ else
+ return nullptr;
+}
+
+Qt::Orientations FlowLayout::expandingDirections() const
+{
+ return Qt::Horizontal | Qt::Vertical;
+}
+
+bool FlowLayout::hasHeightForWidth() const
+{
+ return true;
+}
+
+int FlowLayout::heightForWidth(int width) const
+{
+ int height = doLayout(QRect(0, 0, width, 0), true);
+ return height;
+}
+
+void FlowLayout::setGeometry(const QRect &rect)
+{
+ QLayout::setGeometry(rect);
+ doLayout(rect, false);
+}
+
+QSize FlowLayout::sizeHint() const
+{
+ return minimumSize();
+}
+
+QSize FlowLayout::minimumSize() const
+{
+ QSize size(0, 0);
+
+ for (QLayoutItem* item : itemList) {
+ int w = item->geometry().x() + item->geometry().width();
+ if (w > size.width())
+ size.setWidth(w);
+
+ int h = item->geometry().y() + item->geometry().height();
+ if (h > size.height())
+ size.setHeight(h);
+ }
+
+ size += QSize(2 * margin(), 2 * margin());
+
+ return size;
+}
+
+int FlowLayout::doLayout(const QRect &rect, bool testOnly) const
+{
+ int left, top, right, bottom;
+ getContentsMargins(&left, &top, &right, &bottom);
+
+ QRect effectiveRect = rect.adjusted(left, top, -right, -bottom);
+ int x = effectiveRect.x();
+ int y = effectiveRect.y();
+
+ int lineHeight = 0;
+ for (QLayoutItem* item : itemList) {
+ QWidget* w = item->widget();
+
+ int spaceX = horizontalSpacing();
+ if (spaceX == -1)
+ spaceX = w->style()->layoutSpacing(
+ QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
+
+ int spaceY = verticalSpacing();
+ if (spaceY == -1)
+ spaceY = w->style()->layoutSpacing(
+ QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
+
+ int nextX = x + item->sizeHint().width() + spaceX;
+ if (((nextX - spaceX) > effectiveRect.right()) && (lineHeight > 0)) {
+ x = effectiveRect.x();
+ y = y + lineHeight + spaceY;
+ nextX = x + item->sizeHint().width() + spaceX;
+ lineHeight = 0;
+ }
+
+ if (!testOnly)
+ item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
+
+ x = nextX;
+ lineHeight = qMax(lineHeight, item->sizeHint().height());
+ }
+
+ int height = y + lineHeight - rect.y() + bottom;
+
+ if (m_parent)
+ m_parent->setMinimumHeight(height);
+
+ return height;
+}
+
+int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const
+{
+ QObject *parent = this->parent();
+
+ if (!parent)
+ return -1;
+
+ if (parent->isWidgetType()) {
+ QWidget *pw = qobject_cast<QWidget*>(parent);
+ return pw->style()->pixelMetric(pm, nullptr, pw);
+ } else
+ return static_cast<QLayout*>(parent)->spacing();
+}
--- /dev/null
+/****************************************************************************
+ **
+ ** Copyright (C) 2015 The Qt Company Ltd.
+ ** Contact: http://www.qt.io/licensing/
+ **
+ ** This file is part of the examples of the Qt Toolkit.
+ **
+ ** $QT_BEGIN_LICENSE:BSD$
+ ** You may use this file under the terms of the BSD license as follows:
+ **
+ ** "Redistribution and use in source and binary forms, with or without
+ ** modification, are permitted provided that the following conditions are
+ ** met:
+ ** * Redistributions of source code must retain the above copyright
+ ** notice, this list of conditions and the following disclaimer.
+ ** * Redistributions in binary form must reproduce the above copyright
+ ** notice, this list of conditions and the following disclaimer in
+ ** the documentation and/or other materials provided with the
+ ** distribution.
+ ** * Neither the name of The Qt Company Ltd nor the names of its
+ ** contributors may be used to endorse or promote products derived
+ ** from this software without specific prior written permission.
+ **
+ **
+ ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+ **
+ ** $QT_END_LICENSE$
+ **
+ ****************************************************************************/
+
+#ifndef FLOWLAYOUT_H
+#define FLOWLAYOUT_H
+
+#include <QLayout>
+#include <QRect>
+#include <QStyle>
+#include <QWidgetItem>
+
+class FlowLayout : public QLayout
+{
+public:
+ FlowLayout(QWidget *parent, int margin = -1, int hSpacing = -1, int vSpacing = -1);
+ FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1);
+ ~FlowLayout();
+
+ void addItem(QLayoutItem *item);
+ int horizontalSpacing() const;
+ int verticalSpacing() const;
+ Qt::Orientations expandingDirections() const;
+ bool hasHeightForWidth() const;
+ int heightForWidth(int) const;
+ int count() const;
+ QLayoutItem *itemAt(int index) const;
+ QSize minimumSize() const;
+ void setGeometry(const QRect &rect);
+ QSize sizeHint() const;
+ QLayoutItem *takeAt(int index);
+
+private:
+ int doLayout(const QRect &rect, bool testOnly) const;
+ int smartSpacing(QStyle::PixelMetric pm) const;
+
+ QWidget* m_parent;
+ QList<QLayoutItem*> itemList;
+ int m_hSpace, m_vSpace;
+};
+
+#endif
using std::pair;
using std::string;
using std::shared_ptr;
+using std::vector;
using sigrok::Context;
using sigrok::InputFormat;
namespace widgets {
ImportMenu::ImportMenu(QWidget *parent, shared_ptr<Context> context,
- QAction *open_action) :
+ vector<QAction *>open_actions) :
QMenu(parent),
context_(context),
mapper_(this)
{
assert(context);
- if (open_action) {
- addAction(open_action);
- setDefaultAction(open_action);
+ if (!open_actions.empty()) {
+ bool first_action = true;
+ for (auto open_action : open_actions) {
+ addAction(open_action);
+
+ if (first_action) {
+ first_action = false;
+ setDefaultAction(open_action);
+ }
+ }
addSeparator();
}
#include <QSignalMapper>
using std::shared_ptr;
+using std::vector;
namespace sigrok {
class Context;
public:
ImportMenu(QWidget *parent, shared_ptr<sigrok::Context> context,
- QAction *open_action = nullptr);
+ vector<QAction *>open_actions = vector<QAction *>());
private Q_SLOTS:
void on_action(QObject *action);
#include <QApplication>
#include <QDesktopWidget>
#include <QLineEdit>
+#include <QScrollBar>
+#include <QStyle>
#include <QtGui>
#include "popup.hpp"
const unsigned int Popup::ArrowOverlap = 3;
const unsigned int Popup::MarginWidth = 6;
+
+QWidthAdjustingScrollArea::QWidthAdjustingScrollArea(QWidget* parent) :
+ QScrollArea(parent)
+{
+}
+
+void QWidthAdjustingScrollArea::setWidget(QWidget* w)
+{
+ QScrollArea::setWidget(w);
+ // It happens that QScrollArea already filters widget events,
+ // but that's an implementation detail that we shouldn't rely on.
+ w->installEventFilter(this);
+}
+
+bool QWidthAdjustingScrollArea::eventFilter(QObject* obj, QEvent* ev)
+{
+ if (obj == widget() && ev->type() == QEvent::Resize) {
+ if (widget()->height() > height())
+ setMinimumWidth(widget()->width() + qApp->style()->pixelMetric(QStyle::PM_ScrollBarExtent));
+ else
+ setMinimumWidth(widget()->width());
+ }
+
+ return QScrollArea::eventFilter(obj, ev);
+}
+
+
Popup::Popup(QWidget *parent) :
QWidget(parent, Qt::Popup | Qt::FramelessWindowHint),
point_(),
#ifndef PULSEVIEW_PV_WIDGETS_POPUP_HPP
#define PULSEVIEW_PV_WIDGETS_POPUP_HPP
+#include <QScrollArea>
#include <QWidget>
namespace pv {
namespace widgets {
+
+// A regular QScrollArea has a fixed size and provides scroll bars when the
+// content can't be shown in its entirety. However, we want no horizontal
+// scroll bar and want the scroll area to adjust its width to fit the content
+// instead.
+// Inspired by https://stackoverflow.com/questions/21253755/qscrollarea-with-dynamically-changing-contents?answertab=votes#tab-top
+class QWidthAdjustingScrollArea : public QScrollArea
+{
+ Q_OBJECT
+
+public:
+ QWidthAdjustingScrollArea(QWidget* parent = nullptr);
+ void setWidget(QWidget* w);
+ bool eventFilter(QObject* obj, QEvent* ev);
+};
+
+
class Popup : public QWidget
{
Q_OBJECT
, stepsize_("1e-6")
{
connect(this, SIGNAL(editingFinished()), this, SLOT(on_editingFinished()));
+ connect(lineEdit(), SIGNAL(editingFinished()), this, SLOT(on_editingFinished()));
updateEdit();
}
void TimestampSpinBox::on_editingFinished()
{
- if (!lineEdit()->isModified())
- return;
- lineEdit()->setModified(false);
-
QRegExp re(R"(\s*([-+]?)\s*([0-9]+\.?[0-9]*).*)");
if (re.exactMatch(text())) {
captures.removeFirst(); // remove entire match
QString str = captures.join("");
setValue(pv::util::Timestamp(str.toStdString()));
+
} else {
// replace the malformed entered string with the old value
updateEdit();
{
QString newtext = pv::util::format_time_si(
value_, pv::util::SIPrefix::none, precision_);
+ const QSignalBlocker blocker(lineEdit());
+ // Keep cursor position
+ int cursor = lineEdit()->cursorPosition();
lineEdit()->setText(newtext);
+ lineEdit()->setCursorPosition(cursor);
}
} // namespace widgets
class WellArray : public QWidget
{
Q_OBJECT
- Q_PROPERTY(int selectedColumn READ selectedColumn) // clazy-exclude:qproperty-without-notify
- Q_PROPERTY(int selectedRow READ selectedRow) // clazy-exclude:qproperty-without-notify
+ 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);
${PROJECT_SOURCE_DIR}/pv/prop/string.cpp
${PROJECT_SOURCE_DIR}/pv/popups/channels.cpp
${PROJECT_SOURCE_DIR}/pv/popups/deviceoptions.cpp
+ ${PROJECT_SOURCE_DIR}/pv/subwindows/subwindowbase.cpp
${PROJECT_SOURCE_DIR}/pv/toolbars/mainbar.cpp
${PROJECT_SOURCE_DIR}/pv/views/trace/analogsignal.cpp
${PROJECT_SOURCE_DIR}/pv/views/trace/cursor.cpp
${PROJECT_SOURCE_DIR}/pv/views/trace/header.cpp
${PROJECT_SOURCE_DIR}/pv/views/trace/marginwidget.cpp
${PROJECT_SOURCE_DIR}/pv/views/trace/logicsignal.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/timeitem.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/flowlayout.cpp
${PROJECT_SOURCE_DIR}/pv/widgets/importmenu.cpp
${PROJECT_SOURCE_DIR}/pv/widgets/popup.cpp
${PROJECT_SOURCE_DIR}/pv/widgets/popuptoolbutton.cpp
${PROJECT_SOURCE_DIR}/pv/prop/int.hpp
${PROJECT_SOURCE_DIR}/pv/prop/property.hpp
${PROJECT_SOURCE_DIR}/pv/prop/string.hpp
+ ${PROJECT_SOURCE_DIR}/pv/subwindows/subwindowbase.hpp
${PROJECT_SOURCE_DIR}/pv/toolbars/mainbar.hpp
${PROJECT_SOURCE_DIR}/pv/views/trace/analogsignal.hpp
${PROJECT_SOURCE_DIR}/pv/views/trace/cursor.hpp
${PROJECT_SOURCE_DIR}/pv/views/trace/header.hpp
${PROJECT_SOURCE_DIR}/pv/views/trace/logicsignal.hpp
${PROJECT_SOURCE_DIR}/pv/views/trace/marginwidget.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/timeitem.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/flowlayout.hpp
${PROJECT_SOURCE_DIR}/pv/widgets/importmenu.hpp
${PROJECT_SOURCE_DIR}/pv/widgets/popup.hpp
${PROJECT_SOURCE_DIR}/pv/widgets/popuptoolbutton.hpp
${PROJECT_SOURCE_DIR}/pv/data/decode/decoder.cpp
${PROJECT_SOURCE_DIR}/pv/data/decode/row.cpp
${PROJECT_SOURCE_DIR}/pv/data/decode/rowdata.cpp
+ ${PROJECT_SOURCE_DIR}/pv/subwindows/decoder_selector/item.cpp
+ ${PROJECT_SOURCE_DIR}/pv/subwindows/decoder_selector/model.cpp
+ ${PROJECT_SOURCE_DIR}/pv/subwindows/decoder_selector/subwindow.cpp
+ ${PROJECT_SOURCE_DIR}/pv/views/decoder_binary/view.cpp
+ ${PROJECT_SOURCE_DIR}/pv/views/decoder_binary/QHexView.cpp
${PROJECT_SOURCE_DIR}/pv/views/trace/decodetrace.cpp
${PROJECT_SOURCE_DIR}/pv/widgets/decodergroupbox.cpp
${PROJECT_SOURCE_DIR}/pv/widgets/decodermenu.cpp
list(APPEND pulseview_TEST_HEADERS
${PROJECT_SOURCE_DIR}/pv/data/decodesignal.hpp
+ ${PROJECT_SOURCE_DIR}/pv/subwindows/decoder_selector/subwindow.hpp
+ ${PROJECT_SOURCE_DIR}/pv/views/decoder_binary/view.hpp
+ ${PROJECT_SOURCE_DIR}/pv/views/decoder_binary/QHexView.hpp
${PROJECT_SOURCE_DIR}/pv/views/trace/decodetrace.hpp
${PROJECT_SOURCE_DIR}/pv/widgets/decodergroupbox.hpp
${PROJECT_SOURCE_DIR}/pv/widgets/decodermenu.hpp
--- /dev/null
+<RCC>
+ <qresource prefix="/">
+ <file>l10n/de.qm</file>
+ </qresource>
+</RCC>