MathSignal: Implement custom math signal dialog master
authorSoeren Apel <soeren@apelpie.net>
Sun, 27 Sep 2020 21:18:53 +0000 (23:18 +0200)
committerSoeren Apel <soeren@apelpie.net>
Sun, 27 Sep 2020 21:19:29 +0000 (23:19 +0200)
60 files changed:
CMakeLists.txt
README
icons/add-math-signal.svg [new file with mode: 0644]
icons/math.svg [new file with mode: 0644]
l10n/de.ts
l10n/es_MX.ts [new file with mode: 0644]
main.cpp
pulseview.qrc
pv/application.cpp
pv/application.hpp
pv/binding/device.cpp
pv/binding/device.hpp
pv/data/analog.cpp
pv/data/analog.hpp
pv/data/analogsegment.cpp
pv/data/analogsegment.hpp
pv/data/decode/decoder.hpp
pv/data/decodesignal.cpp
pv/data/decodesignal.hpp
pv/data/logicsegment.cpp
pv/data/logicsegment.hpp
pv/data/mathsignal.cpp [new file with mode: 0644]
pv/data/mathsignal.hpp [new file with mode: 0644]
pv/data/segment.cpp
pv/data/segment.hpp
pv/data/signalbase.cpp
pv/data/signalbase.hpp
pv/dialogs/settings.cpp
pv/dialogs/settings.hpp
pv/exprtk.hpp [new file with mode: 0644]
pv/popups/channels.cpp
pv/popups/channels.hpp
pv/prop/bool.hpp
pv/prop/enum.hpp
pv/prop/int.cpp
pv/prop/int.hpp
pv/prop/property.hpp
pv/prop/string.hpp
pv/session.cpp
pv/session.hpp
pv/toolbars/mainbar.cpp
pv/toolbars/mainbar.hpp
pv/util.hpp
pv/views/tabular_decoder/model.cpp
pv/views/tabular_decoder/view.cpp
pv/views/trace/analogsignal.cpp
pv/views/trace/analogsignal.hpp
pv/views/trace/decodetrace.cpp
pv/views/trace/decodetrace.hpp
pv/views/trace/logicsignal.cpp
pv/views/trace/logicsignal.hpp
pv/views/trace/mathsignal.cpp [new file with mode: 0644]
pv/views/trace/mathsignal.hpp [new file with mode: 0644]
pv/views/trace/signal.cpp
pv/views/trace/signal.hpp
pv/views/trace/trace.cpp
pv/views/trace/trace.hpp
pv/views/trace/view.cpp
test/CMakeLists.txt
translations.qrc

index bd5de9ec8730296474c0b5365fbeaddd495a7264..160a0c0a984b585c580913960506eef82b622d16 100644 (file)
@@ -21,9 +21,9 @@
 
 cmake_minimum_required(VERSION 2.8.12)
 
-include(GNUInstallDirs)
+project(pulseview C CXX)
 
-project(pulseview)
+include(GNUInstallDirs)
 
 # Let AUTOMOC and AUTOUIC process GENERATED files.
 if(POLICY CMP0071)
@@ -258,6 +258,7 @@ set(pulseview_SOURCES
        pv/data/analogsegment.cpp
        pv/data/logic.cpp
        pv/data/logicsegment.cpp
+       pv/data/mathsignal.cpp
        pv/data/signalbase.cpp
        pv/data/signaldata.cpp
        pv/data/segment.cpp
@@ -285,6 +286,7 @@ set(pulseview_SOURCES
        pv/views/trace/cursorpair.cpp
        pv/views/trace/flag.cpp
        pv/views/trace/header.cpp
+       pv/views/trace/mathsignal.cpp
        pv/views/trace/marginwidget.cpp
        pv/views/trace/logicsignal.cpp
        pv/views/trace/ruler.cpp
@@ -320,6 +322,7 @@ set(pulseview_SOURCES
 
 # This list includes only QObject derived class headers.
 set(pulseview_HEADERS
+       pv/exprtk.hpp
        pv/logging.hpp
        pv/globalsettings.hpp
        pv/mainwindow.hpp
@@ -331,6 +334,7 @@ set(pulseview_HEADERS
        pv/data/analogsegment.hpp
        pv/data/logic.hpp
        pv/data/logicsegment.hpp
+       pv/data/mathsignal.hpp
        pv/data/signalbase.hpp
        pv/dialogs/connect.hpp
        pv/dialogs/inputoutputoptions.hpp
@@ -351,6 +355,7 @@ set(pulseview_HEADERS
        pv/views/trace/flag.hpp
        pv/views/trace/header.hpp
        pv/views/trace/logicsignal.hpp
+       pv/views/trace/mathsignal.hpp
        pv/views/trace/marginwidget.hpp
        pv/views/trace/ruler.hpp
        pv/views/trace/signal.hpp
diff --git a/README b/README
index a35cb8d58c09c610aa51eee3ced239701c22e32b..d4056b3cd33c0d03b1045bfe05e5d6f76a480d68 100644 (file)
--- a/README
+++ b/README
@@ -71,6 +71,11 @@ icons/information.svg: Bobarino
     GFDL 1.2 or later / CC-BY-SA 3.0
     https://en.wikipedia.org/wiki/File:Information.svg#Licensing
 
+icons/add-math-channel.svg: Inductiveload
+  https://en.wikipedia.org/wiki/File:Icon_Mathematical_Plot.svg
+  License:
+    Public Domain
+
 QDarkStyleSheet: Colin Duquesnoy
   https://github.com/ColinDuquesnoy/QDarkStyleSheet
   License:
@@ -83,12 +88,17 @@ DarkStyle: Juergen Skrotzky
     MIT license
     https://github.com/Jorgen-VikingGod/Qt-Frameless-Window-DarkStyle#licence
 
-QHexView:
+QHexView: Victor Anjin
   https://github.com/virinext/QHexView
   License:
     MIT license
     https://github.com/virinext/QHexView/blob/master/LICENSE
 
+ExprTk: Arash Partow
+  https://www.partow.net/programming/exprtk/index.html
+  License:
+    MIT license
+
 
 Mailing list
 ------------
diff --git a/icons/add-math-signal.svg b/icons/add-math-signal.svg
new file mode 100644 (file)
index 0000000..4fa7124
--- /dev/null
@@ -0,0 +1,191 @@
+<?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"
+   id="svg11300"
+   height="48px"
+   width="48px"
+   version="1.1"
+   sodipodi:docname="add-math-channel.svg"
+   inkscape:version="0.92.4 5da689c313, 2019-01-14">
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1022"
+     id="namedview124"
+     showgrid="false"
+     inkscape:zoom="17.0625"
+     inkscape:cx="12.893773"
+     inkscape:cy="24"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg11300" />
+  <defs
+     id="defs3">
+    <radialGradient
+       id="radialGradient6719"
+       cx="605.71"
+       xlink:href="#linearGradient5060"
+       gradientUnits="userSpaceOnUse"
+       cy="486.65"
+       r="117.14"
+       gradientTransform="matrix(-2.7744,0,0,1.9697,112.76,-872.89)" />
+    <linearGradient
+       id="linearGradient5060">
+      <stop
+         id="stop5062"
+         offset="0" />
+      <stop
+         id="stop5064"
+         stop-opacity="0"
+         offset="1" />
+    </linearGradient>
+    <radialGradient
+       id="radialGradient6717"
+       cx="605.71"
+       xlink:href="#linearGradient5060"
+       gradientUnits="userSpaceOnUse"
+       cy="486.65"
+       r="117.14"
+       gradientTransform="matrix(2.7744,0,0,1.9697,-1891.6,-872.89)" />
+    <linearGradient
+       id="linearGradient6715"
+       y2="609.51"
+       gradientUnits="userSpaceOnUse"
+       y1="366.65"
+       gradientTransform="matrix(2.7744,0,0,1.9697,-1892.2,-872.89)"
+       x2="302.86"
+       x1="302.86">
+      <stop
+         id="stop5050"
+         stop-opacity="0"
+         offset="0" />
+      <stop
+         id="stop5056"
+         offset=".5" />
+      <stop
+         id="stop5052"
+         stop-opacity="0"
+         offset="1" />
+    </linearGradient>
+    <radialGradient
+       id="radialGradient2939"
+       cx="25.458"
+       gradientUnits="userSpaceOnUse"
+       cy="35.596"
+       r="20.531"
+       gradientTransform="matrix(2.1282,0,0,2.1283,-28.519,-40.418)">
+      <stop
+         id="stop2935"
+         stop-color="#9cbcde"
+         offset="0" />
+      <stop
+         id="stop2937"
+         stop-color="#204a87"
+         offset="1" />
+    </radialGradient>
+  </defs>
+  <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/by-sa/2.0/" />
+        <dc:title>Accessibility</dc:title>
+        <dc:subject>
+          <rdf:Bag>
+            <rdf:li>accessibility</rdf:li>
+            <rdf:li>assist</rdf:li>
+          </rdf:Bag>
+        </dc:subject>
+      </cc:Work>
+      <cc:License
+         rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
+        <cc:permits
+           rdf:resource="http://web.resource.org/cc/Reproduction" />
+        <cc:permits
+           rdf:resource="http://web.resource.org/cc/Distribution" />
+        <cc:requires
+           rdf:resource="http://web.resource.org/cc/Notice" />
+        <cc:requires
+           rdf:resource="http://web.resource.org/cc/Attribution" />
+        <cc:permits
+           rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+        <cc:requires
+           rdf:resource="http://web.resource.org/cc/ShareAlike" />
+      </cc:License>
+    </rdf:RDF>
+  </metadata>
+  <path
+     style="fill:none;stroke:#ff0000;stroke-width:1px"
+     inkscape:connector-curvature="0"
+     d="m 12.5,36 c 3,-8 8.155,-12.981 10.5,-13 3.431,-0.028 5,6 9,6 3.606,0 7,-11 8,-19"
+     id="path9990" />
+  <path
+     style="fill:none;stroke:#000000;stroke-width:2;stroke-opacity:1"
+     inkscape:connector-curvature="0"
+     d="M 12,10 V 36 H 38"
+     id="path9988" />
+  <path
+     style="fill:#000000;fill-rule:evenodd;stroke:#000000;stroke-opacity:1;fill-opacity:1;stroke-linejoin:miter;paint-order:normal;stroke-linecap:round"
+     inkscape:connector-curvature="0"
+     d="m 12,7 -3,6 h 6 L 12,7"
+     id="path9992" />
+  <path
+     style="fill:#000000;fill-rule:evenodd;stroke:#000000;stroke-opacity:1;fill-opacity:1;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-linecap:round"
+     inkscape:connector-curvature="0"
+     d="m 41,36 -6,-3 v 6 l 6,-3"
+     id="path9994" />
+  <g
+     style="fill:#000000;stroke:#000000;stroke-width:0.14230999;stroke-opacity:1;fill-opacity:1"
+     transform="matrix(3.5134,0,0,3.5134,-53.717,-11.668)"
+     id="g10167">
+    <g
+       style="fill:#000000;fill-rule:evenodd;stroke:#000000;stroke-width:2.07540011;stroke-miterlimit:10.43299961;stroke-opacity:1;fill-opacity:1"
+       stroke-miterlimit="10.433"
+       transform="matrix(0.068571,0,0,-0.068571,-12,36)"
+       xml:space="preserve"
+       id="g10169"><path
+         inkscape:connector-curvature="0"
+         d="m 474.28,425.93 h 4.29 c 0.99,0 1.49,0 1.49,0.99 0,0.55 -0.5,0.55 -1.35,0.55 h -4.13 c 0.55,2.89 0.5,2.79 1.05,5.68 0.19,1.05 0.89,4.58 1.19,5.18 0.45,0.94 1.3,1.69 2.34,1.69 0.2,0 1.5,0 2.45,-0.89 -2.2,-0.2 -2.7,-1.95 -2.7,-2.69 0,-1.15 0.9,-1.75 1.85,-1.75 1.29,0 2.74,1.1 2.74,2.99 0,2.29 -2.29,3.44 -4.34,3.44 -1.69,0 -4.83,-0.9 -6.32,-5.83 -0.3,-1.05 -0.45,-1.54 -1.65,-7.82 h -3.43 c -0.95,0 -1.5,0 -1.5,-0.95 0,-0.59 0.45,-0.59 1.4,-0.59 h 3.29 l -3.74,-19.68 c -0.9,-4.83 -1.75,-9.37 -4.33,-9.37 -0.2,0 -1.45,0 -2.4,0.9 2.29,0.15 2.74,1.94 2.74,2.69 0,1.15 -0.89,1.74 -1.84,1.74 -1.29,0 -2.74,-1.09 -2.74,-2.98 0,-2.25 2.19,-3.44 4.24,-3.44 2.73,0 4.73,2.94 5.62,4.83 1.6,3.14 2.74,9.17 2.79,9.51 z"
+         id="path10171"
+         style="stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1" /><path
+         inkscape:connector-curvature="0"
+         d="m 501.49,394.04 v 0.03 0.03 l -0.01,0.01 v 0.02 0.01 l -0.01,0.02 v 0.02 l -0.01,0.01 -0.01,0.02 -0.01,0.02 -0.01,0.03 -0.01,0.02 -0.02,0.02 -0.02,0.03 -0.02,0.03 -0.02,0.03 -0.03,0.03 -0.03,0.04 -0.01,0.02 -0.02,0.02 -0.02,0.02 -0.02,0.02 -0.02,0.02 -0.02,0.03 -0.02,0.02 -0.02,0.02 -0.02,0.03 -0.03,0.02 -0.02,0.03 -0.03,0.03 -0.02,0.03 -0.03,0.03 -0.03,0.03 -0.03,0.03 -0.03,0.03 -0.03,0.03 -0.04,0.04 -0.03,0.03 -0.04,0.04 -0.03,0.03 -0.04,0.04 -0.04,0.04 c -6.23,6.28 -7.82,15.69 -7.82,23.31 0,8.67 1.89,17.34 8.02,23.57 0.65,0.59 0.65,0.69 0.65,0.84 0,0.35 -0.2,0.5 -0.5,0.5 -0.5,0 -4.98,-3.39 -7.92,-9.72 -2.54,-5.47 -3.14,-11 -3.14,-15.19 0,-3.88 0.55,-9.91 3.29,-15.54 2.99,-6.12 7.27,-9.36 7.77,-9.36 0.3,0 0.5,0.15 0.5,0.49 z"
+         id="path10173"
+         style="stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1" /><path
+         inkscape:connector-curvature="0"
+         d="m 521.64,421.04 0.03,0.13 0.03,0.15 0.05,0.17 0.04,0.18 0.06,0.19 0.06,0.21 0.06,0.21 0.07,0.23 0.08,0.23 0.09,0.24 0.1,0.25 0.1,0.24 0.11,0.25 0.12,0.25 0.13,0.25 0.14,0.25 0.15,0.24 0.15,0.24 0.17,0.23 0.18,0.22 0.18,0.22 0.2,0.2 0.21,0.19 0.22,0.18 0.23,0.16 0.25,0.14 0.12,0.07 0.13,0.06 0.13,0.05 0.14,0.05 0.14,0.05 0.14,0.04 0.14,0.03 0.15,0.03 0.15,0.02 0.16,0.02 0.16,0.01 h 0.16 c 0.25,0 1.45,0 2.49,-0.65 -1.4,-0.25 -2.39,-1.49 -2.39,-2.68 0,-0.8 0.55,-1.75 1.89,-1.75 1.1,0 2.69,0.9 2.69,2.89 0,2.59 -2.94,3.29 -4.63,3.29 -2.89,0 -4.64,-2.64 -5.23,-3.79 -1.24,3.29 -3.94,3.79 -5.38,3.79 -5.18,0 -8.02,-6.43 -8.02,-7.67 0,-0.5 0.5,-0.5 0.6,-0.5 0.39,0 0.54,0.1 0.64,0.54 1.7,5.29 4.99,6.53 6.68,6.53 0.94,0 2.69,-0.45 2.69,-3.33 0,-1.55 -0.85,-4.89 -2.69,-11.86 -0.8,-3.09 -2.54,-5.18 -4.73,-5.18 -0.3,0 -1.45,0 -2.49,0.65 1.24,0.25 2.34,1.29 2.34,2.69 0,1.34 -1.1,1.74 -1.85,1.74 -1.49,0 -2.73,-1.3 -2.73,-2.89 0,-2.29 2.48,-3.29 4.68,-3.29 3.28,0 5.08,3.49 5.22,3.79 0.61,-1.85 2.4,-3.79 5.39,-3.79 5.13,0 7.97,6.43 7.97,7.68 0,0.49 -0.45,0.49 -0.6,0.49 -0.45,0 -0.55,-0.2 -0.65,-0.55 -1.64,-5.33 -5.03,-6.52 -6.62,-6.52 -1.94,0 -2.74,1.59 -2.74,3.29 0,1.09 0.3,2.19 0.85,4.38 z"
+         id="path10175"
+         style="stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1" /><path
+         inkscape:connector-curvature="0"
+         d="m 547.4,418.45 -0.01,0.37 v 0.39 l -0.01,0.39 -0.01,0.41 -0.04,0.84 -0.06,0.88 -0.09,0.92 -0.1,0.94 -0.14,0.98 -0.16,1 -0.2,1.02 -0.23,1.04 -0.27,1.05 -0.3,1.06 -0.35,1.06 -0.39,1.07 -0.22,0.53 -0.22,0.54 -0.24,0.53 -0.25,0.53 c -2.99,6.12 -7.27,9.36 -7.77,9.36 -0.3,0 -0.5,-0.2 -0.5,-0.5 0,-0.15 0,-0.25 0.94,-1.15 4.89,-4.92 7.73,-12.85 7.73,-23.26 0,-8.52 -1.85,-17.28 -8.02,-23.56 -0.65,-0.6 -0.65,-0.69 -0.65,-0.85 0,-0.29 0.2,-0.49 0.5,-0.49 0.5,0 4.98,3.38 7.92,9.71 2.54,5.48 3.14,11.01 3.14,15.19 z"
+         id="path10177"
+         style="stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1" /></g>  </g>
+</svg>
diff --git a/icons/math.svg b/icons/math.svg
new file mode 100644 (file)
index 0000000..afdaeb6
--- /dev/null
@@ -0,0 +1,197 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="73.108994mm"
+   height="72.888451mm"
+   viewBox="0 0 73.108993 72.88845"
+   version="1.1"
+   id="svg8"
+   inkscape:version="0.92.4 5da689c313, 2019-01-14"
+   sodipodi:docname="math.svg">
+  <defs
+     id="defs2" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="2.3994141"
+     inkscape:cx="136.49166"
+     inkscape:cy="131.54997"
+     inkscape:document-units="mm"
+     inkscape:current-layer="layer2"
+     showgrid="false"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0"
+     inkscape:window-width="1920"
+     inkscape:window-height="1022"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata5">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:groupmode="layer"
+     id="layer2"
+     inkscape:label="bg"
+     style="display:inline"
+     transform="translate(-9.0421371,-8.6010609)">
+    <rect
+       style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+       id="rect901"
+       width="33.522072"
+       height="33.522072"
+       x="9.0421381"
+       y="8.6010609"
+       ry="7.3880887" />
+    <rect
+       ry="7.3880887"
+       y="8.6010609"
+       x="48.629059"
+       height="33.522072"
+       width="33.522072"
+       id="rect4606"
+       style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+    <rect
+       style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+       id="rect4608"
+       width="33.522072"
+       height="33.522072"
+       x="48.629059"
+       y="47.967442"
+       ry="7.3880887" />
+    <rect
+       ry="7.3880887"
+       y="47.967442"
+       x="9.0421371"
+       height="33.522072"
+       width="33.522072"
+       id="rect4610"
+       style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer3"
+     inkscape:label="text"
+     style="display:inline"
+     transform="translate(-9.0421371,-8.6010609)">
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.3812499px;line-height:125%;font-family:Sans;-inkscape-font-specification:Helvetica;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="-26.023714"
+       y="23.928589"
+       id="text4615"><tspan
+         sodipodi:role="line"
+         id="tspan4613"
+         x="-26.023714"
+         y="26.073511"
+         style="line-height:25.34355545px;stroke-width:0.26458332px" /></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.3812499px;line-height:125%;font-family:Sans;-inkscape-font-specification:Helvetica;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="-6.8615932"
+       y="27.153271"
+       id="text4619"><tspan
+         sodipodi:role="line"
+         id="tspan4617"
+         x="-6.8615932"
+         y="29.298193"
+         style="stroke-width:0.26458332px" /></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.3812499px;line-height:125%;font-family:Sans;-inkscape-font-specification:Helvetica;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="-22.715614"
+       y="52.267971"
+       id="text4635"><tspan
+         sodipodi:role="line"
+         id="tspan4633"
+         x="-22.715614"
+         y="54.412891"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:42.33333206px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332px" /></text>
+    <g
+       id="g4686"
+       style="fill:#fcfcfc;fill-opacity:1">
+      <text
+         id="text4627"
+         y="39.476654"
+         x="13.673477"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.3812499px;line-height:125%;font-family:Sans;-inkscape-font-specification:Helvetica;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#fcfcfc;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+         xml:space="preserve"><tspan
+           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:42.33333206px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#fcfcfc;fill-opacity:1;stroke-width:0.26458332px"
+           y="39.476654"
+           x="13.673477"
+           id="tspan4625"
+           sodipodi:role="line">+</tspan></text>
+      <text
+         transform="scale(1.4000838,0.71424298)"
+         id="text4631"
+         y="51.256554"
+         x="36.829548"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.33394933px;line-height:125%;font-family:Sans;-inkscape-font-specification:Helvetica;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#fcfcfc;fill-opacity:1;stroke:none;stroke-width:0.37043878px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+         xml:space="preserve"><tspan
+           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:59.27021027px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#fcfcfc;fill-opacity:1;stroke-width:0.37043878px"
+           y="51.256554"
+           x="36.829548"
+           id="tspan4629"
+           sodipodi:role="line">-</tspan></text>
+      <text
+         transform="rotate(-45)"
+         xml:space="preserve"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.3812499px;line-height:125%;font-family:Sans;-inkscape-font-specification:Helvetica;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#fcfcfc;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+         x="-39.823532"
+         y="78.197281"
+         id="text4639"><tspan
+           sodipodi:role="line"
+           id="tspan4637"
+           x="-39.823532"
+           y="78.197281"
+           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:42.33333206px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#fcfcfc;fill-opacity:1;stroke-width:0.26458332px">+</tspan></text>
+      <text
+         transform="scale(1.4000838,0.71424297)"
+         id="text4631-7"
+         y="106.67771"
+         x="36.993214"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.33394957px;line-height:125%;font-family:Sans;-inkscape-font-specification:Helvetica;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;display:inline;fill:#fcfcfc;fill-opacity:1;stroke:none;stroke-width:0.37043881px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+         xml:space="preserve"><tspan
+           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:59.27021027px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#fcfcfc;fill-opacity:1;stroke-width:0.37043881px"
+           y="106.67771"
+           x="36.993214"
+           id="tspan4629-6"
+           sodipodi:role="line">-</tspan></text>
+      <circle
+         r="2.976748"
+         cy="57.78373"
+         cx="65.500908"
+         id="path4659"
+         style="opacity:1;fill:#fcfcfc;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
+      <circle
+         r="2.976748"
+         cy="71.785759"
+         cx="65.500366"
+         id="path4659-0"
+         style="display:inline;opacity:1;fill:#fcfcfc;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
+    </g>
+  </g>
+</svg>
index 94c0901504096bf1ce5473dd19521ea925223acc..5553fa8b0469d2fa98775b8be5f2703014e82b9a 100644 (file)
@@ -4,7 +4,7 @@
 <context>
     <name>Application</name>
     <message>
-        <location filename="../pv/application.cpp" line="121"/>
+        <location filename="../pv/application.cpp" line="129"/>
         <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>
@@ -47,7 +47,7 @@
 <context>
     <name>QHexView</name>
     <message>
-        <location filename="../pv/views/decoder_binary/QHexView.cpp" line="288"/>
+        <location filename="../pv/views/decoder_binary/QHexView.cpp" line="291"/>
         <source>No data available</source>
         <translation>Keine Daten vorhanden</translation>
     </message>
         <translation>Suche nach Geräten, die von Treiber %1 angesprochen werden können...</translation>
     </message>
     <message>
-        <location filename="../main.cpp" line="113"/>
+        <location filename="../main.cpp" line="114"/>
         <source>Stack trace of previous crash:</source>
         <translatorcomment>Internal message</translatorcomment>
         <translation></translation>
     </message>
     <message>
-        <location filename="../main.cpp" line="127"/>
+        <location filename="../main.cpp" line="128"/>
         <source>Don&apos;t show this message again</source>
         <translation>Diese Meldung in Zukunft nicht mehr anzeigen</translation>
     </message>
     <message>
-        <location filename="../main.cpp" line="130"/>
+        <location filename="../main.cpp" line="131"/>
         <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>
@@ -93,67 +93,67 @@ A human-readable form has been saved to disk and was written to the log. You may
 <context>
     <name>pv::MainWindow</name>
     <message>
-        <location filename="../pv/mainwindow.cpp" line="69"/>
+        <location filename="../pv/mainwindow.cpp" line="70"/>
         <source>PulseView</source>
         <translatorcomment>Name</translatorcomment>
         <translation></translation>
     </message>
     <message>
-        <location filename="../pv/mainwindow.cpp" line="276"/>
+        <location filename="../pv/mainwindow.cpp" line="279"/>
         <source>Decoder Selector</source>
         <translation>Protokolldekoder</translation>
     </message>
     <message>
-        <location filename="../pv/mainwindow.cpp" line="329"/>
+        <location filename="../pv/mainwindow.cpp" line="332"/>
         <source>Session %1</source>
         <translation>Analysesitzung %1</translation>
     </message>
     <message>
-        <location filename="../pv/mainwindow.cpp" line="511"/>
+        <location filename="../pv/mainwindow.cpp" line="514"/>
         <source>Create New Session</source>
         <translation>Neue Analysesitzung</translation>
     </message>
     <message>
-        <location filename="../pv/mainwindow.cpp" line="517"/>
+        <location filename="../pv/mainwindow.cpp" line="520"/>
         <source>Start/Stop Acquisition</source>
         <translation>Datenerfassung starten/stoppen</translation>
     </message>
     <message>
-        <location filename="../pv/mainwindow.cpp" line="525"/>
+        <location filename="../pv/mainwindow.cpp" line="528"/>
         <source>Settings</source>
         <translation>Einstellungen</translation>
     </message>
     <message>
-        <location filename="../pv/mainwindow.cpp" line="577"/>
+        <location filename="../pv/mainwindow.cpp" line="580"/>
         <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"/>
+        <location filename="../pv/mainwindow.cpp" line="635"/>
+        <location filename="../pv/mainwindow.cpp" line="829"/>
+        <location filename="../pv/mainwindow.cpp" line="855"/>
         <source>Confirmation</source>
         <translation>Bestätigung</translation>
     </message>
     <message>
-        <location filename="../pv/mainwindow.cpp" line="633"/>
+        <location filename="../pv/mainwindow.cpp" line="636"/>
         <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"/>
+        <location filename="../pv/mainwindow.cpp" line="583"/>
         <source>Run</source>
         <translation>Starten</translation>
     </message>
     <message>
-        <location filename="../pv/mainwindow.cpp" line="586"/>
+        <location filename="../pv/mainwindow.cpp" line="589"/>
         <source>Stop</source>
         <translation>Stoppen</translation>
     </message>
     <message>
-        <location filename="../pv/mainwindow.cpp" line="827"/>
-        <location filename="../pv/mainwindow.cpp" line="853"/>
+        <location filename="../pv/mainwindow.cpp" line="830"/>
+        <location filename="../pv/mainwindow.cpp" line="856"/>
         <source>This session contains unsaved data. Close it anyway?</source>
         <translation>Die Daten dieser Analysesitzung wurden nicht gespeichert. Trotzdem schließen?</translation>
     </message>
@@ -161,47 +161,51 @@ A human-readable form has been saved to disk and was written to the log. You may
 <context>
     <name>pv::Session</name>
     <message>
-        <location filename="../pv/session.cpp" line="481"/>
+        <location filename="../pv/session.cpp" line="521"/>
         <source>Failed to select device</source>
         <translation>Fehler beim Auswählen des Gerätes</translation>
     </message>
     <message>
-        <location filename="../pv/session.cpp" line="530"/>
+        <location filename="../pv/session.cpp" line="578"/>
         <source>Failed to open device</source>
         <translation>Fehler beim Öffnen des Gerätes</translation>
     </message>
     <message>
-        <location filename="../pv/session.cpp" line="632"/>
+        <location filename="../pv/session.cpp" line="684"/>
         <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>
+        <translation type="vanished">Unerwartetes Importformat: %s</translation>
+    </message>
+    <message>
+        <location filename="../pv/session.cpp" line="685"/>
+        <source>Unexpected input format: %1</source>
+        <translation>Unerwartetes Importformat: %1</translation>
     </message>
     <message>
-        <location filename="../pv/session.cpp" line="668"/>
+        <location filename="../pv/session.cpp" line="720"/>
         <source>Failed to load %1</source>
         <translation>Fehler beim Laden von %1</translation>
     </message>
     <message>
-        <location filename="../pv/session.cpp" line="703"/>
+        <location filename="../pv/session.cpp" line="759"/>
         <source>No active device set, can&apos;t start acquisition.</source>
         <translation>Kein Gerät aktiv, kann Datenerfassung nicht starten.</translation>
     </message>
     <message>
-        <location filename="../pv/session.cpp" line="716"/>
+        <location filename="../pv/session.cpp" line="772"/>
         <source>No channels enabled.</source>
         <translation>Keine aktiven Kanäle vorhanden.</translation>
     </message>
     <message>
-        <location filename="../pv/session.cpp" line="1171"/>
+        <location filename="../pv/session.cpp" line="1261"/>
         <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"/>
+        <location filename="../pv/session.cpp" line="1467"/>
         <source>Can&apos;t handle more than 64 logic channels.</source>
         <translatorcomment>Internal message</translatorcomment>
         <translation></translation>
@@ -239,32 +243,32 @@ A human-readable form has been saved to disk and was written to the log. You may
 <context>
     <name>pv::data::DecodeSignal</name>
     <message>
-        <location filename="../pv/data/decodesignal.cpp" line="194"/>
+        <location filename="../pv/data/decodesignal.cpp" line="198"/>
         <source>No decoders</source>
         <translation>Keine Protokolldekoder</translation>
     </message>
     <message>
-        <location filename="../pv/data/decodesignal.cpp" line="201"/>
+        <location filename="../pv/data/decodesignal.cpp" line="205"/>
         <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"/>
+        <location filename="../pv/data/decodesignal.cpp" line="219"/>
         <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"/>
+        <location filename="../pv/data/decodesignal.cpp" line="238"/>
         <source>No input data</source>
         <translation>Keine Daten zum Auswerten vorhanden</translation>
     </message>
     <message>
-        <location filename="../pv/data/decodesignal.cpp" line="1160"/>
+        <location filename="../pv/data/decodesignal.cpp" line="1180"/>
         <source>Decoder reported an error</source>
         <translation>Protokolldekoder meldet Fehler</translation>
     </message>
     <message>
-        <location filename="../pv/data/decodesignal.cpp" line="1296"/>
+        <location filename="../pv/data/decodesignal.cpp" line="1316"/>
         <source>Failed to create decoder instance</source>
         <translation>Fehler beim Erzeugen des Protokolldekoders</translation>
     </message>
@@ -272,52 +276,52 @@ A human-readable form has been saved to disk and was written to the log. You may
 <context>
     <name>pv::data::SignalBase</name>
     <message>
-        <location filename="../pv/data/signalbase.cpp" line="409"/>
+        <location filename="../pv/data/signalbase.cpp" line="485"/>
         <source>Signal average</source>
         <translation>Durchschnittlicher Signalpegel</translation>
     </message>
     <message>
-        <location filename="../pv/data/signalbase.cpp" line="410"/>
+        <location filename="../pv/data/signalbase.cpp" line="486"/>
         <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"/>
+        <location filename="../pv/data/signalbase.cpp" line="487"/>
         <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"/>
+        <location filename="../pv/data/signalbase.cpp" line="488"/>
         <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"/>
+        <location filename="../pv/data/signalbase.cpp" line="489"/>
         <source>1.5V (for TTL)</source>
         <translation>1.5V (für TTL)</translation>
     </message>
     <message>
-        <location filename="../pv/data/signalbase.cpp" line="418"/>
+        <location filename="../pv/data/signalbase.cpp" line="494"/>
         <source>Signal average +/- 15%</source>
         <translation>Durchschnittlicher Signalpegel +/- 15%</translation>
     </message>
     <message>
-        <location filename="../pv/data/signalbase.cpp" line="419"/>
+        <location filename="../pv/data/signalbase.cpp" line="495"/>
         <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"/>
+        <location filename="../pv/data/signalbase.cpp" line="496"/>
         <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"/>
+        <location filename="../pv/data/signalbase.cpp" line="497"/>
         <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"/>
+        <location filename="../pv/data/signalbase.cpp" line="498"/>
         <source>0.8V/2.0V (for TTL)</source>
         <translation>0.8V/2.0V (für TTL)</translation>
     </message>
@@ -549,7 +553,7 @@ A human-readable form has been saved to disk and was written to the log. You may
     <message>
         <location filename="../pv/dialogs/settings.cpp" line="409"/>
         <source>Always show all &amp;rows, even if no annotation is visible</source>
-        <translation>Immer alle &amp;Reihen anzeigen, auch wenn hierfür keine dekodierten Werte vorliegen</translation>
+        <translation>Immer alle &amp;Kategorien (Zeilen) anzeigen, auch wenn hierfür keine dekodierten Werte vorliegen</translation>
     </message>
     <message>
         <location filename="../pv/dialogs/settings.cpp" line="417"/>
@@ -559,7 +563,7 @@ A human-readable form has been saved to disk and was written to the log. You may
     <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>
+        <translation>%s = Start-/Endsample; %d: Dekodername; %r: Kategorie (Name der Zeile); %c: Unterkategorie</translation>
     </message>
     <message>
         <location filename="../pv/dialogs/settings.cpp" line="421"/>
@@ -624,32 +628,37 @@ A human-readable form has been saved to disk and was written to the log. You may
         <translation>Unterstützte Protokolldekoder:</translation>
     </message>
     <message>
-        <location filename="../pv/dialogs/settings.cpp" line="537"/>
+        <location filename="../pv/dialogs/settings.cpp" line="504"/>
+        <source>Available Translations:</source>
+        <translation>Verfügbare Übersetzungen:</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="552"/>
         <source>Log level:</source>
         <translation>Log-Level:</translation>
     </message>
     <message>
-        <location filename="../pv/dialogs/settings.cpp" line="542"/>
+        <location filename="../pv/dialogs/settings.cpp" line="557"/>
         <source> lines</source>
         <translation> Zeilen</translation>
     </message>
     <message>
-        <location filename="../pv/dialogs/settings.cpp" line="551"/>
+        <location filename="../pv/dialogs/settings.cpp" line="566"/>
         <source>Length of background buffer:</source>
         <translation>Länge des Logpuffers:</translation>
     </message>
     <message>
-        <location filename="../pv/dialogs/settings.cpp" line="557"/>
+        <location filename="../pv/dialogs/settings.cpp" line="572"/>
         <source>&amp;Save to File</source>
         <translation>&amp;Speichern</translation>
     </message>
     <message>
-        <location filename="../pv/dialogs/settings.cpp" line="564"/>
+        <location filename="../pv/dialogs/settings.cpp" line="579"/>
         <source>&amp;Pop out</source>
         <translation>&amp;Abdocken</translation>
     </message>
     <message>
-        <location filename="../pv/dialogs/settings.cpp" line="633"/>
+        <location filename="../pv/dialogs/settings.cpp" line="648"/>
         <source>You selected a dark theme.
 Should I set the user-adjustable colors to better suit your choice?
 
@@ -660,7 +669,7 @@ Sollen die benutzerspezifischen Farben entsprechend angepasst werden, damit sie
 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"/>
+        <location filename="../pv/dialogs/settings.cpp" line="654"/>
         <source>You selected a bright theme.
 Should I set the user-adjustable colors to better suit your choice?
 
@@ -671,37 +680,37 @@ Sollen die benutzerspezifischen Farben entsprechend angepasst werden, damit sie
 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"/>
+        <location filename="../pv/dialogs/settings.cpp" line="807"/>
         <source>Save Log</source>
         <translation>Log speichern</translation>
     </message>
     <message>
-        <location filename="../pv/dialogs/settings.cpp" line="792"/>
+        <location filename="../pv/dialogs/settings.cpp" line="807"/>
         <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"/>
+        <location filename="../pv/dialogs/settings.cpp" line="819"/>
         <source>Success</source>
         <translation>Erfolg</translation>
     </message>
     <message>
-        <location filename="../pv/dialogs/settings.cpp" line="804"/>
+        <location filename="../pv/dialogs/settings.cpp" line="819"/>
         <source>Log saved to %1.</source>
         <translation>Log als %1 gespeichert.</translation>
     </message>
     <message>
-        <location filename="../pv/dialogs/settings.cpp" line="814"/>
+        <location filename="../pv/dialogs/settings.cpp" line="829"/>
         <source>Error</source>
         <translation>Fehler</translation>
     </message>
     <message>
-        <location filename="../pv/dialogs/settings.cpp" line="814"/>
+        <location filename="../pv/dialogs/settings.cpp" line="829"/>
         <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"/>
+        <location filename="../pv/dialogs/settings.cpp" line="843"/>
         <source>%1 Log</source>
         <translation></translation>
     </message>
@@ -887,166 +896,175 @@ Bei einer Änderung benötigt PulseView eventuell einen Neustart, damit alles ko
 <context>
     <name>pv::toolbars::MainBar</name>
     <message>
-        <location filename="../pv/toolbars/mainbar.cpp" line="120"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="121"/>
         <source>New &amp;View</source>
         <translation>Neue &amp;Ansicht</translation>
     </message>
     <message>
-        <location filename="../pv/toolbars/mainbar.cpp" line="126"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="127"/>
         <source>&amp;Open...</source>
         <translation>&amp;Öffnen...</translation>
     </message>
     <message>
-        <location filename="../pv/toolbars/mainbar.cpp" line="133"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="134"/>
         <source>Restore Session Setu&amp;p...</source>
         <translation>&amp;Konfiguration der Analysesitzung laden...</translation>
     </message>
     <message>
-        <location filename="../pv/toolbars/mainbar.cpp" line="137"/>
         <source>&amp;Save As...</source>
-        <translation>&amp;Speichern als...</translation>
+        <translation type="vanished">&amp;Speichern als...</translation>
     </message>
     <message>
-        <location filename="../pv/toolbars/mainbar.cpp" line="144"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="138"/>
+        <source>&amp;Save...</source>
+        <translation>&amp;Speichern...</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="145"/>
+        <source>Save &amp;As...</source>
+        <translation>Speichern &amp;als...</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="151"/>
         <source>Save Selected &amp;Range As...</source>
         <translation>Ausgewählten &amp;Bereich speichern als...</translation>
     </message>
     <message>
-        <location filename="../pv/toolbars/mainbar.cpp" line="151"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="158"/>
         <source>Save Session Setu&amp;p...</source>
         <translation>&amp;Konfiguration der Analysesitzung speichern...</translation>
     </message>
     <message>
-        <location filename="../pv/toolbars/mainbar.cpp" line="157"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="164"/>
         <source>&amp;Export</source>
         <translation></translation>
     </message>
     <message>
-        <location filename="../pv/toolbars/mainbar.cpp" line="163"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="170"/>
         <source>&amp;Import</source>
         <translation></translation>
     </message>
     <message>
-        <location filename="../pv/toolbars/mainbar.cpp" line="167"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="174"/>
         <source>&amp;Connect to Device...</source>
         <translation>Mit Gerät &amp;verbinden...</translation>
     </message>
     <message>
-        <location filename="../pv/toolbars/mainbar.cpp" line="228"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="236"/>
         <source>Add protocol decoder</source>
         <translation>Protokolldekoder hinzufügen</translation>
     </message>
     <message>
-        <location filename="../pv/toolbars/mainbar.cpp" line="244"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="252"/>
         <source>Configure Device</source>
         <translation>Gerät konfigurieren</translation>
     </message>
     <message>
-        <location filename="../pv/toolbars/mainbar.cpp" line="248"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="256"/>
         <source>Configure Channels</source>
         <translation>Kanäle konfigurieren</translation>
     </message>
     <message>
-        <location filename="../pv/toolbars/mainbar.cpp" line="357"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="370"/>
         <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"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="433"/>
         <source>Failed to get sample rate:</source>
         <translation>Konnte Abtastrate nicht abfragen:</translation>
     </message>
     <message>
-        <location filename="../pv/toolbars/mainbar.cpp" line="461"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="474"/>
         <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"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="564"/>
         <source>Failed to configure samplerate:</source>
         <translation>Konnte Abtastrate nicht einstellen:</translation>
     </message>
     <message>
-        <location filename="../pv/toolbars/mainbar.cpp" line="578"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="591"/>
         <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"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="629"/>
         <source>Missing Cursors</source>
         <translation>Fehlende Auswahl</translation>
     </message>
     <message>
-        <location filename="../pv/toolbars/mainbar.cpp" line="616"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="629"/>
         <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"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="647"/>
         <source>Invalid Range</source>
         <translation>Auswahl ungültig</translation>
     </message>
     <message>
-        <location filename="../pv/toolbars/mainbar.cpp" line="634"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="647"/>
         <source>The cursors don&apos;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"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="659"/>
         <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"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="667"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="717"/>
         <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"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="671"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="848"/>
         <source>Save File</source>
         <translation>Speichern</translation>
     </message>
     <message>
-        <location filename="../pv/toolbars/mainbar.cpp" line="670"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="683"/>
         <source>Export %1</source>
         <translation>%1 exportieren</translation>
     </message>
     <message>
-        <location filename="../pv/toolbars/mainbar.cpp" line="696"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="714"/>
         <source>%1 files</source>
         <translation>%1-Dateien</translation>
     </message>
     <message>
-        <location filename="../pv/toolbars/mainbar.cpp" line="707"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="725"/>
         <source>Import File</source>
         <translation>Dateiimport</translation>
     </message>
     <message>
-        <location filename="../pv/toolbars/mainbar.cpp" line="716"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="734"/>
         <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"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="807"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="865"/>
         <source>Open File</source>
         <translation>Öffnen</translation>
     </message>
     <message>
-        <location filename="../pv/toolbars/mainbar.cpp" line="789"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="807"/>
         <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"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="848"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="865"/>
         <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"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="926"/>
         <source>Total sampling time: %1</source>
         <translatorcomment>Internal message</translatorcomment>
         <translation></translation>
@@ -1142,85 +1160,166 @@ Bei einer Änderung benötigt PulseView eventuell einen Neustart, damit alles ko
         <translation type="vanished">Hex-Dumps (*.txt);;Alle Dateien (*)</translation>
     </message>
 </context>
+<context>
+    <name>pv::views::tabular_decoder::AnnotationCollectionModel</name>
+    <message>
+        <location filename="../pv/views/tabular_decoder/model.cpp" line="56"/>
+        <source>Sample</source>
+        <translation>Sample</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/model.cpp" line="57"/>
+        <source>Time</source>
+        <translation>Zeit</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/model.cpp" line="58"/>
+        <source>Decoder</source>
+        <translation>Dekoder</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/model.cpp" line="59"/>
+        <source>Ann Row</source>
+        <translation>Kategorie</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/model.cpp" line="60"/>
+        <source>Ann Class</source>
+        <translation>Unterkategorie</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/model.cpp" line="61"/>
+        <source>Value</source>
+        <translation>Wert</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/model.cpp" line="83"/>
+        <source>s</source>
+        <translation>s</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/model.cpp" line="83"/>
+        <source>sa</source>
+        <translation>sa</translation>
+    </message>
+</context>
+<context>
+    <name>pv::views::tabular_decoder::View</name>
+    <message>
+        <location filename="../pv/views/tabular_decoder/view.cpp" line="176"/>
+        <source>Decoder:</source>
+        <translation>Dekoder:</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/view.cpp" line="198"/>
+        <source>Hide Hidden Rows/Classes</source>
+        <translation>Verstecke nicht angezeigte (Unter-)Kategorien</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/view.cpp" line="202"/>
+        <source>&amp;Save...</source>
+        <translation>&amp;Speichern...</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/view.cpp" line="374"/>
+        <source>Save Annotations as CSV</source>
+        <translation>Dekodierte Werte als CSV speichern</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/view.cpp" line="374"/>
+        <source>CSV Files (*.csv);;Text Files (*.txt);;All Files (*)</source>
+        <translation>CSV-Dateien (*.csv);;Textdateien (*.txt);;Alle Dateien (*)</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/view.cpp" line="442"/>
+        <source>Error</source>
+        <translation>Fehler</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/view.cpp" line="442"/>
+        <source>File %1 could not be written to.</source>
+        <translation>Konnte Datei %1 nicht speichern.</translation>
+    </message>
+</context>
 <context>
     <name>pv::views::trace::AnalogSignal</name>
     <message>
-        <location filename="../pv/views/trace/analogsignal.cpp" line="988"/>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="994"/>
         <source>Number of pos vertical divs</source>
         <translation>Anzahl Unterteilungen im Positiven</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/analogsignal.cpp" line="995"/>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1001"/>
         <source>Number of neg vertical divs</source>
         <translation>Anzahl Unterteilungen im Negativen</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/analogsignal.cpp" line="1000"/>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1006"/>
         <source> pixels</source>
         <translation> Pixel</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/analogsignal.cpp" line="1004"/>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1010"/>
         <source>Div height</source>
         <translation>Höhe einer Unterteilung</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/analogsignal.cpp" line="1021"/>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1027"/>
         <source>V/div</source>
         <translation>V/div</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/analogsignal.cpp" line="1025"/>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1031"/>
         <source>Vertical resolution</source>
         <translation>Vertikale Auflösung</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/analogsignal.cpp" line="1034"/>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1040"/>
         <source>Autoranging</source>
         <translation>Automatische Skalierung</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/analogsignal.cpp" line="1039"/>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1045"/>
         <source>none</source>
         <translation>keine</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/analogsignal.cpp" line="1041"/>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1047"/>
         <source>to logic via threshold</source>
         <translation>zu Logik mittels Schwellwert</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/analogsignal.cpp" line="1043"/>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1049"/>
         <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"/>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1055"/>
         <source>Conversion</source>
         <translation>Konvertierung</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/analogsignal.cpp" line="1058"/>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1064"/>
         <source>Conversion threshold(s)</source>
         <translation>Konvertierungs-Schwellwert(e)</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/analogsignal.cpp" line="1068"/>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1074"/>
         <source>analog</source>
         <translation>nur analog</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/analogsignal.cpp" line="1069"/>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1075"/>
         <source>converted</source>
         <translation>nur konvertiert</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/analogsignal.cpp" line="1070"/>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1076"/>
         <source>analog+converted</source>
         <translation>analog+konvertiert</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/analogsignal.cpp" line="1075"/>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1081"/>
         <source>Show traces for</source>
         <translation>Anzuzeigende Signale</translation>
     </message>
@@ -1254,118 +1353,118 @@ Bei einer Änderung benötigt PulseView eventuell einen Neustart, damit alles ko
 <context>
     <name>pv::views::trace::DecodeTrace</name>
     <message>
-        <location filename="../pv/views/trace/decodetrace.cpp" line="448"/>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="449"/>
         <source>&lt;p&gt;&lt;i&gt;No decoders in the stack&lt;/i&gt;&lt;/p&gt;</source>
         <translation>&lt;p&gt;&lt;i&gt;Keine Protokolldekoder vorhanden&lt;/i&gt;&lt;/p&gt;</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/decodetrace.cpp" line="459"/>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="460"/>
         <source>&lt;i&gt;* Required channels&lt;/i&gt;</source>
         <translation>&lt;i&gt;* Notwendige Kanäle&lt;/i&gt;</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/decodetrace.cpp" line="463"/>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="464"/>
         <source>Stack Decoder</source>
         <translation>Protokolldekoder stapeln</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/decodetrace.cpp" line="464"/>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="465"/>
         <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"/>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="479"/>
         <source>Delete</source>
         <translation>Löschen</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/decodetrace.cpp" line="520"/>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="521"/>
         <source>Resume decoding</source>
         <translation>Dekodierung fortsetzen</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/decodetrace.cpp" line="527"/>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="528"/>
         <source>Pause decoding</source>
         <translation>Dekodierung anhalten</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/decodetrace.cpp" line="535"/>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="536"/>
         <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"/>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="545"/>
         <source>Export all annotations</source>
         <translation>Alle dekodierten Werte exportieren</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/decodetrace.cpp" line="551"/>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="552"/>
         <source>Export all annotations for this row</source>
-        <translation>Alle dekodierten Werte dieser Zeile exportieren</translation>
+        <translation>Alle dekodierten Werte dieser Kategorie (Zeile) exportieren</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/decodetrace.cpp" line="560"/>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="561"/>
         <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"/>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="568"/>
         <source>Export annotations for this row, starting here</source>
-        <translation>Alle dekodierten Werte dieser Zeile ab hier exportieren</translation>
+        <translation>Alle dekodierten Werte dieser Kategorie (Zeile) ab hier exportieren</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/decodetrace.cpp" line="576"/>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="577"/>
         <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"/>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="584"/>
         <source>Export annotations for this row within cursor range</source>
-        <translation>Alle dekodierten Werte dieser Zeile innerhalb des gewählten Bereiches exportieren</translation>
+        <translation>Alle dekodierten Werte dieser Kategorie (Zeile) innerhalb des gewählten Bereiches exportieren</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/decodetrace.cpp" line="1100"/>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="1076"/>
         <source>%1:
 %2</source>
         <translation></translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/decodetrace.cpp" line="1144"/>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="1120"/>
         <source>&lt;b&gt;%1&lt;/b&gt; (%2) %3</source>
         <translation></translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/decodetrace.cpp" line="1214"/>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="1190"/>
         <source>Export annotations</source>
         <translation>Dekodierte Werte exportieren</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/decodetrace.cpp" line="1214"/>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="1190"/>
         <source>Text Files (*.txt);;All Files (*)</source>
         <translation>Textdateien (*.txt);;Alle Dateien (*)</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/decodetrace.cpp" line="1279"/>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="1255"/>
         <source>Error</source>
         <translation>Fehler</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/decodetrace.cpp" line="1279"/>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="1255"/>
         <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"/>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="1308"/>
         <source>Show this row</source>
-        <translation>Diese Zeile anzeigen</translation>
+        <translation>Diese Kategorie anzeigen</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/decodetrace.cpp" line="1343"/>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="1319"/>
         <source>Show All</source>
         <translation>Alle anzeigen</translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/decodetrace.cpp" line="1351"/>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="1327"/>
         <source>Hide All</source>
         <translation>Alle verstecken</translation>
     </message>
@@ -1475,12 +1574,12 @@ Bei einer Änderung benötigt PulseView eventuell einen Neustart, damit alles ko
 <context>
     <name>pv::views::trace::Signal</name>
     <message>
-        <location filename="../pv/views/trace/signal.cpp" line="148"/>
+        <location filename="../pv/views/trace/signal.cpp" line="153"/>
         <source>Name</source>
         <translation></translation>
     </message>
     <message>
-        <location filename="../pv/views/trace/signal.cpp" line="159"/>
+        <location filename="../pv/views/trace/signal.cpp" line="164"/>
         <source>Disable</source>
         <translation>Deaktivieren</translation>
     </message>
diff --git a/l10n/es_MX.ts b/l10n/es_MX.ts
new file mode 100644 (file)
index 0000000..b7426b0
--- /dev/null
@@ -0,0 +1,1619 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="es_MX">
+<context>
+    <name>Application</name>
+    <message>
+        <location filename="../pv/application.cpp" line="129"/>
+        <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>Algunas partes de la aplicación aún pueden usar el idioma anterior. Volver a abrir las ventanas afectadas o reiniciar la aplicación solucionará esto.</translation>
+    </message>
+</context>
+<context>
+    <name>QApplication</name>
+    <message>
+        <location filename="../pv/devicemanager.cpp" line="274"/>
+        <source>Error when scanning device driver &apos;%1&apos;: %2</source>
+        <translation>Error al escanear el controlador del dispositivo &apos;%1&apos;: %2</translation>
+    </message>
+    <message>
+        <location filename="../pv/devices/device.cpp" line="70"/>
+        <source>Querying config key %1 is not allowed</source>
+        <translation>No se permite consultar la clave de configuración %1</translation>
+    </message>
+    <message>
+        <location filename="../pv/devices/device.cpp" line="79"/>
+        <source>Querying config key %1 resulted in %2</source>
+        <translation>La consulta de la clave de configuración %1 resultó en %2</translation>
+    </message>
+    <message>
+        <location filename="../pv/devices/device.cpp" line="93"/>
+        <source>Unknown type supplied when attempting to query %1</source>
+        <translation>Tipo desconocido proporcionado al intentar consultar %1</translation>
+    </message>
+</context>
+<context>
+    <name>QHexView</name>
+    <message>
+        <location filename="../pv/views/decoder_binary/QHexView.cpp" line="291"/>
+        <source>No data available</source>
+        <translation>Datos no disponibles</translation>
+    </message>
+</context>
+<context>
+    <name>QObject</name>
+    <message>
+        <location filename="../main.cpp" line="114"/>
+        <source>Stack trace of previous crash:</source>
+        <translation>Stack trace de crash previo:</translation>
+    </message>
+    <message>
+        <location filename="../main.cpp" line="128"/>
+        <source>Don&apos;t show this message again</source>
+        <translation>No mostrar este mensaje de nuevo</translation>
+    </message>
+    <message>
+        <location filename="../main.cpp" line="131"/>
+        <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>
+        <translation>Cuando %1 se bloqueó por última vez, creó un stack trace.\nSe guardó un formulario legible en el disco y fue escrito en el log. Puedes acceder a el desde el dialogo de configuración.</translation>
+    </message>
+    <message>
+        <location filename="../pv/devicemanager.cpp" line="65"/>
+        <source>Cancel</source>
+        <translation>Cancelar</translation>
+    </message>
+    <message>
+        <location filename="../pv/devicemanager.cpp" line="96"/>
+        <source>Scanning for devices that driver %1 can access...</source>
+        <translation>Buscando dispositivos a los que el controlador %1 puede acceder...</translation>
+    </message>
+</context>
+<context>
+    <name>pv::MainWindow</name>
+    <message>
+        <location filename="../pv/mainwindow.cpp" line="70"/>
+        <source>PulseView</source>
+        <translation>PulseView</translation>
+    </message>
+    <message>
+        <location filename="../pv/mainwindow.cpp" line="279"/>
+        <source>Decoder Selector</source>
+        <translation>Selección de decoder</translation>
+    </message>
+    <message>
+        <location filename="../pv/mainwindow.cpp" line="332"/>
+        <source>Session %1</source>
+        <translation>Sesión %1</translation>
+    </message>
+    <message>
+        <location filename="../pv/mainwindow.cpp" line="514"/>
+        <source>Create New Session</source>
+        <translation>Crear Nueva Sesión</translation>
+    </message>
+    <message>
+        <location filename="../pv/mainwindow.cpp" line="520"/>
+        <source>Start/Stop Acquisition</source>
+        <translation>Iniciar/Detener Adquisición</translation>
+    </message>
+    <message>
+        <location filename="../pv/mainwindow.cpp" line="528"/>
+        <source>Settings</source>
+        <translation>Configuraciones</translation>
+    </message>
+    <message>
+        <location filename="../pv/mainwindow.cpp" line="580"/>
+        <source>Reload</source>
+        <translation>Recargar</translation>
+    </message>
+    <message>
+        <location filename="../pv/mainwindow.cpp" line="580"/>
+        <location filename="../pv/mainwindow.cpp" line="583"/>
+        <source>Run</source>
+        <translation>Ejecutar</translation>
+    </message>
+    <message>
+        <location filename="../pv/mainwindow.cpp" line="589"/>
+        <source>Stop</source>
+        <translation>Detener</translation>
+    </message>
+    <message>
+        <location filename="../pv/mainwindow.cpp" line="635"/>
+        <location filename="../pv/mainwindow.cpp" line="829"/>
+        <location filename="../pv/mainwindow.cpp" line="855"/>
+        <source>Confirmation</source>
+        <translation>Confirmación</translation>
+    </message>
+    <message>
+        <location filename="../pv/mainwindow.cpp" line="636"/>
+        <source>There is unsaved data. Close anyway?</source>
+        <translation>Hay datos sin guardar. Cerrar de todos modos?</translation>
+    </message>
+    <message>
+        <location filename="../pv/mainwindow.cpp" line="830"/>
+        <location filename="../pv/mainwindow.cpp" line="856"/>
+        <source>This session contains unsaved data. Close it anyway?</source>
+        <translation>Esta sesión contiene datos sin almacenar. Cerrar de todos modos?</translation>
+    </message>
+</context>
+<context>
+    <name>pv::Session</name>
+    <message>
+        <location filename="../pv/session.cpp" line="521"/>
+        <source>Failed to select device</source>
+        <translation>Error al seleccionar dispositivo</translation>
+    </message>
+    <message>
+        <location filename="../pv/session.cpp" line="578"/>
+        <source>Failed to open device</source>
+        <translation>Error al abrir dispositivo</translation>
+    </message>
+    <message>
+        <location filename="../pv/session.cpp" line="684"/>
+        <source>Error</source>
+        <translation>Error</translation>
+    </message>
+    <message>
+        <source>Unexpected input format: %s</source>
+        <translation type="vanished">Formato de entrada inesperado: %s</translation>
+    </message>
+    <message>
+        <location filename="../pv/session.cpp" line="685"/>
+        <source>Unexpected input format: %1</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../pv/session.cpp" line="720"/>
+        <source>Failed to load %1</source>
+        <translation>Error al cargar %1</translation>
+    </message>
+    <message>
+        <location filename="../pv/session.cpp" line="759"/>
+        <source>No active device set, can&apos;t start acquisition.</source>
+        <translation>No hay un dispositivo activo configurado, no se puede iniciar la adquisición.</translation>
+    </message>
+    <message>
+        <location filename="../pv/session.cpp" line="772"/>
+        <source>No channels enabled.</source>
+        <translation>No hay canales habilitados.</translation>
+    </message>
+    <message>
+        <location filename="../pv/session.cpp" line="1261"/>
+        <source>Out of memory, acquisition stopped.</source>
+        <translation>Sin memoria, la adquisición se detuvo.</translation>
+    </message>
+    <message>
+        <location filename="../pv/session.cpp" line="1467"/>
+        <source>Can&apos;t handle more than 64 logic channels.</source>
+        <translation>No puede manejar más de 64 canales lógicos.</translation>
+    </message>
+</context>
+<context>
+    <name>pv::StoreSession</name>
+    <message>
+        <location filename="../pv/storesession.cpp" line="114"/>
+        <source>Can&apos;t save logic channel without data.</source>
+        <translation>No se puede guardar el canal lógico sin datos.</translation>
+    </message>
+    <message>
+        <location filename="../pv/storesession.cpp" line="130"/>
+        <source>Can&apos;t save analog channel without data.</source>
+        <translation>No se puede guardar el canal analógico sin datos.</translation>
+    </message>
+    <message>
+        <location filename="../pv/storesession.cpp" line="142"/>
+        <source>No channels enabled.</source>
+        <translation>No hay canales habilitados.</translation>
+    </message>
+    <message>
+        <location filename="../pv/storesession.cpp" line="167"/>
+        <source>Can&apos;t save range without sample data.</source>
+        <translation>No se puede guardar el rango sin datos de muestra.</translation>
+    </message>
+    <message>
+        <location filename="../pv/storesession.cpp" line="188"/>
+        <location filename="../pv/storesession.cpp" line="295"/>
+        <source>Error while saving: </source>
+        <translation>Error al guardar: </translation>
+    </message>
+</context>
+<context>
+    <name>pv::binding::Device</name>
+    <message>
+        <location filename="../pv/binding/device.cpp" line="97"/>
+        <source>No Limit</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>pv::data::DecodeSignal</name>
+    <message>
+        <location filename="../pv/data/decodesignal.cpp" line="198"/>
+        <source>No decoders</source>
+        <translation>Sin decodificadores</translation>
+    </message>
+    <message>
+        <location filename="../pv/data/decodesignal.cpp" line="205"/>
+        <source>There are no channels assigned to this decoder</source>
+        <translation>No hay canales asignados a este decodificador</translation>
+    </message>
+    <message>
+        <location filename="../pv/data/decodesignal.cpp" line="219"/>
+        <source>One or more required channels have not been specified</source>
+        <translation>No se han especificado uno o más canales requeridos</translation>
+    </message>
+    <message>
+        <location filename="../pv/data/decodesignal.cpp" line="238"/>
+        <source>No input data</source>
+        <translation>Sin datos de entrada</translation>
+    </message>
+    <message>
+        <location filename="../pv/data/decodesignal.cpp" line="1172"/>
+        <source>Decoder reported an error</source>
+        <translation>El decodificador reportó un error</translation>
+    </message>
+    <message>
+        <location filename="../pv/data/decodesignal.cpp" line="1308"/>
+        <source>Failed to create decoder instance</source>
+        <translation>Error al crear la instancia del decodificador</translation>
+    </message>
+</context>
+<context>
+    <name>pv::data::SignalBase</name>
+    <message>
+        <location filename="../pv/data/signalbase.cpp" line="485"/>
+        <source>Signal average</source>
+        <translation>Promedio de la señal</translation>
+    </message>
+    <message>
+        <location filename="../pv/data/signalbase.cpp" line="486"/>
+        <source>0.9V (for 1.8V CMOS)</source>
+        <translation>0.9V (para 1.8V CMOS)</translation>
+    </message>
+    <message>
+        <location filename="../pv/data/signalbase.cpp" line="487"/>
+        <source>1.8V (for 3.3V CMOS)</source>
+        <translation>1.8V (para 3.3V CMOS)</translation>
+    </message>
+    <message>
+        <location filename="../pv/data/signalbase.cpp" line="488"/>
+        <source>2.5V (for 5.0V CMOS)</source>
+        <translation>2.5V (para 5.0V CMOS)</translation>
+    </message>
+    <message>
+        <location filename="../pv/data/signalbase.cpp" line="489"/>
+        <source>1.5V (for TTL)</source>
+        <translation>1.5V (para TTL)</translation>
+    </message>
+    <message>
+        <location filename="../pv/data/signalbase.cpp" line="494"/>
+        <source>Signal average +/- 15%</source>
+        <translation>Promedio de la señal +/- 15%</translation>
+    </message>
+    <message>
+        <location filename="../pv/data/signalbase.cpp" line="495"/>
+        <source>0.3V/1.2V (for 1.8V CMOS)</source>
+        <translation>0.3V/1.2V (para 1.8V CMOS)</translation>
+    </message>
+    <message>
+        <location filename="../pv/data/signalbase.cpp" line="496"/>
+        <source>0.7V/2.5V (for 3.3V CMOS)</source>
+        <translation>0.7V/2.5V (para 3.3V CMOS)</translation>
+    </message>
+    <message>
+        <location filename="../pv/data/signalbase.cpp" line="497"/>
+        <source>1.3V/3.7V (for 5.0V CMOS)</source>
+        <translation>1.3V/3.7V (para 5.0V CMOS)</translation>
+    </message>
+    <message>
+        <location filename="../pv/data/signalbase.cpp" line="498"/>
+        <source>0.8V/2.0V (for TTL)</source>
+        <translation>0.8V/2.0V (para TTL)</translation>
+    </message>
+</context>
+<context>
+    <name>pv::dialogs::Connect</name>
+    <message>
+        <location filename="../pv/dialogs/connect.cpp" line="58"/>
+        <source>&amp;Scan for devices using driver above</source>
+        <translation>E&amp;Scanea por dispositivos utilizando el driver de arriba</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/connect.cpp" line="63"/>
+        <source>Connect to Device</source>
+        <translation>Conectarse al dispositivo</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/connect.cpp" line="75"/>
+        <source>Step 1: Choose the driver</source>
+        <translation>Paso 1: Elige el driver</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/connect.cpp" line="79"/>
+        <source>&amp;USB</source>
+        <translation>&amp;USB</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/connect.cpp" line="80"/>
+        <source>Serial &amp;Port</source>
+        <translation>&amp;Puerto Serial</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/connect.cpp" line="81"/>
+        <source>&amp;TCP/IP</source>
+        <translation>&amp;TCP/IP</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/connect.cpp" line="116"/>
+        <source>Protocol:</source>
+        <translation>Protocolo:</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/connect.cpp" line="134"/>
+        <source>Step 2: Choose the interface</source>
+        <translation>Paso 2: Elige la interfaz</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/connect.cpp" line="140"/>
+        <source>Step 3: Scan for devices</source>
+        <translation>Paso 3: Escanear por dispositivos</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/connect.cpp" line="146"/>
+        <source>Step 4: Select the device</source>
+        <translation>Paso 4: Selecciona el dispositivo</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>General</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="143"/>
+        <source>Views</source>
+        <translation>Vistas</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="153"/>
+        <location filename="../pv/dialogs/settings.cpp" line="397"/>
+        <source>Decoders</source>
+        <translation>Decodificadores</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="163"/>
+        <source>About</source>
+        <translation>Acerca de</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="172"/>
+        <source>Logging</source>
+        <translation>Logging</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="236"/>
+        <source>User interface language</source>
+        <translation>Lenguaje de la interfaz de usuario</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="247"/>
+        <source>User interface theme</source>
+        <translation>Tema de la interfaz de usuario</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>(Es posible que deba reiniciar PulseView para que se actualicen todos los elementos de la interfaz de usuario)</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="255"/>
+        <source>System Default</source>
+        <translation>Default del sistema</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="268"/>
+        <source>Qt widget style</source>
+        <translation>Estilo de Qt widget</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="270"/>
+        <source>(Dark themes look best with the Fusion style)</source>
+        <translation>(Los temas oscuros se ven mejor con el estilo Fusion)</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="277"/>
+        <source>Save session &amp;setup along with .sr file</source>
+        <translation>Guardar &amp;configuración de sesión junto al archivo .sr</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="291"/>
+        <source>Trace View</source>
+        <translation>Vista de trazo</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="299"/>
+        <source>Use colored trace &amp;background</source>
+        <translation>Use &amp;fondo de trazas coloreado</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="303"/>
+        <source>Constantly perform &amp;zoom-to-fit during acquisition</source>
+        <translation>Realizar constantemente zoom para &amp;ajustar durante la adquisición</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="307"/>
+        <source>Perform a zoom-to-&amp;fit when acquisition stops</source>
+        <translation>Realice un zoom para ajustar cuando la adquisición se &amp;detenga</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="311"/>
+        <source>Show time zero at the trigger</source>
+        <translation>Mostrar el tiempo cero en el trigger</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="315"/>
+        <source>Always keep &amp;newest samples at the right edge during capture</source>
+        <translation>Mantenga siempre las muestras más &amp;recientes en el borde derecho durante la captura</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="319"/>
+        <source>Show data &amp;sampling points</source>
+        <translation>Mostrar puntos de datos &amp;sampleados</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="323"/>
+        <source>Fill high areas of logic signals</source>
+        <translation>Rellenar áreas altas de señales lógicas</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="330"/>
+        <source>Color to fill high areas of logic signals with</source>
+        <translation>Color para llenar áreas altas de señales lógicas</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="334"/>
+        <source>Show analog minor grid in addition to div grid</source>
+        <translation>Mostrar cuadrícula menor analogíca además de cuadrícula por div</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="338"/>
+        <source>Highlight mouse cursor using a vertical marker line</source>
+        <translation>Resalte el cursor del mouse usando una línea de marcador vertical</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> píxeles</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="347"/>
+        <source>Maximum distance from edges before markers snap to them</source>
+        <translation>Distancia máxima desde los bordes antes de que los marcadores se ajusten a ellos</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="354"/>
+        <source>Color to fill cursor area with</source>
+        <translation>Color para llenar el área del cursor</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="357"/>
+        <source>None</source>
+        <translation>Ninguna</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="358"/>
+        <source>Background</source>
+        <translation>Fondo</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="359"/>
+        <source>Dots</source>
+        <translation>Puntos</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="364"/>
+        <source>Conversion threshold display mode (analog traces only)</source>
+        <translation>Modo de visualización del umbral de conversión (solo trazas analógicas)</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="373"/>
+        <source>Default analog trace div height</source>
+        <translation>Altura de div de trazo análogo por defecto</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="382"/>
+        <source>Default logic trace height</source>
+        <translation>Altura de trazo lógico por defecto</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="405"/>
+        <source>Allow configuration of &amp;initial signal state</source>
+        <translation>Permitir configuración de estado de señal &amp;inicial</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="409"/>
+        <source>Always show all &amp;rows, even if no annotation is visible</source>
+        <translation>Mostrar siempre todas las &amp;filas, incluso si no hay ninguna anotación visible</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="417"/>
+        <source>Annotation export format</source>
+        <translation>Formato de exportación de anotaciones</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 = rango de muestra; %d: nombre del decodificador; %r: nombre de fila; %c: nombre de clase</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: texto de anotación más largo; %a: todos los textos de anotación; %q: use comillas</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="441"/>
+        <source>%1&lt;br /&gt;&lt;a href=&quot;http://%2&quot;&gt;%2&lt;/a&gt;</source>
+        <translation>%1&lt;br /&gt;&lt;a href=&quot;http://%2&quot;&gt;%2&lt;/a&gt;</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="442"/>
+        <source>GNU GPL, version 3 or later</source>
+        <translation>GNU GPL, versión 3 o posterior</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="453"/>
+        <source>Versions, libraries and features:</source>
+        <translation>Versiones, librerías y características:</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="460"/>
+        <source>Firmware search paths:</source>
+        <translation>Rutas de búsqueda de firmware:</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="467"/>
+        <source>Protocol decoder search paths:</source>
+        <translation>Ruta de búsqueda del decodificador de protocolo:</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="474"/>
+        <source>Supported hardware drivers:</source>
+        <translation>Drivers de hardware soportados:</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="481"/>
+        <source>Supported input formats:</source>
+        <translation>Formatos de entrada soportados:</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="488"/>
+        <source>Supported output formats:</source>
+        <translation>Formatos de salida soportados:</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="496"/>
+        <source>Supported protocol decoders:</source>
+        <translation>Decodificadores de protocolo soportados:</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="504"/>
+        <source>Available Translations:</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="552"/>
+        <source>Log level:</source>
+        <translation>Nivel de log:</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="557"/>
+        <source> lines</source>
+        <translation> líneas</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="566"/>
+        <source>Length of background buffer:</source>
+        <translation>Longitud del búfer de fondo:</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="572"/>
+        <source>&amp;Save to File</source>
+        <translation>&amp;Guardar en archivo</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="579"/>
+        <source>&amp;Pop out</source>
+        <translation>&amp;Pop out</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="648"/>
+        <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>Seleccionaste el tema obscuro.\nDebería de establecer los colores ajustables por el usuario que mejor se ajustan a tu elección?\n\nPor favor ten en cuenta que Pulseview tal vez se tenga que reiniciar para mostrar correctamente.</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="654"/>
+        <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>Seleccionaste el tema brillante.\nDebería de establecer los colores ajustables por el usuario que mejor se ajustan a tu elección?\n\nPor favor ten en cuenta que Pulseview tal vez se tenga que reiniciar para mostrar correctamente.</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="807"/>
+        <source>Save Log</source>
+        <translation>Guardar log</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="807"/>
+        <source>Log Files (*.txt *.log);;All Files (*)</source>
+        <translation>Archivos de log (*.txt *.log);;Todos los archivos (*)</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="819"/>
+        <source>Success</source>
+        <translation>Éxito</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="819"/>
+        <source>Log saved to %1.</source>
+        <translation>Log guardado en %1.</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="829"/>
+        <source>Error</source>
+        <translation>Error</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="829"/>
+        <source>File %1 could not be written to.</source>
+        <translation>No se pudo escribir en el archivo%1.</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/settings.cpp" line="843"/>
+        <source>%1 Log</source>
+        <translation>%1 Log</translation>
+    </message>
+</context>
+<context>
+    <name>pv::dialogs::StoreProgress</name>
+    <message>
+        <location filename="../pv/dialogs/storeprogress.cpp" line="44"/>
+        <source>Saving...</source>
+        <translation>Guardando...</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/storeprogress.cpp" line="44"/>
+        <source>Cancel</source>
+        <translation>Cancelar</translation>
+    </message>
+    <message>
+        <location filename="../pv/dialogs/storeprogress.cpp" line="85"/>
+        <source>Failed to save session.</source>
+        <translation>Error al guardar sesión.</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>Todo</translation>
+    </message>
+    <message>
+        <location filename="../pv/popups/channels.cpp" line="64"/>
+        <location filename="../pv/popups/channels.cpp" line="65"/>
+        <source>Logic</source>
+        <translation>Lógico</translation>
+    </message>
+    <message>
+        <location filename="../pv/popups/channels.cpp" line="66"/>
+        <location filename="../pv/popups/channels.cpp" line="67"/>
+        <source>Analog</source>
+        <translation>Análogo</translation>
+    </message>
+    <message>
+        <location filename="../pv/popups/channels.cpp" line="68"/>
+        <source>Named</source>
+        <translation>Nombrado</translation>
+    </message>
+    <message>
+        <location filename="../pv/popups/channels.cpp" line="69"/>
+        <source>Unnamed</source>
+        <translation>Sin nombre</translation>
+    </message>
+    <message>
+        <location filename="../pv/popups/channels.cpp" line="70"/>
+        <source>Changing</source>
+        <translation>Cambiando</translation>
+    </message>
+    <message>
+        <location filename="../pv/popups/channels.cpp" line="71"/>
+        <source>Non-changing</source>
+        <translation>Sin cambios</translation>
+    </message>
+    <message>
+        <location filename="../pv/popups/channels.cpp" line="141"/>
+        <source>Disable: </source>
+        <translation>Deshabilitar: </translation>
+    </message>
+    <message>
+        <location filename="../pv/popups/channels.cpp" line="149"/>
+        <source>Enable: </source>
+        <translation>Habilitar: </translation>
+    </message>
+    <message>
+        <location filename="../pv/popups/channels.cpp" line="281"/>
+        <location filename="../pv/popups/channels.cpp" line="301"/>
+        <source>None</source>
+        <translation>Ninguna</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>
+        <translation>La consulta de la clave de configuración %1 resultó en %2</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>
+        <translation>La consulta de la clave de configuración %1 resultó en %2</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>
+        <translation>La consulta de la clave de configuración %1 resultó en %2</translation>
+    </message>
+</context>
+<context>
+    <name>pv::prop::Int</name>
+    <message>
+        <location filename="../pv/prop/int.cpp" line="63"/>
+        <location filename="../pv/prop/int.cpp" line="127"/>
+        <source>Querying config key %1 resulted in %2</source>
+        <translation>La consulta de la clave de configuración %1 resultó en %2</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>
+        <translation>La consulta de la clave de configuración %1 resultó en %2</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>Decoder</translation>
+    </message>
+    <message>
+        <location filename="../pv/subwindows/decoder_selector/model.cpp" line="41"/>
+        <source>Name</source>
+        <translation>Nombre</translation>
+    </message>
+    <message>
+        <location filename="../pv/subwindows/decoder_selector/model.cpp" line="42"/>
+        <source>ID</source>
+        <translation>ID</translation>
+    </message>
+    <message>
+        <location filename="../pv/subwindows/decoder_selector/model.cpp" line="49"/>
+        <source>All Decoders</source>
+        <translation>Todos los decoders</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>Seleccione un decoder para ver su descripción aquí.</translation>
+    </message>
+    <message>
+        <location filename="../pv/subwindows/decoder_selector/subwindow.cpp" line="247"/>
+        <source>, %1</source>
+        <translation>, %1</translation>
+    </message>
+    <message>
+        <location filename="../pv/subwindows/decoder_selector/subwindow.cpp" line="264"/>
+        <source>&lt;p align=&apos;right&apos;&gt;Tags: %1&lt;/p&gt;</source>
+        <translation>&lt;p align=&apos;right&apos;&gt;Tags: %1&lt;/p&gt;</translation>
+    </message>
+    <message>
+        <location filename="../pv/subwindows/decoder_selector/subwindow.cpp" line="311"/>
+        <source>Protocol decoder &lt;b&gt;%1&lt;/b&gt; requires input type &lt;b&gt;%2&lt;/b&gt; which several decoders provide.&lt;br&gt;Choose which one to use:&lt;br&gt;</source>
+        <translation>Decodificador de protocolo &lt;b&gt;%1&lt;/b&gt; requiere tipo de entrada &lt;b&gt;%2&lt;/b&gt; que proporcionan varios decodificadores.&lt;br&gt;Elige cúal usar:&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../pv/subwindows/decoder_selector/subwindow.cpp" line="319"/>
+        <source>Choose Decoder</source>
+        <translation>Elige Decoder</translation>
+    </message>
+</context>
+<context>
+    <name>pv::toolbars::MainBar</name>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="121"/>
+        <source>New &amp;View</source>
+        <translation>Nueva &amp;Vista</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="127"/>
+        <source>&amp;Open...</source>
+        <translation>&amp;Abrir...</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="134"/>
+        <source>Restore Session Setu&amp;p...</source>
+        <translation>Restaurar Configu&amp;ración de Sesión...</translation>
+    </message>
+    <message>
+        <source>&amp;Save As...</source>
+        <translation type="vanished">G&amp;uardar Como...</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="138"/>
+        <source>&amp;Save...</source>
+        <translation type="unfinished">&amp;Guardar...</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="145"/>
+        <source>Save &amp;As...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="151"/>
+        <source>Save Selected &amp;Range As...</source>
+        <translation>Guardar &amp;Rango Seleccionado Como...</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="158"/>
+        <source>Save Session Setu&amp;p...</source>
+        <translation>Guardar Confi&amp;guración de Sesión...</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="164"/>
+        <source>&amp;Export</source>
+        <translation>&amp;Exporta</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="170"/>
+        <source>&amp;Import</source>
+        <translation>&amp;Importa</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="174"/>
+        <source>&amp;Connect to Device...</source>
+        <translation>&amp;Conecta a Dispositivo...</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="236"/>
+        <source>Add protocol decoder</source>
+        <translation>Agregar decodificador de protocolo</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="252"/>
+        <source>Configure Device</source>
+        <translation>Configura Dispositivo</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="256"/>
+        <source>Configure Channels</source>
+        <translation>Configura Canales</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="370"/>
+        <source>Failed to get sample rate list:</source>
+        <translation>Error al obtener la lista de frecuencia de muestreo:</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="433"/>
+        <source>Failed to get sample rate:</source>
+        <translation>Error al obtener la lista de frecuencia de muestreo:</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="474"/>
+        <source>Failed to get sample limit list:</source>
+        <translation>Error al obtener la lista de límites de muestra:</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="564"/>
+        <source>Failed to configure samplerate:</source>
+        <translation>Error al configurar frecuencia de muestreo:</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="591"/>
+        <source>Failed to configure sample count:</source>
+        <translation>Error al configurar cuenta de muestras:</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="629"/>
+        <source>Missing Cursors</source>
+        <translation>Cursores Faltantes</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="629"/>
+        <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>Debe configurar los cursores antes de poder guardar los datos encerrados en un archivo de sesión (por ejemplo, usando el botón Mostrar Cursores).</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="647"/>
+        <source>Invalid Range</source>
+        <translation>Rango Inválido</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="647"/>
+        <source>The cursors don&apos;t define a valid range of samples.</source>
+        <translation>Los cursores no definen un rango válido de muestras.</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="659"/>
+        <source>%1 files </source>
+        <translation>%1 archivos </translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="667"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="717"/>
+        <source>All Files</source>
+        <translation>Todos los archivos</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="671"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="848"/>
+        <source>Save File</source>
+        <translation>Guardar Archivo</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="683"/>
+        <source>Export %1</source>
+        <translation>Exporta %1</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="714"/>
+        <source>%1 files</source>
+        <translation>%1 archivos</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="725"/>
+        <source>Import File</source>
+        <translation>Importa Archivo</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="734"/>
+        <source>Import %1</source>
+        <translation>Importa %1</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="807"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="865"/>
+        <source>Open File</source>
+        <translation>Abrir Archivo</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="807"/>
+        <source>sigrok Sessions (*.sr);;All Files (*)</source>
+        <translation>Sesiones sigrok (*sr);;Todos los archivos (*)</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="848"/>
+        <location filename="../pv/toolbars/mainbar.cpp" line="865"/>
+        <source>PulseView Session Setups (*.pvs);;All Files (*)</source>
+        <translation>Configurciones de Sesión de PulseView (*.pvs);;Todos los Archivos (*)</translation>
+    </message>
+    <message>
+        <location filename="../pv/toolbars/mainbar.cpp" line="926"/>
+        <source>Total sampling time: %1</source>
+        <translation>Tiempo de muestreo total: %1</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>Decodificador:</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/decoder_binary/view.cpp" line="87"/>
+        <source>Show data as</source>
+        <translation>Muestra datos como</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/decoder_binary/view.cpp" line="93"/>
+        <source>Hexdump</source>
+        <translation>Hexdump</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/decoder_binary/view.cpp" line="110"/>
+        <source>&amp;Save...</source>
+        <translation>&amp;Guardar...</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>Guardar Datos Binarios</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/decoder_binary/view.cpp" line="258"/>
+        <source>Binary Data Files (*.bin);;All Files (*)</source>
+        <translation>Archivos de Datos Binarios (*.txt);;Todos los archivos (*)</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>Error</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>No se pudo escribir en el archivo%1.</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/decoder_binary/view.cpp" line="298"/>
+        <source>Hex Dumps (*.txt);;All Files (*)</source>
+        <translation>Hex Dumps (*.txt);;Todos los archivos (*)</translation>
+    </message>
+</context>
+<context>
+    <name>pv::views::tabular_decoder::AnnotationCollectionModel</name>
+    <message>
+        <location filename="../pv/views/tabular_decoder/model.cpp" line="56"/>
+        <source>Sample</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/model.cpp" line="57"/>
+        <source>Time</source>
+        <translation type="unfinished">Tiempo</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/model.cpp" line="58"/>
+        <source>Decoder</source>
+        <translation type="unfinished">Decoder</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/model.cpp" line="59"/>
+        <source>Ann Row</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/model.cpp" line="60"/>
+        <source>Ann Class</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/model.cpp" line="61"/>
+        <source>Value</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/model.cpp" line="83"/>
+        <source>s</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/model.cpp" line="83"/>
+        <source>sa</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>pv::views::tabular_decoder::View</name>
+    <message>
+        <location filename="../pv/views/tabular_decoder/view.cpp" line="176"/>
+        <source>Decoder:</source>
+        <translation type="unfinished">Decodificador:</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/view.cpp" line="198"/>
+        <source>Hide Hidden Rows/Classes</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/view.cpp" line="202"/>
+        <source>&amp;Save...</source>
+        <translation type="unfinished">&amp;Guardar...</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/view.cpp" line="374"/>
+        <source>Save Annotations as CSV</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/view.cpp" line="374"/>
+        <source>CSV Files (*.csv);;Text Files (*.txt);;All Files (*)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/view.cpp" line="442"/>
+        <source>Error</source>
+        <translation type="unfinished">Error</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/tabular_decoder/view.cpp" line="442"/>
+        <source>File %1 could not be written to.</source>
+        <translation type="unfinished">No se pudo escribir en el archivo%1.</translation>
+    </message>
+</context>
+<context>
+    <name>pv::views::trace::AnalogSignal</name>
+    <message>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="994"/>
+        <source>Number of pos vertical divs</source>
+        <translation>Número de divisiones verticales pos</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1001"/>
+        <source>Number of neg vertical divs</source>
+        <translation>Número de divisiones verticales neg</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1006"/>
+        <source> pixels</source>
+        <translation> píxeles</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1010"/>
+        <source>Div height</source>
+        <translation>Altura de div</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1027"/>
+        <source>V/div</source>
+        <translation>V/div</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1031"/>
+        <source>Vertical resolution</source>
+        <translation>Resolución vertical</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1040"/>
+        <source>Autoranging</source>
+        <translation>Autorango</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1045"/>
+        <source>none</source>
+        <translation>ninguna</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1047"/>
+        <source>to logic via threshold</source>
+        <translation>a nivel lógico a partir de umbral</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1049"/>
+        <source>to logic via schmitt-trigger</source>
+        <translation>a nivel lógico a partir de schmitt trigger</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1055"/>
+        <source>Conversion</source>
+        <translation>Conversión</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1064"/>
+        <source>Conversion threshold(s)</source>
+        <translation>Umbral(es) de conversión</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1074"/>
+        <source>analog</source>
+        <translation>análogo</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1075"/>
+        <source>converted</source>
+        <translation>convertida</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1076"/>
+        <source>analog+converted</source>
+        <translation>Analógico+convertido</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/analogsignal.cpp" line="1081"/>
+        <source>Show traces for</source>
+        <translation>Mostrar trazos para</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>Deshabilita snapping</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>Muestra intervalo</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/cursorpair.cpp" line="140"/>
+        <source>Display frequency</source>
+        <translation>Muestra frecuencia</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/cursorpair.cpp" line="152"/>
+        <source>Display samples</source>
+        <translation>Muestra samples</translation>
+    </message>
+</context>
+<context>
+    <name>pv::views::trace::DecodeTrace</name>
+    <message>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="449"/>
+        <source>&lt;p&gt;&lt;i&gt;No decoders in the stack&lt;/i&gt;&lt;/p&gt;</source>
+        <translation>&lt;p&gt;&lt;i&gt;No hay decodificadores en el stack.&lt;/i&gt;&lt;/p&gt;</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="460"/>
+        <source>&lt;i&gt;* Required channels&lt;/i&gt;</source>
+        <translation>&lt;i&gt;* Canales requeridos&lt;/i&gt;</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="464"/>
+        <source>Stack Decoder</source>
+        <translation>Decodificadores</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="465"/>
+        <source>Stack a higher-level decoder on top of this one</source>
+        <translation>Apilar un decodificador de nivel superior encima de este</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="479"/>
+        <source>Delete</source>
+        <translation>Eliminar</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="521"/>
+        <source>Resume decoding</source>
+        <translation>Reanudar decodificación</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="528"/>
+        <source>Pause decoding</source>
+        <translation>Pausar decodificación</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="536"/>
+        <source>Copy annotation text to clipboard</source>
+        <translation>Copiar texto de anotación al clipboard</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="545"/>
+        <source>Export all annotations</source>
+        <translation>Exportar todas las anotaciones</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="552"/>
+        <source>Export all annotations for this row</source>
+        <translation>Exportar todas las anotaciones para esta fila</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="561"/>
+        <source>Export all annotations, starting here</source>
+        <translation>Exportar todas las anotaciones, comenzando aquí</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="568"/>
+        <source>Export annotations for this row, starting here</source>
+        <translation>Exportar todas las anotaciones para esta fila, comenzando aquí</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="577"/>
+        <source>Export all annotations within cursor range</source>
+        <translation>Exportar todas las anotaciones dentro del rango del cursor</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="584"/>
+        <source>Export annotations for this row within cursor range</source>
+        <translation>Exportar todas las anotaciones para esta fila dentro del rango del cursor</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="1076"/>
+        <source>%1:
+%2</source>
+        <translation>%1\n%2</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="1120"/>
+        <source>&lt;b&gt;%1&lt;/b&gt; (%2) %3</source>
+        <translation>&lt;b&gt;%1&lt;/b&gt; (%2) %3</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="1190"/>
+        <source>Export annotations</source>
+        <translation>Exportar anotaciones</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="1190"/>
+        <source>Text Files (*.txt);;All Files (*)</source>
+        <translation>Archivos de texto (*.txt *.log);;Todos los archivos (*)</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="1255"/>
+        <source>Error</source>
+        <translation>Error</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="1255"/>
+        <source>File %1 could not be written to.</source>
+        <translation>No se pudo escribir en el archivo%1.</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="1308"/>
+        <source>Show this row</source>
+        <translation>Muestra esta fila</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="1319"/>
+        <source>Show All</source>
+        <translation>Muestra todo</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/decodetrace.cpp" line="1327"/>
+        <source>Hide All</source>
+        <translation>Oculta todo</translation>
+    </message>
+</context>
+<context>
+    <name>pv::views::trace::Flag</name>
+    <message>
+        <location filename="../pv/views/trace/flag.cpp" line="132"/>
+        <source>Text</source>
+        <translation>Texto</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/flag.cpp" line="141"/>
+        <source>Delete</source>
+        <translation>Eliminar</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/flag.cpp" line="146"/>
+        <source>Disable snapping</source>
+        <translation>Deshabilita snapping</translation>
+    </message>
+</context>
+<context>
+    <name>pv::views::trace::Header</name>
+    <message>
+        <location filename="../pv/views/trace/header.cpp" line="137"/>
+        <source>Group</source>
+        <translation>Grupo</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>Sin trigger</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/logicsignal.cpp" line="456"/>
+        <source>Trigger on rising edge</source>
+        <translation>Trigger en flanco de subida</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/logicsignal.cpp" line="461"/>
+        <source>Trigger on high level</source>
+        <translation>Trigger en nivel alto</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/logicsignal.cpp" line="466"/>
+        <source>Trigger on falling edge</source>
+        <translation>Trigger en flanco de bajada</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/logicsignal.cpp" line="471"/>
+        <source>Trigger on low level</source>
+        <translation>Trigger en nivel bajo</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/logicsignal.cpp" line="476"/>
+        <source>Trigger on rising or falling edge</source>
+        <translation>Trigger en flanco de subida o bajada</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/logicsignal.cpp" line="563"/>
+        <source> pixels</source>
+        <translation> pixeles</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/logicsignal.cpp" line="567"/>
+        <source>Trace height</source>
+        <translation>Altura del trazo</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>Crear marcador aqui</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/ruler.cpp" line="157"/>
+        <source>Set as zero point</source>
+        <translation>Establecer como punto cero</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/ruler.cpp" line="162"/>
+        <source>Reset zero point</source>
+        <translation>Reiniciar punto cero</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/ruler.cpp" line="175"/>
+        <source>Disable mouse hover marker</source>
+        <translation>Deshabilitar marcador de desplazamiento del mouse</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/ruler.cpp" line="175"/>
+        <source>Enable mouse hover marker</source>
+        <translation>Habilitar marcador de desplazamiento del mouse</translation>
+    </message>
+</context>
+<context>
+    <name>pv::views::trace::Signal</name>
+    <message>
+        <location filename="../pv/views/trace/signal.cpp" line="153"/>
+        <source>Name</source>
+        <translation>Nombre</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/signal.cpp" line="164"/>
+        <source>Disable</source>
+        <translation>Deshabilitar</translation>
+    </message>
+</context>
+<context>
+    <name>pv::views::trace::StandardBar</name>
+    <message>
+        <location filename="../pv/views/trace/standardbar.cpp" line="54"/>
+        <source>Zoom &amp;In</source>
+        <translation>&amp;Acercar</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/standardbar.cpp" line="62"/>
+        <source>Zoom &amp;Out</source>
+        <translation>A&amp;lejar</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/standardbar.cpp" line="70"/>
+        <source>Zoom to &amp;Fit</source>
+        <translation>Hacer Zoom para &amp;Encajar</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/standardbar.cpp" line="82"/>
+        <source>Show &amp;Cursors</source>
+        <translation>Mostrar &amp;Cursores</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/standardbar.cpp" line="85"/>
+        <source>Display last segment only</source>
+        <translation>Mostrar solo el último segmento</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/standardbar.cpp" line="90"/>
+        <source>Display last complete segment only</source>
+        <translation>Mostrar solo el último segmento completo</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/standardbar.cpp" line="95"/>
+        <source>Display a single segment</source>
+        <translation>Mostrar solo un segmento</translation>
+    </message>
+</context>
+<context>
+    <name>pv::views::trace::TimeMarker</name>
+    <message>
+        <location filename="../pv/views/trace/timemarker.cpp" line="191"/>
+        <source>Time</source>
+        <translation>Tiempo</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>Crear marcador aqui</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/trace.cpp" line="315"/>
+        <source>Color</source>
+        <translation>Color</translation>
+    </message>
+    <message>
+        <location filename="../pv/views/trace/trace.cpp" line="380"/>
+        <source>Name</source>
+        <translation>Nombre</translation>
+    </message>
+</context>
+<context>
+    <name>pv::views::trace::TraceGroup</name>
+    <message>
+        <location filename="../pv/views/trace/tracegroup.cpp" line="140"/>
+        <source>Ungroup</source>
+        <translation>Desagrupar</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>Mostrar / ocultar este trazo de decodificador</translation>
+    </message>
+    <message>
+        <location filename="../pv/widgets/decodergroupbox.cpp" line="58"/>
+        <source>Delete this decoder trace</source>
+        <translation>Eliminar este trazo de decodificador</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>&lt;No Device&gt;</source>
+        <translation>&lt;Sin dispositivo&gt;</translation>
+    </message>
+</context>
+<context>
+    <name>pv::widgets::ExportMenu</name>
+    <message>
+        <location filename="../pv/widgets/exportmenu.cpp" line="71"/>
+        <source>Export %1...</source>
+        <translation>Exporta %1...</translation>
+    </message>
+</context>
+<context>
+    <name>pv::widgets::ImportMenu</name>
+    <message>
+        <location filename="../pv/widgets/importmenu.cpp" line="68"/>
+        <source>Import %1...</source>
+        <translation>Importa %1...</translation>
+    </message>
+</context>
+</TS>
index b0e631f6382cbd4697d2171e30ebab8620065989..a307159c5005f92dfe26212e1c7f90fd1e7ca898 100644 (file)
--- a/main.cpp
+++ b/main.cpp
@@ -278,6 +278,7 @@ int main(int argc, char *argv[])
        qRegisterMetaType<uint64_t>("uint64_t");
        qRegisterMetaType<pv::util::Timestamp>("util::Timestamp");
        qRegisterMetaType<SharedPtrToSegment>("SharedPtrToSegment");
+       qRegisterMetaType<shared_ptr<pv::data::SignalBase>>("shared_ptr<SignalBase>");
 
        // Prepare the global settings since logging needs them early on
        pv::GlobalSettings settings;
index 4427a759cf3266060af90815b94ee6dfadebd908..706fe8d03274a355b73dc91e209f5814b060ab89 100644 (file)
@@ -1,6 +1,7 @@
 <RCC>
     <qresource prefix="/">
        <file>icons/add-decoder.svg</file>
+       <file>icons/add-math-signal.svg</file>
        <file>icons/application-exit.png</file>
        <file>icons/channels.svg</file>
        <file>icons/decoder-delete.svg</file>
@@ -9,6 +10,7 @@
        <file>icons/document-new.png</file>
        <file>icons/document-open.png</file>
        <file>icons/document-save-as.png</file>
+       <file>icons/math.svg</file>
        <file>icons/edit-paste.svg</file>
        <file>icons/help-browser.png</file>
        <file>icons/information.svg</file>
index 6f666c5c308762c55ea0990297f1ea9fea1536eb..3a0ac0f13a5dc7986ca737615d9858998f775055 100644 (file)
@@ -36,6 +36,8 @@
 #include <libsigrokdecode/libsigrokdecode.h>
 #endif
 
+#include <pv/exprtk.hpp>
+
 #include "application.hpp"
 #include "config.h"
 #include "globalsettings.hpp"
@@ -65,7 +67,7 @@ Application::Application(int &argc, char* argv[]) :
        setOrganizationDomain("sigrok.org");
 }
 
-QStringList Application::get_languages()
+const QStringList Application::get_languages() const
 {
        QStringList files = QDir(":/l10n/").entryList(QStringList("*.qm"), QDir::Files);
 
@@ -81,6 +83,14 @@ QStringList Application::get_languages()
        return result;
 }
 
+const QString Application::get_language_editors(const QString& language) const
+{
+       if (language == "de") return "Sören Apel, Uwe Hermann";
+       if (language == "es_mx") return "Carlos Diaz";
+
+       return QString();
+}
+
 void Application::switch_language(const QString& language)
 {
        removeTranslator(&app_translator_);
@@ -140,6 +150,7 @@ void Application::collect_version_info(shared_ptr<sigrok::Context> context)
        version_info_.emplace_back("Qt", qVersion());
        version_info_.emplace_back("glibmm", PV_GLIBMM_VERSION);
        version_info_.emplace_back("Boost", BOOST_LIB_VERSION);
+       version_info_.emplace_back("exprtk", QString::fromUtf8(exprtk::information::date));
 
        version_info_.emplace_back("libsigrok", QString("%1/%2 (rt: %3/%4)")
                .arg(SR_PACKAGE_VERSION_STRING, SR_LIB_VERSION_STRING,
index 61fe46e2480dc855210e586230c2e6960e070101..75ddbc0603a5b62936caa028def364dd85b38fa7 100644 (file)
@@ -41,8 +41,10 @@ class Application : public QApplication, public pv::GlobalSettingsInterface
 public:
        Application(int &argc, char* argv[]);
 
-       QStringList get_languages();
+       const QStringList get_languages() const;
+       const QString get_language_editors(const QString& language) const;
        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);
index 37c9d43d55248218b4e7e247cd6173fa1696b664..96217051f8433819cb9e0b575d8be28cc5a7e242 100644 (file)
@@ -61,10 +61,6 @@ Device::Device(shared_ptr<sigrok::Configurable> configurable) :
 
                auto capabilities = configurable->config_capabilities(key);
 
-               if (!capabilities.count(Capability::GET) ||
-                       !capabilities.count(Capability::SET))
-                       continue;
-
                string name_str;
                try {
                        name_str = key->description();
@@ -74,6 +70,13 @@ Device::Device(shared_ptr<sigrok::Configurable> configurable) :
 
                const QString name = QString::fromStdString(name_str);
 
+               if (!capabilities.count(Capability::GET) ||
+                       !capabilities.count(Capability::SET)) {
+                       qDebug() << QString(tr("Note for device developers: Ignoring device configuration capability '%1' " \
+                               "as it is missing GET and/or SET")).arg(name);
+                       continue;
+               }
+
                const Property::Getter get = [&, key]() {
                        return configurable_->config_get(key); };
                const Property::Setter set = [&, key](Glib::VariantBase value) {
@@ -91,6 +94,12 @@ Device::Device(shared_ptr<sigrok::Configurable> configurable) :
                        bind_int(name, "", "%", pair<int64_t, int64_t>(0, 100), get, set);
                        break;
 
+               case SR_CONF_LIMIT_FRAMES:
+                       // Value 0 means that there is no limit
+                       bind_int(name, "", "", pair<int64_t, int64_t>(0, 1000000), get, set,
+                               tr("No Limit"));
+                       break;
+
                case SR_CONF_PATTERN_MODE:
                case SR_CONF_BUFFERSIZE:
                case SR_CONF_TRIGGER_SOURCE:
@@ -178,13 +187,13 @@ void Device::bind_enum(const QString &name, const QString &desc,
 }
 
 void Device::bind_int(const QString &name, const QString &desc, QString suffix,
-       optional< pair<int64_t, int64_t> > range,
-       Property::Getter getter, Property::Setter setter)
+       optional< pair<int64_t, int64_t> > range, Property::Getter getter,
+       Property::Setter setter, QString special_value_text)
 {
        assert(configurable_);
 
        properties_.push_back(shared_ptr<Property>(new Int(name, desc, suffix,
-               range, getter, setter)));
+               range, getter, setter, special_value_text)));
 }
 
 QString Device::print_timebase(Glib::VariantBase gvar)
index 9f5daf574a250beef9e3b5030e75599f88766fc1..30d89ddd493a106e2c423844661465dbc268df06 100644 (file)
@@ -62,7 +62,8 @@ private:
                function<QString (Glib::VariantBase)> printer = print_gvariant);
        void bind_int(const QString &name, const QString &desc, QString suffix,
                boost::optional< pair<int64_t, int64_t> > range,
-               prop::Property::Getter getter, prop::Property::Setter setter);
+               prop::Property::Getter getter, prop::Property::Setter setter,
+               QString special_value_text = "");
 
        static QString print_timebase(Glib::VariantBase gvar);
        static QString print_vdiv(Glib::VariantBase gvar);
index 02cb83e40c7e474729c7c6d82035f673138f6d36..0b5c37729ea0908c4e6a3a2b39a727088de014a9 100644 (file)
@@ -39,6 +39,8 @@ Analog::Analog() :
 void Analog::push_segment(shared_ptr<AnalogSegment> &segment)
 {
        segments_.push_back(segment);
+
+       connect(segment.get(), SIGNAL(completed()), this, SLOT(on_segment_completed()));
 }
 
 const deque< shared_ptr<AnalogSegment> >& Analog::analog_segments() const
@@ -95,5 +97,10 @@ void Analog::notify_min_max_changed(float min, float max)
        min_max_changed(min, max);
 }
 
+void Analog::on_segment_completed()
+{
+       segment_completed();
+}
+
 } // namespace data
 } // namespace pv
index 630075da99a773f3d17183443a4d491b051d3816..630dc9db435bf36617aca313d220de8126a4ebdf 100644 (file)
@@ -73,6 +73,11 @@ Q_SIGNALS:
 
        void min_max_changed(float min, float max);
 
+       void segment_completed();
+
+private Q_SLOTS:
+       void on_segment_completed();
+
 private:
        double samplerate_;
        deque< shared_ptr<AnalogSegment> > segments_;
index e6f9626c8c07bdaf0cd0382c674ac50b4771e2b2..579013097a713d3792da81aea0bebf09d45fb95e 100644 (file)
@@ -96,6 +96,16 @@ void AnalogSegment::append_interleaved_samples(const float *data,
                        prev_sample_count + 1, prev_sample_count + 1);
 }
 
+float AnalogSegment::get_sample(int64_t sample_num) const
+{
+       assert(sample_num >= 0);
+       assert(sample_num <= (int64_t)sample_count_);
+
+       lock_guard<recursive_mutex> lock(mutex_);  // Because of free_unused_memory()
+
+       return *((const float*)get_raw_sample(sample_num));
+}
+
 void AnalogSegment::get_samples(int64_t start_sample, int64_t end_sample,
        float* dest) const
 {
index 846837ce75f47c2df2afedeaae64f9159d3bd53b..633170439b8b0b0cc40372b1c538e4ec38b7501e 100644 (file)
@@ -81,6 +81,7 @@ public:
        void append_interleaved_samples(const float *data,
                size_t sample_count, size_t stride);
 
+       float get_sample(int64_t sample_num) const;
        void get_samples(int64_t start_sample, int64_t end_sample, float* dest) const;
 
        const pair<float, float> get_min_max() const;
index 6e11e83f374e23e4c4487ff2cd2148fbb0143ddc..86a371f26c67c11d72abfe80a3715cf2292921ae 100644 (file)
@@ -82,7 +82,7 @@ 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;
+       shared_ptr<const pv::data::SignalBase> assigned_signal;
        const QString name, desc;
        int initial_pin_state;
        const shared_ptr<Decoder> decoder_;
index 98275bad4b5f3fb17c84830ad916f5d3906ae533..ed29cafe45ac4cde4202fcb144f56c7f89437866 100644 (file)
@@ -170,7 +170,7 @@ void DecodeSignal::reset_decode(bool shutting_down)
        logic_mux_data_invalid_ = true;
 
        if (!error_message_.isEmpty()) {
-               error_message_ = QString();
+               error_message_.clear();
                // TODO Emulate noquote()
                qDebug().nospace() << name() << ": Error cleared";
        }
@@ -234,10 +234,8 @@ void DecodeSignal::begin_decode()
        // Receive notifications when new sample data is available
        connect_input_notifiers();
 
-       if (get_input_segment_count() == 0) {
+       if (get_input_segment_count() == 0)
                set_error_message(tr("No input data"));
-               return;
-       }
 
        // Make sure the logic output data is complete and up-to-date
        logic_mux_interrupt_ = false;
@@ -267,12 +265,6 @@ bool DecodeSignal::is_paused() const
        return decode_paused_;
 }
 
-QString DecodeSignal::error_message() const
-{
-       lock_guard<mutex> lock(output_mutex_);
-       return error_message_;
-}
-
 const vector<decode::DecodeChannel> DecodeSignal::get_channels() const
 {
        return channels_;
@@ -317,7 +309,7 @@ void DecodeSignal::auto_assign_signals(const shared_ptr<Decoder> dec)
                }
 
                if (match) {
-                       ch.assigned_signal = match.get();
+                       ch.assigned_signal = match;
                        new_assignment = true;
                }
        }
@@ -330,7 +322,7 @@ void DecodeSignal::auto_assign_signals(const shared_ptr<Decoder> dec)
        }
 }
 
-void DecodeSignal::assign_signal(const uint16_t channel_id, const SignalBase *signal)
+void DecodeSignal::assign_signal(const uint16_t channel_id, shared_ptr<const SignalBase> signal)
 {
        for (decode::DecodeChannel& ch : channels_)
                if (ch.id == channel_id) {
@@ -348,7 +340,7 @@ int DecodeSignal::get_assigned_signal_count() const
 {
        // Count all channels that have a signal assigned to them
        return count_if(channels_.begin(), channels_.end(),
-               [](decode::DecodeChannel ch) { return ch.assigned_signal; });
+               [](decode::DecodeChannel ch) { return ch.assigned_signal.get(); });
 }
 
 void DecodeSignal::set_initial_pin_state(const uint16_t channel_id, const int init_state)
@@ -395,18 +387,21 @@ int64_t DecodeSignal::get_working_sample_count(uint32_t segment_id) const
 
        for (const decode::DecodeChannel& ch : channels_)
                if (ch.assigned_signal) {
+                       if (!ch.assigned_signal->logic_data())
+                               return 0;
+
                        no_signals_assigned = false;
 
                        const shared_ptr<Logic> logic_data = ch.assigned_signal->logic_data();
-                       if (!logic_data || logic_data->logic_segments().empty())
+                       if (logic_data->logic_segments().empty())
                                return 0;
 
-                       try {
-                               const shared_ptr<LogicSegment> segment = logic_data->logic_segments().at(segment_id);
-                               count = min(count, (int64_t)segment->get_sample_count());
-                       } catch (out_of_range&) {
+                       if (segment_id >= logic_data->logic_segments().size())
                                return 0;
-                       }
+
+                       const shared_ptr<const LogicSegment> segment = logic_data->logic_segments()[segment_id]->get_shared_ptr();
+                       if (segment)
+                               count = min(count, (int64_t)segment->get_sample_count());
                }
 
        return (no_signals_assigned ? 0 : count);
@@ -513,18 +508,14 @@ void DecodeSignal::get_annotation_subset(deque<const Annotation*> &dest,
 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)
+       if ((segments_.size() == 0) || (segment_id >= segments_.size()))
                return 0;
 
-       try {
-               const DecodeSegment *segment = &(segments_.at(segment_id));
+       const DecodeSegment *segment = &(segments_[segment_id]);
 
-               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
-       }
+       for (const DecodeBinaryClass& bc : segment->binary_classes)
+               if ((bc.decoder == dec) && (bc.info->bin_class_id == bin_class_id))
+                       return bc.chunks.size();
 
        return 0;
 }
@@ -533,18 +524,17 @@ 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)
 {
-       try {
-               const DecodeSegment *segment = &(segments_.at(segment_id));
+       if (segment_id >= segments_.size())
+               return;
 
-               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
-       }
+       const DecodeSegment *segment = &(segments_[segment_id]);
+
+       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;
+               }
 }
 
 void DecodeSignal::get_merged_binary_data_chunks_by_sample(uint32_t segment_id,
@@ -553,39 +543,38 @@ void DecodeSignal::get_merged_binary_data_chunks_by_sample(uint32_t segment_id,
 {
        assert(dest != nullptr);
 
-       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 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
-       }
+       if (segment_id >= segments_.size())
+               return;
+
+       const DecodeSegment *segment = &(segments_[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 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;
+               }
 }
 
 void DecodeSignal::get_merged_binary_data_chunks_by_offset(uint32_t segment_id,
@@ -594,54 +583,52 @@ void DecodeSignal::get_merged_binary_data_chunks_by_offset(uint32_t segment_id,
 {
        assert(dest != nullptr);
 
-       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;
+       if (segment_id >= segments_.size())
+               return;
+
+       const DecodeSegment *segment = &(segments_[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();
                }
-       } catch (out_of_range&) {
-               // Do nothing
+               offset += chunk.data.size();
+               if (offset >= end)
+                       break;
        }
 }
 
 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));
+       if (segment_id >= segments_.size())
+               return nullptr;
 
-               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
-       }
+       const DecodeSegment *segment = &(segments_[segment_id]);
+
+       for (const DecodeBinaryClass& bc : segment->binary_classes)
+               if ((bc.decoder == dec) && (bc.info->bin_class_id == bin_class_id))
+                       return &bc;
 
        return nullptr;
 }
@@ -649,14 +636,12 @@ const DecodeBinaryClass* DecodeSignal::get_binary_data_class(uint32_t segment_id
 const deque<const Annotation*>* DecodeSignal::get_all_annotations_by_segment(
        uint32_t segment_id) const
 {
-       try {
-               const DecodeSegment *segment = &(segments_.at(segment_id));
-               return &(segment->all_annotations);
-       } catch (out_of_range&) {
-               // Do nothing
-       }
+       if (segment_id >= segments_.size())
+               return nullptr;
 
-       return nullptr;
+       const DecodeSegment *segment = &(segments_[segment_id]);
+
+       return &(segment->all_annotations);
 }
 
 void DecodeSignal::save_settings(QSettings &settings) const
@@ -823,7 +808,7 @@ void DecodeSignal::restore_settings(QSettings &settings)
 
                for (const shared_ptr<data::SignalBase>& signal : signalbases)
                        if ((signal->name() == assigned_signal_name) && (signal->type() != SignalBase::DecodeChannel))
-                               channel->assigned_signal = signal.get();
+                               channel->assigned_signal = signal;
 
                channel->initial_pin_state = settings.value("initial_pin_state").toInt();
 
@@ -838,11 +823,28 @@ void DecodeSignal::restore_settings(QSettings &settings)
        begin_decode();
 }
 
-void DecodeSignal::set_error_message(QString msg)
+bool DecodeSignal::all_input_segments_complete(uint32_t segment_id) const
 {
-       error_message_ = msg;
-       // TODO Emulate noquote()
-       qDebug().nospace() << name() << ": " << msg;
+       bool all_complete = true;
+
+       for (const decode::DecodeChannel& ch : channels_)
+               if (ch.assigned_signal) {
+                       if (!ch.assigned_signal->logic_data())
+                               continue;
+
+                       const shared_ptr<Logic> logic_data = ch.assigned_signal->logic_data();
+                       if (logic_data->logic_segments().empty())
+                               return false;
+
+                       if (segment_id >= logic_data->logic_segments().size())
+                               return false;
+
+                       const shared_ptr<const LogicSegment> segment = logic_data->logic_segments()[segment_id]->get_shared_ptr();
+                       if (segment && !segment->is_complete())
+                               all_complete = false;
+               }
+
+       return all_complete;
 }
 
 uint32_t DecodeSignal::get_input_segment_count() const
@@ -866,7 +868,7 @@ uint32_t DecodeSignal::get_input_segment_count() const
        return (no_signals_assigned ? 0 : count);
 }
 
-uint32_t DecodeSignal::get_input_samplerate(uint32_t segment_id) const
+double DecodeSignal::get_input_samplerate(uint32_t segment_id) const
 {
        double samplerate = 0;
 
@@ -877,8 +879,10 @@ uint32_t DecodeSignal::get_input_samplerate(uint32_t segment_id) const
                                continue;
 
                        try {
-                               const shared_ptr<LogicSegment> segment = logic_data->logic_segments().at(segment_id);
-                               samplerate = segment->samplerate();
+                               const shared_ptr<const LogicSegment> segment =
+                                       logic_data->logic_segments().at(segment_id)->get_shared_ptr();
+                               if (segment)
+                                       samplerate = segment->samplerate();
                        } catch (out_of_range&) {
                                // Do nothing
                        }
@@ -1005,7 +1009,7 @@ void DecodeSignal::mux_logic_samples(uint32_t segment_id, const int64_t start, c
                return;
 
        // Fetch the channel segments and their data
-       vector<shared_ptr<LogicSegment> > segments;
+       vector<shared_ptr<const LogicSegment> > segments;
        vector<const uint8_t*> signal_data;
        vector<uint8_t> signal_in_bytepos;
        vector<uint8_t> signal_in_bitpos;
@@ -1014,14 +1018,19 @@ void DecodeSignal::mux_logic_samples(uint32_t segment_id, const int64_t start, c
                if (ch.assigned_signal) {
                        const shared_ptr<Logic> logic_data = ch.assigned_signal->logic_data();
 
-                       shared_ptr<LogicSegment> segment;
-                       try {
-                               segment = logic_data->logic_segments().at(segment_id);
-                       } catch (out_of_range&) {
+                       shared_ptr<const LogicSegment> segment;
+                       if (segment_id < logic_data->logic_segments().size()) {
+                               segment = logic_data->logic_segments().at(segment_id)->get_shared_ptr();
+                       } else {
                                qDebug() << "Muxer error for" << name() << ":" << ch.assigned_signal->name() \
                                        << "has no logic segment" << segment_id;
+                               logic_mux_interrupt_ = true;
                                return;
                        }
+
+                       if (!segment)
+                               return;
+
                        segments.push_back(segment);
 
                        uint8_t* data = new uint8_t[(end - start) * segment->unit_size()];
@@ -1033,7 +1042,6 @@ void DecodeSignal::mux_logic_samples(uint32_t segment_id, const int64_t start, c
                        signal_in_bitpos.push_back(bitpos % 8);
                }
 
-
        shared_ptr<LogicSegment> output_segment;
        try {
                output_segment = logic_mux_data_->logic_segments().at(segment_id);
@@ -1041,6 +1049,7 @@ void DecodeSignal::mux_logic_samples(uint32_t segment_id, const int64_t start, c
                qDebug() << "Muxer error for" << name() << ": no logic mux segment" \
                        << segment_id << "in mux_logic_samples(), mux segments size is" \
                        << logic_mux_data_->logic_segments().size();
+               logic_mux_interrupt_ = true;
                return;
        }
 
@@ -1084,82 +1093,101 @@ void DecodeSignal::mux_logic_samples(uint32_t segment_id, const int64_t start, c
 
 void DecodeSignal::logic_mux_proc()
 {
-       uint32_t segment_id = 0;
+       uint32_t input_segment_count;
+       do {
+               input_segment_count = get_input_segment_count();
+               if (input_segment_count == 0) {
+                       // Wait for input data
+                       unique_lock<mutex> logic_mux_lock(logic_mux_mutex_);
+                       logic_mux_cond_.wait(logic_mux_lock);
+               }
+       } while ((!logic_mux_interrupt_) && (input_segment_count == 0));
+
+       if (logic_mux_interrupt_)
+               return;
 
        assert(logic_mux_data_);
 
+       uint32_t segment_id = 0;
+
        // Create initial logic mux segment
        shared_ptr<LogicSegment> output_segment =
-               make_shared<LogicSegment>(*logic_mux_data_, segment_id,
-                       logic_mux_unit_size_, 0);
+               make_shared<LogicSegment>(*logic_mux_data_, segment_id, logic_mux_unit_size_, 0);
        logic_mux_data_->push_segment(output_segment);
 
        output_segment->set_samplerate(get_input_samplerate(0));
 
+       // Logic mux data is being updated
+       logic_mux_data_invalid_ = false;
+
+       uint64_t samples_to_process;
        do {
-               const uint64_t input_sample_count = get_working_sample_count(segment_id);
-               const uint64_t output_sample_count = output_segment->get_sample_count();
+               do {
+                       const uint64_t input_sample_count = get_working_sample_count(segment_id);
+                       const uint64_t output_sample_count = output_segment->get_sample_count();
 
-               const uint64_t samples_to_process =
-                       (input_sample_count > output_sample_count) ?
-                       (input_sample_count - output_sample_count) : 0;
+                       samples_to_process =
+                               (input_sample_count > output_sample_count) ?
+                               (input_sample_count - output_sample_count) : 0;
 
-               // Process the samples if necessary...
-               if (samples_to_process > 0) {
-                       const uint64_t unit_size = output_segment->unit_size();
-                       const uint64_t chunk_sample_count = DecodeChunkLength / unit_size;
+                       if (samples_to_process > 0) {
+                               const uint64_t unit_size = output_segment->unit_size();
+                               const uint64_t chunk_sample_count = DecodeChunkLength / unit_size;
 
-                       uint64_t processed_samples = 0;
-                       do {
-                               const uint64_t start_sample = output_sample_count + processed_samples;
-                               const uint64_t sample_count =
-                                       min(samples_to_process - processed_samples,     chunk_sample_count);
+                               uint64_t processed_samples = 0;
+                               do {
+                                       const uint64_t start_sample = output_sample_count + processed_samples;
+                                       const uint64_t sample_count =
+                                               min(samples_to_process - processed_samples,     chunk_sample_count);
 
-                               mux_logic_samples(segment_id, start_sample, start_sample + sample_count);
-                               processed_samples += sample_count;
+                                       mux_logic_samples(segment_id, start_sample, start_sample + sample_count);
+                                       processed_samples += sample_count;
 
-                               // ...and process the newly muxed logic data
-                               decode_input_cond_.notify_one();
-                       } while (!logic_mux_interrupt_ && (processed_samples < samples_to_process));
-               }
+                                       // ...and process the newly muxed logic data
+                                       decode_input_cond_.notify_one();
+                               } while (!logic_mux_interrupt_ && (processed_samples < samples_to_process));
+                       }
+               } while (!logic_mux_interrupt_ && (samples_to_process > 0));
 
-               if (samples_to_process == 0) {
-                       // TODO Optimize this by caching the input segment count and only
-                       // querying it when the cached value was reached
-                       if (segment_id < get_input_segment_count() - 1) {
-                               // Process next segment
-                               segment_id++;
+               if (!logic_mux_interrupt_) {
+                       // samples_to_process is now 0, we've exhausted the currently available input data
 
-                               output_segment =
-                                       make_shared<LogicSegment>(*logic_mux_data_, segment_id,
-                                               logic_mux_unit_size_, 0);
-                               logic_mux_data_->push_segment(output_segment);
+                       // If the input segments are complete, we've completed this segment
+                       if (all_input_segments_complete(segment_id)) {
+                               if (!output_segment->is_complete())
+                                       output_segment->set_complete();
 
-                               output_segment->set_samplerate(get_input_samplerate(segment_id));
+                               if (segment_id < get_input_segment_count() - 1) {
+                                       // Process next segment
+                                       segment_id++;
 
-                       } else {
-                               // All segments have been processed
-                               logic_mux_data_invalid_ = false;
+                                       output_segment =
+                                               make_shared<LogicSegment>(*logic_mux_data_, segment_id,
+                                                       logic_mux_unit_size_, 0);
+                                       logic_mux_data_->push_segment(output_segment);
 
-                               // Wait for more input
+                                       output_segment->set_samplerate(get_input_samplerate(segment_id));
+                               }
+                       }
+
+                       if (segment_id == (get_input_segment_count() - 1)) {
+                               // Wait for more input data if we're processing the currently last segment
                                unique_lock<mutex> logic_mux_lock(logic_mux_mutex_);
                                logic_mux_cond_.wait(logic_mux_lock);
                        }
                }
-
        } while (!logic_mux_interrupt_);
 }
 
 void DecodeSignal::decode_data(
        const int64_t abs_start_samplenum, const int64_t sample_count,
-       const shared_ptr<LogicSegment> input_segment)
+       const shared_ptr<const LogicSegment> input_segment)
 {
        const int64_t unit_size = input_segment->unit_size();
        const int64_t chunk_sample_count = DecodeChunkLength / unit_size;
 
        for (int64_t i = abs_start_samplenum;
-               error_message_.isEmpty() && !decode_interrupt_ &&
-                       (i < (abs_start_samplenum + sample_count));
+               !decode_interrupt_ && (i < (abs_start_samplenum + sample_count));
                i += chunk_sample_count) {
 
                const int64_t chunk_end = min(i + chunk_sample_count,
@@ -1176,8 +1204,10 @@ void DecodeSignal::decode_data(
                input_segment->get_samples(i, chunk_end, chunk);
 
                if (srd_session_send(srd_session_, i, chunk_end, chunk,
-                               data_size, unit_size) != SRD_OK)
+                               data_size, unit_size) != SRD_OK) {
                        set_error_message(tr("Decoder reported an error"));
+                       decode_interrupt_ = true;
+               }
 
                delete[] chunk;
 
@@ -1203,16 +1233,20 @@ void DecodeSignal::decode_proc()
        current_segment_id_ = 0;
 
        // If there is no input data available yet, wait until it is or we're interrupted
-       if (logic_mux_data_->logic_segments().size() == 0) {
-               unique_lock<mutex> input_wait_lock(input_mutex_);
-               decode_input_cond_.wait(input_wait_lock);
-       }
+       do {
+               if (logic_mux_data_->logic_segments().size() == 0) {
+                       // Wait for input data
+                       unique_lock<mutex> input_wait_lock(input_mutex_);
+                       decode_input_cond_.wait(input_wait_lock);
+               }
+       } while ((!decode_interrupt_) && (logic_mux_data_->logic_segments().size() == 0));
 
        if (decode_interrupt_)
                return;
 
-       shared_ptr<LogicSegment> input_segment = logic_mux_data_->logic_segments().front();
-       assert(input_segment);
+       shared_ptr<const LogicSegment> input_segment = logic_mux_data_->logic_segments().front()->get_shared_ptr();
+       if (!input_segment)
+               return;
 
        // Create the initial segment and set its sample rate so that we can pass it to SRD
        create_decode_segment();
@@ -1221,57 +1255,60 @@ void DecodeSignal::decode_proc()
 
        start_srd_session();
 
-       uint64_t sample_count = 0;
+       uint64_t samples_to_process = 0;
        uint64_t abs_start_samplenum = 0;
        do {
                // Keep processing new samples until we exhaust the input data
                do {
-                       lock_guard<mutex> input_lock(input_mutex_);
-                       sample_count = input_segment->get_sample_count() - abs_start_samplenum;
+                       samples_to_process = input_segment->get_sample_count() - abs_start_samplenum;
 
-                       if (sample_count > 0) {
-                               decode_data(abs_start_samplenum, sample_count, input_segment);
-                               abs_start_samplenum += sample_count;
+                       if (samples_to_process > 0) {
+                               decode_data(abs_start_samplenum, samples_to_process, input_segment);
+                               abs_start_samplenum += samples_to_process;
                        }
-               } while (error_message_.isEmpty() && (sample_count > 0) && !decode_interrupt_);
-
-               if (error_message_.isEmpty() && !decode_interrupt_ && sample_count == 0) {
-                       if (current_segment_id_ < logic_mux_data_->logic_segments().size() - 1) {
-                               // Process next segment
-                               current_segment_id_++;
-
-                               try {
-                                       input_segment = logic_mux_data_->logic_segments().at(current_segment_id_);
-                               } catch (out_of_range&) {
-                                       qDebug() << "Decode error for" << name() << ": no logic mux segment" \
-                                               << current_segment_id_ << "in decode_proc(), mux segments size is" \
-                                               << logic_mux_data_->logic_segments().size();
-                                       return;
+               } while (!decode_interrupt_ && (samples_to_process > 0));
+
+               if (!decode_interrupt_) {
+                       // samples_to_process is now 0, we've exhausted the currently available input data
+
+                       // If the input segment is complete, we've exhausted this segment
+                       if (input_segment->is_complete()) {
+                               if (current_segment_id_ < (logic_mux_data_->logic_segments().size() - 1)) {
+                                       // Process next segment
+                                       current_segment_id_++;
+
+                                       try {
+                                               input_segment = logic_mux_data_->logic_segments().at(current_segment_id_);
+                                       } catch (out_of_range&) {
+                                               qDebug() << "Decode error for" << name() << ": no logic mux segment" \
+                                                       << current_segment_id_ << "in decode_proc(), mux segments size is" \
+                                                       << logic_mux_data_->logic_segments().size();
+                                               decode_interrupt_ = true;
+                                               return;
+                                       }
+                                       abs_start_samplenum = 0;
+
+                                       // Create the next segment and set its metadata
+                                       create_decode_segment();
+                                       segments_.at(current_segment_id_).samplerate = input_segment->samplerate();
+                                       segments_.at(current_segment_id_).start_time = input_segment->start_time();
+
+                                       // Reset decoder state but keep the decoder stack intact
+                                       terminate_srd_session();
+                               } else {
+                                       // All segments have been processed
+                                       if (!decode_interrupt_)
+                                               decode_finished();
                                }
-                               abs_start_samplenum = 0;
-
-                               // Create the next segment and set its metadata
-                               create_decode_segment();
-                               segments_.at(current_segment_id_).samplerate = input_segment->samplerate();
-                               segments_.at(current_segment_id_).start_time = input_segment->start_time();
-
-                               // Reset decoder state but keep the decoder stack intact
-                               terminate_srd_session();
-                       } else {
-                               // All segments have been processed
-                               decode_finished();
+                       }
 
-                               // Wait for new input data or an interrupt was requested
+                       if (current_segment_id_ == (logic_mux_data_->logic_segments().size() - 1)) {
+                               // Wait for more input data if we're processing the currently last segment
                                unique_lock<mutex> input_wait_lock(input_mutex_);
                                decode_input_cond_.wait(input_wait_lock);
                        }
                }
-       } while (error_message_.isEmpty() && !decode_interrupt_);
-
-       // Potentially reap decoders when the application no longer is
-       // interested in their (pending) results.
-       if (decode_interrupt_)
-               terminate_srd_session();
+       } while (!decode_interrupt_);
 }
 
 void DecodeSignal::start_srd_session()
@@ -1388,11 +1425,11 @@ void DecodeSignal::connect_input_notifiers()
                if (!ch.assigned_signal)
                        continue;
 
-               const data::SignalBase *signal = ch.assigned_signal;
-               connect(signal, SIGNAL(samples_cleared()),
-                       this, SLOT(on_data_cleared()));
-               connect(signal, SIGNAL(samples_added(uint64_t, uint64_t, uint64_t)),
-                       this, SLOT(on_data_received()));
+               const data::SignalBase *signal = ch.assigned_signal.get();
+               connect(signal, &data::SignalBase::samples_cleared,
+                       this, &DecodeSignal::on_data_cleared);
+               connect(signal, &data::SignalBase::samples_added,
+                       this, &DecodeSignal::on_data_received);
        }
 }
 
@@ -1591,6 +1628,11 @@ void DecodeSignal::on_data_received()
        // to work with
        if ((!error_message_.isEmpty()) && (get_input_segment_count() == 0))
                return;
+       else {
+               error_message_.clear();
+               // TODO Emulate noquote()
+               qDebug().nospace() << name() << ": Error cleared";
+       }
 
        if (!logic_mux_thread_.joinable())
                begin_decode();
index 2a6d125f06024dd14494097f834a00d6f44bd8fc..fad3db78548457d25e61f9b4f7dc0bf8042243cf 100644 (file)
@@ -27,7 +27,6 @@
 #include <vector>
 
 #include <QSettings>
-#include <QString>
 
 #include <libsigrokdecode/libsigrokdecode.h>
 
@@ -115,11 +114,10 @@ public:
        void pause_decode();
        void resume_decode();
        bool is_paused() const;
-       QString error_message() const;
 
        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);
+       void assign_signal(const uint16_t channel_id, shared_ptr<const SignalBase> signal);
        int get_assigned_signal_count() const;
 
        void set_initial_pin_state(const uint16_t channel_id, const int init_state);
@@ -189,10 +187,9 @@ public:
        virtual void restore_settings(QSettings &settings);
 
 private:
-       void set_error_message(QString msg);
-
+       bool all_input_segments_complete(uint32_t segment_id) const;
        uint32_t get_input_segment_count() const;
-       uint32_t get_input_samplerate(uint32_t segment_id) const;
+       double get_input_samplerate(uint32_t segment_id) const;
 
        Decoder* get_decoder_by_instance(const srd_decoder *const srd_dec);
 
@@ -204,7 +201,7 @@ private:
        void logic_mux_proc();
 
        void decode_data(const int64_t abs_start_samplenum, const int64_t sample_count,
-               const shared_ptr<LogicSegment> input_segment);
+               const shared_ptr<const LogicSegment> input_segment);
        void decode_proc();
 
        void start_srd_session();
@@ -260,8 +257,6 @@ private:
        atomic<bool> decode_interrupt_, logic_mux_interrupt_;
 
        bool decode_paused_;
-
-       QString error_message_;
 };
 
 } // namespace data
index a7df235a32d1c7a613de2824b14ca5ae6edff113..be56c1505263fd44c19901ce8b055112f6888378 100644 (file)
@@ -63,10 +63,24 @@ LogicSegment::LogicSegment(pv::data::Logic& owner, uint32_t segment_id,
 LogicSegment::~LogicSegment()
 {
        lock_guard<recursive_mutex> lock(mutex_);
+
        for (MipMapLevel &l : mip_map_)
                free(l.data);
 }
 
+shared_ptr<const LogicSegment> LogicSegment::get_shared_ptr() const
+{
+       shared_ptr<const Segment> ptr = nullptr;
+
+       try {
+               ptr = shared_from_this();
+       } catch (std::exception& e) {
+               /* Do nothing, ptr remains a null pointer */
+       }
+
+       return ptr ? std::dynamic_pointer_cast<const LogicSegment>(ptr) : nullptr;
+}
+
 template <class T>
 void LogicSegment::downsampleTmain(const T*&in, T &acc, T &prev)
 {
@@ -328,6 +342,7 @@ void LogicSegment::append_payload(shared_ptr<sigrok::Logic> logic)
 
 void LogicSegment::append_payload(void *data, uint64_t data_size)
 {
+       assert(unit_size_ > 0);
        assert((data_size % unit_size_) == 0);
 
        lock_guard<recursive_mutex> lock(mutex_);
index 1f151eeb7a71e2e0f5e931f6ec0f3a7a08d7e0b4..2e37ed2d248df56c22d1e0d1cd703d7835425fcb 100644 (file)
@@ -75,6 +75,14 @@ public:
 
        virtual ~LogicSegment();
 
+       /**
+        * Using enable_shared_from_this prevents the normal use of shared_ptr
+        * instances by users of LogicSegment instances. Instead, shared_ptrs may
+        * only be created by the instance itself.
+        * See https://en.cppreference.com/w/cpp/memory/enable_shared_from_this
+        */
+       shared_ptr<const LogicSegment> get_shared_ptr() const;
+
        void append_payload(shared_ptr<sigrok::Logic> logic);
        void append_payload(void *data, uint64_t data_size);
 
diff --git a/pv/data/mathsignal.cpp b/pv/data/mathsignal.cpp
new file mode 100644 (file)
index 0000000..f7de2ff
--- /dev/null
@@ -0,0 +1,615 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * 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
+ * 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 <limits>
+
+#include <QDebug>
+
+#include "mathsignal.hpp"
+
+#include <extdef.h>
+#include <pv/globalsettings.hpp>
+#include <pv/session.hpp>
+#include <pv/data/analogsegment.hpp>
+#include <pv/data/signalbase.hpp>
+
+using std::dynamic_pointer_cast;
+using std::make_shared;
+using std::min;
+using std::unique_lock;
+
+namespace pv {
+namespace data {
+
+#define MATH_ERR_NONE           0
+#define MATH_ERR_EMPTY_EXPR     1
+#define MATH_ERR_EXPRESSION     2
+#define MATH_ERR_INVALID_SIGNAL 3
+#define MATH_ERR_ENABLE         4
+
+const int64_t MathSignal::ChunkLength = 256 * 1024;
+
+
+template<typename T>
+struct fnc_sample : public exprtk::igeneric_function<T>
+{
+       typedef typename exprtk::igeneric_function<T>::parameter_list_t parameter_list_t;
+       typedef typename exprtk::igeneric_function<T>::generic_type generic_type;
+       typedef typename generic_type::scalar_view scalar_t;
+       typedef typename generic_type::string_view string_t;
+
+       fnc_sample(MathSignal& owner) :
+               exprtk::igeneric_function<T>("ST"),  // Require channel name and sample number
+               owner_(owner),
+               sig_data(nullptr)
+       {
+       }
+
+       T operator()(parameter_list_t parameters)
+       {
+               const string_t exprtk_sig_name = string_t(parameters[0]);
+               const scalar_t exprtk_sample_num = scalar_t(parameters[1]);
+
+               const std::string str_sig_name = to_str(exprtk_sig_name);
+               const double sample_num = exprtk_sample_num();
+
+               if (sample_num < 0)
+                       return 0;
+
+               if (!sig_data)
+                       sig_data = owner_.signal_from_name(str_sig_name);
+
+               if (!sig_data)
+                       // There doesn't actually exist a signal with that name
+                       return 0;
+
+               owner_.update_signal_sample(sig_data, current_segment, sample_num);
+
+               return T(sig_data->sample_value);
+       }
+
+       MathSignal& owner_;
+       uint32_t current_segment;
+       signal_data* sig_data;
+};
+
+
+MathSignal::MathSignal(pv::Session &session) :
+       SignalBase(nullptr, SignalBase::MathChannel),
+       session_(session),
+       use_custom_sample_rate_(false),
+       use_custom_sample_count_(false),
+       expression_(""),
+       error_type_(MATH_ERR_NONE),
+       exprtk_unknown_symbol_table_(nullptr),
+       exprtk_symbol_table_(nullptr),
+       exprtk_expression_(nullptr),
+       exprtk_parser_(nullptr),
+       fnc_sample_(nullptr)
+{
+       uint32_t sig_idx = session_.get_next_signal_index(MathChannel);
+       set_name(QString(tr("Math%1")).arg(sig_idx));
+       set_color(AnalogSignalColors[(sig_idx - 1) % countof(AnalogSignalColors)]);
+
+       set_data(std::make_shared<data::Analog>());
+
+       connect(&session_, SIGNAL(capture_state_changed(int)),
+               this, SLOT(on_capture_state_changed(int)));
+}
+
+MathSignal::~MathSignal()
+{
+       reset_generation();
+}
+
+void MathSignal::save_settings(QSettings &settings) const
+{
+       SignalBase::save_settings(settings);
+
+       settings.setValue("expression", expression_);
+
+       settings.setValue("custom_sample_rate", (qulonglong)custom_sample_rate_);
+       settings.setValue("custom_sample_count", (qulonglong)custom_sample_count_);
+       settings.setValue("use_custom_sample_rate", use_custom_sample_rate_);
+       settings.setValue("use_custom_sample_count", use_custom_sample_count_);
+}
+
+void MathSignal::restore_settings(QSettings &settings)
+{
+       SignalBase::restore_settings(settings);
+
+       if (settings.contains("expression"))
+               expression_ = settings.value("expression").toString();
+
+       if (settings.contains("custom_sample_rate"))
+               custom_sample_rate_ = settings.value("custom_sample_rate").toULongLong();
+
+       if (settings.contains("custom_sample_count"))
+               custom_sample_count_ = settings.value("custom_sample_count").toULongLong();
+
+       if (settings.contains("use_custom_sample_rate"))
+               use_custom_sample_rate_ = settings.value("use_custom_sample_rate").toBool();
+
+       if (settings.contains("use_custom_sample_count"))
+               use_custom_sample_count_ = settings.value("use_custom_sample_count").toBool();
+}
+
+QString MathSignal::get_expression() const
+{
+       return expression_;
+}
+
+void MathSignal::set_expression(QString expression)
+{
+       expression_ = expression;
+
+       begin_generation();
+}
+
+void MathSignal::set_error(uint8_t type, QString msg)
+{
+       error_type_ = type;
+       error_message_ = msg;
+       // TODO Emulate noquote()
+       qDebug().nospace() << name() << ": " << msg << "(Expression: '" + expression_ + "')";
+
+       error_message_changed(msg);
+}
+
+uint64_t MathSignal::get_working_sample_count(uint32_t segment_id) const
+{
+       // The working sample count is the highest sample number for
+       // which all used signals have data available, so go through all
+       // channels and use the lowest overall sample count of the segment
+
+       int64_t result = std::numeric_limits<int64_t>::max();
+
+       if (use_custom_sample_count_)
+               // A custom sample count implies that only one segment will be created
+               result = (segment_id == 0) ? custom_sample_count_ : 0;
+       else {
+               if (input_signals_.size() > 0) {
+                       for (auto input_signal : input_signals_) {
+                               const shared_ptr<SignalBase>& sb = input_signal.second.sb;
+
+                               shared_ptr<Analog> a = sb->analog_data();
+                               auto analog_segments = a->analog_segments();
+
+                               if (analog_segments.size() == 0) {
+                                       result = 0;
+                                       continue;
+                               }
+
+                               const uint32_t highest_segment_id = (analog_segments.size() - 1);
+                               if (segment_id > highest_segment_id)
+                                       continue;
+
+                               const shared_ptr<AnalogSegment> segment = analog_segments.at(segment_id);
+                               result = min(result, (int64_t)segment->get_sample_count());
+                       }
+               } else
+                       result = session_.get_segment_sample_count(segment_id);
+       }
+
+       return result;
+}
+
+void MathSignal::update_completeness(uint32_t segment_id, uint64_t output_sample_count)
+{
+       bool output_complete = true;
+
+       if (input_signals_.size() > 0) {
+               for (auto input_signal : input_signals_) {
+                       const shared_ptr<SignalBase>& sb = input_signal.second.sb;
+
+                       shared_ptr<Analog> a = sb->analog_data();
+                       auto analog_segments = a->analog_segments();
+
+                       if (analog_segments.size() == 0) {
+                               output_complete = false;
+                               continue;
+                       }
+
+                       const uint32_t highest_segment_id = (analog_segments.size() - 1);
+                       if (segment_id > highest_segment_id) {
+                               output_complete = false;
+                               continue;
+                       }
+
+                       const shared_ptr<AnalogSegment> segment = analog_segments.at(segment_id);
+                       if (!segment->is_complete()) {
+                               output_complete = false;
+                               continue;
+                       }
+
+                       if (output_sample_count < segment->get_sample_count())
+                               output_complete = false;
+               }
+       } else {
+               // We're done when we generated as many samples as the stopped session is long
+               if ((session_.get_capture_state() != Session::Stopped) ||
+                       (output_sample_count < session_.get_segment_sample_count(segment_id)))
+                       output_complete = false;
+       }
+
+       if (output_complete)
+               analog_data()->analog_segments().at(segment_id)->set_complete();
+}
+
+void MathSignal::reset_generation()
+{
+       if (gen_thread_.joinable()) {
+               gen_interrupt_ = true;
+               gen_input_cond_.notify_one();
+               gen_thread_.join();
+       }
+
+       data_->clear();
+       input_signals_.clear();
+
+       if (exprtk_parser_) {
+               delete exprtk_parser_;
+               exprtk_parser_ = nullptr;
+       }
+
+       if (exprtk_expression_) {
+               delete exprtk_expression_;
+               exprtk_expression_ = nullptr;
+       }
+
+       if (exprtk_symbol_table_) {
+               delete exprtk_symbol_table_;
+               exprtk_symbol_table_ = nullptr;
+       }
+
+       if (exprtk_unknown_symbol_table_) {
+               delete exprtk_unknown_symbol_table_;
+               exprtk_unknown_symbol_table_ = nullptr;
+       }
+
+       if (fnc_sample_) {
+               delete fnc_sample_;
+               fnc_sample_ = nullptr;
+       }
+
+       if (!error_message_.isEmpty()) {
+               error_message_.clear();
+               error_type_ = MATH_ERR_NONE;
+               // TODO Emulate noquote()
+               qDebug().nospace() << name() << ": Error cleared";
+       }
+
+       generation_chunk_size_ = ChunkLength;
+}
+
+void MathSignal::begin_generation()
+{
+       reset_generation();
+
+       if (expression_.isEmpty()) {
+               set_error(MATH_ERR_EMPTY_EXPR, tr("No expression defined, nothing to do"));
+               return;
+       }
+
+       disconnect(this, SLOT(on_data_received()));
+       disconnect(this, SLOT(on_enabled_changed()));
+
+       fnc_sample_ = new fnc_sample<double>(*this);
+
+       exprtk_unknown_symbol_table_ = new exprtk::symbol_table<double>();
+
+       exprtk_symbol_table_ = new exprtk::symbol_table<double>();
+       exprtk_symbol_table_->add_constant("T", 1 / session_.get_samplerate());
+       exprtk_symbol_table_->add_function("sample", *fnc_sample_);
+       exprtk_symbol_table_->add_variable("t", exprtk_current_time_);
+       exprtk_symbol_table_->add_variable("s", exprtk_current_sample_);
+       exprtk_symbol_table_->add_constants();
+
+       exprtk_expression_ = new exprtk::expression<double>();
+       exprtk_expression_->register_symbol_table(*exprtk_unknown_symbol_table_);
+       exprtk_expression_->register_symbol_table(*exprtk_symbol_table_);
+
+       exprtk_parser_ = new exprtk::parser<double>();
+       exprtk_parser_->enable_unknown_symbol_resolver();
+
+       if (!exprtk_parser_->compile(expression_.toStdString(), *exprtk_expression_)) {
+               QString error_details;
+               size_t error_count = exprtk_parser_->error_count();
+
+               for (size_t i = 0; i < error_count; i++) {
+                       typedef exprtk::parser_error::type error_t;
+                       error_t error = exprtk_parser_->get_error(i);
+                       exprtk::parser_error::update_error(error, expression_.toStdString());
+
+                       QString error_detail = tr("%1 at line %2, column %3: %4");
+                       if ((error_count > 1) && (i < (error_count - 1)))
+                               error_detail += "\n";
+
+                       error_details += error_detail \
+                               .arg(exprtk::parser_error::to_str(error.mode).c_str()) \
+                               .arg(error.line_no) \
+                               .arg(error.column_no) \
+                               .arg(error.diagnostic.c_str());
+               }
+               set_error(MATH_ERR_EXPRESSION, error_details);
+       } else {
+               // Resolve unknown scalars to signals and add them to the input signal list
+               vector<string> unknowns;
+               exprtk_unknown_symbol_table_->get_variable_list(unknowns);
+               for (string& unknown : unknowns) {
+                       signal_data* sig_data = signal_from_name(unknown);
+                       const shared_ptr<SignalBase> signal = (sig_data) ? (sig_data->sb) : nullptr;
+                       if (!signal || (!signal->analog_data())) {
+                               set_error(MATH_ERR_INVALID_SIGNAL, QString(tr("\"%1\" isn't a valid analog signal")) \
+                                       .arg(QString::fromStdString(unknown)));
+                       } else
+                               sig_data->ref = &(exprtk_unknown_symbol_table_->variable_ref(unknown));
+               }
+       }
+
+       QString disabled_signals;
+       if (!all_input_signals_enabled(disabled_signals) && error_message_.isEmpty())
+               set_error(MATH_ERR_ENABLE,
+                       tr("No data will be generated as %1 must be enabled").arg(disabled_signals));
+
+       if (error_message_.isEmpty()) {
+               // Connect to the session data notification if we have no input signals
+               if (input_signals_.empty())
+                       connect(&session_, SIGNAL(data_received()),
+                               this, SLOT(on_data_received()));
+
+               gen_interrupt_ = false;
+               gen_thread_ = std::thread(&MathSignal::generation_proc, this);
+       }
+}
+
+uint64_t MathSignal::generate_samples(uint32_t segment_id, const uint64_t start_sample,
+       const int64_t sample_count)
+{
+       uint64_t count = 0;
+
+       shared_ptr<Analog> analog = dynamic_pointer_cast<Analog>(data_);
+       shared_ptr<AnalogSegment> segment = analog->analog_segments().at(segment_id);
+
+       // Keep the math functions segment IDs in sync
+       fnc_sample_->current_segment = segment_id;
+
+       const double sample_rate = data_->get_samplerate();
+
+       exprtk_current_sample_ = start_sample;
+
+       float *sample_data = new float[sample_count];
+
+       for (int64_t i = 0; i < sample_count; i++) {
+               exprtk_current_time_ = exprtk_current_sample_ / sample_rate;
+
+               for (auto& entry : input_signals_) {
+                       signal_data* sig_data  = &(entry.second);
+                       update_signal_sample(sig_data, segment_id, exprtk_current_sample_);
+               }
+
+               double value = exprtk_expression_->value();
+               sample_data[i] = value;
+               exprtk_current_sample_ += 1;
+               count++;
+
+               // If during the evaluation of the expression it was found that this
+               // math signal itself is being accessed, the chunk size was reduced
+               // to 1, which means we must stop after this sample we just generated
+               if (generation_chunk_size_ == 1)
+                       break;
+       }
+
+       segment->append_interleaved_samples(sample_data, count, 1);
+
+       delete[] sample_data;
+
+       return count;
+}
+
+void MathSignal::generation_proc()
+{
+       // Don't do anything until we have a valid sample rate
+       do {
+               if (use_custom_sample_rate_)
+                       data_->set_samplerate(custom_sample_rate_);
+               else
+                       data_->set_samplerate(session_.get_samplerate());
+
+               if (data_->get_samplerate() == 1) {
+                       unique_lock<mutex> gen_input_lock(input_mutex_);
+                       gen_input_cond_.wait(gen_input_lock);
+               }
+       } while ((!gen_interrupt_) && (data_->get_samplerate() == 1));
+
+       if (gen_interrupt_)
+               return;
+
+       uint32_t segment_id = 0;
+       shared_ptr<Analog> analog = analog_data();
+
+       // Create initial analog segment
+       shared_ptr<AnalogSegment> output_segment =
+               make_shared<AnalogSegment>(*analog.get(), segment_id, analog->get_samplerate());
+       analog->push_segment(output_segment);
+
+       // Create analog samples
+       do {
+               const uint64_t input_sample_count = get_working_sample_count(segment_id);
+               const uint64_t output_sample_count = output_segment->get_sample_count();
+
+               const uint64_t samples_to_process =
+                       (input_sample_count > output_sample_count) ?
+                       (input_sample_count - output_sample_count) : 0;
+
+               // Process the samples if necessary...
+               if (samples_to_process > 0) {
+                       uint64_t processed_samples = 0;
+                       do {
+                               const uint64_t start_sample = output_sample_count + processed_samples;
+                               uint64_t sample_count =
+                                       min(samples_to_process - processed_samples,     generation_chunk_size_);
+
+                               sample_count = generate_samples(segment_id, start_sample, sample_count);
+                               processed_samples += sample_count;
+
+                               // Notify consumers of this signal's data
+                               samples_added(segment_id, start_sample, start_sample + processed_samples);
+                       } while (!gen_interrupt_ && (processed_samples < samples_to_process));
+               }
+
+               update_completeness(segment_id, output_sample_count);
+
+               if (output_segment->is_complete() && (segment_id < session_.get_highest_segment_id())) {
+                               // Process next segment
+                               segment_id++;
+
+                               output_segment =
+                                       make_shared<AnalogSegment>(*analog.get(), segment_id, analog->get_samplerate());
+                               analog->push_segment(output_segment);
+               }
+
+               if (!gen_interrupt_ && (samples_to_process == 0)) {
+                       // Wait for more input
+                       unique_lock<mutex> gen_input_lock(input_mutex_);
+                       gen_input_cond_.wait(gen_input_lock);
+               }
+       } while (!gen_interrupt_);
+}
+
+signal_data* MathSignal::signal_from_name(const std::string& name)
+{
+       // If the expression contains the math signal itself, we must add every sample to
+       // the output segment immediately so that it can be accessed
+       const QString sig_name = QString::fromStdString(name);
+       if (sig_name == this->name())
+               generation_chunk_size_ = 1;
+
+       // Look up signal in the map and if it doesn't exist yet, add it for future use
+
+       auto element = input_signals_.find(name);
+
+       if (element != input_signals_.end()) {
+               return &(element->second);
+       } else {
+               const vector< shared_ptr<SignalBase> > signalbases = session_.signalbases();
+
+               for (const shared_ptr<SignalBase>& sb : signalbases)
+                       if (sb->name() == sig_name) {
+                               if (!sb->analog_data())
+                                       continue;
+
+                               connect(sb->analog_data().get(), SIGNAL(samples_added(SharedPtrToSegment, uint64_t, uint64_t)),
+                                       this, SLOT(on_data_received()));
+                               connect(sb->analog_data().get(), SIGNAL(segment_completed()),
+                                       this, SLOT(on_data_received()));
+
+                               connect(sb.get(), SIGNAL(enabled_changed(bool)),
+                                       this, SLOT(on_enabled_changed()));
+
+                               return &(input_signals_.insert({name, signal_data(sb)}).first->second);
+                       }
+       }
+
+       // If we reach this point, no valid signal was found with the supplied name
+       if (error_type_ == MATH_ERR_NONE)
+               set_error(MATH_ERR_INVALID_SIGNAL, QString(tr("\"%1\" isn't a valid analog signal")) \
+                       .arg(QString::fromStdString(name)));
+
+       return nullptr;
+}
+
+void MathSignal::update_signal_sample(signal_data* sig_data, uint32_t segment_id, uint64_t sample_num)
+{
+       assert(sig_data);
+
+       // Update the value only if a different sample is requested
+       if (sig_data->sample_num == sample_num)
+               return;
+
+       assert(sig_data->sb);
+       const shared_ptr<pv::data::Analog> analog = sig_data->sb->analog_data();
+       assert(analog);
+
+       assert(segment_id < analog->analog_segments().size());
+
+       const shared_ptr<AnalogSegment> segment = analog->analog_segments().at(segment_id);
+
+       sig_data->sample_num = sample_num;
+
+       if (sample_num < segment->get_sample_count())
+               sig_data->sample_value = segment->get_sample(sample_num);
+       else
+               sig_data->sample_value = 0;
+
+       // We only have a reference if this signal is used as a scalar;
+       // if it's used by a function, it's null
+       if (sig_data->ref)
+               *(sig_data->ref) = sig_data->sample_value;
+}
+
+bool MathSignal::all_input_signals_enabled(QString &disabled_signals) const
+{
+       bool all_enabled = true;
+
+       disabled_signals.clear();
+
+       for (auto input_signal : input_signals_) {
+               const shared_ptr<SignalBase>& sb = input_signal.second.sb;
+
+               if (!sb->enabled()) {
+                       all_enabled = false;
+                       disabled_signals += disabled_signals.isEmpty() ?
+                               sb->name() : ", " + sb->name();
+               }
+       }
+
+       return all_enabled;
+}
+
+void MathSignal::on_capture_state_changed(int state)
+{
+       if (state == Session::Running)
+               begin_generation();
+
+       // Make sure we don't miss any input samples, just in case
+       if (state == Session::Stopped)
+               gen_input_cond_.notify_one();
+}
+
+void MathSignal::on_data_received()
+{
+       gen_input_cond_.notify_one();
+}
+
+void MathSignal::on_enabled_changed()
+{
+       QString disabled_signals;
+       if (!all_input_signals_enabled(disabled_signals) &&
+               ((error_type_ == MATH_ERR_NONE) || (error_type_ == MATH_ERR_ENABLE)))
+               set_error(MATH_ERR_ENABLE,
+                       tr("No data will be generated as %1 must be enabled").arg(disabled_signals));
+       else if (disabled_signals.isEmpty() && (error_type_ == MATH_ERR_ENABLE)) {
+               error_type_ = MATH_ERR_NONE;
+               error_message_.clear();
+       }
+}
+
+} // namespace data
+} // namespace pv
diff --git a/pv/data/mathsignal.hpp b/pv/data/mathsignal.hpp
new file mode 100644 (file)
index 0000000..e40dbfb
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PULSEVIEW_PV_DATA_MATHSIGNAL_HPP
+#define PULSEVIEW_PV_DATA_MATHSIGNAL_HPP
+
+#define exprtk_disable_rtl_io_file /* Potential security issue, doubt anyone would use those anyway */
+#define exprtk_disable_rtl_vecops  /* Vector ops are rather useless for math channels */
+#define exprtk_disable_caseinsensitivity /* So that we can have both 't' and 'T' */
+
+#include <limits>
+
+#include <QString>
+
+#include <pv/exprtk.hpp>
+#include <pv/util.hpp>
+#include <pv/data/analog.hpp>
+#include <pv/data/signalbase.hpp>
+
+using std::atomic;
+using std::condition_variable;
+using std::mutex;
+using std::numeric_limits;
+using std::shared_ptr;
+
+namespace pv {
+class Session;
+
+namespace data {
+
+class SignalBase;
+
+template<typename T>
+struct fnc_sample;
+
+struct signal_data {
+       signal_data(const shared_ptr<SignalBase> _sb) :
+               sb(_sb), sample_num(numeric_limits<uint64_t>::max()), sample_value(0), ref(nullptr)
+       {}
+
+       const shared_ptr<SignalBase> sb;
+       uint64_t sample_num;
+       double sample_value;
+       double* ref;
+};
+
+class MathSignal : public SignalBase
+{
+       Q_OBJECT
+       Q_PROPERTY(QString expression READ get_expression WRITE set_expression NOTIFY expression_changed)
+
+private:
+       static const int64_t ChunkLength;
+
+public:
+       MathSignal(pv::Session &session);
+       virtual ~MathSignal();
+
+       virtual void save_settings(QSettings &settings) const;
+       virtual void restore_settings(QSettings &settings);
+
+       QString get_expression() const;
+       void set_expression(QString expression);
+
+private:
+       void set_error(uint8_t type, QString msg);
+
+       /**
+        * Returns the number of samples that can be worked on,
+        * i.e. the number of samples where samples are available
+        * for all connected channels.
+        * If the math signal uses no input channels, this is the
+        * number of samples in the session.
+        */
+       uint64_t get_working_sample_count(uint32_t segment_id) const;
+
+       void update_completeness(uint32_t segment_id, uint64_t output_sample_count);
+
+       void reset_generation();
+       virtual void begin_generation();
+
+       uint64_t generate_samples(uint32_t segment_id, const uint64_t start_sample,
+               const int64_t sample_count);
+       void generation_proc();
+
+       signal_data* signal_from_name(const std::string& name);
+       void update_signal_sample(signal_data* sig_data, uint32_t segment_id, uint64_t sample_num);
+
+       bool all_input_signals_enabled(QString &disabled_signals) const;
+
+Q_SIGNALS:
+       void samples_cleared();
+
+       void samples_added(uint64_t segment_id, uint64_t start_sample,
+               uint64_t end_sample);
+
+       void min_max_changed(float min, float max);
+
+       void expression_changed(QString expression);
+
+private Q_SLOTS:
+       void on_capture_state_changed(int state);
+       void on_data_received();
+
+       void on_enabled_changed();
+
+private:
+       pv::Session &session_;
+
+       uint64_t custom_sample_rate_;
+       uint64_t custom_sample_count_;
+       bool use_custom_sample_rate_, use_custom_sample_count_;
+       uint64_t generation_chunk_size_;
+       map<std::string, signal_data> input_signals_;
+
+       QString expression_;
+
+       uint8_t error_type_;
+
+       mutable mutex input_mutex_;
+       mutable condition_variable gen_input_cond_;
+
+       std::thread gen_thread_;
+       atomic<bool> gen_interrupt_;
+
+       exprtk::symbol_table<double> *exprtk_unknown_symbol_table_, *exprtk_symbol_table_;
+       exprtk::expression<double> *exprtk_expression_;
+       exprtk::parser<double> *exprtk_parser_;
+       double exprtk_current_time_, exprtk_current_sample_;
+
+       fnc_sample<double>* fnc_sample_;
+
+       // Give sig_sample access to the private helper functions
+       friend struct fnc_sample<double>;
+};
+
+} // namespace data
+} // namespace pv
+
+#endif // PULSEVIEW_PV_DATA_MATHSIGNAL_HPP
index 2bac00621a0eb2de8dfd15c65f1cdfa5b9f22e4c..4533c9b41ef7eae4d5f092c05cb58970f8af7b8d 100644 (file)
@@ -46,7 +46,6 @@ Segment::Segment(uint32_t segment_id, uint64_t samplerate, unsigned int unit_siz
        mem_optimization_requested_(false),
        is_complete_(false)
 {
-       lock_guard<recursive_mutex> lock(mutex_);
        assert(unit_size_ > 0);
 
        // Determine the number of samples we can fit in one chunk
@@ -70,7 +69,6 @@ Segment::~Segment()
 
 uint64_t Segment::get_sample_count() const
 {
-       lock_guard<recursive_mutex> lock(mutex_);
        return sample_count_;
 }
 
@@ -102,6 +100,8 @@ uint32_t Segment::segment_id() const
 void Segment::set_complete()
 {
        is_complete_ = true;
+
+       completed();
 }
 
 bool Segment::is_complete() const
@@ -211,21 +211,34 @@ void Segment::append_samples(void* data, uint64_t samples)
        sample_count_ += samples;
 }
 
-void Segment::get_raw_samples(uint64_t start, uint64_t count,
-       uint8_t* dest) const
+const uint8_t* Segment::get_raw_sample(uint64_t sample_num) const
+{
+       assert(sample_num <= sample_count_);
+
+       uint64_t chunk_num = (sample_num * unit_size_) / chunk_size_;
+       uint64_t chunk_offs = (sample_num * unit_size_) % chunk_size_;
+
+       lock_guard<recursive_mutex> lock(mutex_);  // Because of free_unused_memory()
+
+       const uint8_t* chunk = data_chunks_[chunk_num];
+
+       return chunk + chunk_offs;
+}
+
+void Segment::get_raw_samples(uint64_t start, uint64_t count, uint8_t* dest) const
 {
        assert(start < sample_count_);
        assert(start + count <= sample_count_);
        assert(count > 0);
        assert(dest != nullptr);
 
-       lock_guard<recursive_mutex> lock(mutex_);
-
        uint8_t* dest_ptr = dest;
 
        uint64_t chunk_num = (start * unit_size_) / chunk_size_;
        uint64_t chunk_offs = (start * unit_size_) % chunk_size_;
 
+       lock_guard<recursive_mutex> lock(mutex_);  // Because of free_unused_memory()
+
        while (count > 0) {
                const uint8_t* chunk = data_chunks_[chunk_num];
 
index 8465949c3a13e444846b9596e2aa413592092f5c..66085fafc935d151f5ded69cc7863bff984858a4 100644 (file)
 
 #include "pv/util.hpp"
 
+#include <atomic>
 #include <mutex>
 #include <thread>
-#include <vector>
+#include <deque>
 
 #include <QObject>
 
+using std::atomic;
 using std::recursive_mutex;
-using std::vector;
+using std::deque;
 
 namespace SegmentTest {
 struct SmallSize8Single;
@@ -81,9 +83,13 @@ public:
 
        void free_unused_memory();
 
+Q_SIGNALS:
+       void completed();
+
 protected:
        void append_single_sample(void *data);
        void append_samples(void *data, uint64_t samples);
+       const uint8_t* get_raw_sample(uint64_t sample_num) const;
        void get_raw_samples(uint64_t start, uint64_t count, uint8_t *dest) const;
 
        SegmentDataIterator* begin_sample_iteration(uint64_t start);
@@ -94,10 +100,10 @@ protected:
 
        uint32_t segment_id_;
        mutable recursive_mutex mutex_;
-       vector<uint8_t*> data_chunks_;
+       deque<uint8_t*> data_chunks_;
        uint8_t* current_chunk_;
        uint64_t used_samples_, unused_samples_;
-       uint64_t sample_count_;
+       atomic<uint64_t> sample_count_;
        pv::util::Timestamp start_time_;
        double samplerate_;
        uint64_t chunk_size_;
index e1091f110d398fd02095114a57862f060f120a8d..578d908f8d52a036b4e5c9f4a553a34865264bb3 100644 (file)
@@ -28,8 +28,9 @@
 
 #include <QDebug>
 
-#include <pv/binding/decoder.hpp>
+#include <extdef.h>
 #include <pv/session.hpp>
+#include <pv/binding/decoder.hpp>
 
 using std::dynamic_pointer_cast;
 using std::make_shared;
@@ -41,6 +42,33 @@ using std::unique_lock;
 namespace pv {
 namespace data {
 
+const QColor SignalBase::AnalogSignalColors[8] =
+{
+       QColor(0xC4, 0xA0, 0x00),       // Yellow   HSV:  49 / 100 / 77
+       QColor(0x87, 0x20, 0x7A),       // Magenta  HSV: 308 /  70 / 53
+       QColor(0x20, 0x4A, 0x87),       // Blue     HSV: 216 /  76 / 53
+       QColor(0x4E, 0x9A, 0x06),       // Green    HSV:  91 /  96 / 60
+       QColor(0xBF, 0x6E, 0x00),       // Orange   HSV:  35 / 100 / 75
+       QColor(0x5E, 0x20, 0x80),       // Purple   HSV: 280 /  75 / 50
+       QColor(0x20, 0x80, 0x7A),       // Turqoise HSV: 177 /  75 / 50
+       QColor(0x80, 0x20, 0x24)        // Red      HSV: 358 /  75 / 50
+};
+
+const QColor SignalBase::LogicSignalColors[10] =
+{
+       QColor(0x16, 0x19, 0x1A),       // Black
+       QColor(0x8F, 0x52, 0x02),       // Brown
+       QColor(0xCC, 0x00, 0x00),       // Red
+       QColor(0xF5, 0x79, 0x00),       // Orange
+       QColor(0xED, 0xD4, 0x00),       // Yellow
+       QColor(0x73, 0xD2, 0x16),       // Green
+       QColor(0x34, 0x65, 0xA4),       // Blue
+       QColor(0x75, 0x50, 0x7B),       // Violet
+       QColor(0x88, 0x8A, 0x85),       // Grey
+       QColor(0xEE, 0xEE, 0xEC),       // White
+};
+
+
 const int SignalBase::ColorBGAlpha = 8 * 256 / 100;
 const uint64_t SignalBase::ConversionBlockSize = 4096;
 const uint32_t SignalBase::ConversionDelay = 1000;  // 1 second
@@ -95,7 +123,8 @@ SignalBase::SignalBase(shared_ptr<sigrok::Channel> channel, ChannelType channel_
        group_(nullptr),
        conversion_type_(NoConversion),
        min_value_(0),
-       max_value_(0)
+       max_value_(0),
+       error_message_("")
 {
        if (channel_) {
                internal_name_ = QString::fromStdString(channel_->name());
@@ -106,6 +135,13 @@ SignalBase::SignalBase(shared_ptr<sigrok::Channel> channel, ChannelType channel_
                this, SLOT(on_delayed_conversion_start()));
        delayed_conversion_starter_.setSingleShot(true);
        delayed_conversion_starter_.setInterval(ConversionDelay);
+
+       // Only logic and analog SR channels can have their colors auto-set
+       // because for them, we have an index that can be used
+       if (channel_type == LogicChannel)
+               set_color(LogicSignalColors[index() % countof(LogicSignalColors)]);
+       else if (channel_type == AnalogChannel)
+               set_color(AnalogSignalColors[index() % countof(AnalogSignalColors)]);
 }
 
 SignalBase::~SignalBase()
@@ -118,6 +154,12 @@ shared_ptr<sigrok::Channel> SignalBase::channel() const
        return channel_;
 }
 
+bool SignalBase::is_generated() const
+{
+       // Only signals associated with a device have a corresponding sigrok channel
+       return channel_ == nullptr;
+}
+
 bool SignalBase::enabled() const
 {
        return (channel_) ? channel_->enabled() : true;
@@ -217,6 +259,11 @@ QColor SignalBase::bgcolor() const
        return bgcolor_;
 }
 
+QString SignalBase::get_error_message() const
+{
+       return error_message_;
+}
+
 void SignalBase::set_data(shared_ptr<pv::data::SignalData> data)
 {
        if (data_) {
@@ -225,13 +272,10 @@ void SignalBase::set_data(shared_ptr<pv::data::SignalData> data)
                disconnect(data.get(), SIGNAL(samples_added(shared_ptr<Segment>, uint64_t, uint64_t)),
                        this, SLOT(on_samples_added(shared_ptr<Segment>, uint64_t, uint64_t)));
 
-               if (channel_type_ == AnalogChannel) {
-                       shared_ptr<Analog> analog = analog_data();
-                       assert(analog);
-
+               shared_ptr<Analog> analog = analog_data();
+               if (analog)
                        disconnect(analog.get(), SIGNAL(min_max_changed(float, float)),
                                this, SLOT(on_min_max_changed(float, float)));
-               }
        }
 
        data_ = data;
@@ -242,13 +286,10 @@ void SignalBase::set_data(shared_ptr<pv::data::SignalData> data)
                connect(data.get(), SIGNAL(samples_added(SharedPtrToSegment, uint64_t, uint64_t)),
                        this, SLOT(on_samples_added(SharedPtrToSegment, uint64_t, uint64_t)));
 
-               if (channel_type_ == AnalogChannel) {
-                       shared_ptr<Analog> analog = analog_data();
-                       assert(analog);
-
+               shared_ptr<Analog> analog = analog_data();
+               if (analog)
                        connect(analog.get(), SIGNAL(min_max_changed(float, float)),
                                this, SLOT(on_min_max_changed(float, float)));
-               }
        }
 }
 
@@ -263,20 +304,18 @@ void SignalBase::clear_sample_data()
 
 shared_ptr<data::Analog> SignalBase::analog_data() const
 {
-       shared_ptr<Analog> result = nullptr;
-
-       if (channel_type_ == AnalogChannel)
-               result = dynamic_pointer_cast<Analog>(data_);
+       if (!data_)
+               return nullptr;
 
-       return result;
+       return dynamic_pointer_cast<Analog>(data_);
 }
 
 shared_ptr<data::Logic> SignalBase::logic_data() const
 {
-       shared_ptr<Logic> result = nullptr;
+       if (!data_)
+               return nullptr;
 
-       if (channel_type_ == LogicChannel)
-               result = dynamic_pointer_cast<Logic>(data_);
+       shared_ptr<Logic> result = dynamic_pointer_cast<Logic>(data_);
 
        if (((conversion_type_ == A2LConversionByThreshold) ||
                (conversion_type_ == A2LConversionBySchmittTrigger)))
@@ -289,25 +328,25 @@ bool SignalBase::segment_is_complete(uint32_t segment_id) const
 {
        bool result = true;
 
-       if (channel_type_ == AnalogChannel)
+       shared_ptr<Analog> adata = analog_data();
+       if (adata)
        {
-               shared_ptr<Analog> data = dynamic_pointer_cast<Analog>(data_);
-               auto segments = data->analog_segments();
+               auto segments = adata->analog_segments();
                try {
                        result = segments.at(segment_id)->is_complete();
                } catch (out_of_range&) {
                        // Do nothing
                }
-       }
-
-       if (channel_type_ == LogicChannel)
-       {
-               shared_ptr<Logic> data = dynamic_pointer_cast<Logic>(data_);
-               auto segments = data->logic_segments();
-               try {
-                       result = segments.at(segment_id)->is_complete();
-               } catch (out_of_range&) {
-                       // Do nothing
+       } else {
+               shared_ptr<Logic> ldata = logic_data();
+               if (ldata) {
+                       shared_ptr<Logic> data = dynamic_pointer_cast<Logic>(data_);
+                       auto segments = data->logic_segments();
+                       try {
+                               result = segments.at(segment_id)->is_complete();
+                       } catch (out_of_range&) {
+                               // Do nothing
+                       }
                }
        }
 
@@ -318,21 +357,16 @@ bool SignalBase::has_samples() const
 {
        bool result = false;
 
-       if (channel_type_ == AnalogChannel)
-       {
-               shared_ptr<Analog> data = dynamic_pointer_cast<Analog>(data_);
-               if (data) {
-                       auto segments = data->analog_segments();
-                       if ((segments.size() > 0) && (segments.front()->get_sample_count() > 0))
-                               result = true;
-               }
-       }
-
-       if (channel_type_ == LogicChannel)
+       shared_ptr<Analog> adata = analog_data();
+       if (adata)
        {
-               shared_ptr<Logic> data = dynamic_pointer_cast<Logic>(data_);
-               if (data) {
-                       auto segments = data->logic_segments();
+               auto segments = adata->analog_segments();
+               if ((segments.size() > 0) && (segments.front()->get_sample_count() > 0))
+                       result = true;
+       } else {
+               shared_ptr<Logic> ldata = logic_data();
+               if (ldata) {
+                       auto segments = ldata->logic_segments();
                        if ((segments.size() > 0) && (segments.front()->get_sample_count() > 0))
                                result = true;
                }
@@ -343,18 +377,13 @@ bool SignalBase::has_samples() const
 
 double SignalBase::get_samplerate() const
 {
-       if (channel_type_ == AnalogChannel)
-       {
-               shared_ptr<Analog> data = dynamic_pointer_cast<Analog>(data_);
-               if (data)
-                       return data->get_samplerate();
-       }
-
-       if (channel_type_ == LogicChannel)
-       {
-               shared_ptr<Logic> data = dynamic_pointer_cast<Logic>(data_);
-               if (data)
-                       return data->get_samplerate();
+       shared_ptr<Analog> adata = analog_data();
+       if (adata)
+               return adata->get_samplerate();
+       else {
+               shared_ptr<Logic> ldata = logic_data();
+               if (ldata)
+                       return ldata->get_samplerate();
        }
 
        // Default samplerate is 1 Hz
@@ -580,23 +609,25 @@ void SignalBase::restore_settings(QSettings &settings)
 
 bool SignalBase::conversion_is_a2l() const
 {
-       return ((channel_type_ == AnalogChannel) &&
-               ((conversion_type_ == A2LConversionByThreshold) ||
+       return (((conversion_type_ == A2LConversionByThreshold) ||
                (conversion_type_ == A2LConversionBySchmittTrigger)));
 }
 
-void SignalBase::convert_single_segment_range(AnalogSegment *asegment,
-       LogicSegment *lsegment, uint64_t start_sample, uint64_t end_sample)
+void SignalBase::convert_single_segment_range(shared_ptr<AnalogSegment> asegment,
+       shared_ptr<LogicSegment> lsegment, uint64_t start_sample, uint64_t end_sample)
 {
        if (end_sample > start_sample) {
                tie(min_value_, max_value_) = asegment->get_min_max();
 
                // Create sigrok::Analog instance
                float *asamples = new float[ConversionBlockSize];
+               assert(asamples);
                uint8_t *lsamples = new uint8_t[ConversionBlockSize];
+               assert(lsamples);
 
                vector<shared_ptr<sigrok::Channel> > channels;
-               channels.push_back(channel_);
+               if (channel_)
+                       channels.push_back(channel_);
 
                vector<const sigrok::QuantityFlag*> mq_flags;
                const sigrok::Quantity * const mq = sigrok::Quantity::VOLTAGE;
@@ -657,6 +688,7 @@ void SignalBase::convert_single_segment_range(AnalogSegment *asegment,
 
                                lsegment->append_payload(logic->data_pointer(), logic->data_length());
                                samples_added(lsegment->segment_id(), i, i + ConversionBlockSize);
+
                                i += ConversionBlockSize;
                        }
 
@@ -682,7 +714,8 @@ void SignalBase::convert_single_segment_range(AnalogSegment *asegment,
        }
 }
 
-void SignalBase::convert_single_segment(AnalogSegment *asegment, LogicSegment *lsegment)
+void SignalBase::convert_single_segment(shared_ptr<AnalogSegment> asegment,
+       shared_ptr<LogicSegment> lsegment)
 {
        uint64_t start_sample, end_sample, old_end_sample;
        start_sample = end_sample = 0;
@@ -736,7 +769,7 @@ void SignalBase::conversion_thread_proc()
 
        uint32_t segment_id = 0;
 
-       AnalogSegment *asegment = analog_data->analog_segments().front().get();
+       shared_ptr<AnalogSegment> asegment = analog_data->analog_segments().front();
        assert(asegment);
 
        const shared_ptr<Logic> logic_data = dynamic_pointer_cast<Logic>(converted_data_);
@@ -749,7 +782,7 @@ void SignalBase::conversion_thread_proc()
                logic_data->push_segment(new_segment);
        }
 
-       LogicSegment *lsegment = logic_data->logic_segments().front().get();
+       shared_ptr<LogicSegment> lsegment = logic_data->logic_segments().front();
        assert(lsegment);
 
        do {
@@ -758,11 +791,12 @@ void SignalBase::conversion_thread_proc()
                // Only advance to next segment if the current input segment is complete
                if (asegment->is_complete() &&
                        analog_data->analog_segments().size() > logic_data->logic_segments().size()) {
+
                        // There are more segments to process
                        segment_id++;
 
                        try {
-                               asegment = analog_data->analog_segments().at(segment_id).get();
+                               asegment = analog_data->analog_segments().at(segment_id);
                        } catch (out_of_range&) {
                                qDebug() << "Conversion error for" << name() << ": no analog segment" \
                                        << segment_id << ", segments size is" << analog_data->analog_segments().size();
@@ -773,13 +807,13 @@ void SignalBase::conversion_thread_proc()
                                *logic_data.get(), segment_id, 1, asegment->samplerate());
                        logic_data->push_segment(new_segment);
 
-                       lsegment = logic_data->logic_segments().back().get();
-               } else {
-                       // No more samples/segments to process, wait for data or interrupt
-                       if (!conversion_interrupt_) {
-                               unique_lock<mutex> input_lock(conversion_input_mutex_);
-                               conversion_input_cond_.wait(input_lock);
-                       }
+                       lsegment = logic_data->logic_segments().back();
+               }
+
+               // No more samples/segments to process, wait for data or interrupt
+               if (!conversion_interrupt_) {
+                       unique_lock<mutex> input_lock(conversion_input_mutex_);
+                       conversion_input_cond_.wait(input_lock);
                }
        } while (!conversion_interrupt_);
 }
@@ -795,11 +829,20 @@ void SignalBase::start_conversion(bool delayed_start)
 
        if (converted_data_)
                converted_data_->clear();
+
        samples_cleared();
 
        conversion_interrupt_ = false;
-       conversion_thread_ = std::thread(
-               &SignalBase::conversion_thread_proc, this);
+       conversion_thread_ = std::thread(&SignalBase::conversion_thread_proc, this);
+}
+
+void SignalBase::set_error_message(QString msg)
+{
+       error_message_ = msg;
+       // TODO Emulate noquote()
+       qDebug().nospace() << name() << ": " << msg;
+
+       error_message_changed(msg);
 }
 
 void SignalBase::stop_conversion()
index f646a40dcc24f7255822b6f4a9584a4f721c92b6..ee927aae27bf911c96f2c396a4a14fc42a22d33a 100644 (file)
@@ -87,6 +87,7 @@ private:
 class SignalBase : public QObject, public enable_shared_from_this<SignalBase>
 {
        Q_OBJECT
+       Q_PROPERTY(QString error_message READ get_error_message)
 
 public:
        enum ChannelType {
@@ -112,6 +113,9 @@ public:
                DynamicPreset = 0  ///< Conversion uses calculated values
        };
 
+       static const QColor AnalogSignalColors[8];
+       static const QColor LogicSignalColors[10];
+
 private:
        static const int ColorBGAlpha;
        static const uint64_t ConversionBlockSize;
@@ -128,6 +132,11 @@ public:
         */
        shared_ptr<sigrok::Channel> channel() const;
 
+       /**
+        * Returns whether this channel is generated or a channel associated with the device.
+        */
+       bool is_generated() const;
+
        /**
         * Returns enabled status of this channel.
         */
@@ -217,6 +226,11 @@ public:
         */
        QColor bgcolor() const;
 
+       /**
+        * Returns the current error message text.
+        */
+       virtual QString get_error_message() const;
+
        /**
         * Sets the internal data object.
         */
@@ -334,37 +348,36 @@ public:
 #endif
 
        virtual void save_settings(QSettings &settings) const;
-
        virtual void restore_settings(QSettings &settings);
 
        void start_conversion(bool delayed_start=false);
 
+protected:
+       virtual void set_error_message(QString msg);
+
 private:
+       void stop_conversion();
+
        bool conversion_is_a2l() const;
 
        uint8_t convert_a2l_threshold(float threshold, float value);
        uint8_t convert_a2l_schmitt_trigger(float lo_thr, float hi_thr,
                float value, uint8_t &state);
 
-       void convert_single_segment_range(AnalogSegment *asegment,
-               LogicSegment *lsegment, uint64_t start_sample, uint64_t end_sample);
-       void convert_single_segment(pv::data::AnalogSegment *asegment,
-               pv::data::LogicSegment *lsegment);
+       void convert_single_segment_range(shared_ptr<AnalogSegment> asegment,
+               shared_ptr<LogicSegment> lsegment, uint64_t start_sample, uint64_t end_sample);
+       void convert_single_segment(shared_ptr<AnalogSegment> asegment,
+               shared_ptr<LogicSegment> lsegment);
        void conversion_thread_proc();
 
-       void stop_conversion();
-
 Q_SIGNALS:
        void enabled_changed(const bool &value);
-
        void name_changed(const QString &name);
-
        void color_changed(const QColor &color);
-
+       void error_message_changed(const QString &msg);
        void conversion_type_changed(const ConversionType t);
 
        void samples_cleared();
-
        void samples_added(uint64_t segment_id, uint64_t start_sample,
                uint64_t end_sample);
 
@@ -402,9 +415,13 @@ protected:
        QString internal_name_, name_;
        QColor color_, bgcolor_;
        unsigned int index_;
+
+       QString error_message_;
 };
 
 } // namespace data
 } // namespace pv
 
+Q_DECLARE_METATYPE(shared_ptr<pv::data::SignalBase>);
+
 #endif // PULSEVIEW_PV_DATA_SIGNALBASE_HPP
index 9f458c99953e8c26c1721b5b9e819e73f9f1a1f7..efe854af4efea9c916153a419b86a47d197992bb 100644 (file)
@@ -222,8 +222,8 @@ QWidget *Settings::get_general_settings_form(QWidget *parent) const
 
        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());
+               const QLocale locale = QLocale(language);
+               const QString desc = locale.languageToString(locale.language());
                language_cb->addItem(desc, language);
 
                if (language == current_language) {
@@ -467,6 +467,7 @@ QWidget *Settings::get_about_page(QWidget *parent) const
                tr("Protocol decoder search paths:") + "</b></td></tr>");
        for (QString &entry : a->get_pd_path_list())
                s.append(QString("<tr><td colspan=\"2\">%1</td></tr>").arg(entry));
+       s.append(tr("<tr><td colspan=\"2\">(Note: Set environment variable SIGROKDECODE_DIR to add a custom directory)</td></tr>"));
 #endif
 
        s.append("<tr><td colspan=\"2\"></td></tr>");
@@ -499,6 +500,21 @@ QWidget *Settings::get_about_page(QWidget *parent) const
                        .arg(entry.first, entry.second));
 #endif
 
+       s.append("<tr><td colspan=\"2\"></td></tr>");
+       s.append("<tr><td colspan=\"2\"><b>" +
+               tr("Available Translations:") + "</b></td></tr>");
+       for (const QString& language : a->get_languages()) {
+               if (language == "en")
+                       continue;
+
+               const QLocale locale = QLocale(language);
+               const QString desc = locale.languageToString(locale.language());
+               const QString editors = a->get_language_editors(language);
+
+               s.append(QString("<tr><td class=\"id\"><i>%1</i></td><td>(%2)</td></tr>")
+                       .arg(desc, editors));
+       }
+
        s.append("</table>");
 
        QTextDocument *supported_doc = new QTextDocument();
index 157fa6ba1de7881a3c265ffa4b7790af957b56be..18920b814e3b1511a547e1658a8f4c84cda4d2ab 100644 (file)
@@ -53,10 +53,10 @@ public:
        QWidget *get_about_page(QWidget *parent) const;
        QWidget *get_logging_page(QWidget *parent) const;
 
+private Q_SLOTS:
        void accept();
        void reject();
 
-private Q_SLOTS:
        void on_page_changed(QListWidgetItem *current, QListWidgetItem *previous);
        void on_general_language_changed(const QString &text);
        void on_general_theme_changed(int value);
diff --git a/pv/exprtk.hpp b/pv/exprtk.hpp
new file mode 100644 (file)
index 0000000..d1f6cf1
--- /dev/null
@@ -0,0 +1,39052 @@
+/*
+ ******************************************************************
+ *           C++ Mathematical Expression Toolkit Library          *
+ *                                                                *
+ * Author: Arash Partow (1999-2020)                               *
+ * URL: http://www.partow.net/programming/exprtk/index.html       *
+ *                                                                *
+ * Copyright notice:                                              *
+ * Free use of the C++ Mathematical Expression Toolkit Library is *
+ * permitted under the guidelines and in accordance with the most *
+ * current version of the MIT License.                            *
+ * http://www.opensource.org/licenses/MIT                         *
+ *                                                                *
+ * Example expressions:                                           *
+ * (00) (y + x / y) * (x - y / x)                                 *
+ * (01) (x^2 / sin(2 * pi / y)) - x / 2                           *
+ * (02) sqrt(1 - (x^2))                                           *
+ * (03) 1 - sin(2 * x) + cos(pi / y)                              *
+ * (04) a * exp(2 * t) + c                                        *
+ * (05) if(((x + 2) == 3) and ((y + 5) <= 9),1 + w, 2 / z)        *
+ * (06) (avg(x,y) <= x + y ? x - y : x * y) + 2 * pi / x          *
+ * (07) z := x + sin(2 * pi / y)                                  *
+ * (08) u := 2 * (pi * z) / (w := x + cos(y / pi))                *
+ * (09) clamp(-1,sin(2 * pi * x) + cos(y / 2 * pi),+1)            *
+ * (10) inrange(-2,m,+2) == if(({-2 <= m} and [m <= +2]),1,0)     *
+ * (11) (2sin(x)cos(2y)7 + 1) == (2 * sin(x) * cos(2*y) * 7 + 1)  *
+ * (12) (x ilike 's*ri?g') and [y < (3 z^7 + w)]                  *
+ *                                                                *
+ ******************************************************************
+*/
+
+
+#ifndef INCLUDE_EXPRTK_HPP
+#define INCLUDE_EXPRTK_HPP
+
+
+#include <algorithm>
+#include <cctype>
+#include <cmath>
+#include <complex>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <deque>
+#include <exception>
+#include <functional>
+#include <iterator>
+#include <limits>
+#include <list>
+#include <map>
+#include <set>
+#include <stack>
+#include <stdexcept>
+#include <string>
+#include <utility>
+#include <vector>
+
+
+namespace exprtk
+{
+   #ifdef exprtk_enable_debugging
+     #define exprtk_debug(params) printf params
+   #else
+     #define exprtk_debug(params) (void)0
+   #endif
+
+   #define exprtk_error_location             \
+   "exprtk.hpp:" + details::to_str(__LINE__) \
+
+   #if defined(__GNUC__) && (__GNUC__  >= 7)
+
+      #define exprtk_disable_fallthrough_begin                      \
+      _Pragma ("GCC diagnostic push")                               \
+      _Pragma ("GCC diagnostic ignored \"-Wimplicit-fallthrough\"") \
+
+      #define exprtk_disable_fallthrough_end                        \
+      _Pragma ("GCC diagnostic pop")                                \
+
+   #else
+      #define exprtk_disable_fallthrough_begin (void)0;
+      #define exprtk_disable_fallthrough_end   (void)0;
+   #endif
+
+   namespace details
+   {
+      typedef unsigned char            uchar_t;
+      typedef char                      char_t;
+      typedef uchar_t*               uchar_ptr;
+      typedef char_t*                 char_ptr;
+      typedef uchar_t const*        uchar_cptr;
+      typedef char_t const*          char_cptr;
+      typedef unsigned long long int _uint64_t;
+      typedef long long int           _int64_t;
+
+      inline bool is_whitespace(const char_t c)
+      {
+         return (' '  == c) || ('\n' == c) ||
+                ('\r' == c) || ('\t' == c) ||
+                ('\b' == c) || ('\v' == c) ||
+                ('\f' == c) ;
+      }
+
+      inline bool is_operator_char(const char_t c)
+      {
+         return ('+' == c) || ('-' == c) ||
+                ('*' == c) || ('/' == c) ||
+                ('^' == c) || ('<' == c) ||
+                ('>' == c) || ('=' == c) ||
+                (',' == c) || ('!' == c) ||
+                ('(' == c) || (')' == c) ||
+                ('[' == c) || (']' == c) ||
+                ('{' == c) || ('}' == c) ||
+                ('%' == c) || (':' == c) ||
+                ('?' == c) || ('&' == c) ||
+                ('|' == c) || (';' == c) ;
+      }
+
+      inline bool is_letter(const char_t c)
+      {
+         return (('a' <= c) && (c <= 'z')) ||
+                (('A' <= c) && (c <= 'Z')) ;
+      }
+
+      inline bool is_digit(const char_t c)
+      {
+         return ('0' <= c) && (c <= '9');
+      }
+
+      inline bool is_letter_or_digit(const char_t c)
+      {
+         return is_letter(c) || is_digit(c);
+      }
+
+      inline bool is_left_bracket(const char_t c)
+      {
+         return ('(' == c) || ('[' == c) || ('{' == c);
+      }
+
+      inline bool is_right_bracket(const char_t c)
+      {
+         return (')' == c) || (']' == c) || ('}' == c);
+      }
+
+      inline bool is_bracket(const char_t c)
+      {
+         return is_left_bracket(c) || is_right_bracket(c);
+      }
+
+      inline bool is_sign(const char_t c)
+      {
+         return ('+' == c) || ('-' == c);
+      }
+
+      inline bool is_invalid(const char_t c)
+      {
+         return !is_whitespace   (c) &&
+                !is_operator_char(c) &&
+                !is_letter       (c) &&
+                !is_digit        (c) &&
+                ('.'  != c)          &&
+                ('_'  != c)          &&
+                ('$'  != c)          &&
+                ('~'  != c)          &&
+                ('\'' != c);
+      }
+
+      #ifndef exprtk_disable_caseinsensitivity
+      inline void case_normalise(std::string& s)
+      {
+         for (std::size_t i = 0; i < s.size(); ++i)
+         {
+            s[i] = static_cast<std::string::value_type>(std::tolower(s[i]));
+         }
+      }
+
+      inline bool imatch(const char_t c1, const char_t c2)
+      {
+         return std::tolower(c1) == std::tolower(c2);
+      }
+
+      inline bool imatch(const std::string& s1, const std::string& s2)
+      {
+         if (s1.size() == s2.size())
+         {
+            for (std::size_t i = 0; i < s1.size(); ++i)
+            {
+               if (std::tolower(s1[i]) != std::tolower(s2[i]))
+               {
+                  return false;
+               }
+            }
+
+            return true;
+         }
+
+         return false;
+      }
+
+      struct ilesscompare
+      {
+         inline bool operator() (const std::string& s1, const std::string& s2) const
+         {
+            const std::size_t length = std::min(s1.size(),s2.size());
+
+            for (std::size_t i = 0; i < length;  ++i)
+            {
+               const char_t c1 = static_cast<char>(std::tolower(s1[i]));
+               const char_t c2 = static_cast<char>(std::tolower(s2[i]));
+
+               if (c1 > c2)
+                  return false;
+               else if (c1 < c2)
+                  return true;
+            }
+
+            return s1.size() < s2.size();
+         }
+      };
+
+      #else
+      inline void case_normalise(std::string&)
+      {}
+
+      inline bool imatch(const char_t c1, const char_t c2)
+      {
+         return c1 == c2;
+      }
+
+      inline bool imatch(const std::string& s1, const std::string& s2)
+      {
+         return s1 == s2;
+      }
+
+      struct ilesscompare
+      {
+         inline bool operator() (const std::string& s1, const std::string& s2) const
+         {
+            return s1 < s2;
+         }
+      };
+      #endif
+
+      inline bool is_valid_sf_symbol(const std::string& symbol)
+      {
+         // Special function: $f12 or $F34
+         return (4 == symbol.size())  &&
+                ('$' == symbol[0])    &&
+                imatch('f',symbol[1]) &&
+                is_digit(symbol[2])   &&
+                is_digit(symbol[3]);
+      }
+
+      inline const char_t& front(const std::string& s)
+      {
+         return s[0];
+      }
+
+      inline const char_t& back(const std::string& s)
+      {
+         return s[s.size() - 1];
+      }
+
+      inline std::string to_str(int i)
+      {
+         if (0 == i)
+            return std::string("0");
+
+         std::string result;
+
+         if (i < 0)
+         {
+            for ( ; i; i /= 10)
+            {
+               result += '0' + char(-(i % 10));
+            }
+
+            result += '-';
+         }
+         else
+         {
+            for ( ; i; i /= 10)
+            {
+               result += '0' + char(i % 10);
+            }
+         }
+
+         std::reverse(result.begin(), result.end());
+
+         return result;
+      }
+
+      inline std::string to_str(std::size_t i)
+      {
+         return to_str(static_cast<int>(i));
+      }
+
+      inline bool is_hex_digit(const std::string::value_type digit)
+      {
+         return (('0' <= digit) && (digit <= '9')) ||
+                (('A' <= digit) && (digit <= 'F')) ||
+                (('a' <= digit) && (digit <= 'f')) ;
+      }
+
+      inline uchar_t hex_to_bin(uchar_t h)
+      {
+         if (('0' <= h) && (h <= '9'))
+            return (h - '0');
+         else
+            return static_cast<unsigned char>(std::toupper(h) - 'A');
+      }
+
+      template <typename Iterator>
+      inline void parse_hex(Iterator& itr, Iterator end, std::string::value_type& result)
+      {
+         if (
+              (end !=  (itr    )) &&
+              (end !=  (itr + 1)) &&
+              (end !=  (itr + 2)) &&
+              (end !=  (itr + 3)) &&
+              ('0' == *(itr    )) &&
+              (
+                ('x' == *(itr + 1)) ||
+                ('X' == *(itr + 1))
+              ) &&
+              (is_hex_digit(*(itr + 2))) &&
+              (is_hex_digit(*(itr + 3)))
+            )
+         {
+            result = hex_to_bin(static_cast<uchar_t>(*(itr + 2))) << 4 |
+                     hex_to_bin(static_cast<uchar_t>(*(itr + 3))) ;
+            itr += 3;
+         }
+         else
+            result = '\0';
+      }
+
+      inline void cleanup_escapes(std::string& s)
+      {
+         typedef std::string::iterator str_itr_t;
+
+         str_itr_t itr1 = s.begin();
+         str_itr_t itr2 = s.begin();
+         str_itr_t end  = s.end  ();
+
+         std::size_t removal_count  = 0;
+
+         while (end != itr1)
+         {
+            if ('\\' == (*itr1))
+            {
+               ++removal_count;
+
+               if (end == ++itr1)
+                  break;
+               else if ('\\' != (*itr1))
+               {
+                  switch (*itr1)
+                  {
+                     case 'n' : (*itr1) = '\n'; break;
+                     case 'r' : (*itr1) = '\r'; break;
+                     case 't' : (*itr1) = '\t'; break;
+                     case '0' : parse_hex(itr1, end, (*itr1));
+                                removal_count += 3;
+                                break;
+                  }
+
+                  continue;
+               }
+            }
+
+            if (itr1 != itr2)
+            {
+               (*itr2) = (*itr1);
+            }
+
+            ++itr1;
+            ++itr2;
+         }
+
+         s.resize(s.size() - removal_count);
+      }
+
+      class build_string
+      {
+      public:
+
+         build_string(const std::size_t& initial_size = 64)
+         {
+            data_.reserve(initial_size);
+         }
+
+         inline build_string& operator << (const std::string& s)
+         {
+            data_ += s;
+            return (*this);
+         }
+
+         inline build_string& operator << (char_cptr s)
+         {
+            data_ += std::string(s);
+            return (*this);
+         }
+
+         inline operator std::string () const
+         {
+            return data_;
+         }
+
+         inline std::string as_string() const
+         {
+            return data_;
+         }
+
+      private:
+
+         std::string data_;
+      };
+
+      static const std::string reserved_words[] =
+                                  {
+                                    "break",  "case",  "continue",  "default",  "false",  "for",
+                                    "if", "else", "ilike",  "in", "like", "and",  "nand", "nor",
+                                    "not",  "null",  "or",   "repeat", "return",  "shl",  "shr",
+                                    "swap", "switch", "true",  "until", "var",  "while", "xnor",
+                                    "xor", "&", "|"
+                                  };
+
+      static const std::size_t reserved_words_size = sizeof(reserved_words) / sizeof(std::string);
+
+      static const std::string reserved_symbols[] =
+                                  {
+                                    "abs",  "acos",  "acosh",  "and",  "asin",  "asinh", "atan",
+                                    "atanh", "atan2", "avg",  "break", "case", "ceil",  "clamp",
+                                    "continue",   "cos",   "cosh",   "cot",   "csc",  "default",
+                                    "deg2grad",  "deg2rad",   "equal",  "erf",   "erfc",  "exp",
+                                    "expm1",  "false",   "floor",  "for",   "frac",  "grad2deg",
+                                    "hypot", "iclamp", "if",  "else", "ilike", "in",  "inrange",
+                                    "like",  "log",  "log10", "log2",  "logn",  "log1p", "mand",
+                                    "max", "min",  "mod", "mor",  "mul", "ncdf",  "nand", "nor",
+                                    "not",   "not_equal",   "null",   "or",   "pow",  "rad2deg",
+                                    "repeat", "return", "root", "round", "roundn", "sec", "sgn",
+                                    "shl", "shr", "sin", "sinc", "sinh", "sqrt",  "sum", "swap",
+                                    "switch", "tan",  "tanh", "true",  "trunc", "until",  "var",
+                                    "while", "xnor", "xor", "&", "|"
+                                  };
+
+      static const std::size_t reserved_symbols_size = sizeof(reserved_symbols) / sizeof(std::string);
+
+      static const std::string base_function_list[] =
+                                  {
+                                    "abs", "acos",  "acosh", "asin",  "asinh", "atan",  "atanh",
+                                    "atan2",  "avg",  "ceil",  "clamp",  "cos",  "cosh",  "cot",
+                                    "csc",  "equal",  "erf",  "erfc",  "exp",  "expm1", "floor",
+                                    "frac", "hypot", "iclamp",  "like", "log", "log10",  "log2",
+                                    "logn", "log1p", "mand", "max", "min", "mod", "mor",  "mul",
+                                    "ncdf",  "pow",  "root",  "round",  "roundn",  "sec", "sgn",
+                                    "sin", "sinc", "sinh", "sqrt", "sum", "swap", "tan", "tanh",
+                                    "trunc",  "not_equal",  "inrange",  "deg2grad",   "deg2rad",
+                                    "rad2deg", "grad2deg"
+                                  };
+
+      static const std::size_t base_function_list_size = sizeof(base_function_list) / sizeof(std::string);
+
+      static const std::string logic_ops_list[] =
+                                  {
+                                    "and", "nand", "nor", "not", "or",  "xnor", "xor", "&", "|"
+                                  };
+
+      static const std::size_t logic_ops_list_size = sizeof(logic_ops_list) / sizeof(std::string);
+
+      static const std::string cntrl_struct_list[] =
+                                  {
+                                     "if", "switch", "for", "while", "repeat", "return"
+                                  };
+
+      static const std::size_t cntrl_struct_list_size = sizeof(cntrl_struct_list) / sizeof(std::string);
+
+      static const std::string arithmetic_ops_list[] =
+                                  {
+                                    "+", "-", "*", "/", "%", "^"
+                                  };
+
+      static const std::size_t arithmetic_ops_list_size = sizeof(arithmetic_ops_list) / sizeof(std::string);
+
+      static const std::string assignment_ops_list[] =
+                                  {
+                                    ":=", "+=", "-=",
+                                    "*=", "/=", "%="
+                                  };
+
+      static const std::size_t assignment_ops_list_size = sizeof(assignment_ops_list) / sizeof(std::string);
+
+      static const std::string inequality_ops_list[] =
+                                  {
+                                     "<",  "<=", "==",
+                                     "=",  "!=", "<>",
+                                    ">=",  ">"
+                                  };
+
+      static const std::size_t inequality_ops_list_size = sizeof(inequality_ops_list) / sizeof(std::string);
+
+      inline bool is_reserved_word(const std::string& symbol)
+      {
+         for (std::size_t i = 0; i < reserved_words_size; ++i)
+         {
+            if (imatch(symbol, reserved_words[i]))
+            {
+               return true;
+            }
+         }
+
+         return false;
+      }
+
+      inline bool is_reserved_symbol(const std::string& symbol)
+      {
+         for (std::size_t i = 0; i < reserved_symbols_size; ++i)
+         {
+            if (imatch(symbol, reserved_symbols[i]))
+            {
+               return true;
+            }
+         }
+
+         return false;
+      }
+
+      inline bool is_base_function(const std::string& function_name)
+      {
+         for (std::size_t i = 0; i < base_function_list_size; ++i)
+         {
+            if (imatch(function_name, base_function_list[i]))
+            {
+               return true;
+            }
+         }
+
+         return false;
+      }
+
+      inline bool is_control_struct(const std::string& cntrl_strct)
+      {
+         for (std::size_t i = 0; i < cntrl_struct_list_size; ++i)
+         {
+            if (imatch(cntrl_strct, cntrl_struct_list[i]))
+            {
+               return true;
+            }
+         }
+
+         return false;
+      }
+
+      inline bool is_logic_opr(const std::string& lgc_opr)
+      {
+         for (std::size_t i = 0; i < logic_ops_list_size; ++i)
+         {
+            if (imatch(lgc_opr, logic_ops_list[i]))
+            {
+               return true;
+            }
+         }
+
+         return false;
+      }
+
+      struct cs_match
+      {
+         static inline bool cmp(const char_t c0, const char_t c1)
+         {
+            return (c0 == c1);
+         }
+      };
+
+      struct cis_match
+      {
+         static inline bool cmp(const char_t c0, const char_t c1)
+         {
+            return (std::tolower(c0) == std::tolower(c1));
+         }
+      };
+
+      template <typename Iterator, typename Compare>
+      inline bool match_impl(const Iterator pattern_begin,
+                             const Iterator pattern_end  ,
+                             const Iterator data_begin   ,
+                             const Iterator data_end     ,
+                             const typename std::iterator_traits<Iterator>::value_type& zero_or_more,
+                             const typename std::iterator_traits<Iterator>::value_type& zero_or_one )
+      {
+         const Iterator null_itr(0);
+
+         Iterator d_itr    = data_begin;
+         Iterator p_itr    = pattern_begin;
+         Iterator tb_p_itr = null_itr;
+         Iterator tb_d_itr = null_itr;
+
+         while (d_itr != data_end)
+         {
+            if (zero_or_more == *p_itr)
+            {
+               while ((pattern_end != p_itr) && ((zero_or_more == *p_itr) || (zero_or_one == *p_itr)))
+               {
+                  ++p_itr;
+               }
+
+               if (pattern_end == p_itr)
+                  return true;
+
+               const typename std::iterator_traits<Iterator>::value_type c = *(p_itr);
+
+               while ((data_end != d_itr) && !Compare::cmp(c,*d_itr))
+               {
+                  ++d_itr;
+               }
+
+               tb_p_itr = p_itr;
+               tb_d_itr = d_itr;
+
+               continue;
+            }
+            else if (!Compare::cmp(*p_itr, *d_itr) && (zero_or_one != *p_itr))
+            {
+               if (null_itr == tb_d_itr)
+                  return false;
+
+               d_itr = tb_d_itr++;
+               p_itr = tb_p_itr;
+
+               continue;
+            }
+
+            ++p_itr;
+            ++d_itr;
+         }
+
+         while ((pattern_end != p_itr) && ((zero_or_more == *p_itr) || (zero_or_one == *p_itr)))
+         {
+            ++p_itr;
+         }
+
+         return (pattern_end == p_itr);
+      }
+
+      inline bool wc_match(const std::string& wild_card,
+                           const std::string& str)
+      {
+         return match_impl<char_cptr,cs_match>(wild_card.data(),
+                                               wild_card.data() + wild_card.size(),
+                                               str.data(),
+                                               str.data() + str.size(),
+                                               '*',
+                                               '?');
+      }
+
+      inline bool wc_imatch(const std::string& wild_card,
+                            const std::string& str)
+      {
+         return match_impl<char_cptr,cis_match>(wild_card.data(),
+                                                wild_card.data() + wild_card.size(),
+                                                str.data(),
+                                                str.data() + str.size(),
+                                                '*',
+                                                '?');
+      }
+
+      inline bool sequence_match(const std::string& pattern,
+                                 const std::string& str,
+                                 std::size_t&       diff_index,
+                                 char_t&            diff_value)
+      {
+         if (str.empty())
+         {
+            return ("Z" == pattern);
+         }
+         else if ('*' == pattern[0])
+            return false;
+
+         typedef std::string::const_iterator itr_t;
+
+         itr_t p_itr = pattern.begin();
+         itr_t s_itr = str    .begin();
+
+         itr_t p_end = pattern.end();
+         itr_t s_end = str    .end();
+
+         while ((s_end != s_itr) && (p_end != p_itr))
+         {
+            if ('*' == (*p_itr))
+            {
+               const char_t target = static_cast<char>(std::toupper(*(p_itr - 1)));
+
+               if ('*' == target)
+               {
+                  diff_index = static_cast<std::size_t>(std::distance(str.begin(),s_itr));
+                  diff_value = static_cast<char>(std::toupper(*p_itr));
+
+                  return false;
+               }
+               else
+                  ++p_itr;
+
+               while (s_itr != s_end)
+               {
+                  if (target != std::toupper(*s_itr))
+                     break;
+                  else
+                     ++s_itr;
+               }
+
+               continue;
+            }
+            else if (
+                      ('?' != *p_itr) &&
+                      std::toupper(*p_itr) != std::toupper(*s_itr)
+                    )
+            {
+               diff_index = static_cast<std::size_t>(std::distance(str.begin(),s_itr));
+               diff_value = static_cast<char>(std::toupper(*p_itr));
+
+               return false;
+            }
+
+            ++p_itr;
+            ++s_itr;
+         }
+
+         return (
+                  (s_end == s_itr) &&
+                  (
+                    (p_end ==  p_itr) ||
+                    ('*'   == *p_itr)
+                  )
+                );
+      }
+
+      static const double pow10[] = {
+                                      1.0,
+                                      1.0E+001, 1.0E+002, 1.0E+003, 1.0E+004,
+                                      1.0E+005, 1.0E+006, 1.0E+007, 1.0E+008,
+                                      1.0E+009, 1.0E+010, 1.0E+011, 1.0E+012,
+                                      1.0E+013, 1.0E+014, 1.0E+015, 1.0E+016
+                                    };
+
+      static const std::size_t pow10_size = sizeof(pow10) / sizeof(double);
+
+      namespace numeric
+      {
+         namespace constant
+         {
+            static const double e       =  2.71828182845904523536028747135266249775724709369996;
+            static const double pi      =  3.14159265358979323846264338327950288419716939937510;
+            static const double pi_2    =  1.57079632679489661923132169163975144209858469968755;
+            static const double pi_4    =  0.78539816339744830961566084581987572104929234984378;
+            static const double pi_180  =  0.01745329251994329576923690768488612713442871888542;
+            static const double _1_pi   =  0.31830988618379067153776752674502872406891929148091;
+            static const double _2_pi   =  0.63661977236758134307553505349005744813783858296183;
+            static const double _180_pi = 57.29577951308232087679815481410517033240547246656443;
+            static const double log2    =  0.69314718055994530941723212145817656807550013436026;
+            static const double sqrt2   =  1.41421356237309504880168872420969807856967187537695;
+         }
+
+         namespace details
+         {
+            struct unknown_type_tag { unknown_type_tag() {} };
+            struct real_type_tag    { real_type_tag   () {} };
+            struct complex_type_tag { complex_type_tag() {} };
+            struct int_type_tag     { int_type_tag    () {} };
+
+            template <typename T>
+            struct number_type
+            {
+               typedef unknown_type_tag type;
+               number_type() {}
+            };
+
+            #define exprtk_register_real_type_tag(T)             \
+            template<> struct number_type<T>                     \
+            { typedef real_type_tag type; number_type() {} };    \
+
+            #define exprtk_register_complex_type_tag(T)          \
+            template<> struct number_type<std::complex<T> >      \
+            { typedef complex_type_tag type; number_type() {} }; \
+
+            #define exprtk_register_int_type_tag(T)              \
+            template<> struct number_type<T>                     \
+            { typedef int_type_tag type; number_type() {} };     \
+
+            exprtk_register_real_type_tag(double     )
+            exprtk_register_real_type_tag(long double)
+            exprtk_register_real_type_tag(float      )
+
+            exprtk_register_complex_type_tag(double     )
+            exprtk_register_complex_type_tag(long double)
+            exprtk_register_complex_type_tag(float      )
+
+            exprtk_register_int_type_tag(short         )
+            exprtk_register_int_type_tag(int           )
+            exprtk_register_int_type_tag(_int64_t      )
+            exprtk_register_int_type_tag(unsigned short)
+            exprtk_register_int_type_tag(unsigned int  )
+            exprtk_register_int_type_tag(_uint64_t     )
+
+            #undef exprtk_register_real_type_tag
+            #undef exprtk_register_int_type_tag
+
+            template <typename T>
+            struct epsilon_type
+            {
+               static inline T value()
+               {
+                  const T epsilon = T(0.0000000001);
+                  return epsilon;
+               }
+            };
+
+            template <>
+            struct epsilon_type <float>
+            {
+               static inline float value()
+               {
+                  const float epsilon = float(0.000001f);
+                  return epsilon;
+               }
+            };
+
+            template <>
+            struct epsilon_type <long double>
+            {
+               static inline long double value()
+               {
+                  const long double epsilon = (long double)(0.000000000001);
+                  return epsilon;
+               }
+            };
+
+            template <typename T>
+            inline bool is_nan_impl(const T v, real_type_tag)
+            {
+               return std::not_equal_to<T>()(v,v);
+            }
+
+            template <typename T>
+            inline int to_int32_impl(const T v, real_type_tag)
+            {
+               return static_cast<int>(v);
+            }
+
+            template <typename T>
+            inline _int64_t to_int64_impl(const T v, real_type_tag)
+            {
+               return static_cast<_int64_t>(v);
+            }
+
+            template <typename T>
+            inline bool is_true_impl(const T v)
+            {
+               return std::not_equal_to<T>()(T(0),v);
+            }
+
+            template <typename T>
+            inline bool is_false_impl(const T v)
+            {
+               return std::equal_to<T>()(T(0),v);
+            }
+
+            template <typename T>
+            inline T abs_impl(const T v, real_type_tag)
+            {
+               return ((v < T(0)) ? -v : v);
+            }
+
+            template <typename T>
+            inline T min_impl(const T v0, const T v1, real_type_tag)
+            {
+               return std::min<T>(v0,v1);
+            }
+
+            template <typename T>
+            inline T max_impl(const T v0, const T v1, real_type_tag)
+            {
+               return std::max<T>(v0,v1);
+            }
+
+            template <typename T>
+            inline T equal_impl(const T v0, const T v1, real_type_tag)
+            {
+               const T epsilon = epsilon_type<T>::value();
+               return (abs_impl(v0 - v1,real_type_tag()) <= (std::max(T(1),std::max(abs_impl(v0,real_type_tag()),abs_impl(v1,real_type_tag()))) * epsilon)) ? T(1) : T(0);
+            }
+
+            inline float equal_impl(const float v0, const float v1, real_type_tag)
+            {
+               const float epsilon = epsilon_type<float>::value();
+               return (abs_impl(v0 - v1,real_type_tag()) <= (std::max(1.0f,std::max(abs_impl(v0,real_type_tag()),abs_impl(v1,real_type_tag()))) * epsilon)) ? 1.0f : 0.0f;
+            }
+
+            template <typename T>
+            inline T equal_impl(const T v0, const T v1, int_type_tag)
+            {
+               return (v0 == v1) ? 1 : 0;
+            }
+
+            template <typename T>
+            inline T expm1_impl(const T v, real_type_tag)
+            {
+               // return std::expm1<T>(v);
+               if (abs_impl(v,real_type_tag()) < T(0.00001))
+                  return v + (T(0.5) * v * v);
+               else
+                  return std::exp(v) - T(1);
+            }
+
+            template <typename T>
+            inline T expm1_impl(const T v, int_type_tag)
+            {
+               return T(std::exp<double>(v)) - T(1);
+            }
+
+            template <typename T>
+            inline T nequal_impl(const T v0, const T v1, real_type_tag)
+            {
+               typedef real_type_tag rtg;
+               const T epsilon = epsilon_type<T>::value();
+               return (abs_impl(v0 - v1,rtg()) > (std::max(T(1),std::max(abs_impl(v0,rtg()),abs_impl(v1,rtg()))) * epsilon)) ? T(1) : T(0);
+            }
+
+            inline float nequal_impl(const float v0, const float v1, real_type_tag)
+            {
+               typedef real_type_tag rtg;
+               const float epsilon = epsilon_type<float>::value();
+               return (abs_impl(v0 - v1,rtg()) > (std::max(1.0f,std::max(abs_impl(v0,rtg()),abs_impl(v1,rtg()))) * epsilon)) ? 1.0f : 0.0f;
+            }
+
+            template <typename T>
+            inline T nequal_impl(const T v0, const T v1, int_type_tag)
+            {
+               return (v0 != v1) ? 1 : 0;
+            }
+
+            template <typename T>
+            inline T modulus_impl(const T v0, const T v1, real_type_tag)
+            {
+               return std::fmod(v0,v1);
+            }
+
+            template <typename T>
+            inline T modulus_impl(const T v0, const T v1, int_type_tag)
+            {
+               return v0 % v1;
+            }
+
+            template <typename T>
+            inline T pow_impl(const T v0, const T v1, real_type_tag)
+            {
+               return std::pow(v0,v1);
+            }
+
+            template <typename T>
+            inline T pow_impl(const T v0, const T v1, int_type_tag)
+            {
+               return std::pow(static_cast<double>(v0),static_cast<double>(v1));
+            }
+
+            template <typename T>
+            inline T logn_impl(const T v0, const T v1, real_type_tag)
+            {
+               return std::log(v0) / std::log(v1);
+            }
+
+            template <typename T>
+            inline T logn_impl(const T v0, const T v1, int_type_tag)
+            {
+               return static_cast<T>(logn_impl<double>(static_cast<double>(v0),static_cast<double>(v1),real_type_tag()));
+            }
+
+            template <typename T>
+            inline T log1p_impl(const T v, real_type_tag)
+            {
+               if (v > T(-1))
+               {
+                  if (abs_impl(v,real_type_tag()) > T(0.0001))
+                  {
+                     return std::log(T(1) + v);
+                  }
+                  else
+                     return (T(-0.5) * v + T(1)) * v;
+               }
+               else
+                  return std::numeric_limits<T>::quiet_NaN();
+            }
+
+            template <typename T>
+            inline T log1p_impl(const T v, int_type_tag)
+            {
+               if (v > T(-1))
+               {
+                  return std::log(T(1) + v);
+               }
+               else
+                  return std::numeric_limits<T>::quiet_NaN();
+            }
+
+            template <typename T>
+            inline T root_impl(const T v0, const T v1, real_type_tag)
+            {
+               if (v1 < T(0))
+                  return std::numeric_limits<T>::quiet_NaN();
+
+               const std::size_t n = static_cast<std::size_t>(v1);
+
+               if ((v0 < T(0)) && (0 == (n % 2)))
+                  return std::numeric_limits<T>::quiet_NaN();
+
+               return std::pow(v0, T(1) / n);
+            }
+
+            template <typename T>
+            inline T root_impl(const T v0, const T v1, int_type_tag)
+            {
+               return root_impl<double>(static_cast<double>(v0),static_cast<double>(v1),real_type_tag());
+            }
+
+            template <typename T>
+            inline T round_impl(const T v, real_type_tag)
+            {
+               return ((v < T(0)) ? std::ceil(v - T(0.5)) : std::floor(v + T(0.5)));
+            }
+
+            template <typename T>
+            inline T roundn_impl(const T v0, const T v1, real_type_tag)
+            {
+               const int index = std::max<int>(0, std::min<int>(pow10_size - 1, (int)std::floor(v1)));
+               const T p10 = T(pow10[index]);
+
+               if (v0 < T(0))
+                  return T(std::ceil ((v0 * p10) - T(0.5)) / p10);
+               else
+                  return T(std::floor((v0 * p10) + T(0.5)) / p10);
+            }
+
+            template <typename T>
+            inline T roundn_impl(const T v0, const T, int_type_tag)
+            {
+               return v0;
+            }
+
+            template <typename T>
+            inline T hypot_impl(const T v0, const T v1, real_type_tag)
+            {
+               return std::sqrt((v0 * v0) + (v1 * v1));
+            }
+
+            template <typename T>
+            inline T hypot_impl(const T v0, const T v1, int_type_tag)
+            {
+               return static_cast<T>(std::sqrt(static_cast<double>((v0 * v0) + (v1 * v1))));
+            }
+
+            template <typename T>
+            inline T atan2_impl(const T v0, const T v1, real_type_tag)
+            {
+               return std::atan2(v0,v1);
+            }
+
+            template <typename T>
+            inline T atan2_impl(const T, const T, int_type_tag)
+            {
+               return 0;
+            }
+
+            template <typename T>
+            inline T shr_impl(const T v0, const T v1, real_type_tag)
+            {
+               return v0 * (T(1) / std::pow(T(2),static_cast<T>(static_cast<int>(v1))));
+            }
+
+            template <typename T>
+            inline T shr_impl(const T v0, const T v1, int_type_tag)
+            {
+               return v0 >> v1;
+            }
+
+            template <typename T>
+            inline T shl_impl(const T v0, const T v1, real_type_tag)
+            {
+               return v0 * std::pow(T(2),static_cast<T>(static_cast<int>(v1)));
+            }
+
+            template <typename T>
+            inline T shl_impl(const T v0, const T v1, int_type_tag)
+            {
+               return v0 << v1;
+            }
+
+            template <typename T>
+            inline T sgn_impl(const T v, real_type_tag)
+            {
+                    if (v > T(0)) return T(+1);
+               else if (v < T(0)) return T(-1);
+               else               return T( 0);
+            }
+
+            template <typename T>
+            inline T sgn_impl(const T v, int_type_tag)
+            {
+                    if (v > T(0)) return T(+1);
+               else if (v < T(0)) return T(-1);
+               else               return T( 0);
+            }
+
+            template <typename T>
+            inline T and_impl(const T v0, const T v1, real_type_tag)
+            {
+               return (is_true_impl(v0) && is_true_impl(v1)) ? T(1) : T(0);
+            }
+
+            template <typename T>
+            inline T and_impl(const T v0, const T v1, int_type_tag)
+            {
+               return v0 && v1;
+            }
+
+            template <typename T>
+            inline T nand_impl(const T v0, const T v1, real_type_tag)
+            {
+               return (is_false_impl(v0) || is_false_impl(v1)) ? T(1) : T(0);
+            }
+
+            template <typename T>
+            inline T nand_impl(const T v0, const T v1, int_type_tag)
+            {
+               return !(v0 && v1);
+            }
+
+            template <typename T>
+            inline T or_impl(const T v0, const T v1, real_type_tag)
+            {
+               return (is_true_impl(v0) || is_true_impl(v1)) ? T(1) : T(0);
+            }
+
+            template <typename T>
+            inline T or_impl(const T v0, const T v1, int_type_tag)
+            {
+               return (v0 || v1);
+            }
+
+            template <typename T>
+            inline T nor_impl(const T v0, const T v1, real_type_tag)
+            {
+               return (is_false_impl(v0) && is_false_impl(v1)) ? T(1) : T(0);
+            }
+
+            template <typename T>
+            inline T nor_impl(const T v0, const T v1, int_type_tag)
+            {
+               return !(v0 || v1);
+            }
+
+            template <typename T>
+            inline T xor_impl(const T v0, const T v1, real_type_tag)
+            {
+               return (is_false_impl(v0) != is_false_impl(v1)) ? T(1) : T(0);
+            }
+
+            template <typename T>
+            inline T xor_impl(const T v0, const T v1, int_type_tag)
+            {
+               return v0 ^ v1;
+            }
+
+            template <typename T>
+            inline T xnor_impl(const T v0, const T v1, real_type_tag)
+            {
+               const bool v0_true = is_true_impl(v0);
+               const bool v1_true = is_true_impl(v1);
+
+               if ((v0_true &&  v1_true) || (!v0_true && !v1_true))
+                  return T(1);
+               else
+                  return T(0);
+            }
+
+            template <typename T>
+            inline T xnor_impl(const T v0, const T v1, int_type_tag)
+            {
+               const bool v0_true = is_true_impl(v0);
+               const bool v1_true = is_true_impl(v1);
+
+               if ((v0_true &&  v1_true) || (!v0_true && !v1_true))
+                  return T(1);
+               else
+                  return T(0);
+            }
+
+            #if (defined(_MSC_VER) && (_MSC_VER >= 1900)) || !defined(_MSC_VER)
+            #define exprtk_define_erf(TT,impl)           \
+            inline TT erf_impl(TT v) { return impl(v); } \
+
+            exprtk_define_erf(      float,::erff)
+            exprtk_define_erf(     double,::erf )
+            exprtk_define_erf(long double,::erfl)
+            #undef exprtk_define_erf
+            #endif
+
+            template <typename T>
+            inline T erf_impl(T v, real_type_tag)
+            {
+               #if defined(_MSC_VER) && (_MSC_VER < 1900)
+               // Credits: Abramowitz & Stegun Equations 7.1.25-28
+               static const T c[] = {
+                                      T( 1.26551223), T(1.00002368),
+                                      T( 0.37409196), T(0.09678418),
+                                      T(-0.18628806), T(0.27886807),
+                                      T(-1.13520398), T(1.48851587),
+                                      T(-0.82215223), T(0.17087277)
+                                    };
+
+               const T t = T(1) / (T(1) + T(0.5) * abs_impl(v,real_type_tag()));
+
+               T result = T(1) - t * std::exp((-v * v) -
+                                      c[0] + t * (c[1] + t *
+                                     (c[2] + t * (c[3] + t *
+                                     (c[4] + t * (c[5] + t *
+                                     (c[6] + t * (c[7] + t *
+                                     (c[8] + t * (c[9]))))))))));
+
+               return (v >= T(0)) ? result : -result;
+               #else
+               return erf_impl(v);
+               #endif
+            }
+
+            template <typename T>
+            inline T erf_impl(T v, int_type_tag)
+            {
+               return erf_impl(static_cast<double>(v),real_type_tag());
+            }
+
+            #if (defined(_MSC_VER) && (_MSC_VER >= 1900)) || !defined(_MSC_VER)
+            #define exprtk_define_erfc(TT,impl)           \
+            inline TT erfc_impl(TT v) { return impl(v); } \
+
+            exprtk_define_erfc(      float,::erfcf)
+            exprtk_define_erfc(     double,::erfc )
+            exprtk_define_erfc(long double,::erfcl)
+            #undef exprtk_define_erfc
+            #endif
+
+            template <typename T>
+            inline T erfc_impl(T v, real_type_tag)
+            {
+               #if defined(_MSC_VER) && (_MSC_VER < 1900)
+               return T(1) - erf_impl(v,real_type_tag());
+               #else
+               return erfc_impl(v);
+               #endif
+            }
+
+            template <typename T>
+            inline T erfc_impl(T v, int_type_tag)
+            {
+               return erfc_impl(static_cast<double>(v),real_type_tag());
+            }
+
+            template <typename T>
+            inline T ncdf_impl(T v, real_type_tag)
+            {
+               T cnd = T(0.5) * (T(1) + erf_impl(
+                                           abs_impl(v,real_type_tag()) /
+                                           T(numeric::constant::sqrt2),real_type_tag()));
+               return  (v < T(0)) ? (T(1) - cnd) : cnd;
+            }
+
+            template <typename T>
+            inline T ncdf_impl(T v, int_type_tag)
+            {
+               return ncdf_impl(static_cast<double>(v),real_type_tag());
+            }
+
+            template <typename T>
+            inline T sinc_impl(T v, real_type_tag)
+            {
+               if (std::abs(v) >= std::numeric_limits<T>::epsilon())
+                   return(std::sin(v) / v);
+               else
+                  return T(1);
+            }
+
+            template <typename T>
+            inline T sinc_impl(T v, int_type_tag)
+            {
+               return sinc_impl(static_cast<double>(v),real_type_tag());
+            }
+
+            template <typename T> inline T  acos_impl(const T v, real_type_tag) { return std::acos (v); }
+            template <typename T> inline T acosh_impl(const T v, real_type_tag) { return std::log(v + std::sqrt((v * v) - T(1))); }
+            template <typename T> inline T  asin_impl(const T v, real_type_tag) { return std::asin (v); }
+            template <typename T> inline T asinh_impl(const T v, real_type_tag) { return std::log(v + std::sqrt((v * v) + T(1))); }
+            template <typename T> inline T  atan_impl(const T v, real_type_tag) { return std::atan (v); }
+            template <typename T> inline T atanh_impl(const T v, real_type_tag) { return (std::log(T(1) + v) - std::log(T(1) - v)) / T(2); }
+            template <typename T> inline T  ceil_impl(const T v, real_type_tag) { return std::ceil (v); }
+            template <typename T> inline T   cos_impl(const T v, real_type_tag) { return std::cos  (v); }
+            template <typename T> inline T  cosh_impl(const T v, real_type_tag) { return std::cosh (v); }
+            template <typename T> inline T   exp_impl(const T v, real_type_tag) { return std::exp  (v); }
+            template <typename T> inline T floor_impl(const T v, real_type_tag) { return std::floor(v); }
+            template <typename T> inline T   log_impl(const T v, real_type_tag) { return std::log  (v); }
+            template <typename T> inline T log10_impl(const T v, real_type_tag) { return std::log10(v); }
+            template <typename T> inline T  log2_impl(const T v, real_type_tag) { return std::log(v)/T(numeric::constant::log2); }
+            template <typename T> inline T   neg_impl(const T v, real_type_tag) { return -v;            }
+            template <typename T> inline T   pos_impl(const T v, real_type_tag) { return +v;            }
+            template <typename T> inline T   sin_impl(const T v, real_type_tag) { return std::sin  (v); }
+            template <typename T> inline T  sinh_impl(const T v, real_type_tag) { return std::sinh (v); }
+            template <typename T> inline T  sqrt_impl(const T v, real_type_tag) { return std::sqrt (v); }
+            template <typename T> inline T   tan_impl(const T v, real_type_tag) { return std::tan  (v); }
+            template <typename T> inline T  tanh_impl(const T v, real_type_tag) { return std::tanh (v); }
+            template <typename T> inline T   cot_impl(const T v, real_type_tag) { return T(1) / std::tan(v); }
+            template <typename T> inline T   sec_impl(const T v, real_type_tag) { return T(1) / std::cos(v); }
+            template <typename T> inline T   csc_impl(const T v, real_type_tag) { return T(1) / std::sin(v); }
+            template <typename T> inline T   r2d_impl(const T v, real_type_tag) { return (v * T(numeric::constant::_180_pi)); }
+            template <typename T> inline T   d2r_impl(const T v, real_type_tag) { return (v * T(numeric::constant::pi_180));  }
+            template <typename T> inline T   d2g_impl(const T v, real_type_tag) { return (v * T(20.0/9.0)); }
+            template <typename T> inline T   g2d_impl(const T v, real_type_tag) { return (v * T(9.0/20.0)); }
+            template <typename T> inline T  notl_impl(const T v, real_type_tag) { return (std::not_equal_to<T>()(T(0),v) ? T(0) : T(1)); }
+            template <typename T> inline T  frac_impl(const T v, real_type_tag) { return (v - static_cast<long long>(v)); }
+            template <typename T> inline T trunc_impl(const T v, real_type_tag) { return T(static_cast<long long>(v));    }
+
+            template <typename T> inline T const_pi_impl(real_type_tag) { return T(numeric::constant::pi); }
+            template <typename T> inline T const_e_impl (real_type_tag) { return T(numeric::constant::e);  }
+
+            template <typename T> inline T   abs_impl(const T v, int_type_tag) { return ((v >= T(0)) ? v : -v); }
+            template <typename T> inline T   exp_impl(const T v, int_type_tag) { return std::exp  (v); }
+            template <typename T> inline T   log_impl(const T v, int_type_tag) { return std::log  (v); }
+            template <typename T> inline T log10_impl(const T v, int_type_tag) { return std::log10(v); }
+            template <typename T> inline T  log2_impl(const T v, int_type_tag) { return std::log(v)/T(numeric::constant::log2); }
+            template <typename T> inline T   neg_impl(const T v, int_type_tag) { return -v;            }
+            template <typename T> inline T   pos_impl(const T v, int_type_tag) { return +v;            }
+            template <typename T> inline T  ceil_impl(const T v, int_type_tag) { return v;             }
+            template <typename T> inline T floor_impl(const T v, int_type_tag) { return v;             }
+            template <typename T> inline T round_impl(const T v, int_type_tag) { return v;             }
+            template <typename T> inline T  notl_impl(const T v, int_type_tag) { return !v;            }
+            template <typename T> inline T  sqrt_impl(const T v, int_type_tag) { return std::sqrt (v); }
+            template <typename T> inline T  frac_impl(const T  , int_type_tag) { return T(0);          }
+            template <typename T> inline T trunc_impl(const T v, int_type_tag) { return v;             }
+            template <typename T> inline T  acos_impl(const T  , int_type_tag) { return std::numeric_limits<T>::quiet_NaN(); }
+            template <typename T> inline T acosh_impl(const T  , int_type_tag) { return std::numeric_limits<T>::quiet_NaN(); }
+            template <typename T> inline T  asin_impl(const T  , int_type_tag) { return std::numeric_limits<T>::quiet_NaN(); }
+            template <typename T> inline T asinh_impl(const T  , int_type_tag) { return std::numeric_limits<T>::quiet_NaN(); }
+            template <typename T> inline T  atan_impl(const T  , int_type_tag) { return std::numeric_limits<T>::quiet_NaN(); }
+            template <typename T> inline T atanh_impl(const T  , int_type_tag) { return std::numeric_limits<T>::quiet_NaN(); }
+            template <typename T> inline T   cos_impl(const T  , int_type_tag) { return std::numeric_limits<T>::quiet_NaN(); }
+            template <typename T> inline T  cosh_impl(const T  , int_type_tag) { return std::numeric_limits<T>::quiet_NaN(); }
+            template <typename T> inline T   sin_impl(const T  , int_type_tag) { return std::numeric_limits<T>::quiet_NaN(); }
+            template <typename T> inline T  sinh_impl(const T  , int_type_tag) { return std::numeric_limits<T>::quiet_NaN(); }
+            template <typename T> inline T   tan_impl(const T  , int_type_tag) { return std::numeric_limits<T>::quiet_NaN(); }
+            template <typename T> inline T  tanh_impl(const T  , int_type_tag) { return std::numeric_limits<T>::quiet_NaN(); }
+            template <typename T> inline T   cot_impl(const T  , int_type_tag) { return std::numeric_limits<T>::quiet_NaN(); }
+            template <typename T> inline T   sec_impl(const T  , int_type_tag) { return std::numeric_limits<T>::quiet_NaN(); }
+            template <typename T> inline T   csc_impl(const T  , int_type_tag) { return std::numeric_limits<T>::quiet_NaN(); }
+
+            template <typename T>
+            inline bool is_integer_impl(const T& v, real_type_tag)
+            {
+               return std::equal_to<T>()(T(0),std::fmod(v,T(1)));
+            }
+
+            template <typename T>
+            inline bool is_integer_impl(const T&, int_type_tag)
+            {
+               return true;
+            }
+         }
+
+         template <typename Type>
+         struct numeric_info { enum { length = 0, size = 32, bound_length = 0, min_exp = 0, max_exp = 0 }; };
+
+         template<> struct numeric_info<int>         { enum { length = 10, size = 16, bound_length = 9}; };
+         template<> struct numeric_info<float>       { enum { min_exp =  -38, max_exp =  +38}; };
+         template<> struct numeric_info<double>      { enum { min_exp = -308, max_exp = +308}; };
+         template<> struct numeric_info<long double> { enum { min_exp = -308, max_exp = +308}; };
+
+         template <typename T>
+         inline int to_int32(const T v)
+         {
+            const typename details::number_type<T>::type num_type;
+            return to_int32_impl(v, num_type);
+         }
+
+         template <typename T>
+         inline _int64_t to_int64(const T v)
+         {
+            const typename details::number_type<T>::type num_type;
+            return to_int64_impl(v, num_type);
+         }
+
+         template <typename T>
+         inline bool is_nan(const T v)
+         {
+            const typename details::number_type<T>::type num_type;
+            return is_nan_impl(v, num_type);
+         }
+
+         template <typename T>
+         inline T min(const T v0, const T v1)
+         {
+            const typename details::number_type<T>::type num_type;
+            return min_impl(v0, v1, num_type);
+         }
+
+         template <typename T>
+         inline T max(const T v0, const T v1)
+         {
+            const typename details::number_type<T>::type num_type;
+            return max_impl(v0, v1, num_type);
+         }
+
+         template <typename T>
+         inline T equal(const T v0, const T v1)
+         {
+            const typename details::number_type<T>::type num_type;
+            return equal_impl(v0, v1, num_type);
+         }
+
+         template <typename T>
+         inline T nequal(const T v0, const T v1)
+         {
+            const typename details::number_type<T>::type num_type;
+            return nequal_impl(v0, v1, num_type);
+         }
+
+         template <typename T>
+         inline T modulus(const T v0, const T v1)
+         {
+            const typename details::number_type<T>::type num_type;
+            return modulus_impl(v0, v1, num_type);
+         }
+
+         template <typename T>
+         inline T pow(const T v0, const T v1)
+         {
+            const typename details::number_type<T>::type num_type;
+            return pow_impl(v0, v1, num_type);
+         }
+
+         template <typename T>
+         inline T logn(const T v0, const T v1)
+         {
+            const typename details::number_type<T>::type num_type;
+            return logn_impl(v0, v1, num_type);
+         }
+
+         template <typename T>
+         inline T root(const T v0, const T v1)
+         {
+            const typename details::number_type<T>::type num_type;
+            return root_impl(v0, v1, num_type);
+         }
+
+         template <typename T>
+         inline T roundn(const T v0, const T v1)
+         {
+            const typename details::number_type<T>::type num_type;
+            return roundn_impl(v0, v1, num_type);
+         }
+
+         template <typename T>
+         inline T hypot(const T v0, const T v1)
+         {
+            const typename details::number_type<T>::type num_type;
+            return hypot_impl(v0, v1, num_type);
+         }
+
+         template <typename T>
+         inline T atan2(const T v0, const T v1)
+         {
+            const typename details::number_type<T>::type num_type;
+            return atan2_impl(v0, v1, num_type);
+         }
+
+         template <typename T>
+         inline T shr(const T v0, const T v1)
+         {
+            const typename details::number_type<T>::type num_type;
+            return shr_impl(v0, v1, num_type);
+         }
+
+         template <typename T>
+         inline T shl(const T v0, const T v1)
+         {
+            const typename details::number_type<T>::type num_type;
+            return shl_impl(v0, v1, num_type);
+         }
+
+         template <typename T>
+         inline T and_opr(const T v0, const T v1)
+         {
+            const typename details::number_type<T>::type num_type;
+            return and_impl(v0, v1, num_type);
+         }
+
+         template <typename T>
+         inline T nand_opr(const T v0, const T v1)
+         {
+            const typename details::number_type<T>::type num_type;
+            return nand_impl(v0, v1, num_type);
+         }
+
+         template <typename T>
+         inline T or_opr(const T v0, const T v1)
+         {
+            const typename details::number_type<T>::type num_type;
+            return or_impl(v0, v1, num_type);
+         }
+
+         template <typename T>
+         inline T nor_opr(const T v0, const T v1)
+         {
+            const typename details::number_type<T>::type num_type;
+            return nor_impl(v0, v1, num_type);
+         }
+
+         template <typename T>
+         inline T xor_opr(const T v0, const T v1)
+         {
+            const typename details::number_type<T>::type num_type;
+            return xor_impl(v0, v1, num_type);
+         }
+
+         template <typename T>
+         inline T xnor_opr(const T v0, const T v1)
+         {
+            const typename details::number_type<T>::type num_type;
+            return xnor_impl(v0, v1, num_type);
+         }
+
+         template <typename T>
+         inline bool is_integer(const T v)
+         {
+            const typename details::number_type<T>::type num_type;
+            return is_integer_impl(v, num_type);
+         }
+
+         template <typename T, unsigned int N>
+         struct fast_exp
+         {
+            static inline T result(T v)
+            {
+               unsigned int k = N;
+               T l = T(1);
+
+               while (k)
+               {
+                  if (k & 1)
+                  {
+                     l *= v;
+                     --k;
+                  }
+
+                  v *= v;
+                  k >>= 1;
+               }
+
+               return l;
+            }
+         };
+
+         template <typename T> struct fast_exp<T,10> { static inline T result(T v) { T v_5 = fast_exp<T,5>::result(v); return v_5 * v_5; } };
+         template <typename T> struct fast_exp<T, 9> { static inline T result(T v) { return fast_exp<T,8>::result(v) * v; } };
+         template <typename T> struct fast_exp<T, 8> { static inline T result(T v) { T v_4 = fast_exp<T,4>::result(v); return v_4 * v_4; } };
+         template <typename T> struct fast_exp<T, 7> { static inline T result(T v) { return fast_exp<T,6>::result(v) * v; } };
+         template <typename T> struct fast_exp<T, 6> { static inline T result(T v) { T v_3 = fast_exp<T,3>::result(v); return v_3 * v_3; } };
+         template <typename T> struct fast_exp<T, 5> { static inline T result(T v) { return fast_exp<T,4>::result(v) * v; } };
+         template <typename T> struct fast_exp<T, 4> { static inline T result(T v) { T v_2 = v * v; return v_2 * v_2; } };
+         template <typename T> struct fast_exp<T, 3> { static inline T result(T v) { return v * v * v; } };
+         template <typename T> struct fast_exp<T, 2> { static inline T result(T v) { return v * v;     } };
+         template <typename T> struct fast_exp<T, 1> { static inline T result(T v) { return v;         } };
+         template <typename T> struct fast_exp<T, 0> { static inline T result(T  ) { return T(1);      } };
+
+         #define exprtk_define_unary_function(FunctionName)        \
+         template <typename T>              &n