Codebase list pulseview / 097a744
New upstream version 0.4.1 Jonathan McDowell 5 years ago
265 changed file(s) with 15398 addition(s) and 4071 deletion(s). Raw diff Collapse all Expand all
2323
2424 project(pulseview)
2525
26 # Let AUTOMOC and AUTOUIC process GENERATED files.
27 if(POLICY CMP0071)
28 cmake_policy(SET CMP0071 NEW)
29 endif()
30
31 # Only interpret if() arguments as variables or keywords when unquoted.
32 if(POLICY CMP0054)
33 cmake_policy(SET CMP0054 NEW)
34 endif()
35
2636 list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMake")
2737
2838 #===============================================================================
2939 #= User Options
3040 #-------------------------------------------------------------------------------
3141
32 option(DISABLE_WERROR "Build without -Werror" FALSE)
42 option(DISABLE_WERROR "Build without -Werror" TRUE)
3343 option(ENABLE_SIGNALS "Build with UNIX signals" TRUE)
44 option(ENABLE_STACKTRACE "Enable stack trace when crashing" FALSE)
3445 option(ENABLE_DECODE "Build with libsigrokdecode" TRUE)
35 option(ENABLE_TESTS "Enable unit tests" TRUE)
46 option(ENABLE_TESTS "Enable unit tests" FALSE)
3647 option(STATIC_PKGDEPS_LIBS "Statically link to (pkg-config) libraries" FALSE)
3748
3849 if(WIN32)
5162 endif()
5263
5364 #===============================================================================
65 #= Documentation
66 #-------------------------------------------------------------------------------
67
68 add_subdirectory(manual)
69
70 #===============================================================================
5471 #= Dependencies
5572 #-------------------------------------------------------------------------------
5673
5774 list(APPEND PKGDEPS glib-2.0>=2.28.0)
5875 list(APPEND PKGDEPS glibmm-2.4>=2.28.0)
5976
60 list(APPEND PKGDEPS libsigrokcxx>=0.5.0)
77 set(LIBSR_CXX_BINDING "libsigrokcxx>=0.5.1")
78 list(APPEND PKGDEPS "${LIBSR_CXX_BINDING}")
6179
6280 if(ENABLE_DECODE)
63 list(APPEND PKGDEPS libsigrokdecode>=0.5.0)
81 list(APPEND PKGDEPS libsigrokdecode>=0.5.2)
6482 endif()
6583
6684 if(ANDROID)
6886 endif()
6987
7088 find_package(PkgConfig)
89 pkg_check_modules(LIBSRCXX QUIET ${LIBSR_CXX_BINDING})
90 if(NOT LIBSRCXX_FOUND OR NOT LIBSRCXX_VERSION)
91 message(FATAL_ERROR "libsigrok C++ bindings missing, check libsigrok's 'configure' output (missing dependencies?)")
92 endif()
7193 pkg_check_modules(PKGDEPS REQUIRED ${PKGDEPS})
7294
7395 set(CMAKE_AUTOMOC TRUE)
7799 if(WIN32)
78100 # MXE workaround: Use pkg-config to find Qt5 libs.
79101 # https://github.com/mxe/mxe/issues/1642
80 pkg_check_modules(QT5ALL REQUIRED Qt5Widgets Qt5Gui Qt5Svg)
102 # Not required (and doesn't work) on MSYS2.
103 if(NOT DEFINED ENV{MSYSTEM})
104 pkg_check_modules(QT5ALL REQUIRED Qt5Widgets Qt5Gui Qt5Svg)
105 endif()
81106 endif()
82107
83108 set(QT_LIBRARIES Qt5::Gui Qt5::Widgets Qt5::Svg)
86111 if(ENABLE_TESTS)
87112 list(APPEND BOOSTCOMPS unit_test_framework)
88113 endif()
89 find_package(Boost 1.55 COMPONENTS ${BOOSTCOMPS} REQUIRED)
114
115 if(ENABLE_STACKTRACE)
116 find_package(Boost 1.65.1 COMPONENTS ${BOOSTCOMPS} REQUIRED)
117 else()
118 find_package(Boost 1.55 COMPONENTS ${BOOSTCOMPS} REQUIRED)
119 endif()
90120
91121 # Find the platform's thread library (needed for C++11 threads).
92122 # This will set ${CMAKE_THREAD_LIBS_INIT} to the correct, OS-specific value.
152182 #-------------------------------------------------------------------------------
153183
154184 set(PV_TITLE PulseView)
155 set(PV_VERSION_STRING "0.4.0")
185 set(PV_VERSION_STRING "0.4.1")
156186
157187 set(PV_GLIBMM_VERSION ${PKGDEPS_glibmm-2.4_VERSION})
158188
166196 string(SUBSTRING "${PV_HASH}" 0 7 PV_SHORTHASH)
167197 set(PV_VERSION_STRING "${PV_VERSION_STRING}-git-${PV_SHORTHASH}")
168198 endif()
199
200 # Non-tagged releases use the unstable manual
201 set(PV_MANUAL_VERSION "unstable")
202 else()
203 # Tagged releases use a fixed manual version
204 set(PV_MANUAL_VERSION ${PV_VERSION_STRING})
169205 endif()
170206
171207 if(PV_VERSION_STRING MATCHES "^([0-9]+)\\.([0-9]+)\\.([0-9]+)(-[-0-9a-z]*)?$")
175211 set(PV_VERSION_SUFFIX ${CMAKE_MATCH_4})
176212 endif()
177213
178 message("-- ${PV_TITLE} version: ${PV_VERSION_STRING}")
214 message(STATUS "${PV_TITLE} version: ${PV_VERSION_STRING}")
179215
180216 configure_file (
181217 ${PROJECT_SOURCE_DIR}/config.h.in
191227 pv/application.cpp
192228 pv/devicemanager.cpp
193229 pv/globalsettings.cpp
230 pv/logging.cpp
194231 pv/mainwindow.cpp
195232 pv/session.cpp
196233 pv/storesession.cpp
233270 pv/views/trace/rowitem.cpp
234271 pv/views/trace/ruler.cpp
235272 pv/views/trace/signal.cpp
236 pv/views/trace/signalscalehandle.cpp
237273 pv/views/trace/timeitem.cpp
238274 pv/views/trace/timemarker.cpp
239275 pv/views/trace/trace.cpp
250286 pv/views/trace/viewwidget.cpp
251287 pv/views/viewbase.cpp
252288 pv/views/trace/standardbar.cpp
253 pv/widgets/colourbutton.cpp
254 pv/widgets/colourpopup.cpp
289 pv/widgets/colorbutton.cpp
290 pv/widgets/colorpopup.cpp
255291 pv/widgets/devicetoolbutton.cpp
256292 pv/widgets/exportmenu.cpp
257293 pv/widgets/importmenu.cpp
264300
265301 # This list includes only QObject derived class headers.
266302 set(pulseview_HEADERS
303 pv/logging.hpp
267304 pv/globalsettings.hpp
268305 pv/mainwindow.hpp
269306 pv/session.hpp
296333 pv/views/trace/rowitem.hpp
297334 pv/views/trace/ruler.hpp
298335 pv/views/trace/signal.hpp
299 pv/views/trace/signalscalehandle.hpp
300336 pv/views/trace/timeitem.hpp
301337 pv/views/trace/timemarker.hpp
302338 pv/views/trace/trace.hpp
309345 pv/views/trace/viewwidget.hpp
310346 pv/views/viewbase.hpp
311347 pv/views/trace/standardbar.hpp
312 pv/widgets/colourbutton.hpp
313 pv/widgets/colourpopup.hpp
348 pv/widgets/colorbutton.hpp
349 pv/widgets/colorpopup.hpp
314350 pv/widgets/devicetoolbutton.hpp
315351 pv/widgets/exportmenu.hpp
316352 pv/widgets/importmenu.hpp
333369 if(ENABLE_DECODE)
334370 list(APPEND pulseview_SOURCES
335371 pv/binding/decoder.cpp
336 pv/data/decoderstack.cpp
372 pv/data/decodesignal.cpp
337373 pv/data/decode/annotation.cpp
338374 pv/data/decode/decoder.cpp
339375 pv/data/decode/row.cpp
344380 )
345381
346382 list(APPEND pulseview_HEADERS
347 pv/data/decoderstack.hpp
383 pv/data/decodesignal.hpp
348384 pv/views/trace/decodetrace.hpp
349385 pv/widgets/decodergroupbox.hpp
350386 pv/widgets/decodermenu.hpp
387423
388424 if(ENABLE_SIGNALS)
389425 add_definitions(-DENABLE_SIGNALS)
426 endif()
427
428 if(ENABLE_STACKTRACE)
429 add_definitions(-DENABLE_STACKTRACE)
390430 endif()
391431
392432 #===============================================================================
429469 if(WIN32)
430470 # On Windows we need to statically link the libqsvg imageformat
431471 # plugin (and the QtSvg component) for SVG graphics/icons to work.
432 # We also need QWindowsIntegrationPlugin, Qt5PlatformSupport, and all
433 # Qt libs and their dependencies.
472 # We also need QWindowsIntegrationPlugin, Qt5PlatformSupport (only for
473 # Qt < 5.8.0), and all Qt libs and their dependencies.
434474 add_definitions(-DQT_STATICPLUGIN)
435475 list(APPEND PULSEVIEW_LINK_LIBS Qt5::QSvgPlugin)
436476 list(APPEND PULSEVIEW_LINK_LIBS Qt5::QWindowsIntegrationPlugin)
437 list(APPEND PULSEVIEW_LINK_LIBS -lQt5PlatformSupport ${QT5ALL_LDFLAGS})
477 if(Qt5Gui_VERSION VERSION_LESS 5.8.0)
478 list(APPEND PULSEVIEW_LINK_LIBS -lQt5PlatformSupport)
479 endif()
480 list(APPEND PULSEVIEW_LINK_LIBS ${QT5ALL_LDFLAGS})
481 endif()
482
483 if(ENABLE_STACKTRACE)
484 # Needed to resolve dladdr.
485 list(APPEND PULSEVIEW_LINK_LIBS "-ldl")
438486 endif()
439487
440488 if(ANDROID)
449497
450498 target_link_libraries(${PROJECT_NAME} ${PULSEVIEW_LINK_LIBS})
451499
452 if(WIN32)
500 if(WIN32 AND NOT ${CMAKE_BUILD_TYPE} STREQUAL "Debug")
453501 # Pass -mwindows so that no "DOS box" opens when PulseView is started.
454502 set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-mwindows")
455503 endif()
55
66 DOXYFILE_ENCODING = UTF-8
77 PROJECT_NAME = "PulseView"
8 PROJECT_NUMBER = "0.4.0"
8 PROJECT_NUMBER = "0.4.1"
99 PROJECT_BRIEF = "A Qt-based sigrok GUI"
1010 PROJECT_LOGO = icons/pulseview.png
1111 OUTPUT_DIRECTORY = doxy
44 Coding style
55 ------------
66
7 This project is programmed using the Linux kernel coding style, see
8 http://lxr.linux.no/linux/Documentation/CodingStyle for details.
7 This project is programmed using the Linux kernel coding style:
8
9 https://www.kernel.org/doc/html/latest/process/coding-style.html
910
1011 Please use the same style for any code contributions, thanks!
1112
1617 Contributions
1718 -------------
1819
19 - Patches should be sent to the development mailinglist at
20 - In order to contribute you should ideally clone the git repository and
21 let us know (preferably via IRC, or via the mailing list) from where to
22 pull/review your changes. You can use github.com, or any other public git
23 hosting site.
24
25 - Alternatively, patches can be sent to the development mailinglist at
2026 sigrok-devel@lists.sourceforge.net (please subscribe to the list first).
2127
2228 https://lists.sourceforge.net/lists/listinfo/sigrok-devel
23
24 - Alternatively, you can also clone the git repository and let us know
25 from where to pull/review your changes. You can use gitorious.org,
26 github.com, or any other public git hosting site.
2729
2830
2931 Random notes
2121 - libboost-filesystem
2222 - libboost-serialization
2323 - libboost-test (optional, only needed to run the unit tests)
24 - libsigrokcxx >= 0.5.0 (libsigrok C++ bindings)
25 - libsigrokdecode >= 0.5.0
24 - libboost-stacktrace (optional, only needed for debugging)
25 - libsigrokcxx >= 0.5.1 (libsigrok C++ bindings)
26 - libsigrokdecode >= 0.5.2
2627 - libsigrokandroidutils >= 0.1.0 (optional, only needed on Android)
28 - asciidoctor (optional, only needed to build the HTML manual)
29 - asciidoctor-pdf (optional, only needed to build the PDF manual)
2730
2831
2932 Building and installing
5558 $ cmake ..
5659 $ make package_source
5760
61
62 Generating the manual
63 ---------------------
64
65 To generate the HTML manual, you only need Asciidoctor. If you also want
66 to generate the PDF manual, you need to install asciidoctor-pdf as well
67 and make it available for execution:
68
69 $ gem install --pre asciidoctor-pdf
70 $ export PATH=~/.gem/ruby/2.3.0/bin:$PATH
71
72 Then, to build the PulseView manual, run:
73
74 $ make manual
75
76 Note: The stylesheet used is a lightly modified version of "Read The Docs"
77 from the Asciidoctor stylesheet factory:
78 https://asciidoctor.org/docs/user-manual/#stylesheet-factory
+164
-0
NEWS less more
0 0.4.1 (2018-10-29)
1 ------------------
2
3 * Updated build requirements:
4 - libsigrokcxx >= 0.5.1 (libsigrok C++ bindings)
5 - libsigrokdecode >= 0.5.2
6 - libboost-stacktrace (optional, only needed for debugging)
7 - asciidoctor (optional, only needed to build the HTML manual)
8 - asciidoctor-pdf (optional, only needed to build the PDF manual)
9 * Add support for snapping cursors and markers to signal edges (bug #684).
10 - When the mouse cursor is in a channel, snapping will only be performed
11 for edges of that specific channel.
12 - When the mouse cursor is not in a channel, snapping will happen for any
13 edge of any channel (bugs #1292, #1294).
14 - When moving both cursors at the same time, the left one will snap to
15 edges while the right one will not (e.g. useful for measurements).
16 - The edge to snap to is chosen based on heuristics involving the edge
17 density near the mouse cursor (to try to get the most useful matches).
18 * Command-line options:
19 - Add support for loading multiple files from the command-line (bug #1040).
20 - Add support for input format options for the -I parameter (bug #951).
21 Example: pulseview -I csv:header:first-channel=2 -i filename.csv
22 - Try to autodetect the input format when -I is not supplied (bug #1015).
23 - Add support for -d/--driver, i.e. driver scan options (bug #953).
24 Example: pulseview -d ols:conn=/dev/ttyACM0
25 - Add a -D/--dont-scan option, don't auto-scan for devices (bug #1116).
26 - The -V option now shows the full PulseView/libs version info (bug #1213).
27 * Add support for per-channel analog-to-logic conversion (A2L) via either
28 the "threshold" or "schmitt-trigger" method, which allows running protocol
29 decoders on the converted channels.
30 * Cursors:
31 - Add a tooltip when there's not enough space to show the
32 interval/frequency measurement values (bug #1222).
33 - Measurements are always shown with 12 digit precision (bug #870).
34 * Add theme (and Qt UI style) support, including two "dark" themes.
35 * Add a PulseView manual (HTML and PDF format), generated by "make manual".
36 * manpage: Document all new command-line options.
37 * Add a "fill logic signal high areas" feature with configurable color.
38 * Add segment/frame support and a selector UI for e.g. oscilloscope frames.
39 * Add multi-segment protocol decoding support.
40 * Add support for an (optional) vertical mouse hover line (bug #770).
41 * The obsolete signal scale handle has been replaced by another mechanism.
42 * Accept user-entered sample rates when external clock is enabled.
43 * Slightly more user-friendly scan dropdown for VXI vs. Raw TCP (bug #1146).
44 * Suppport for new libsigrok(cxx) config keys:
45 - SR_CONF_DATA_SOURCE
46 - SR_CONF_EXTERNAL_CLOCK_SOURCE
47 - SR_CONF_AVERAGING
48 - SR_CONF_AVG_SAMPLES (including support for list of values)
49 * Fix long UI hangs when changing decoder options/channels (bug #1174).
50 * Many internal refactorings and flexibility/performance improvements.
51 - Decrease the number of trace redrawing operations in a few places.
52 - Use emplace_back() in various places to avoid some allocations.
53 - Increase decode chunk size to 256kB for better performance.
54 - Increase input file chunk size to 4MB for improved performance.
55 - Speed up painting by not unnecessarily copying decoder annotations.
56 - Improve MipMap downsampling code, speeding up e.g. file loading.
57 - Fix various memory leaks and other issues reported by valgrind.
58 - Various fixes for issues reported by clang-tidy and clazy.
59 - Fix some issues reported by Coverity.
60 - Fix various gcc 8 warnings/errors.
61 * Decoder channel name (auto-)assignment:
62 - Auto-match e.g. "SCL analog" to the "SCL" decoder channel.
63 - Ignore "insignificant" characters (-_.) for name matching (bug #1270).
64 - Fix a channel auto-assignment issue with disabled channels (bug #1182).
65 - Fix a channel auto-assignment issue with multiple matches (bug #1201).
66 - Fix an issue where channel names weren't updated correctly (bug #1089).
67 - Fix multiple decoder channel assignment issues (bug #1024).
68 - Fix an issue where channel auto-assignment was incorrectly applied.
69 * Fix an issue where decoder warnings weren't always shown (bug #982).
70 * Fix an issue with incorrect samplerates during multiple decoder runs.
71 * Fix a decoder restarting issue after reloading an input file.
72 * Fix an issue where existing decoder traces were not shown in new views.
73 * Fix an issue where stacked decoders weren't restored (bug #832).
74 * Fix an issue where decoder channel mappings were not restored (bug #831).
75 * Fix an issue where decoder options were not restored (bug #831).
76 * Fix an issue where not all decoder stacks were restored (bug #888).
77 * Fix an issue where header area tooltips were incorrectly shown.
78 * Fix trace resizing when new annotation classes appear.
79 * Fix an issue with ruler updating after restoring a session.
80 * Fix an issue with header resizing events.
81 * Fix an issue where decoders would not restart upon A2L conversion changes.
82 * Fix an issue with bit IDs which caused incorrect decode runs.
83 * Avoid a crash by ignoring invalid loglevel (-l option) specs (bug #1071).
84 * Fix an issue where settings callbacks could not be unregistered.
85 * Ruler tick legends now don't partly disappear when scrolled off the ends.
86 * Fix file extension filter in "Import File" dialog (bug #1039).
87 * Fix a crash when trying to save invalid trace ranges (bug #1038).
88 * Fix a crash related to A2L conversion changes (bug #1132).
89 * Fix a crash when config_list() was returning errors (bug #928).
90 * Fix a crash when config_get() was returning errors (bug #1035).
91 * Fix a crash when read_config() calls were failing.
92 * Fix a crash caused by incorrectly sized sample buffers (bug #1166).
93 * Fix an issue where decoder option changes affected other options (bug #1162).
94 * Resize trace when hiding/deleting a stacked PD.
95 * Only pass non-zero samplerate metadata to protocol decoders (bug #1118).
96 * Fix an issue when processing packets without sample data.
97 * Fix inconsistent decoder annotation colors upon multiple runs (bug #709).
98 * Fix two minor UI issues with the decoding "progress bar" display.
99 * Fix some voltage threshold UI widget and default value issues (bug #1149).
100 * Fix an issue when loading settings saved via older Boost lib (bug #1203).
101 * Avoid qDebug().noquote() for now, would require more recent Qt (bug #1169).
102 * Fix an issue where the last analog sample was not being shown (bug #956).
103 * Fix an issue where markers were not removed upon new sessions (bug #540).
104 * Fix an issue where the "show cursors" button wasn't working (bug #1212).
105 * Fix a crash by forbidding UI trigger changes during acquisitions (bug #807).
106 * Fix an issue where trigger markers would disappear (bug #1226).
107 * Fix a crash when running out of memory during acquisitions (bug #975).
108 * Fix an issue where multiple decoder traces would overlap (bug #1204).
109 * Fix an issue with silent failures during file loading (bug #1259).
110 * Avoid incorrectly hardcoded ConfigKey::SAMPLERATE (bug #651).
111 * Prevent some crashes when scanning for devices.
112 * Show a slightly more specific error for "Failed to select device".
113 * Remove the "1:1 zoom button" (bug #1198).
114 * Cache device triggers instead of querying multiple times (bug #979).
115 * Add new context menus (right-click in the trace area or header):
116 - Add annotation exporting support from a decoder's context menu:
117 All, only a specified row, all from mouse position, all between cursors.
118 - Add a facility to pause/resume decoding via a decoder's context menu.
119 - Add a "Create marker here" context menu item.
120 - Add "Set as zero point" context menu item.
121 - Header: Add an "Enable/disable mouse hover marker" context menu item.
122 * Settings:
123 - Enable the "show sampling points" setting by default.
124 - Enable the mouse hover marker by default.
125 - Add a "zoom-to-fit when acquisition stops" setting (bug #236).
126 - Add a "default div height" setting.
127 - Add a "logic trace height" setting.
128 - Add a "Show conversion thresholds in analog traces" setting.
129 - Add a "Show time zero at the trigger" setting.
130 - Show firmware and decoder search paths in the settings dialog (bug #1128).
131 - Show logs from libsigrok, libsigrokdecode, and PulseView itself.
132 - Change page list design, also fixes UI inconsistencies (bug #1095).
133 - Only show drivers PulseView will actually use (bug #1153).
134 - Make the version info in the dialog selectable for copy-paste (bug #1264).
135 - Add annotation export formatting setting.
136 - Add a setting for the snap-to-edge distance (in pixels).
137 * HACKING: Prefer git pull requests over mailing list patches.
138 * UI: Use slider instead of combo box for contiguous ranges.
139 * Reset and re-use existing decoder sessions (no full reconstruction).
140 * Trace view: Make the zero line for analog traces thicker.
141 * PulseView .desktop file: Fix a "desktop-file-validate" error.
142 * Add experimental (default-off) boost::stacktrace support.
143 * Add more options to show/hide certain channels (bug #1023):
144 - Enable: All / Logic / Analog / Unnamed / Non-chaning
145 - Disable: All / Logic / Analog / Named / Changing
146 * Build system:
147 - Set CMake policy CMP0071 to NEW (bug #1143).
148 - Set CMake policy CMP0054 to NEW, fixes a warning.
149 - Show a helpful message when libsigrokcxx is not found (bug #1199).
150 - Fix a build issue with ENABLE_DECODE=n.
151 * Windows:
152 - Have debug builds (-DCMAKE_BUILD_TYPE=Debug) show/open a "DOS box".
153 - Fix a MinGW compile error due to a missing #include.
154 - Fix a Windows XP crash caused by a missing cast (bug #1125).
155 - Fix a Windows XP crash caused by incorrect segment handling (bug #1139).
156 - CMakeLists.txt: Only add Qt5PlatformSupport for Qt < 5.8.0.
157 - Fix a build issue on MSYS2 by applying an MXE workaround only on MXE.
158 - Installer: Add debug shortcut (Windows start menu) for -l 5.
159 - Installer: Add links to the HTML/PDF manual.
160 * Mac OS X:
161 - Fix a crash with large acquisitions on Mac OS X (bug #1284).
162 - Work around a QColor serialization bug on Mac OS X.
163
0164 0.4.0 (2017-06-12)
1165 ------------------
2166
4343 Copyright (C) 2010,2011,2012,2013 Contributor Name
4444
4545
46 Icons authors and licenses
47 --------------------------
46 Resource authors and licenses
47 -----------------------------
4848
4949 icons/information.svg: Bobarino
5050 https://en.wikipedia.org/wiki/File:Information.svg
51 License:
52 GFDL 1.2 or later / CC-BY-SA 3.0
53 https://en.wikipedia.org/wiki/File:Information.svg#Licensing
54
55 QDarkStyleSheet: Colin Duquesnoy
56 https://github.com/ColinDuquesnoy/QDarkStyleSheet
57 License:
58 CC-BY 4.0
59 https://github.com/ColinDuquesnoy/QDarkStyleSheet/blob/master/LICENSE.md
60
61 DarkStyle: Juergen Skrotzky
62 https://github.com/Jorgen-VikingGod/Qt-Frameless-Window-DarkStyle
63 License:
64 MIT license
65 https://github.com/Jorgen-VikingGod/Qt-Frameless-Window-DarkStyle#licence
5166
5267
5368 Mailing list
2222
2323 #include <android/log.h>
2424
25 #include <stdint.h>
25 #include <cstdint>
2626 #include <libsigrok/libsigrok.h>
2727
2828 #include "android/loghandler.hpp"
2929
3030 namespace pv {
31
32 static sr_log_callback prev_sr_log_cb;
33 static void *prev_sr_log_cb_data;
34
35 #ifdef ENABLE_DECODE
36 static srd_log_callback prev_srd_log_cb;
37 static void *prev_srd_log_cb_data;
38 #endif
3139
3240 int AndroidLogHandler::sr_callback(void *cb_data, int loglevel, const char *format, va_list args)
3341 {
3947 [SR_LOG_DBG] = ANDROID_LOG_DEBUG,
4048 [SR_LOG_SPEW] = ANDROID_LOG_VERBOSE,
4149 };
50 va_list args2;
4251 int ret;
4352
4453 /* This specific log callback doesn't need the void pointer data. */
4554 (void)cb_data;
55
56 /* Call the previously registered log callback (library's default). */
57 va_copy(args2, args);
58 if (prev_sr_log_cb)
59 prev_sr_log_cb(prev_sr_log_cb_data, loglevel, format, args2);
60 va_end(args2);
4661
4762 /* Only output messages of at least the selected loglevel(s). */
4863 if (loglevel > sr_log_loglevel_get())
6984 [SRD_LOG_DBG] = ANDROID_LOG_DEBUG,
7085 [SRD_LOG_SPEW] = ANDROID_LOG_VERBOSE,
7186 };
87 va_list args2;
7288 int ret;
7389
7490 /* This specific log callback doesn't need the void pointer data. */
7591 (void)cb_data;
92
93 /* Call the previously registered log callback (library's default). */
94 va_copy(args2, args);
95 if (prev_srd_log_cb)
96 prev_srd_log_cb(prev_srd_log_cb_data, loglevel, format, args2);
97 va_end(args2);
7698
7799 /* Only output messages of at least the selected loglevel(s). */
78100 if (loglevel > srd_log_loglevel_get())
93115
94116 void AndroidLogHandler::install_callbacks()
95117 {
118 sr_log_callback_get(&prev_sr_log_cb, &prev_sr_log_cb_data);
96119 sr_log_callback_set(sr_callback, nullptr);
97120 #ifdef ENABLE_DECODE
121 srd_log_callback_get(&prev_srd_log_cb, &prev_srd_log_cb_data);
98122 srd_log_callback_set(srd_callback, nullptr);
99123 #endif
100124 }
66 Exec=pulseview
77 Icon=pulseview
88 Type=Application
9 MimeType=application/vnd.sigrok.session
9 MimeType=application/vnd.sigrok.session;
8080 !define SHCNF_IDLIST 0
8181
8282
83 # --- Functions ---------------------------------------------------------------
83 # --- Functions/Macros --------------------------------------------------------
8484
8585 Function register_sr_files
8686 ${registerExtension} "$INSTDIR\pulseview.exe" ".sr" "sigrok session file"
8989 System::Call 'Shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_IDLIST}, i 0, i 0)'
9090 FunctionEnd
9191
92 # Inspired by http://nsis.sourceforge.net/Create_Internet_Shorcuts_during_installation
93 !Macro "CreateURL" "URLFile" "URLSite" "URLDesc"
94 WriteINIStr "$INSTDIR\${URLFile}.URL" "InternetShortcut" "URL" "${URLSite}"
95 CreateShortCut "$SMPROGRAMS\sigrok\PulseView\${URLFile}.lnk" "$INSTDIR\${URLFile}.url" "" \
96 "$INSTDIR\pulseview.exe" 0 "SW_SHOWNORMAL" "" "${URLDesc}"
97 !MacroEnd
9298
9399 # --- MUI pages ---------------------------------------------------------------
94100
176182 0 SW_SHOWNORMAL \
177183 "" "Open-source, portable sigrok GUI"
178184
185 # Create a shortcut for the PulseView application running in debug mode.
186 CreateShortCut "$SMPROGRAMS\sigrok\PulseView\PulseView (Debug).lnk" \
187 "$INSTDIR\pulseview.exe" "-l 5" "$INSTDIR\pulseview.exe" \
188 0 SW_SHOWNORMAL \
189 "" "Open-source, portable sigrok GUI (debug log level)"
190
179191 # Create a shortcut for the uninstaller.
180192 CreateShortCut "$SMPROGRAMS\sigrok\PulseView\Uninstall PulseView.lnk" \
181193 "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0 \
190202 CreateShortCut "$SMPROGRAMS\sigrok\PulseView\Zadig (PulseView, Win XP).lnk" \
191203 "$INSTDIR\zadig_xp.exe" "" "$INSTDIR\zadig_xp.exe" 0 \
192204 SW_SHOWNORMAL "" "Zadig (PulseView, Win XP)"
205
206 # Create shortcuts to the HTML and PDF manuals, respectively.
207 !InsertMacro "CreateURL" "PulseView HTML manual" "https://sigrok.org/doc/pulseview/@PV_MANUAL_VERSION@/manual.html" "PulseView HTML manual"
208 !InsertMacro "CreateURL" "PulseView PDF manual" "https://sigrok.org/doc/pulseview/@PV_MANUAL_VERSION@/manual.pdf" "PulseView PDF manual"
193209
194210 # Create registry keys for "Add/remove programs" in the control panel.
195211 WriteRegStr HKLM "${REGSTR}" "DisplayName" "PulseView"
266282 # Delete the example *.sr files.
267283 RMDir /r "$INSTDIR\examples\*"
268284
285 # Delete the URL files for the manual.
286 Delete "$INSTDIR\PulseView HTML manual.url"
287 Delete "$INSTDIR\PulseView PDF manual.url"
288
269289 # Delete the install directory and its sub-directories.
270290 RMDir "$INSTDIR\share"
271291 RMDir "$INSTDIR\examples"
273293
274294 # Delete the links from the start menu.
275295 Delete "$SMPROGRAMS\sigrok\PulseView\PulseView.lnk"
296 Delete "$SMPROGRAMS\sigrok\PulseView\PulseView (Debug).lnk"
276297 Delete "$SMPROGRAMS\sigrok\PulseView\Uninstall PulseView.lnk"
277298 Delete "$SMPROGRAMS\sigrok\PulseView\Zadig (PulseView).lnk"
278299 Delete "$SMPROGRAMS\sigrok\PulseView\Zadig (PulseView, Win XP).lnk"
279300 Delete "$SMPROGRAMS\sigrok\PulseView\Examples (PulseView).lnk"
280301
302 # Delete the links to the manual.
303 Delete "$SMPROGRAMS\sigrok\PulseView\PulseView HTML manual.lnk"
304 Delete "$SMPROGRAMS\sigrok\PulseView\PulseView PDF manual.lnk"
305
281306 # Delete the sub-directory in the start menu.
282307 RMDir "$SMPROGRAMS\sigrok\PulseView"
283308 RMDir "$SMPROGRAMS\sigrok"
0 .TH PULSEVIEW 1 "June 6, 2017"
0 .TH PULSEVIEW 1 "March 30, 2018"
11 .SH "NAME"
22 PulseView \- Qt-based LA/scope/MSO GUI for sigrok
33 .SH "SYNOPSIS"
3939 .TP
4040 .B "\-V, \-\-version"
4141 Show version information and exit.
42 .TP
43 .BR "\-d, \-\-driver " <drivername>
44 Specify the capture device to connect to. If the
45 .B \-\-driver
46 option is not supplied, PulseView attempts to re-connect to the
47 most recently used device, or auto-detect available devices.
48 .TP
49 .BR "\-D, \-\-dont\-scan "
50 Usually PulseView automatically scans all drivers to find suitable
51 devices during program startup. This option disables the auto-scan.
52 Users can either specify the
53 .B \-\-driver
54 option to pick a device at startup, or interactively scan for devices
55 after PulseView has finished starting up.
4256 .TP
4357 .BR "\-i, \-\-input\-file " <filename>
4458 Load input from a file. If the
Binary diff not shown
Binary diff not shown
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://creativecommons.org/ns#"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="35.433071"
12 height="35.433071"
13 id="svg2"
14 version="1.1"
15 inkscape:version="0.48.5 r10040"
16 sodipodi:docname="view-displaymode-last_complete_segment.svg">
17 <sodipodi:namedview
18 id="base"
19 pagecolor="#ffffff"
20 bordercolor="#666666"
21 borderopacity="1.0"
22 inkscape:pageopacity="0.0"
23 inkscape:pageshadow="2"
24 inkscape:zoom="15.465778"
25 inkscape:cx="7.8560557"
26 inkscape:cy="17.231594"
27 inkscape:document-units="px"
28 inkscape:current-layer="layer1"
29 showgrid="false"
30 units="mm"
31 inkscape:window-width="1369"
32 inkscape:window-height="743"
33 inkscape:window-x="174"
34 inkscape:window-y="153"
35 inkscape:window-maximized="0" />
36 <defs
37 id="defs4" />
38 <metadata
39 id="metadata7">
40 <rdf:RDF>
41 <cc:Work
42 rdf:about="">
43 <dc:format>image/svg+xml</dc:format>
44 <dc:type
45 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
46 <dc:title />
47 </cc:Work>
48 </rdf:RDF>
49 </metadata>
50 <g
51 inkscape:groupmode="layer"
52 id="layer2"
53 inkscape:label="Layer"
54 style="display:none">
55 <g
56 transform="translate(0,-1016.9291)"
57 style="display:inline"
58 id="g7555-6">
59 <path
60 inkscape:connector-curvature="0"
61 id="path3768-2-3-5-9"
62 d="m 0,1044.2466 35.26169,0"
63 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
64 <path
65 inkscape:connector-curvature="0"
66 id="path3768-2-6"
67 d="m 0,1020.9238 35.26169,0"
68 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
69 <path
70 inkscape:connector-curvature="0"
71 id="path3768-1-9"
72 d="m 0,1029.0753 35.26169,0"
73 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
74 <path
75 inkscape:connector-curvature="0"
76 id="path3755-4-29"
77 d="m 3.8604174,1017.11 0,15.8294"
78 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
79 <path
80 inkscape:connector-curvature="0"
81 id="path3755-4-0-6"
82 d="m 7.7208349,1017.11 0,15.8294"
83 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
84 <path
85 inkscape:connector-curvature="0"
86 id="path3755-4-5-8"
87 d="m 11.581253,1017.11 0,15.8294"
88 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
89 <path
90 inkscape:connector-curvature="0"
91 id="path3755-4-9-14"
92 d="m 15.441669,1017.11 0,15.8294"
93 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
94 <path
95 inkscape:connector-curvature="0"
96 id="path3755-4-7-90"
97 d="m 19.302086,1017.11 0,15.8294"
98 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
99 <path
100 inkscape:connector-curvature="0"
101 id="path3755-4-3-32"
102 d="m 23.162504,1017.11 0,15.8294"
103 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
104 <path
105 inkscape:connector-curvature="0"
106 id="path3755-4-1-3"
107 d="m 27.022922,1017.11 0,15.8294"
108 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
109 <path
110 inkscape:connector-curvature="0"
111 id="path3755-4-90-6"
112 d="m 30.883338,1017.11 0,15.8294"
113 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
114 <path
115 inkscape:connector-curvature="0"
116 id="path3755-4-2-4"
117 d="m 34.743757,1017.11 0,15.8294"
118 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
119 <path
120 inkscape:connector-curvature="0"
121 id="path3755-49"
122 d="m 0,1017.11 0,15.8294"
123 style="fill:none;stroke:#000000;stroke-width:0.3772082;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
124 <path
125 inkscape:connector-curvature="0"
126 id="path3768-9"
127 d="m 0,1024.9996 35.26169,0"
128 style="fill:none;stroke:#000000;stroke-width:0.3772082;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
129 <path
130 id="path4190-3"
131 title="sin(x)"
132 d="m 0.17138129,1025.0901 c 0.18430284,-0.3999 0.36860568,-0.8 0.55290852,-1.1919 0.18430286,-0.3921 0.36860569,-0.7762 0.55290859,-1.1447 0.1843028,-0.3686 0.3686057,-0.7216 0.5529085,-1.0521 0.1843028,-0.3303 0.3686057,-0.6383 0.5529085,-0.9175 0.1843029,-0.2794 0.3686057,-0.5299 0.5529085,-0.7468 0.1843029,-0.2172 0.3686057,-0.4004 0.5529086,-0.5466 0.1843028,-0.1461 0.3686056,-0.2549 0.5529085,-0.3244 0.1843028,-0.069 0.3686057,-0.1 0.5529085,-0.09 0.1843029,0.011 0.3686057,0.06 0.5529085,0.1489 0.1843029,0.09 0.3686057,0.217 0.5529086,0.3815 0.1843028,0.1645 0.3686056,0.3655 0.5529085,0.5989 0.1843028,0.2335 0.3686057,0.4995 0.5529085,0.7927 0.1843028,0.2931 0.3686057,0.6135 0.5529085,0.9549 0.1843029,0.3412 0.3686057,0.7034 0.5529086,1.0792 0.1843028,0.3758 0.3686056,0.7653 0.5529085,1.1608 0.1843028,0.3956 0.3686057,0.7969 0.5529085,1.1964 0.1843028,0.3994 0.3686057,0.7969 0.5529085,1.1845 0.1843029,0.3876 0.3686059,0.7653 0.5529088,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208 0.184302,-0.031 0.368605,-0.019 0.552908,0.031 0.184303,0.05 0.368606,0.1392 0.552909,0.2666 0.184303,0.1274 0.368605,0.2927 0.552908,0.4926 0.184303,0.2001 0.368606,0.4347 0.552909,0.6993 0.184303,0.2646 0.368605,0.5594 0.552908,0.8781 0.184303,0.3189 0.368606,0.6618 0.552909,1.0222 0.184303,0.3603 0.368605,0.738 0.552908,1.1256 0.184303,0.3876 0.368606,0.785 0.552909,1.1845 0.184303,0.3994 0.368606,0.8009 0.552908,1.1963 0.184303,0.3955 0.368606,0.7851 0.552909,1.161 0.184303,0.3758 0.368606,0.7379 0.552908,1.0792 0.184303,0.3411 0.368606,0.6616 0.552909,0.9548 0.184303,0.2932 0.368606,0.5592 0.552908,0.7927 0.184303,0.2334 0.368606,0.4343 0.552909,0.5988 0.184303,0.1645 0.368606,0.2926 0.552909,0.3816 0.184302,0.089 0.368605,0.1389 0.552908,0.1489 0.184303,0.011 0.368606,-0.02 0.552909,-0.09 0.184302,-0.069 0.368605,-0.1784 0.552908,-0.3246 0.184303,-0.1461 0.368606,-0.3295 0.552909,-0.5464 0.184302,-0.217 0.368605,-0.4676 0.552908,-0.7469 0.184303,-0.2792 0.368606,-0.5872 0.552909,-0.9177 0.184302,-0.3303 0.368605,-0.6834 0.552908,-1.0519 0.184303,-0.3686 0.368606,-0.7526 0.552909,-1.1446 0.184303,-0.392 0.368605,-0.7921 0.552908,-1.1919"
133 style="fill:none;stroke:#2e0dff;stroke-width:0.30176657;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
134 inkscape:connector-curvature="0" />
135 <path
136 inkscape:connector-curvature="0"
137 id="path3768-2-3-7"
138 d="m 0,1040.0689 35.26169,0"
139 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
140 <path
141 inkscape:connector-curvature="0"
142 id="path3768-1-5-1"
143 d="m 0,1048.373 35.26169,0"
144 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
145 <path
146 inkscape:connector-curvature="0"
147 id="path3755-4-38-0"
148 d="m 3.860417,1036.1837 0,16.1257"
149 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
150 <path
151 inkscape:connector-curvature="0"
152 id="path3755-4-0-5-8"
153 d="m 7.720835,1036.1837 0,16.1257"
154 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
155 <path
156 inkscape:connector-curvature="0"
157 id="path3755-4-5-0-7"
158 d="m 11.581253,1036.1837 0,16.1257"
159 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
160 <path
161 inkscape:connector-curvature="0"
162 id="path3755-4-9-1-2"
163 d="m 15.441669,1036.1837 0,16.1257"
164 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
165 <path
166 inkscape:connector-curvature="0"
167 id="path3755-4-7-9-8"
168 d="m 19.302086,1036.1837 0,16.1257"
169 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
170 <path
171 inkscape:connector-curvature="0"
172 id="path3755-4-3-3-8"
173 d="m 23.162504,1036.1837 0,16.1257"
174 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
175 <path
176 inkscape:connector-curvature="0"
177 id="path3755-4-1-4-9"
178 d="m 27.022922,1036.1837 0,16.1257"
179 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
180 <path
181 inkscape:connector-curvature="0"
182 id="path3755-4-90-5-2"
183 d="m 30.883338,1036.1837 0,16.1257"
184 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
185 <path
186 inkscape:connector-curvature="0"
187 id="path3755-4-2-0-4"
188 d="m 34.743757,1036.1837 0,16.1257"
189 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
190 <path
191 inkscape:connector-curvature="0"
192 id="path3755-6-0"
193 d="m 0,1036.1837 0,16.1257"
194 style="fill:none;stroke:#000000;stroke-width:0.38072109;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
195 <path
196 inkscape:connector-curvature="0"
197 id="path3768-4-0"
198 d="m 0,1052.2209 35.26169,0"
199 style="fill:none;stroke:#000000;stroke-width:0.38072109;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
200 <path
201 inkscape:connector-curvature="0"
202 style="fill:none;stroke:#ff970d;stroke-width:0.30000001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
203 d="m 0.25968796,1051.8993 c 0.18450185,0 0.3690037,0 0.55350556,0 0.18450185,0 0.36900368,0 0.55350558,0 0.1845018,0 0.3690037,0 0.5535055,0 0.1845019,0 0.3690037,0 0.5535056,0 0.1845018,0 0.3690037,0 0.5535055,0 0.1845019,0 0.3690037,0 0.5535056,0 0.1845018,-10e-5 0.3690037,-10e-5 0.5535055,-3e-4 0.1845019,-10e-5 0.3690037,-3e-4 0.5535056,-7e-4 0.1845018,-4e-4 0.3690037,-10e-4 0.5535055,0 0.1845019,0 0.3690038,0 0.5535056,-0.01 0.1845019,0 0.3690037,-0.01 0.5535056,-0.011 0.1845018,-0.01 0.3690037,-0.012 0.5535055,-0.022 0.1845019,-0.01 0.3690037,-0.023 0.5535056,-0.041 0.1845018,-0.018 0.3690037,-0.041 0.5535055,-0.072 0.1845019,-0.031 0.3690037,-0.069 0.5535056,-0.1174 0.1845018,-0.049 0.3690037,-0.1081 0.5535055,-0.1816 0.1845019,-0.073 0.3690037,-0.1613 0.5535056,-0.267 0.1845018,-0.1057 0.3690036,-0.2294 0.5535056,-0.3745 0.184502,-0.1451 0.369003,-0.3119 0.553505,-0.5028 0.184502,-0.1909 0.369004,-0.4062 0.553506,-0.6472 0.184502,-0.241 0.369003,-0.5079 0.553505,-0.8001 0.184502,-0.2922 0.369004,-0.6099 0.553506,-0.9503 0.184502,-0.3405 0.369004,-0.7038 0.553505,-1.0848 0.184502,-0.3811 0.369004,-0.7798 0.553506,-1.1888 0.184502,-0.409 0.369004,-0.8281 0.553506,-1.2479 0.184501,-0.4198 0.369003,-0.8402 0.553505,-1.2503 0.184502,-0.41 0.369004,-0.8095 0.553506,-1.1872 0.184502,-0.3777 0.369003,-0.7333 0.553505,-1.0561 0.184502,-0.3227 0.369004,-0.6123 0.553506,-0.8596 0.184502,-0.2473 0.369003,-0.452 0.553505,-0.6075 0.184502,-0.1555 0.369004,-0.2614 0.553506,-0.3145 0.184502,-0.053 0.369004,-0.053 0.553505,0 0.184502,0.053 0.369004,0.1591 0.553506,0.3145 0.184502,0.1554 0.369004,0.3603 0.553506,0.6075 0.184501,0.2472 0.369003,0.537 0.553505,0.8596 0.184502,0.3227 0.369004,0.6784 0.553506,1.0561 0.184502,0.3776 0.369003,0.7772 0.553505,1.1872 0.184502,0.4101 0.369004,0.8305 0.553506,1.2503 0.184502,0.4198 0.369003,0.8389 0.553505,1.2479 0.184502,0.409 0.369004,0.8077 0.553506,1.1888 0.184502,0.381 0.369004,0.7443 0.553505,1.0848 0.184502,0.3405 0.369004,0.6581 0.553506,0.9503 0.184502,0.2923 0.369004,0.5591 0.553506,0.8001 0.184501,0.241 0.369003,0.4563 0.553505,0.6472 0.184502,0.191 0.369004,0.3576 0.553506,0.5028 0.184502,0.1452 0.369003,0.2688 0.553505,0.3745 0.184502,0.1057 0.369004,0.1934 0.553506,0.267 0.184502,0.074 0.369003,0.1328 0.553505,0.1816 0.184502,0.049 0.369004,0.087 0.553506,0.1174 0.184502,0.031 0.369004,0.054 0.553505,0.072 0.184502,0.018 0.369004,0.031 0.553506,0.041 0.184502,0.01 0.369004,0.017 0.553506,0.022 0.184501,0.01 0.369003,0.01 0.553505,0.011 0.184502,0 0.369004,0 0.553506,0.01 0.184502,10e-4 0.369003,0 0.553505,0 0.184502,4e-4 0.369004,6e-4 0.553506,7e-4 0.184502,2e-4 0.369003,2e-4 0.553505,3e-4 0.184502,0 0.369004,0 0.553506,0 0.184502,0 0.369004,0 0.553505,0 0.184502,0 0.369004,0 0.553506,0 0.184502,0 0.369004,0 0.553506,0 0.184501,0 0.369003,0 0.553505,0 0.184502,0 0.369004,0 0.553506,0"
204 title="pow(sin(x), 10)"
205 id="path7506-5" />
206 </g>
207 </g>
208 <g
209 inkscape:label="Layer 1"
210 inkscape:groupmode="layer"
211 id="layer1"
212 transform="translate(0,-1016.9291)"
213 style="display:inline">
214 <rect
215 style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
216 id="rect4735"
217 width="21.984022"
218 height="15.841428"
219 x="-0.12931778"
220 y="1017.1877" />
221 <path
222 style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
223 d="m -0.38664415,1020.9238 22.23492015,0"
224 id="path3768-2"
225 inkscape:connector-curvature="0" />
226 <path
227 style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
228 d="m -0.25776149,1029.0753 22.04169149,0"
229 id="path3768-1"
230 inkscape:connector-curvature="0" />
231 <path
232 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
233 d="m 3.8604174,1017.11 0,15.8294"
234 id="path3755-4"
235 inkscape:connector-curvature="0" />
236 <path
237 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
238 d="m 7.7208349,1017.11 0,15.8294"
239 id="path3755-4-0"
240 inkscape:connector-curvature="0" />
241 <path
242 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
243 d="m 11.581253,1017.11 0,15.8294"
244 id="path3755-4-5"
245 inkscape:connector-curvature="0" />
246 <path
247 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
248 d="m 15.441669,1017.11 0,15.8294"
249 id="path3755-4-9"
250 inkscape:connector-curvature="0" />
251 <path
252 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
253 d="m 19.302086,1017.11 0,15.8294"
254 id="path3755-4-7"
255 inkscape:connector-curvature="0" />
256 <path
257 style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
258 d="m -0.25689803,1024.9996 22.01207903,0"
259 id="path3768"
260 inkscape:connector-curvature="0" />
261 <path
262 inkscape:connector-curvature="0"
263 style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
264 d="m 0.17138129,1025.0901 c 0.18430284,-0.3999 0.36860568,-0.8 0.55290852,-1.1919 0.18430286,-0.3921 0.36860569,-0.7762 0.55290859,-1.1447 0.1843028,-0.3686 0.3686057,-0.7216 0.5529085,-1.0521 0.1843028,-0.3303 0.3686057,-0.6383 0.5529085,-0.9175 0.1843029,-0.2794 0.3686057,-0.5299 0.5529085,-0.7468 0.1843029,-0.2172 0.3686057,-0.4004 0.5529086,-0.5466 0.1843028,-0.1461 0.3686056,-0.2549 0.5529085,-0.3244 0.1843028,-0.069 0.3686057,-0.1 0.5529085,-0.09 0.1843029,0.011 0.3686057,0.06 0.5529085,0.1489 0.1843029,0.09 0.3686057,0.217 0.5529086,0.3815 0.1843028,0.1645 0.3686056,0.3655 0.5529085,0.5989 0.1843028,0.2335 0.3686057,0.4995 0.5529085,0.7927 0.1843028,0.2931 0.3686057,0.6135 0.5529085,0.9549 0.1843029,0.3412 0.3686057,0.7034 0.5529086,1.0792 0.1843028,0.3758 0.3686056,0.7653 0.5529085,1.1608 0.1843028,0.3956 0.3686057,0.7969 0.5529085,1.1964 0.1843028,0.3994 0.3686057,0.7969 0.5529085,1.1845 0.1843029,0.3876 0.3686059,0.7653 0.5529088,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208"
265 title="sin(x)"
266 id="path4190"
267 sodipodi:nodetypes="ccccccccccscccsccsccccscccccccccccccsccc" />
268 <rect
269 style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
270 id="rect3905"
271 width="22.075964"
272 height="15.974718"
273 x="-0.19596301"
274 y="1017.121" />
275 <rect
276 style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
277 id="rect4735-9"
278 width="21.984022"
279 height="15.841428"
280 x="3.2648256"
281 y="1021.9644" />
282 <path
283 style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
284 d="m 3.0074993,1025.7005 22.2349197,0"
285 id="path3768-2-9"
286 inkscape:connector-curvature="0" />
287 <path
288 style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
289 d="m 3.1363819,1033.852 22.0416911,0"
290 id="path3768-1-4"
291 inkscape:connector-curvature="0" />
292 <path
293 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
294 d="m 7.2545608,1021.8867 0,15.8294"
295 id="path3755-4-2"
296 inkscape:connector-curvature="0" />
297 <path
298 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
299 d="m 11.114978,1021.8867 0,15.8294"
300 id="path3755-4-0-2"
301 inkscape:connector-curvature="0" />
302 <path
303 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
304 d="m 14.975396,1021.8867 0,15.8294"
305 id="path3755-4-5-3"
306 inkscape:connector-curvature="0" />
307 <path
308 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
309 d="m 18.835812,1021.8867 0,15.8294"
310 id="path3755-4-9-8"
311 inkscape:connector-curvature="0" />
312 <path
313 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
314 d="m 22.696229,1021.8867 0,15.8294"
315 id="path3755-4-7-9"
316 inkscape:connector-curvature="0" />
317 <path
318 style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
319 d="m 3.1372454,1029.7763 22.0120786,0"
320 id="path3768-7"
321 inkscape:connector-curvature="0" />
322 <path
323 inkscape:connector-curvature="0"
324 style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
325 d="m 3.5655247,1029.8668 c 0.1843028,-0.3999 0.3686057,-0.8 0.5529085,-1.1919 0.1843029,-0.3921 0.3686057,-0.7762 0.5529086,-1.1447 0.1843028,-0.3686 0.3686057,-0.7216 0.5529085,-1.0521 0.1843028,-0.3303 0.3686057,-0.6383 0.5529085,-0.9175 0.1843029,-0.2794 0.3686057,-0.5299 0.5529085,-0.7468 0.1843029,-0.2172 0.3686057,-0.4004 0.5529086,-0.5466 0.1843028,-0.1461 0.3686056,-0.2549 0.5529085,-0.3244 0.1843028,-0.069 0.3686057,-0.1 0.5529085,-0.09 0.1843029,0.011 0.3686057,0.06 0.5529085,0.1489 0.1843029,0.09 0.3686057,0.217 0.5529086,0.3815 0.1843028,0.1645 0.3686056,0.3655 0.5529085,0.5989 0.1843028,0.2335 0.3686055,0.4995 0.5529085,0.7927 0.184303,0.2931 0.368606,0.6135 0.552909,0.9549 0.184302,0.3412 0.368605,0.7034 0.552908,1.0792 0.184303,0.3758 0.368606,0.7653 0.552909,1.1608 0.184302,0.3956 0.368605,0.7969 0.552908,1.1964 0.184303,0.3994 0.368606,0.7969 0.552909,1.1845 0.184303,0.3876 0.368606,0.7653 0.552908,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208"
326 title="sin(x)"
327 id="path4190-0"
328 sodipodi:nodetypes="ccccccccccscccsccsccccscccccccccccccsccc" />
329 <rect
330 style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
331 id="rect3905-8"
332 width="22.075964"
333 height="15.974718"
334 x="3.1981804"
335 y="1021.8977" />
336 <rect
337 style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
338 id="rect4735-2"
339 width="21.984022"
340 height="15.841428"
341 x="6.5999274"
342 y="1026.7411" />
343 <path
344 style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
345 d="m 6.3426012,1030.4772 22.2349198,0"
346 id="path3768-2-3"
347 inkscape:connector-curvature="0" />
348 <path
349 style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
350 d="m 6.4714839,1038.6287 22.0416911,0"
351 id="path3768-1-3"
352 inkscape:connector-curvature="0" />
353 <path
354 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
355 d="m 10.589663,1026.6634 0,15.8294"
356 id="path3755-4-1"
357 inkscape:connector-curvature="0" />
358 <path
359 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
360 d="m 14.45008,1026.6634 0,15.8294"
361 id="path3755-4-0-3"
362 inkscape:connector-curvature="0" />
363 <path
364 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
365 d="m 18.310498,1026.6634 0,15.8294"
366 id="path3755-4-5-1"
367 inkscape:connector-curvature="0" />
368 <path
369 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
370 d="m 22.170914,1026.6634 0,15.8294"
371 id="path3755-4-9-88"
372 inkscape:connector-curvature="0" />
373 <path
374 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
375 d="m 26.031331,1026.6634 0,15.8294"
376 id="path3755-4-7-2"
377 inkscape:connector-curvature="0" />
378 <path
379 style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
380 d="m 6.4723474,1034.553 22.0120786,0"
381 id="path3768-98"
382 inkscape:connector-curvature="0" />
383 <path
384 inkscape:connector-curvature="0"
385 style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
386 d="m 6.9006267,1034.6435 c 0.1843028,-0.3999 0.3686057,-0.8 0.5529085,-1.1919 0.1843029,-0.3921 0.3686057,-0.7762 0.5529086,-1.1447 0.1843028,-0.3686 0.3686057,-0.7216 0.5529085,-1.0521 0.1843028,-0.3303 0.3686057,-0.6383 0.5529085,-0.9175 0.1843029,-0.2794 0.3686057,-0.5299 0.5529085,-0.7468 0.1843029,-0.2172 0.3686057,-0.4004 0.5529087,-0.5466 0.184303,-0.1461 0.368605,-0.2549 0.552908,-0.3244 0.184303,-0.069 0.368606,-0.1 0.552909,-0.09 0.184303,0.011 0.368606,0.06 0.552908,0.1489 0.184303,0.09 0.368606,0.217 0.552909,0.3815 0.184303,0.1645 0.368606,0.3655 0.552908,0.5989 0.184303,0.2335 0.368606,0.4995 0.552909,0.7927 0.184303,0.2931 0.368606,0.6135 0.552909,0.9549 0.184302,0.3412 0.368605,0.7034 0.552908,1.0792 0.184303,0.3758 0.368606,0.7653 0.552909,1.1608 0.184302,0.3956 0.368605,0.7969 0.552908,1.1964 0.184303,0.3994 0.368606,0.7969 0.552909,1.1845 0.184303,0.3876 0.368605,0.7653 0.552908,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208"
387 title="sin(x)"
388 id="path4190-8"
389 sodipodi:nodetypes="ccccccccccscccsccsccccscccccccccccccsccc" />
390 <rect
391 style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
392 id="rect3905-7"
393 width="22.075964"
394 height="15.974718"
395 x="6.5332823"
396 y="1026.6744" />
397 <rect
398 style="fill:#f6e879;fill-opacity:1;fill-rule:evenodd;stroke:none"
399 id="rect4735-98"
400 width="21.984022"
401 height="15.841428"
402 x="9.935029"
403 y="1031.5178" />
404 <path
405 style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
406 d="m 9.6777028,1035.2539 22.2349202,0"
407 id="path3768-2-4"
408 inkscape:connector-curvature="0" />
409 <path
410 style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
411 d="m 9.8065855,1043.4054 22.0416915,0"
412 id="path3768-1-6"
413 inkscape:connector-curvature="0" />
414 <path
415 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
416 d="m 13.924764,1031.4401 0,15.8294"
417 id="path3755-4-54"
418 inkscape:connector-curvature="0" />
419 <path
420 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
421 d="m 17.785182,1031.4401 0,15.8294"
422 id="path3755-4-0-5"
423 inkscape:connector-curvature="0" />
424 <path
425 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
426 d="m 21.6456,1031.4401 0,15.8294"
427 id="path3755-4-5-9"
428 inkscape:connector-curvature="0" />
429 <path
430 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
431 d="m 25.506016,1031.4401 0,15.8294"
432 id="path3755-4-9-87"
433 inkscape:connector-curvature="0" />
434 <path
435 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
436 d="m 29.366433,1031.4401 0,15.8294"
437 id="path3755-4-7-4"
438 inkscape:connector-curvature="0" />
439 <path
440 style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
441 d="m 9.807449,1039.3297 22.012079,0"
442 id="path3768-6"
443 inkscape:connector-curvature="0" />
444 <path
445 inkscape:connector-curvature="0"
446 style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
447 d="m 10.235728,1039.4202 c 0.184303,-0.3999 0.368606,-0.8 0.552909,-1.1919 0.184303,-0.3921 0.368605,-0.7762 0.552908,-1.1447 0.184303,-0.3686 0.368606,-0.7216 0.552909,-1.0521 0.184303,-0.3303 0.368606,-0.6383 0.552908,-0.9175 0.184303,-0.2794 0.368606,-0.5299 0.552909,-0.7468 0.184303,-0.2172 0.368606,-0.4004 0.552908,-0.5466 0.184303,-0.1461 0.368606,-0.2549 0.552909,-0.3244 0.184303,-0.069 0.368606,-0.1 0.552909,-0.09 0.184302,0.011 0.368605,0.06 0.552908,0.1489 0.184303,0.09 0.368606,0.217 0.552909,0.3815 0.184302,0.1645 0.368605,0.3655 0.552908,0.5989 0.184303,0.2335 0.368606,0.4995 0.552909,0.7927 0.184302,0.2931 0.368605,0.6135 0.552908,0.9549 0.184303,0.3412 0.368606,0.7034 0.552909,1.0792 0.184302,0.3758 0.368605,0.7653 0.552908,1.1608 0.184303,0.3956 0.368606,0.7969 0.552909,1.1964 0.184302,0.3994 0.368605,0.7969 0.552908,1.1845 0.184303,0.3876 0.368606,0.7653 0.552909,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208"
448 title="sin(x)"
449 id="path4190-84"
450 sodipodi:nodetypes="ccccccccccscccsccsccccscccccccccccccsccc" />
451 <rect
452 style="fill:none;stroke:#000000;stroke-width:0.29496232000000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
453 id="rect3905-86"
454 width="22.075964"
455 height="15.974718"
456 x="9.8683844"
457 y="1031.451" />
458 <rect
459 style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
460 id="rect4735-23"
461 width="21.984022"
462 height="15.841428"
463 x="13.270129"
464 y="1036.2946" />
465 <path
466 style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
467 d="m 13.012803,1040.0306 22.23492,0"
468 id="path3768-2-5"
469 inkscape:connector-curvature="0" />
470 <path
471 style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
472 d="m 13.141686,1048.1821 22.041691,0"
473 id="path3768-1-5"
474 inkscape:connector-curvature="0" />
475 <path
476 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
477 d="m 17.259864,1036.2168 0,15.8294"
478 id="path3755-4-6"
479 inkscape:connector-curvature="0" />
480 <path
481 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
482 d="m 21.120282,1036.2168 0,15.8294"
483 id="path3755-4-0-35"
484 inkscape:connector-curvature="0" />
485 <path
486 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
487 d="m 24.9807,1036.2168 0,15.8294"
488 id="path3755-4-5-82"
489 inkscape:connector-curvature="0" />
490 <path
491 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
492 d="m 28.841116,1036.2168 0,15.8294"
493 id="path3755-4-9-5"
494 inkscape:connector-curvature="0" />
495 <path
496 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
497 d="m 32.701533,1036.2168 0,15.8294"
498 id="path3755-4-7-7"
499 inkscape:connector-curvature="0" />
500 <path
501 style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
502 d="m 13.142549,1044.1064 22.012079,0"
503 id="path3768-25"
504 inkscape:connector-curvature="0" />
505 <path
506 inkscape:connector-curvature="0"
507 style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
508 d="m 13.570828,1044.1969 c 0.184303,-0.3999 0.368606,-0.8 0.552909,-1.1919 0.184303,-0.3921 0.368605,-0.7762 0.552908,-1.1447 0.184303,-0.3686 0.368606,-0.7216 0.552909,-1.0521 0.184303,-0.3303 0.368606,-0.6383 0.552908,-0.9175 0.184303,-0.2794 0.368606,-0.5299 0.552909,-0.7468 0.184303,-0.2172 0.368606,-0.4004 0.552908,-0.5466 0.184303,-0.1461 0.368606,-0.2549 0.552909,-0.3244 0.184303,-0.069 0.368606,-0.1 0.552909,-0.09 0.184302,0.011 0.368605,0.06 0.552908,0.1489 0.184303,0.09 0.368606,0.217 0.552909,0.3815 0.184302,0.1645 0.368605,0.3655 0.552908,0.5989 0.184303,0.2335 0.368606,0.4995 0.552909,0.7927 0.184302,0.2931 0.368605,0.6135 0.552908,0.9549 0.184303,0.3412 0.368606,0.7034 0.552909,1.0792 0.184303,0.3758 0.368605,0.7653 0.552908,1.1608 0.184303,0.3956 0.368606,0.7969 0.552909,1.1964 0.184303,0.3994 0.368605,0.7969 0.552908,1.1845 0.184303,0.3876 0.368606,0.7653 0.552909,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022"
509 title="sin(x)"
510 id="path4190-80"
511 sodipodi:nodetypes="ccccccccccscccsccscc" />
512 <rect
513 style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
514 id="rect3905-9"
515 width="22.075964"
516 height="15.974718"
517 x="13.203484"
518 y="1036.2278" />
519 </g>
520 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://creativecommons.org/ns#"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="35.433071"
12 height="35.433071"
13 id="svg2"
14 version="1.1"
15 inkscape:version="0.48.5 r10040"
16 sodipodi:docname="view-displaymode-last_segment.svg">
17 <sodipodi:namedview
18 id="base"
19 pagecolor="#ffffff"
20 bordercolor="#666666"
21 borderopacity="1.0"
22 inkscape:pageopacity="0.0"
23 inkscape:pageshadow="2"
24 inkscape:zoom="15.465778"
25 inkscape:cx="7.8560557"
26 inkscape:cy="17.231594"
27 inkscape:document-units="px"
28 inkscape:current-layer="layer1"
29 showgrid="false"
30 units="mm"
31 inkscape:window-width="1369"
32 inkscape:window-height="743"
33 inkscape:window-x="46"
34 inkscape:window-y="65"
35 inkscape:window-maximized="0" />
36 <defs
37 id="defs4" />
38 <metadata
39 id="metadata7">
40 <rdf:RDF>
41 <cc:Work
42 rdf:about="">
43 <dc:format>image/svg+xml</dc:format>
44 <dc:type
45 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
46 <dc:title />
47 </cc:Work>
48 </rdf:RDF>
49 </metadata>
50 <g
51 inkscape:groupmode="layer"
52 id="layer2"
53 inkscape:label="Layer"
54 style="display:none">
55 <g
56 transform="translate(0,-1016.9291)"
57 style="display:inline"
58 id="g7555-6">
59 <path
60 inkscape:connector-curvature="0"
61 id="path3768-2-3-5-9"
62 d="m 0,1044.2466 35.26169,0"
63 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
64 <path
65 inkscape:connector-curvature="0"
66 id="path3768-2-6"
67 d="m 0,1020.9238 35.26169,0"
68 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
69 <path
70 inkscape:connector-curvature="0"
71 id="path3768-1-9"
72 d="m 0,1029.0753 35.26169,0"
73 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
74 <path
75 inkscape:connector-curvature="0"
76 id="path3755-4-29"
77 d="m 3.8604174,1017.11 0,15.8294"
78 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
79 <path
80 inkscape:connector-curvature="0"
81 id="path3755-4-0-6"
82 d="m 7.7208349,1017.11 0,15.8294"
83 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
84 <path
85 inkscape:connector-curvature="0"
86 id="path3755-4-5-8"
87 d="m 11.581253,1017.11 0,15.8294"
88 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
89 <path
90 inkscape:connector-curvature="0"
91 id="path3755-4-9-14"
92 d="m 15.441669,1017.11 0,15.8294"
93 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
94 <path
95 inkscape:connector-curvature="0"
96 id="path3755-4-7-90"
97 d="m 19.302086,1017.11 0,15.8294"
98 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
99 <path
100 inkscape:connector-curvature="0"
101 id="path3755-4-3-32"
102 d="m 23.162504,1017.11 0,15.8294"
103 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
104 <path
105 inkscape:connector-curvature="0"
106 id="path3755-4-1-3"
107 d="m 27.022922,1017.11 0,15.8294"
108 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
109 <path
110 inkscape:connector-curvature="0"
111 id="path3755-4-90-6"
112 d="m 30.883338,1017.11 0,15.8294"
113 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
114 <path
115 inkscape:connector-curvature="0"
116 id="path3755-4-2-4"
117 d="m 34.743757,1017.11 0,15.8294"
118 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
119 <path
120 inkscape:connector-curvature="0"
121 id="path3755-49"
122 d="m 0,1017.11 0,15.8294"
123 style="fill:none;stroke:#000000;stroke-width:0.3772082;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
124 <path
125 inkscape:connector-curvature="0"
126 id="path3768-9"
127 d="m 0,1024.9996 35.26169,0"
128 style="fill:none;stroke:#000000;stroke-width:0.3772082;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
129 <path
130 id="path4190-3"
131 title="sin(x)"
132 d="m 0.17138129,1025.0901 c 0.18430284,-0.3999 0.36860568,-0.8 0.55290852,-1.1919 0.18430286,-0.3921 0.36860569,-0.7762 0.55290859,-1.1447 0.1843028,-0.3686 0.3686057,-0.7216 0.5529085,-1.0521 0.1843028,-0.3303 0.3686057,-0.6383 0.5529085,-0.9175 0.1843029,-0.2794 0.3686057,-0.5299 0.5529085,-0.7468 0.1843029,-0.2172 0.3686057,-0.4004 0.5529086,-0.5466 0.1843028,-0.1461 0.3686056,-0.2549 0.5529085,-0.3244 0.1843028,-0.069 0.3686057,-0.1 0.5529085,-0.09 0.1843029,0.011 0.3686057,0.06 0.5529085,0.1489 0.1843029,0.09 0.3686057,0.217 0.5529086,0.3815 0.1843028,0.1645 0.3686056,0.3655 0.5529085,0.5989 0.1843028,0.2335 0.3686057,0.4995 0.5529085,0.7927 0.1843028,0.2931 0.3686057,0.6135 0.5529085,0.9549 0.1843029,0.3412 0.3686057,0.7034 0.5529086,1.0792 0.1843028,0.3758 0.3686056,0.7653 0.5529085,1.1608 0.1843028,0.3956 0.3686057,0.7969 0.5529085,1.1964 0.1843028,0.3994 0.3686057,0.7969 0.5529085,1.1845 0.1843029,0.3876 0.3686059,0.7653 0.5529088,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208 0.184302,-0.031 0.368605,-0.019 0.552908,0.031 0.184303,0.05 0.368606,0.1392 0.552909,0.2666 0.184303,0.1274 0.368605,0.2927 0.552908,0.4926 0.184303,0.2001 0.368606,0.4347 0.552909,0.6993 0.184303,0.2646 0.368605,0.5594 0.552908,0.8781 0.184303,0.3189 0.368606,0.6618 0.552909,1.0222 0.184303,0.3603 0.368605,0.738 0.552908,1.1256 0.184303,0.3876 0.368606,0.785 0.552909,1.1845 0.184303,0.3994 0.368606,0.8009 0.552908,1.1963 0.184303,0.3955 0.368606,0.7851 0.552909,1.161 0.184303,0.3758 0.368606,0.7379 0.552908,1.0792 0.184303,0.3411 0.368606,0.6616 0.552909,0.9548 0.184303,0.2932 0.368606,0.5592 0.552908,0.7927 0.184303,0.2334 0.368606,0.4343 0.552909,0.5988 0.184303,0.1645 0.368606,0.2926 0.552909,0.3816 0.184302,0.089 0.368605,0.1389 0.552908,0.1489 0.184303,0.011 0.368606,-0.02 0.552909,-0.09 0.184302,-0.069 0.368605,-0.1784 0.552908,-0.3246 0.184303,-0.1461 0.368606,-0.3295 0.552909,-0.5464 0.184302,-0.217 0.368605,-0.4676 0.552908,-0.7469 0.184303,-0.2792 0.368606,-0.5872 0.552909,-0.9177 0.184302,-0.3303 0.368605,-0.6834 0.552908,-1.0519 0.184303,-0.3686 0.368606,-0.7526 0.552909,-1.1446 0.184303,-0.392 0.368605,-0.7921 0.552908,-1.1919"
133 style="fill:none;stroke:#2e0dff;stroke-width:0.30176657;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
134 inkscape:connector-curvature="0" />
135 <path
136 inkscape:connector-curvature="0"
137 id="path3768-2-3-7"
138 d="m 0,1040.0689 35.26169,0"
139 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
140 <path
141 inkscape:connector-curvature="0"
142 id="path3768-1-5-1"
143 d="m 0,1048.373 35.26169,0"
144 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
145 <path
146 inkscape:connector-curvature="0"
147 id="path3755-4-38-0"
148 d="m 3.860417,1036.1837 0,16.1257"
149 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
150 <path
151 inkscape:connector-curvature="0"
152 id="path3755-4-0-5-8"
153 d="m 7.720835,1036.1837 0,16.1257"
154 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
155 <path
156 inkscape:connector-curvature="0"
157 id="path3755-4-5-0-7"
158 d="m 11.581253,1036.1837 0,16.1257"
159 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
160 <path
161 inkscape:connector-curvature="0"
162 id="path3755-4-9-1-2"
163 d="m 15.441669,1036.1837 0,16.1257"
164 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
165 <path
166 inkscape:connector-curvature="0"
167 id="path3755-4-7-9-8"
168 d="m 19.302086,1036.1837 0,16.1257"
169 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
170 <path
171 inkscape:connector-curvature="0"
172 id="path3755-4-3-3-8"
173 d="m 23.162504,1036.1837 0,16.1257"
174 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
175 <path
176 inkscape:connector-curvature="0"
177 id="path3755-4-1-4-9"
178 d="m 27.022922,1036.1837 0,16.1257"
179 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
180 <path
181 inkscape:connector-curvature="0"
182 id="path3755-4-90-5-2"
183 d="m 30.883338,1036.1837 0,16.1257"
184 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
185 <path
186 inkscape:connector-curvature="0"
187 id="path3755-4-2-0-4"
188 d="m 34.743757,1036.1837 0,16.1257"
189 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
190 <path
191 inkscape:connector-curvature="0"
192 id="path3755-6-0"
193 d="m 0,1036.1837 0,16.1257"
194 style="fill:none;stroke:#000000;stroke-width:0.38072109;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
195 <path
196 inkscape:connector-curvature="0"
197 id="path3768-4-0"
198 d="m 0,1052.2209 35.26169,0"
199 style="fill:none;stroke:#000000;stroke-width:0.38072109;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
200 <path
201 inkscape:connector-curvature="0"
202 style="fill:none;stroke:#ff970d;stroke-width:0.30000001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
203 d="m 0.25968796,1051.8993 c 0.18450185,0 0.3690037,0 0.55350556,0 0.18450185,0 0.36900368,0 0.55350558,0 0.1845018,0 0.3690037,0 0.5535055,0 0.1845019,0 0.3690037,0 0.5535056,0 0.1845018,0 0.3690037,0 0.5535055,0 0.1845019,0 0.3690037,0 0.5535056,0 0.1845018,-10e-5 0.3690037,-10e-5 0.5535055,-3e-4 0.1845019,-10e-5 0.3690037,-3e-4 0.5535056,-7e-4 0.1845018,-4e-4 0.3690037,-10e-4 0.5535055,0 0.1845019,0 0.3690038,0 0.5535056,-0.01 0.1845019,0 0.3690037,-0.01 0.5535056,-0.011 0.1845018,-0.01 0.3690037,-0.012 0.5535055,-0.022 0.1845019,-0.01 0.3690037,-0.023 0.5535056,-0.041 0.1845018,-0.018 0.3690037,-0.041 0.5535055,-0.072 0.1845019,-0.031 0.3690037,-0.069 0.5535056,-0.1174 0.1845018,-0.049 0.3690037,-0.1081 0.5535055,-0.1816 0.1845019,-0.073 0.3690037,-0.1613 0.5535056,-0.267 0.1845018,-0.1057 0.3690036,-0.2294 0.5535056,-0.3745 0.184502,-0.1451 0.369003,-0.3119 0.553505,-0.5028 0.184502,-0.1909 0.369004,-0.4062 0.553506,-0.6472 0.184502,-0.241 0.369003,-0.5079 0.553505,-0.8001 0.184502,-0.2922 0.369004,-0.6099 0.553506,-0.9503 0.184502,-0.3405 0.369004,-0.7038 0.553505,-1.0848 0.184502,-0.3811 0.369004,-0.7798 0.553506,-1.1888 0.184502,-0.409 0.369004,-0.8281 0.553506,-1.2479 0.184501,-0.4198 0.369003,-0.8402 0.553505,-1.2503 0.184502,-0.41 0.369004,-0.8095 0.553506,-1.1872 0.184502,-0.3777 0.369003,-0.7333 0.553505,-1.0561 0.184502,-0.3227 0.369004,-0.6123 0.553506,-0.8596 0.184502,-0.2473 0.369003,-0.452 0.553505,-0.6075 0.184502,-0.1555 0.369004,-0.2614 0.553506,-0.3145 0.184502,-0.053 0.369004,-0.053 0.553505,0 0.184502,0.053 0.369004,0.1591 0.553506,0.3145 0.184502,0.1554 0.369004,0.3603 0.553506,0.6075 0.184501,0.2472 0.369003,0.537 0.553505,0.8596 0.184502,0.3227 0.369004,0.6784 0.553506,1.0561 0.184502,0.3776 0.369003,0.7772 0.553505,1.1872 0.184502,0.4101 0.369004,0.8305 0.553506,1.2503 0.184502,0.4198 0.369003,0.8389 0.553505,1.2479 0.184502,0.409 0.369004,0.8077 0.553506,1.1888 0.184502,0.381 0.369004,0.7443 0.553505,1.0848 0.184502,0.3405 0.369004,0.6581 0.553506,0.9503 0.184502,0.2923 0.369004,0.5591 0.553506,0.8001 0.184501,0.241 0.369003,0.4563 0.553505,0.6472 0.184502,0.191 0.369004,0.3576 0.553506,0.5028 0.184502,0.1452 0.369003,0.2688 0.553505,0.3745 0.184502,0.1057 0.369004,0.1934 0.553506,0.267 0.184502,0.074 0.369003,0.1328 0.553505,0.1816 0.184502,0.049 0.369004,0.087 0.553506,0.1174 0.184502,0.031 0.369004,0.054 0.553505,0.072 0.184502,0.018 0.369004,0.031 0.553506,0.041 0.184502,0.01 0.369004,0.017 0.553506,0.022 0.184501,0.01 0.369003,0.01 0.553505,0.011 0.184502,0 0.369004,0 0.553506,0.01 0.184502,10e-4 0.369003,0 0.553505,0 0.184502,4e-4 0.369004,6e-4 0.553506,7e-4 0.184502,2e-4 0.369003,2e-4 0.553505,3e-4 0.184502,0 0.369004,0 0.553506,0 0.184502,0 0.369004,0 0.553505,0 0.184502,0 0.369004,0 0.553506,0 0.184502,0 0.369004,0 0.553506,0 0.184501,0 0.369003,0 0.553505,0 0.184502,0 0.369004,0 0.553506,0"
204 title="pow(sin(x), 10)"
205 id="path7506-5" />
206 </g>
207 </g>
208 <g
209 inkscape:label="Layer 1"
210 inkscape:groupmode="layer"
211 id="layer1"
212 transform="translate(0,-1016.9291)"
213 style="display:inline">
214 <rect
215 style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
216 id="rect4735"
217 width="21.984022"
218 height="15.841428"
219 x="-0.12931778"
220 y="1017.1877" />
221 <path
222 style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
223 d="m -0.38664415,1020.9238 22.23492015,0"
224 id="path3768-2"
225 inkscape:connector-curvature="0" />
226 <path
227 style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
228 d="m -0.25776149,1029.0753 22.04169149,0"
229 id="path3768-1"
230 inkscape:connector-curvature="0" />
231 <path
232 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
233 d="m 3.8604174,1017.11 0,15.8294"
234 id="path3755-4"
235 inkscape:connector-curvature="0" />
236 <path
237 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
238 d="m 7.7208349,1017.11 0,15.8294"
239 id="path3755-4-0"
240 inkscape:connector-curvature="0" />
241 <path
242 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
243 d="m 11.581253,1017.11 0,15.8294"
244 id="path3755-4-5"
245 inkscape:connector-curvature="0" />
246 <path
247 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
248 d="m 15.441669,1017.11 0,15.8294"
249 id="path3755-4-9"
250 inkscape:connector-curvature="0" />
251 <path
252 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
253 d="m 19.302086,1017.11 0,15.8294"
254 id="path3755-4-7"
255 inkscape:connector-curvature="0" />
256 <path
257 style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
258 d="m -0.25689803,1024.9996 22.01207903,0"
259 id="path3768"
260 inkscape:connector-curvature="0" />
261 <path
262 inkscape:connector-curvature="0"
263 style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
264 d="m 0.17138129,1025.0901 c 0.18430284,-0.3999 0.36860568,-0.8 0.55290852,-1.1919 0.18430286,-0.3921 0.36860569,-0.7762 0.55290859,-1.1447 0.1843028,-0.3686 0.3686057,-0.7216 0.5529085,-1.0521 0.1843028,-0.3303 0.3686057,-0.6383 0.5529085,-0.9175 0.1843029,-0.2794 0.3686057,-0.5299 0.5529085,-0.7468 0.1843029,-0.2172 0.3686057,-0.4004 0.5529086,-0.5466 0.1843028,-0.1461 0.3686056,-0.2549 0.5529085,-0.3244 0.1843028,-0.069 0.3686057,-0.1 0.5529085,-0.09 0.1843029,0.011 0.3686057,0.06 0.5529085,0.1489 0.1843029,0.09 0.3686057,0.217 0.5529086,0.3815 0.1843028,0.1645 0.3686056,0.3655 0.5529085,0.5989 0.1843028,0.2335 0.3686057,0.4995 0.5529085,0.7927 0.1843028,0.2931 0.3686057,0.6135 0.5529085,0.9549 0.1843029,0.3412 0.3686057,0.7034 0.5529086,1.0792 0.1843028,0.3758 0.3686056,0.7653 0.5529085,1.1608 0.1843028,0.3956 0.3686057,0.7969 0.5529085,1.1964 0.1843028,0.3994 0.3686057,0.7969 0.5529085,1.1845 0.1843029,0.3876 0.3686059,0.7653 0.5529088,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208"
265 title="sin(x)"
266 id="path4190"
267 sodipodi:nodetypes="ccccccccccscccsccsccccscccccccccccccsccc" />
268 <rect
269 style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
270 id="rect3905"
271 width="22.075964"
272 height="15.974718"
273 x="-0.19596301"
274 y="1017.121" />
275 <rect
276 style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
277 id="rect4735-9"
278 width="21.984022"
279 height="15.841428"
280 x="3.2648256"
281 y="1021.9644" />
282 <path
283 style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
284 d="m 3.0074993,1025.7005 22.2349197,0"
285 id="path3768-2-9"
286 inkscape:connector-curvature="0" />
287 <path
288 style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
289 d="m 3.1363819,1033.852 22.0416911,0"
290 id="path3768-1-4"
291 inkscape:connector-curvature="0" />
292 <path
293 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
294 d="m 7.2545608,1021.8867 0,15.8294"
295 id="path3755-4-2"
296 inkscape:connector-curvature="0" />
297 <path
298 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
299 d="m 11.114978,1021.8867 0,15.8294"
300 id="path3755-4-0-2"
301 inkscape:connector-curvature="0" />
302 <path
303 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
304 d="m 14.975396,1021.8867 0,15.8294"
305 id="path3755-4-5-3"
306 inkscape:connector-curvature="0" />
307 <path
308 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
309 d="m 18.835812,1021.8867 0,15.8294"
310 id="path3755-4-9-8"
311 inkscape:connector-curvature="0" />
312 <path
313 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
314 d="m 22.696229,1021.8867 0,15.8294"
315 id="path3755-4-7-9"
316 inkscape:connector-curvature="0" />
317 <path
318 style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
319 d="m 3.1372454,1029.7763 22.0120786,0"
320 id="path3768-7"
321 inkscape:connector-curvature="0" />
322 <path
323 inkscape:connector-curvature="0"
324 style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
325 d="m 3.5655247,1029.8668 c 0.1843028,-0.3999 0.3686057,-0.8 0.5529085,-1.1919 0.1843029,-0.3921 0.3686057,-0.7762 0.5529086,-1.1447 0.1843028,-0.3686 0.3686057,-0.7216 0.5529085,-1.0521 0.1843028,-0.3303 0.3686057,-0.6383 0.5529085,-0.9175 0.1843029,-0.2794 0.3686057,-0.5299 0.5529085,-0.7468 0.1843029,-0.2172 0.3686057,-0.4004 0.5529086,-0.5466 0.1843028,-0.1461 0.3686056,-0.2549 0.5529085,-0.3244 0.1843028,-0.069 0.3686057,-0.1 0.5529085,-0.09 0.1843029,0.011 0.3686057,0.06 0.5529085,0.1489 0.1843029,0.09 0.3686057,0.217 0.5529086,0.3815 0.1843028,0.1645 0.3686056,0.3655 0.5529085,0.5989 0.1843028,0.2335 0.3686055,0.4995 0.5529085,0.7927 0.184303,0.2931 0.368606,0.6135 0.552909,0.9549 0.184302,0.3412 0.368605,0.7034 0.552908,1.0792 0.184303,0.3758 0.368606,0.7653 0.552909,1.1608 0.184302,0.3956 0.368605,0.7969 0.552908,1.1964 0.184303,0.3994 0.368606,0.7969 0.552909,1.1845 0.184303,0.3876 0.368606,0.7653 0.552908,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208"
326 title="sin(x)"
327 id="path4190-0"
328 sodipodi:nodetypes="ccccccccccscccsccsccccscccccccccccccsccc" />
329 <rect
330 style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
331 id="rect3905-8"
332 width="22.075964"
333 height="15.974718"
334 x="3.1981804"
335 y="1021.8977" />
336 <rect
337 style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
338 id="rect4735-2"
339 width="21.984022"
340 height="15.841428"
341 x="6.5999274"
342 y="1026.7411" />
343 <path
344 style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
345 d="m 6.3426012,1030.4772 22.2349198,0"
346 id="path3768-2-3"
347 inkscape:connector-curvature="0" />
348 <path
349 style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
350 d="m 6.4714839,1038.6287 22.0416911,0"
351 id="path3768-1-3"
352 inkscape:connector-curvature="0" />
353 <path
354 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
355 d="m 10.589663,1026.6634 0,15.8294"
356 id="path3755-4-1"
357 inkscape:connector-curvature="0" />
358 <path
359 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
360 d="m 14.45008,1026.6634 0,15.8294"
361 id="path3755-4-0-3"
362 inkscape:connector-curvature="0" />
363 <path
364 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
365 d="m 18.310498,1026.6634 0,15.8294"
366 id="path3755-4-5-1"
367 inkscape:connector-curvature="0" />
368 <path
369 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
370 d="m 22.170914,1026.6634 0,15.8294"
371 id="path3755-4-9-88"
372 inkscape:connector-curvature="0" />
373 <path
374 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
375 d="m 26.031331,1026.6634 0,15.8294"
376 id="path3755-4-7-2"
377 inkscape:connector-curvature="0" />
378 <path
379 style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
380 d="m 6.4723474,1034.553 22.0120786,0"
381 id="path3768-98"
382 inkscape:connector-curvature="0" />
383 <path
384 inkscape:connector-curvature="0"
385 style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
386 d="m 6.9006267,1034.6435 c 0.1843028,-0.3999 0.3686057,-0.8 0.5529085,-1.1919 0.1843029,-0.3921 0.3686057,-0.7762 0.5529086,-1.1447 0.1843028,-0.3686 0.3686057,-0.7216 0.5529085,-1.0521 0.1843028,-0.3303 0.3686057,-0.6383 0.5529085,-0.9175 0.1843029,-0.2794 0.3686057,-0.5299 0.5529085,-0.7468 0.1843029,-0.2172 0.3686057,-0.4004 0.5529087,-0.5466 0.184303,-0.1461 0.368605,-0.2549 0.552908,-0.3244 0.184303,-0.069 0.368606,-0.1 0.552909,-0.09 0.184303,0.011 0.368606,0.06 0.552908,0.1489 0.184303,0.09 0.368606,0.217 0.552909,0.3815 0.184303,0.1645 0.368606,0.3655 0.552908,0.5989 0.184303,0.2335 0.368606,0.4995 0.552909,0.7927 0.184303,0.2931 0.368606,0.6135 0.552909,0.9549 0.184302,0.3412 0.368605,0.7034 0.552908,1.0792 0.184303,0.3758 0.368606,0.7653 0.552909,1.1608 0.184302,0.3956 0.368605,0.7969 0.552908,1.1964 0.184303,0.3994 0.368606,0.7969 0.552909,1.1845 0.184303,0.3876 0.368605,0.7653 0.552908,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208"
387 title="sin(x)"
388 id="path4190-8"
389 sodipodi:nodetypes="ccccccccccscccsccsccccscccccccccccccsccc" />
390 <rect
391 style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
392 id="rect3905-7"
393 width="22.075964"
394 height="15.974718"
395 x="6.5332823"
396 y="1026.6744" />
397 <rect
398 style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
399 id="rect4735-98"
400 width="21.984022"
401 height="15.841428"
402 x="9.935029"
403 y="1031.5178" />
404 <path
405 style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
406 d="m 9.6777028,1035.2539 22.2349202,0"
407 id="path3768-2-4"
408 inkscape:connector-curvature="0" />
409 <path
410 style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
411 d="m 9.8065855,1043.4054 22.0416915,0"
412 id="path3768-1-6"
413 inkscape:connector-curvature="0" />
414 <path
415 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
416 d="m 13.924764,1031.4401 0,15.8294"
417 id="path3755-4-54"
418 inkscape:connector-curvature="0" />
419 <path
420 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
421 d="m 17.785182,1031.4401 0,15.8294"
422 id="path3755-4-0-5"
423 inkscape:connector-curvature="0" />
424 <path
425 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
426 d="m 21.6456,1031.4401 0,15.8294"
427 id="path3755-4-5-9"
428 inkscape:connector-curvature="0" />
429 <path
430 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
431 d="m 25.506016,1031.4401 0,15.8294"
432 id="path3755-4-9-87"
433 inkscape:connector-curvature="0" />
434 <path
435 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
436 d="m 29.366433,1031.4401 0,15.8294"
437 id="path3755-4-7-4"
438 inkscape:connector-curvature="0" />
439 <path
440 style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
441 d="m 9.807449,1039.3297 22.012079,0"
442 id="path3768-6"
443 inkscape:connector-curvature="0" />
444 <path
445 inkscape:connector-curvature="0"
446 style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
447 d="m 10.235728,1039.4202 c 0.184303,-0.3999 0.368606,-0.8 0.552909,-1.1919 0.184303,-0.3921 0.368605,-0.7762 0.552908,-1.1447 0.184303,-0.3686 0.368606,-0.7216 0.552909,-1.0521 0.184303,-0.3303 0.368606,-0.6383 0.552908,-0.9175 0.184303,-0.2794 0.368606,-0.5299 0.552909,-0.7468 0.184303,-0.2172 0.368606,-0.4004 0.552908,-0.5466 0.184303,-0.1461 0.368606,-0.2549 0.552909,-0.3244 0.184303,-0.069 0.368606,-0.1 0.552909,-0.09 0.184302,0.011 0.368605,0.06 0.552908,0.1489 0.184303,0.09 0.368606,0.217 0.552909,0.3815 0.184302,0.1645 0.368605,0.3655 0.552908,0.5989 0.184303,0.2335 0.368606,0.4995 0.552909,0.7927 0.184302,0.2931 0.368605,0.6135 0.552908,0.9549 0.184303,0.3412 0.368606,0.7034 0.552909,1.0792 0.184302,0.3758 0.368605,0.7653 0.552908,1.1608 0.184303,0.3956 0.368606,0.7969 0.552909,1.1964 0.184302,0.3994 0.368605,0.7969 0.552908,1.1845 0.184303,0.3876 0.368606,0.7653 0.552909,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208"
448 title="sin(x)"
449 id="path4190-84"
450 sodipodi:nodetypes="ccccccccccscccsccsccccscccccccccccccsccc" />
451 <rect
452 style="fill:none;stroke:#000000;stroke-width:0.29496232000000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
453 id="rect3905-86"
454 width="22.075964"
455 height="15.974718"
456 x="9.8683844"
457 y="1031.451" />
458 <rect
459 style="fill:#f6e879;fill-opacity:1;fill-rule:evenodd;stroke:none"
460 id="rect4735-23"
461 width="21.984022"
462 height="15.841428"
463 x="13.270129"
464 y="1036.2946" />
465 <path
466 style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
467 d="m 13.012803,1040.0306 22.23492,0"
468 id="path3768-2-5"
469 inkscape:connector-curvature="0" />
470 <path
471 style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
472 d="m 13.141686,1048.1821 22.041691,0"
473 id="path3768-1-5"
474 inkscape:connector-curvature="0" />
475 <path
476 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
477 d="m 17.259864,1036.2168 0,15.8294"
478 id="path3755-4-6"
479 inkscape:connector-curvature="0" />
480 <path
481 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
482 d="m 21.120282,1036.2168 0,15.8294"
483 id="path3755-4-0-35"
484 inkscape:connector-curvature="0" />
485 <path
486 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
487 d="m 24.9807,1036.2168 0,15.8294"
488 id="path3755-4-5-82"
489 inkscape:connector-curvature="0" />
490 <path
491 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
492 d="m 28.841116,1036.2168 0,15.8294"
493 id="path3755-4-9-5"
494 inkscape:connector-curvature="0" />
495 <path
496 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
497 d="m 32.701533,1036.2168 0,15.8294"
498 id="path3755-4-7-7"
499 inkscape:connector-curvature="0" />
500 <path
501 style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
502 d="m 13.142549,1044.1064 22.012079,0"
503 id="path3768-25"
504 inkscape:connector-curvature="0" />
505 <path
506 inkscape:connector-curvature="0"
507 style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
508 d="m 13.570828,1044.1969 c 0.184303,-0.3999 0.368606,-0.8 0.552909,-1.1919 0.184303,-0.3921 0.368605,-0.7762 0.552908,-1.1447 0.184303,-0.3686 0.368606,-0.7216 0.552909,-1.0521 0.184303,-0.3303 0.368606,-0.6383 0.552908,-0.9175 0.184303,-0.2794 0.368606,-0.5299 0.552909,-0.7468 0.184303,-0.2172 0.368606,-0.4004 0.552908,-0.5466 0.184303,-0.1461 0.368606,-0.2549 0.552909,-0.3244 0.184303,-0.069 0.368606,-0.1 0.552909,-0.09 0.184302,0.011 0.368605,0.06 0.552908,0.1489 0.184303,0.09 0.368606,0.217 0.552909,0.3815 0.184302,0.1645 0.368605,0.3655 0.552908,0.5989 0.184303,0.2335 0.368606,0.4995 0.552909,0.7927 0.184302,0.2931 0.368605,0.6135 0.552908,0.9549 0.184303,0.3412 0.368606,0.7034 0.552909,1.0792 0.184303,0.3758 0.368605,0.7653 0.552908,1.1608 0.184303,0.3956 0.368606,0.7969 0.552909,1.1964 0.184303,0.3994 0.368605,0.7969 0.552908,1.1845 0.184303,0.3876 0.368606,0.7653 0.552909,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022"
509 title="sin(x)"
510 id="path4190-80"
511 sodipodi:nodetypes="ccccccccccscccsccscc" />
512 <rect
513 style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
514 id="rect3905-9"
515 width="22.075964"
516 height="15.974718"
517 x="13.203484"
518 y="1036.2278" />
519 </g>
520 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://creativecommons.org/ns#"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="35.433071"
12 height="35.433071"
13 id="svg2"
14 version="1.1"
15 inkscape:version="0.48.5 r10040"
16 sodipodi:docname="view-displaymode-single_segment.svg">
17 <sodipodi:namedview
18 id="base"
19 pagecolor="#ffffff"
20 bordercolor="#666666"
21 borderopacity="1.0"
22 inkscape:pageopacity="0.0"
23 inkscape:pageshadow="2"
24 inkscape:zoom="15.465778"
25 inkscape:cx="17.716536"
26 inkscape:cy="17.716536"
27 inkscape:document-units="px"
28 inkscape:current-layer="layer1"
29 showgrid="false"
30 units="mm"
31 inkscape:window-width="1369"
32 inkscape:window-height="743"
33 inkscape:window-x="242"
34 inkscape:window-y="213"
35 inkscape:window-maximized="0" />
36 <defs
37 id="defs4">
38 <filter
39 inkscape:collect="always"
40 id="filter8271">
41 <feGaussianBlur
42 inkscape:collect="always"
43 stdDeviation="0.17615273"
44 id="feGaussianBlur8273" />
45 </filter>
46 </defs>
47 <metadata
48 id="metadata7">
49 <rdf:RDF>
50 <cc:Work
51 rdf:about="">
52 <dc:format>image/svg+xml</dc:format>
53 <dc:type
54 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
55 <dc:title></dc:title>
56 </cc:Work>
57 </rdf:RDF>
58 </metadata>
59 <g
60 inkscape:groupmode="layer"
61 id="layer2"
62 inkscape:label="Layer"
63 style="display:none">
64 <g
65 transform="translate(0,-1016.9291)"
66 style="display:inline"
67 id="g7555-6">
68 <path
69 inkscape:connector-curvature="0"
70 id="path3768-2-3-5-9"
71 d="m 0,1044.2466 35.26169,0"
72 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
73 <path
74 inkscape:connector-curvature="0"
75 id="path3768-2-6"
76 d="m 0,1020.9238 35.26169,0"
77 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
78 <path
79 inkscape:connector-curvature="0"
80 id="path3768-1-9"
81 d="m 0,1029.0753 35.26169,0"
82 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
83 <path
84 inkscape:connector-curvature="0"
85 id="path3755-4-29"
86 d="m 3.8604174,1017.11 0,15.8294"
87 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
88 <path
89 inkscape:connector-curvature="0"
90 id="path3755-4-0-6"
91 d="m 7.7208349,1017.11 0,15.8294"
92 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
93 <path
94 inkscape:connector-curvature="0"
95 id="path3755-4-5-8"
96 d="m 11.581253,1017.11 0,15.8294"
97 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
98 <path
99 inkscape:connector-curvature="0"
100 id="path3755-4-9-14"
101 d="m 15.441669,1017.11 0,15.8294"
102 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
103 <path
104 inkscape:connector-curvature="0"
105 id="path3755-4-7-90"
106 d="m 19.302086,1017.11 0,15.8294"
107 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
108 <path
109 inkscape:connector-curvature="0"
110 id="path3755-4-3-32"
111 d="m 23.162504,1017.11 0,15.8294"
112 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
113 <path
114 inkscape:connector-curvature="0"
115 id="path3755-4-1-3"
116 d="m 27.022922,1017.11 0,15.8294"
117 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
118 <path
119 inkscape:connector-curvature="0"
120 id="path3755-4-90-6"
121 d="m 30.883338,1017.11 0,15.8294"
122 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
123 <path
124 inkscape:connector-curvature="0"
125 id="path3755-4-2-4"
126 d="m 34.743757,1017.11 0,15.8294"
127 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
128 <path
129 inkscape:connector-curvature="0"
130 id="path3755-49"
131 d="m 0,1017.11 0,15.8294"
132 style="fill:none;stroke:#000000;stroke-width:0.3772082;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
133 <path
134 inkscape:connector-curvature="0"
135 id="path3768-9"
136 d="m 0,1024.9996 35.26169,0"
137 style="fill:none;stroke:#000000;stroke-width:0.3772082;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
138 <path
139 id="path4190-3"
140 title="sin(x)"
141 d="m 0.17138129,1025.0901 c 0.18430284,-0.3999 0.36860568,-0.8 0.55290852,-1.1919 0.18430286,-0.3921 0.36860569,-0.7762 0.55290859,-1.1447 0.1843028,-0.3686 0.3686057,-0.7216 0.5529085,-1.0521 0.1843028,-0.3303 0.3686057,-0.6383 0.5529085,-0.9175 0.1843029,-0.2794 0.3686057,-0.5299 0.5529085,-0.7468 0.1843029,-0.2172 0.3686057,-0.4004 0.5529086,-0.5466 0.1843028,-0.1461 0.3686056,-0.2549 0.5529085,-0.3244 0.1843028,-0.069 0.3686057,-0.1 0.5529085,-0.09 0.1843029,0.011 0.3686057,0.06 0.5529085,0.1489 0.1843029,0.09 0.3686057,0.217 0.5529086,0.3815 0.1843028,0.1645 0.3686056,0.3655 0.5529085,0.5989 0.1843028,0.2335 0.3686057,0.4995 0.5529085,0.7927 0.1843028,0.2931 0.3686057,0.6135 0.5529085,0.9549 0.1843029,0.3412 0.3686057,0.7034 0.5529086,1.0792 0.1843028,0.3758 0.3686056,0.7653 0.5529085,1.1608 0.1843028,0.3956 0.3686057,0.7969 0.5529085,1.1964 0.1843028,0.3994 0.3686057,0.7969 0.5529085,1.1845 0.1843029,0.3876 0.3686059,0.7653 0.5529088,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208 0.184302,-0.031 0.368605,-0.019 0.552908,0.031 0.184303,0.05 0.368606,0.1392 0.552909,0.2666 0.184303,0.1274 0.368605,0.2927 0.552908,0.4926 0.184303,0.2001 0.368606,0.4347 0.552909,0.6993 0.184303,0.2646 0.368605,0.5594 0.552908,0.8781 0.184303,0.3189 0.368606,0.6618 0.552909,1.0222 0.184303,0.3603 0.368605,0.738 0.552908,1.1256 0.184303,0.3876 0.368606,0.785 0.552909,1.1845 0.184303,0.3994 0.368606,0.8009 0.552908,1.1963 0.184303,0.3955 0.368606,0.7851 0.552909,1.161 0.184303,0.3758 0.368606,0.7379 0.552908,1.0792 0.184303,0.3411 0.368606,0.6616 0.552909,0.9548 0.184303,0.2932 0.368606,0.5592 0.552908,0.7927 0.184303,0.2334 0.368606,0.4343 0.552909,0.5988 0.184303,0.1645 0.368606,0.2926 0.552909,0.3816 0.184302,0.089 0.368605,0.1389 0.552908,0.1489 0.184303,0.011 0.368606,-0.02 0.552909,-0.09 0.184302,-0.069 0.368605,-0.1784 0.552908,-0.3246 0.184303,-0.1461 0.368606,-0.3295 0.552909,-0.5464 0.184302,-0.217 0.368605,-0.4676 0.552908,-0.7469 0.184303,-0.2792 0.368606,-0.5872 0.552909,-0.9177 0.184302,-0.3303 0.368605,-0.6834 0.552908,-1.0519 0.184303,-0.3686 0.368606,-0.7526 0.552909,-1.1446 0.184303,-0.392 0.368605,-0.7921 0.552908,-1.1919"
142 style="fill:none;stroke:#2e0dff;stroke-width:0.30176657;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
143 inkscape:connector-curvature="0" />
144 <path
145 inkscape:connector-curvature="0"
146 id="path3768-2-3-7"
147 d="m 0,1040.0689 35.26169,0"
148 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
149 <path
150 inkscape:connector-curvature="0"
151 id="path3768-1-5-1"
152 d="m 0,1048.373 35.26169,0"
153 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
154 <path
155 inkscape:connector-curvature="0"
156 id="path3755-4-38-0"
157 d="m 3.860417,1036.1837 0,16.1257"
158 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
159 <path
160 inkscape:connector-curvature="0"
161 id="path3755-4-0-5-8"
162 d="m 7.720835,1036.1837 0,16.1257"
163 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
164 <path
165 inkscape:connector-curvature="0"
166 id="path3755-4-5-0-7"
167 d="m 11.581253,1036.1837 0,16.1257"
168 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
169 <path
170 inkscape:connector-curvature="0"
171 id="path3755-4-9-1-2"
172 d="m 15.441669,1036.1837 0,16.1257"
173 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
174 <path
175 inkscape:connector-curvature="0"
176 id="path3755-4-7-9-8"
177 d="m 19.302086,1036.1837 0,16.1257"
178 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
179 <path
180 inkscape:connector-curvature="0"
181 id="path3755-4-3-3-8"
182 d="m 23.162504,1036.1837 0,16.1257"
183 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
184 <path
185 inkscape:connector-curvature="0"
186 id="path3755-4-1-4-9"
187 d="m 27.022922,1036.1837 0,16.1257"
188 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
189 <path
190 inkscape:connector-curvature="0"
191 id="path3755-4-90-5-2"
192 d="m 30.883338,1036.1837 0,16.1257"
193 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
194 <path
195 inkscape:connector-curvature="0"
196 id="path3755-4-2-0-4"
197 d="m 34.743757,1036.1837 0,16.1257"
198 style="fill:none;stroke:#c9c9c9;stroke-width:0.19036053;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
199 <path
200 inkscape:connector-curvature="0"
201 id="path3755-6-0"
202 d="m 0,1036.1837 0,16.1257"
203 style="fill:none;stroke:#000000;stroke-width:0.38072109;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
204 <path
205 inkscape:connector-curvature="0"
206 id="path3768-4-0"
207 d="m 0,1052.2209 35.26169,0"
208 style="fill:none;stroke:#000000;stroke-width:0.38072109;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
209 <path
210 inkscape:connector-curvature="0"
211 style="fill:none;stroke:#ff970d;stroke-width:0.30000001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
212 d="m 0.25968796,1051.8993 c 0.18450185,0 0.3690037,0 0.55350556,0 0.18450185,0 0.36900368,0 0.55350558,0 0.1845018,0 0.3690037,0 0.5535055,0 0.1845019,0 0.3690037,0 0.5535056,0 0.1845018,0 0.3690037,0 0.5535055,0 0.1845019,0 0.3690037,0 0.5535056,0 0.1845018,-10e-5 0.3690037,-10e-5 0.5535055,-3e-4 0.1845019,-10e-5 0.3690037,-3e-4 0.5535056,-7e-4 0.1845018,-4e-4 0.3690037,-10e-4 0.5535055,0 0.1845019,0 0.3690038,0 0.5535056,-0.01 0.1845019,0 0.3690037,-0.01 0.5535056,-0.011 0.1845018,-0.01 0.3690037,-0.012 0.5535055,-0.022 0.1845019,-0.01 0.3690037,-0.023 0.5535056,-0.041 0.1845018,-0.018 0.3690037,-0.041 0.5535055,-0.072 0.1845019,-0.031 0.3690037,-0.069 0.5535056,-0.1174 0.1845018,-0.049 0.3690037,-0.1081 0.5535055,-0.1816 0.1845019,-0.073 0.3690037,-0.1613 0.5535056,-0.267 0.1845018,-0.1057 0.3690036,-0.2294 0.5535056,-0.3745 0.184502,-0.1451 0.369003,-0.3119 0.553505,-0.5028 0.184502,-0.1909 0.369004,-0.4062 0.553506,-0.6472 0.184502,-0.241 0.369003,-0.5079 0.553505,-0.8001 0.184502,-0.2922 0.369004,-0.6099 0.553506,-0.9503 0.184502,-0.3405 0.369004,-0.7038 0.553505,-1.0848 0.184502,-0.3811 0.369004,-0.7798 0.553506,-1.1888 0.184502,-0.409 0.369004,-0.8281 0.553506,-1.2479 0.184501,-0.4198 0.369003,-0.8402 0.553505,-1.2503 0.184502,-0.41 0.369004,-0.8095 0.553506,-1.1872 0.184502,-0.3777 0.369003,-0.7333 0.553505,-1.0561 0.184502,-0.3227 0.369004,-0.6123 0.553506,-0.8596 0.184502,-0.2473 0.369003,-0.452 0.553505,-0.6075 0.184502,-0.1555 0.369004,-0.2614 0.553506,-0.3145 0.184502,-0.053 0.369004,-0.053 0.553505,0 0.184502,0.053 0.369004,0.1591 0.553506,0.3145 0.184502,0.1554 0.369004,0.3603 0.553506,0.6075 0.184501,0.2472 0.369003,0.537 0.553505,0.8596 0.184502,0.3227 0.369004,0.6784 0.553506,1.0561 0.184502,0.3776 0.369003,0.7772 0.553505,1.1872 0.184502,0.4101 0.369004,0.8305 0.553506,1.2503 0.184502,0.4198 0.369003,0.8389 0.553505,1.2479 0.184502,0.409 0.369004,0.8077 0.553506,1.1888 0.184502,0.381 0.369004,0.7443 0.553505,1.0848 0.184502,0.3405 0.369004,0.6581 0.553506,0.9503 0.184502,0.2923 0.369004,0.5591 0.553506,0.8001 0.184501,0.241 0.369003,0.4563 0.553505,0.6472 0.184502,0.191 0.369004,0.3576 0.553506,0.5028 0.184502,0.1452 0.369003,0.2688 0.553505,0.3745 0.184502,0.1057 0.369004,0.1934 0.553506,0.267 0.184502,0.074 0.369003,0.1328 0.553505,0.1816 0.184502,0.049 0.369004,0.087 0.553506,0.1174 0.184502,0.031 0.369004,0.054 0.553505,0.072 0.184502,0.018 0.369004,0.031 0.553506,0.041 0.184502,0.01 0.369004,0.017 0.553506,0.022 0.184501,0.01 0.369003,0.01 0.553505,0.011 0.184502,0 0.369004,0 0.553506,0.01 0.184502,10e-4 0.369003,0 0.553505,0 0.184502,4e-4 0.369004,6e-4 0.553506,7e-4 0.184502,2e-4 0.369003,2e-4 0.553505,3e-4 0.184502,0 0.369004,0 0.553506,0 0.184502,0 0.369004,0 0.553505,0 0.184502,0 0.369004,0 0.553506,0 0.184502,0 0.369004,0 0.553506,0 0.184501,0 0.369003,0 0.553505,0 0.184502,0 0.369004,0 0.553506,0"
213 title="pow(sin(x), 10)"
214 id="path7506-5" />
215 </g>
216 </g>
217 <g
218 inkscape:label="Layer 1"
219 inkscape:groupmode="layer"
220 id="layer1"
221 transform="translate(0,-1016.9291)"
222 style="display:inline">
223 <rect
224 style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
225 id="rect4735"
226 width="21.984022"
227 height="15.841428"
228 x="-0.12931778"
229 y="1017.1877" />
230 <path
231 style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
232 d="m -0.38664415,1020.9238 22.23492015,0"
233 id="path3768-2"
234 inkscape:connector-curvature="0" />
235 <path
236 style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
237 d="m -0.25776149,1029.0753 22.04169149,0"
238 id="path3768-1"
239 inkscape:connector-curvature="0" />
240 <path
241 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
242 d="m 3.8604174,1017.11 0,15.8294"
243 id="path3755-4"
244 inkscape:connector-curvature="0" />
245 <path
246 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
247 d="m 7.7208349,1017.11 0,15.8294"
248 id="path3755-4-0"
249 inkscape:connector-curvature="0" />
250 <path
251 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
252 d="m 11.581253,1017.11 0,15.8294"
253 id="path3755-4-5"
254 inkscape:connector-curvature="0" />
255 <path
256 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
257 d="m 15.441669,1017.11 0,15.8294"
258 id="path3755-4-9"
259 inkscape:connector-curvature="0" />
260 <path
261 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
262 d="m 19.302086,1017.11 0,15.8294"
263 id="path3755-4-7"
264 inkscape:connector-curvature="0" />
265 <path
266 style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
267 d="m -0.25689803,1024.9996 22.01207903,0"
268 id="path3768"
269 inkscape:connector-curvature="0" />
270 <path
271 inkscape:connector-curvature="0"
272 style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
273 d="m 0.17138129,1025.0901 c 0.18430284,-0.3999 0.36860568,-0.8 0.55290852,-1.1919 0.18430286,-0.3921 0.36860569,-0.7762 0.55290859,-1.1447 0.1843028,-0.3686 0.3686057,-0.7216 0.5529085,-1.0521 0.1843028,-0.3303 0.3686057,-0.6383 0.5529085,-0.9175 0.1843029,-0.2794 0.3686057,-0.5299 0.5529085,-0.7468 0.1843029,-0.2172 0.3686057,-0.4004 0.5529086,-0.5466 0.1843028,-0.1461 0.3686056,-0.2549 0.5529085,-0.3244 0.1843028,-0.069 0.3686057,-0.1 0.5529085,-0.09 0.1843029,0.011 0.3686057,0.06 0.5529085,0.1489 0.1843029,0.09 0.3686057,0.217 0.5529086,0.3815 0.1843028,0.1645 0.3686056,0.3655 0.5529085,0.5989 0.1843028,0.2335 0.3686057,0.4995 0.5529085,0.7927 0.1843028,0.2931 0.3686057,0.6135 0.5529085,0.9549 0.1843029,0.3412 0.3686057,0.7034 0.5529086,1.0792 0.1843028,0.3758 0.3686056,0.7653 0.5529085,1.1608 0.1843028,0.3956 0.3686057,0.7969 0.5529085,1.1964 0.1843028,0.3994 0.3686057,0.7969 0.5529085,1.1845 0.1843029,0.3876 0.3686059,0.7653 0.5529088,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208"
274 title="sin(x)"
275 id="path4190"
276 sodipodi:nodetypes="ccccccccccscccsccsccccscccccccccccccsccc" />
277 <rect
278 style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
279 id="rect3905"
280 width="22.075964"
281 height="15.974718"
282 x="-0.19596301"
283 y="1017.121" />
284 <rect
285 style="fill:#f6e879;fill-opacity:1;fill-rule:evenodd;stroke:none"
286 id="rect4735-9"
287 width="21.984022"
288 height="15.841428"
289 x="3.2648256"
290 y="1021.9644" />
291 <path
292 style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
293 d="m 3.0074993,1025.7005 22.2349197,0"
294 id="path3768-2-9"
295 inkscape:connector-curvature="0" />
296 <path
297 style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
298 d="m 3.1363819,1033.852 22.0416911,0"
299 id="path3768-1-4"
300 inkscape:connector-curvature="0" />
301 <path
302 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
303 d="m 7.2545608,1021.8867 0,15.8294"
304 id="path3755-4-2"
305 inkscape:connector-curvature="0" />
306 <path
307 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
308 d="m 11.114978,1021.8867 0,15.8294"
309 id="path3755-4-0-2"
310 inkscape:connector-curvature="0" />
311 <path
312 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
313 d="m 14.975396,1021.8867 0,15.8294"
314 id="path3755-4-5-3"
315 inkscape:connector-curvature="0" />
316 <path
317 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
318 d="m 18.835812,1021.8867 0,15.8294"
319 id="path3755-4-9-8"
320 inkscape:connector-curvature="0" />
321 <path
322 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
323 d="m 22.696229,1021.8867 0,15.8294"
324 id="path3755-4-7-9"
325 inkscape:connector-curvature="0" />
326 <path
327 style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
328 d="m 3.1372454,1029.7763 22.0120786,0"
329 id="path3768-7"
330 inkscape:connector-curvature="0" />
331 <path
332 inkscape:connector-curvature="0"
333 style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
334 d="m 3.5655247,1029.8668 c 0.1843028,-0.3999 0.3686057,-0.8 0.5529085,-1.1919 0.1843029,-0.3921 0.3686057,-0.7762 0.5529086,-1.1447 0.1843028,-0.3686 0.3686057,-0.7216 0.5529085,-1.0521 0.1843028,-0.3303 0.3686057,-0.6383 0.5529085,-0.9175 0.1843029,-0.2794 0.3686057,-0.5299 0.5529085,-0.7468 0.1843029,-0.2172 0.3686057,-0.4004 0.5529086,-0.5466 0.1843028,-0.1461 0.3686056,-0.2549 0.5529085,-0.3244 0.1843028,-0.069 0.3686057,-0.1 0.5529085,-0.09 0.1843029,0.011 0.3686057,0.06 0.5529085,0.1489 0.1843029,0.09 0.3686057,0.217 0.5529086,0.3815 0.1843028,0.1645 0.3686056,0.3655 0.5529085,0.5989 0.1843028,0.2335 0.3686055,0.4995 0.5529085,0.7927 0.184303,0.2931 0.368606,0.6135 0.552909,0.9549 0.184302,0.3412 0.368605,0.7034 0.552908,1.0792 0.184303,0.3758 0.368606,0.7653 0.552909,1.1608 0.184302,0.3956 0.368605,0.7969 0.552908,1.1964 0.184303,0.3994 0.368606,0.7969 0.552909,1.1845 0.184303,0.3876 0.368606,0.7653 0.552908,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208"
335 title="sin(x)"
336 id="path4190-0"
337 sodipodi:nodetypes="ccccccccccscccsccsccccscccccccccccccsccc" />
338 <rect
339 style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
340 id="rect3905-8"
341 width="22.075964"
342 height="15.974718"
343 x="3.1981804"
344 y="1021.8977" />
345 <rect
346 style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
347 id="rect4735-2"
348 width="21.984022"
349 height="15.841428"
350 x="6.5999274"
351 y="1026.7411" />
352 <path
353 style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
354 d="m 6.3426012,1030.4772 22.2349198,0"
355 id="path3768-2-3"
356 inkscape:connector-curvature="0" />
357 <path
358 style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
359 d="m 6.4714839,1038.6287 22.0416911,0"
360 id="path3768-1-3"
361 inkscape:connector-curvature="0" />
362 <path
363 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
364 d="m 10.589663,1026.6634 0,15.8294"
365 id="path3755-4-1"
366 inkscape:connector-curvature="0" />
367 <path
368 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
369 d="m 14.45008,1026.6634 0,15.8294"
370 id="path3755-4-0-3"
371 inkscape:connector-curvature="0" />
372 <path
373 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
374 d="m 18.310498,1026.6634 0,15.8294"
375 id="path3755-4-5-1"
376 inkscape:connector-curvature="0" />
377 <path
378 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
379 d="m 22.170914,1026.6634 0,15.8294"
380 id="path3755-4-9-88"
381 inkscape:connector-curvature="0" />
382 <path
383 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
384 d="m 26.031331,1026.6634 0,15.8294"
385 id="path3755-4-7-2"
386 inkscape:connector-curvature="0" />
387 <path
388 style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
389 d="m 6.4723474,1034.553 22.0120786,0"
390 id="path3768-98"
391 inkscape:connector-curvature="0" />
392 <path
393 inkscape:connector-curvature="0"
394 style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
395 d="m 6.9006267,1034.6435 c 0.1843028,-0.3999 0.3686057,-0.8 0.5529085,-1.1919 0.1843029,-0.3921 0.3686057,-0.7762 0.5529086,-1.1447 0.1843028,-0.3686 0.3686057,-0.7216 0.5529085,-1.0521 0.1843028,-0.3303 0.3686057,-0.6383 0.5529085,-0.9175 0.1843029,-0.2794 0.3686057,-0.5299 0.5529085,-0.7468 0.1843029,-0.2172 0.3686057,-0.4004 0.5529087,-0.5466 0.184303,-0.1461 0.368605,-0.2549 0.552908,-0.3244 0.184303,-0.069 0.368606,-0.1 0.552909,-0.09 0.184303,0.011 0.368606,0.06 0.552908,0.1489 0.184303,0.09 0.368606,0.217 0.552909,0.3815 0.184303,0.1645 0.368606,0.3655 0.552908,0.5989 0.184303,0.2335 0.368606,0.4995 0.552909,0.7927 0.184303,0.2931 0.368606,0.6135 0.552909,0.9549 0.184302,0.3412 0.368605,0.7034 0.552908,1.0792 0.184303,0.3758 0.368606,0.7653 0.552909,1.1608 0.184302,0.3956 0.368605,0.7969 0.552908,1.1964 0.184303,0.3994 0.368606,0.7969 0.552909,1.1845 0.184303,0.3876 0.368605,0.7653 0.552908,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208"
396 title="sin(x)"
397 id="path4190-8"
398 sodipodi:nodetypes="ccccccccccscccsccsccccscccccccccccccsccc" />
399 <rect
400 style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
401 id="rect3905-7"
402 width="22.075964"
403 height="15.974718"
404 x="6.5332823"
405 y="1026.6744" />
406 <rect
407 style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
408 id="rect4735-98"
409 width="21.984022"
410 height="15.841428"
411 x="9.935029"
412 y="1031.5178" />
413 <path
414 style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
415 d="m 9.6777028,1035.2539 22.2349202,0"
416 id="path3768-2-4"
417 inkscape:connector-curvature="0" />
418 <path
419 style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
420 d="m 9.8065855,1043.4054 22.0416915,0"
421 id="path3768-1-6"
422 inkscape:connector-curvature="0" />
423 <path
424 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
425 d="m 13.924764,1031.4401 0,15.8294"
426 id="path3755-4-54"
427 inkscape:connector-curvature="0" />
428 <path
429 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
430 d="m 17.785182,1031.4401 0,15.8294"
431 id="path3755-4-0-5"
432 inkscape:connector-curvature="0" />
433 <path
434 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
435 d="m 21.6456,1031.4401 0,15.8294"
436 id="path3755-4-5-9"
437 inkscape:connector-curvature="0" />
438 <path
439 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
440 d="m 25.506016,1031.4401 0,15.8294"
441 id="path3755-4-9-87"
442 inkscape:connector-curvature="0" />
443 <path
444 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
445 d="m 29.366433,1031.4401 0,15.8294"
446 id="path3755-4-7-4"
447 inkscape:connector-curvature="0" />
448 <path
449 style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
450 d="m 9.807449,1039.3297 22.012079,0"
451 id="path3768-6"
452 inkscape:connector-curvature="0" />
453 <path
454 inkscape:connector-curvature="0"
455 style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
456 d="m 10.235728,1039.4202 c 0.184303,-0.3999 0.368606,-0.8 0.552909,-1.1919 0.184303,-0.3921 0.368605,-0.7762 0.552908,-1.1447 0.184303,-0.3686 0.368606,-0.7216 0.552909,-1.0521 0.184303,-0.3303 0.368606,-0.6383 0.552908,-0.9175 0.184303,-0.2794 0.368606,-0.5299 0.552909,-0.7468 0.184303,-0.2172 0.368606,-0.4004 0.552908,-0.5466 0.184303,-0.1461 0.368606,-0.2549 0.552909,-0.3244 0.184303,-0.069 0.368606,-0.1 0.552909,-0.09 0.184302,0.011 0.368605,0.06 0.552908,0.1489 0.184303,0.09 0.368606,0.217 0.552909,0.3815 0.184302,0.1645 0.368605,0.3655 0.552908,0.5989 0.184303,0.2335 0.368606,0.4995 0.552909,0.7927 0.184302,0.2931 0.368605,0.6135 0.552908,0.9549 0.184303,0.3412 0.368606,0.7034 0.552909,1.0792 0.184302,0.3758 0.368605,0.7653 0.552908,1.1608 0.184303,0.3956 0.368606,0.7969 0.552909,1.1964 0.184302,0.3994 0.368605,0.7969 0.552908,1.1845 0.184303,0.3876 0.368606,0.7653 0.552909,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022 0.184303,0.3189 0.368606,0.6135 0.552909,0.8782 0.184303,0.2646 0.368605,0.4992 0.552908,0.6992 0.184303,0.2001 0.368606,0.3653 0.552909,0.4927 0.184303,0.1274 0.368605,0.2168 0.552908,0.2665 0.184303,0.05 0.368606,0.06 0.552909,0.031 0.184303,-0.031 0.368605,-0.1 0.552908,-0.2079 0.184303,-0.1088 0.368606,-0.2552 0.552909,-0.4377 0.184303,-0.1824 0.368606,-0.4005 0.552908,-0.6498 0.184303,-0.2495 0.368606,-0.5301 0.552909,-0.8364 0.184303,-0.3065 0.368606,-0.6385 0.552908,-0.9897 0.184303,-0.3513 0.368606,-0.7217 0.552909,-1.1039 0.184303,-0.3821 0.368606,-0.7762 0.552908,-1.1741 0.184303,-0.398 0.368606,-0.8 0.552909,-1.1979 0.184303,-0.398 0.368606,-0.7919 0.552909,-1.1742 0.184302,-0.3821 0.368605,-0.7525 0.552908,-1.1038 0.184303,-0.3512 0.368606,-0.6833 0.552909,-0.9896 0.184302,-0.3064 0.368605,-0.587 0.552908,-0.8364 0.184303,-0.2494 0.368606,-0.4675 0.552909,-0.6499 0.184302,-0.1825 0.368605,-0.3294 0.552908,-0.4377 0.184303,-0.1088 0.368606,-0.1782 0.552909,-0.208"
457 title="sin(x)"
458 id="path4190-84"
459 sodipodi:nodetypes="ccccccccccscccsccsccccscccccccccccccsccc" />
460 <rect
461 style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
462 id="rect3905-86"
463 width="22.075964"
464 height="15.974718"
465 x="9.8683844"
466 y="1031.451" />
467 <rect
468 style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
469 id="rect4735-23"
470 width="21.984022"
471 height="15.841428"
472 x="13.270129"
473 y="1036.2946" />
474 <path
475 style="fill:none;stroke:#c9c9c9;stroke-width:0.1497674;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
476 d="m 13.012803,1040.0306 22.23492,0"
477 id="path3768-2-5"
478 inkscape:connector-curvature="0" />
479 <path
480 style="fill:none;stroke:#c9c9c9;stroke-width:0.14911523;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
481 d="m 13.141686,1048.1821 22.041691,0"
482 id="path3768-1-5"
483 inkscape:connector-curvature="0" />
484 <path
485 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
486 d="m 17.259864,1036.2168 0,15.8294"
487 id="path3755-4-6"
488 inkscape:connector-curvature="0" />
489 <path
490 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
491 d="m 21.120282,1036.2168 0,15.8294"
492 id="path3755-4-0-35"
493 inkscape:connector-curvature="0" />
494 <path
495 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
496 d="m 24.9807,1036.2168 0,15.8294"
497 id="path3755-4-5-82"
498 inkscape:connector-curvature="0" />
499 <path
500 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
501 d="m 28.841116,1036.2168 0,15.8294"
502 id="path3755-4-9-5"
503 inkscape:connector-curvature="0" />
504 <path
505 style="fill:none;stroke:#c9c9c9;stroke-width:0.18860409;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
506 d="m 32.701533,1036.2168 0,15.8294"
507 id="path3755-4-7-7"
508 inkscape:connector-curvature="0" />
509 <path
510 style="fill:none;stroke:#000000;stroke-width:0.29803008;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
511 d="m 13.142549,1044.1064 22.012079,0"
512 id="path3768-25"
513 inkscape:connector-curvature="0" />
514 <path
515 inkscape:connector-curvature="0"
516 style="fill:none;stroke:#2e0dff;stroke-width:0.802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
517 d="m 13.570828,1044.1969 c 0.184303,-0.3999 0.368606,-0.8 0.552909,-1.1919 0.184303,-0.3921 0.368605,-0.7762 0.552908,-1.1447 0.184303,-0.3686 0.368606,-0.7216 0.552909,-1.0521 0.184303,-0.3303 0.368606,-0.6383 0.552908,-0.9175 0.184303,-0.2794 0.368606,-0.5299 0.552909,-0.7468 0.184303,-0.2172 0.368606,-0.4004 0.552908,-0.5466 0.184303,-0.1461 0.368606,-0.2549 0.552909,-0.3244 0.184303,-0.069 0.368606,-0.1 0.552909,-0.09 0.184302,0.011 0.368605,0.06 0.552908,0.1489 0.184303,0.09 0.368606,0.217 0.552909,0.3815 0.184302,0.1645 0.368605,0.3655 0.552908,0.5989 0.184303,0.2335 0.368606,0.4995 0.552909,0.7927 0.184302,0.2931 0.368605,0.6135 0.552908,0.9549 0.184303,0.3412 0.368606,0.7034 0.552909,1.0792 0.184303,0.3758 0.368605,0.7653 0.552908,1.1608 0.184303,0.3956 0.368606,0.7969 0.552909,1.1964 0.184303,0.3994 0.368605,0.7969 0.552908,1.1845 0.184303,0.3876 0.368606,0.7653 0.552909,1.1257 0.184302,0.3603 0.368605,0.7033 0.552908,1.022"
518 title="sin(x)"
519 id="path4190-80"
520 sodipodi:nodetypes="ccccccccccscccsccscc" />
521 <rect
522 style="fill:none;stroke:#000000;stroke-width:0.29496232;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
523 id="rect3905-9"
524 width="22.075964"
525 height="15.974718"
526 x="13.203484"
527 y="1036.2278" />
528 </g>
529 </svg>
icons/zoom-original.png less more
Binary diff not shown
2121 #endif
2222
2323 #include <cstdint>
24 #include <fstream>
25 #include <getopt.h>
26 #include <vector>
27
2428 #include <libsigrokcxx/libsigrokcxx.hpp>
2529
26 #include <getopt.h>
27
30 #include <QCheckBox>
2831 #include <QDebug>
32 #include <QFile>
33 #include <QFileInfo>
34 #include <QMessageBox>
2935 #include <QSettings>
36 #include <QTextStream>
37
38 #include "config.h"
3039
3140 #ifdef ENABLE_SIGNALS
3241 #include "signalhandler.hpp"
3342 #endif
3443
44 #ifdef ENABLE_STACKTRACE
45 #include <signal.h>
46 #include <boost/stacktrace.hpp>
47 #include <QStandardPaths>
48 #endif
49
3550 #include "pv/application.hpp"
3651 #include "pv/devicemanager.hpp"
52 #include "pv/globalsettings.hpp"
53 #include "pv/logging.hpp"
3754 #include "pv/mainwindow.hpp"
55 #include "pv/session.hpp"
56 #include "pv/util.hpp"
57
3858 #ifdef ANDROID
3959 #include <libsigrokandroidutils/libsigrokandroidutils.h>
4060 #include "android/assetreader.hpp"
4161 #include "android/loghandler.hpp"
4262 #endif
4363
44 #include "config.h"
45
4664 #ifdef _WIN32
4765 #include <QtPlugin>
4866 Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
5068 #endif
5169
5270 using std::exception;
71 using std::ifstream;
72 using std::ofstream;
5373 using std::shared_ptr;
5474 using std::string;
75
76 #if ENABLE_STACKTRACE
77 QString stacktrace_filename;
78
79 void signal_handler(int signum)
80 {
81 ::signal(signum, SIG_DFL);
82 boost::stacktrace::safe_dump_to(stacktrace_filename.toLocal8Bit().data());
83 ::raise(SIGABRT);
84 }
85
86 void process_stacktrace(QString temp_path)
87 {
88 const QString stacktrace_outfile = temp_path + "/pv_stacktrace.txt";
89
90 ifstream ifs(stacktrace_filename.toLocal8Bit().data());
91 ofstream ofs(stacktrace_outfile.toLocal8Bit().data(),
92 ofstream::out | ofstream::trunc);
93
94 boost::stacktrace::stacktrace st =
95 boost::stacktrace::stacktrace::from_dump(ifs);
96 ofs << st;
97
98 ofs.close();
99 ifs.close();
100
101 QFile f(stacktrace_outfile);
102 f.open(QFile::ReadOnly | QFile::Text);
103 QTextStream fs(&f);
104 QString stacktrace = fs.readAll();
105 stacktrace = stacktrace.trimmed().replace('\n', "<br />");
106
107 qDebug() << QObject::tr("Stack trace of previous crash:");
108 qDebug() << "---------------------------------------------------------";
109 // Note: qDebug() prints quotation marks for QString output, so we feed it char*
110 qDebug() << stacktrace.toLocal8Bit().data();
111 qDebug() << "---------------------------------------------------------";
112
113 f.close();
114
115 // Remove stack trace so we don't process it again the next time we run
116 QFile::remove(stacktrace_filename.toLocal8Bit().data());
117
118 // Show notification dialog if permitted
119 pv::GlobalSettings settings;
120 if (settings.value(pv::GlobalSettings::Key_Log_NotifyOfStacktrace).toBool()) {
121 QCheckBox *cb = new QCheckBox(QObject::tr("Don't show this message again"));
122
123 QMessageBox msgbox;
124 msgbox.setText(QObject::tr("When %1 last crashed, it created a stack trace.\n" \
125 "A human-readable form has been saved to disk and was written to " \
126 "the log. You may access it from the settings dialog.").arg(PV_TITLE));
127 msgbox.setIcon(QMessageBox::Icon::Information);
128 msgbox.addButton(QMessageBox::Ok);
129 msgbox.setCheckBox(cb);
130
131 QObject::connect(cb, &QCheckBox::stateChanged, [](int state){
132 pv::GlobalSettings settings;
133 settings.setValue(pv::GlobalSettings::Key_Log_NotifyOfStacktrace,
134 !state); });
135
136 msgbox.exec();
137 }
138 }
139 #endif
55140
56141 void usage()
57142 {
65150 "Application Options:\n"
66151 " -V, --version Show release version\n"
67152 " -l, --loglevel Set libsigrok/libsigrokdecode loglevel\n"
153 " -d, --driver Specify the device driver to use\n"
154 " -D, --dont-scan Don't auto-scan for devices, use -d spec only\n"
68155 " -i, --input-file Load input from file\n"
69156 " -I, --input-format Input format\n"
70157 " -c, --clean Don't restore previous sessions on startup\n"
75162 {
76163 int ret = 0;
77164 shared_ptr<sigrok::Context> context;
78 string open_file, open_file_format;
165 string open_file_format, driver;
166 vector<string> open_files;
79167 bool restore_sessions = true;
168 bool do_scan = true;
169 bool show_version = false;
80170
81171 Application a(argc, argv);
82172
92182 {"help", no_argument, nullptr, 'h'},
93183 {"version", no_argument, nullptr, 'V'},
94184 {"loglevel", required_argument, nullptr, 'l'},
185 {"driver", required_argument, nullptr, 'd'},
186 {"dont-scan", no_argument, nullptr, 'D'},
95187 {"input-file", required_argument, nullptr, 'i'},
96188 {"input-format", required_argument, nullptr, 'I'},
97189 {"clean", no_argument, nullptr, 'c'},
190 {"log-to-stdout", no_argument, nullptr, 's'},
98191 {nullptr, 0, nullptr, 0}
99192 };
100193
101194 const int c = getopt_long(argc, argv,
102 "l:Vhc?i:I:", long_options, nullptr);
195 "h?VDcl:d:i:I:", long_options, nullptr);
103196 if (c == -1)
104197 break;
105198
110203 return 0;
111204
112205 case 'V':
113 // Print version info
114 fprintf(stdout, "%s %s\n", PV_TITLE, PV_VERSION_STRING);
115 return 0;
206 show_version = true;
207 break;
116208
117209 case 'l':
118210 {
119211 const int loglevel = atoi(optarg);
212 if (loglevel < 0 || loglevel > 5) {
213 qDebug() << "ERROR: invalid log level spec.";
214 break;
215 }
120216 context->set_log_level(sigrok::LogLevel::get(loglevel));
121217
122218 #ifdef ENABLE_DECODE
131227 break;
132228 }
133229
230 case 'd':
231 driver = optarg;
232 break;
233
234 case 'D':
235 do_scan = false;
236 break;
237
134238 case 'i':
135 open_file = optarg;
239 open_files.emplace_back(optarg);
136240 break;
137241
138242 case 'I':
144248 break;
145249 }
146250 }
147
148 if (argc - optind > 1) {
149 fprintf(stderr, "Only one file can be opened.\n");
150 return 1;
151 }
152
153 if (argc - optind == 1)
154 open_file = argv[argc - 1];
251 argc -= optind;
252 argv += optind;
253
254 for (int i = 0; i < argc; i++)
255 open_files.emplace_back(argv[i]);
256
257 qRegisterMetaType<pv::util::Timestamp>("util::Timestamp");
258 qRegisterMetaType<uint64_t>("uint64_t");
259
260 // Prepare the global settings since logging needs them early on
261 pv::GlobalSettings settings;
262 settings.save_internal_defaults();
263 settings.set_defaults_where_needed();
264 settings.apply_theme();
265
266 pv::logging.init();
155267
156268 // Initialise libsigrok
157269 context = sigrok::Context::create();
270 pv::Session::sr_context = context;
271
272 #if ENABLE_STACKTRACE
273 QString temp_path = QStandardPaths::standardLocations(
274 QStandardPaths::TempLocation).at(0);
275 stacktrace_filename = temp_path + "/pv_stacktrace.dmp";
276 qDebug() << "Stack trace file is" << stacktrace_filename;
277
278 ::signal(SIGSEGV, &signal_handler);
279 ::signal(SIGABRT, &signal_handler);
280
281 if (QFileInfo::exists(stacktrace_filename))
282 process_stacktrace(temp_path);
283 #endif
284
158285 #ifdef ANDROID
159286 context->set_resource_reader(&asset_reader);
160287 #endif
171298 srd_decoder_load_all();
172299 #endif
173300
301 #ifndef ENABLE_STACKTRACE
174302 try {
175 // Create the device manager, initialise the drivers
176 pv::DeviceManager device_manager(context);
177
303 #endif
304
305 // Create the device manager, initialise the drivers
306 pv::DeviceManager device_manager(context, driver, do_scan);
307
308 a.collect_version_info(context);
309 if (show_version) {
310 a.print_version_info();
311 } else {
178312 // Initialise the main window
179313 pv::MainWindow w(device_manager);
180314 w.show();
182316 if (restore_sessions)
183317 w.restore_sessions();
184318
185 if (!open_file.empty())
186 w.add_session_with_file(open_file, open_file_format);
319 if (open_files.empty())
320 w.add_default_session();
187321 else
188 w.add_default_session();
322 for (string& open_file : open_files)
323 w.add_session_with_file(open_file, open_file_format);
189324
190325 #ifdef ENABLE_SIGNALS
191326 if (SignalHandler::prepare_signals()) {
192 SignalHandler *const handler =
193 new SignalHandler(&w);
194 QObject::connect(handler,
195 SIGNAL(int_received()),
327 SignalHandler *const handler = new SignalHandler(&w);
328 QObject::connect(handler, SIGNAL(int_received()),
196329 &w, SLOT(close()));
197 QObject::connect(handler,
198 SIGNAL(term_received()),
330 QObject::connect(handler, SIGNAL(term_received()),
199331 &w, SLOT(close()));
200 } else {
201 qWarning() <<
202 "Could not prepare signal handler.";
203 }
332 } else
333 qWarning() << "Could not prepare signal handler.";
204334 #endif
205335
206336 // Run the application
207337 ret = a.exec();
208
209 } catch (exception e) {
210 qDebug() << e.what();
211 }
338 }
339
340 #ifndef ENABLE_STACKTRACE
341 } catch (exception& e) {
342 qDebug() << "Exception:" << e.what();
343 }
344 #endif
212345
213346 #ifdef ENABLE_DECODE
214347 // Destroy libsigrokdecode
0 ##
1 ## This file is part of the PulseView project.
2 ##
3 ## Copyright (C) 2018 Gerhard Sittig <gerhard.sittig@gmx.net>
4 ##
5 ## This program is free software: you can redistribute it and/or modify
6 ## it under the terms of the GNU General Public License as published by
7 ## the Free Software Foundation, either version 2 of the License, or
8 ## (at your option) any later version.
9 ##
10 ## This program is distributed in the hope that it will be useful,
11 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ## GNU General Public License for more details.
14 ##
15 ## You should have received a copy of the GNU General Public License
16 ## along with this program. If not, see <http://www.gnu.org/licenses/>.
17 ##
18
19 cmake_minimum_required(VERSION 2.8.12)
20
21 # External dependencies, required and optional tools.
22 find_program(ASCIIDOCTOR_EXECUTABLE NAMES asciidoctor)
23 find_program(ASCIIDOCTOR_PDF_EXECUTABLE NAMES asciidoctor-pdf)
24
25 # Tunables.
26 set(STYLES_DIR "asciidoctor-stylesheet-factory/stylesheets")
27 set(STYLE_SHEET "readthedocs.css")
28
29 # Input files.
30 set(MANUAL_SRC "${CMAKE_CURRENT_SOURCE_DIR}/manual.txt")
31
32 # Output files, conversion results.
33 set(MANUAL_OUT_HTML "${CMAKE_CURRENT_BINARY_DIR}/manual.html")
34 set(MANUAL_OUT_PDF "${CMAKE_CURRENT_BINARY_DIR}/manual.pdf")
35
36 # Manual related make(1) targets.
37 add_custom_target(manual-html
38 COMMAND ${ASCIIDOCTOR_EXECUTABLE}
39 -a stylesheet=${STYLE_SHEET}
40 -a stylesdir=${CMAKE_CURRENT_SOURCE_DIR}/${STYLES_DIR}
41 -a toc=left
42 --destination-dir=${CMAKE_CURRENT_BINARY_DIR}
43 ${MANUAL_SRC}
44 BYPRODUCTS ${MANUAL_OUT_HTML}
45 DEPENDS ${MANUAL_SRC}
46 WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
47 COMMENT "Generating manual, HTML output"
48 )
49 if (ASCIIDOCTOR_PDF_EXECUTABLE)
50 add_custom_target(manual-pdf
51 COMMAND ${ASCIIDOCTOR_PDF_EXECUTABLE}
52 -a stylesheet=${STYLE_SHEET}
53 -a stylesdir=${CMAKE_CURRENT_SOURCE_DIR}/${STYLES_DIR}
54 --destination-dir=${CMAKE_CURRENT_BINARY_DIR}
55 ${MANUAL_SRC}
56 BYPRODUCTS ${MANUAL_OUT_PDF}
57 DEPENDS ${MANUAL_SRC}
58 WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
59 COMMENT "Generating manual, HTML output"
60 )
61 else ()
62 add_custom_target(manual-pdf
63 COMMAND ${CMAKE_COMMAND} -E echo
64 "asciidoctor-pdf executable is missing, NOT generating PDF output"
65 DEPENDS ${MANUAL_SRC}
66 WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
67 )
68 endif ()
69 add_custom_target(manual)
70 add_dependencies(manual manual-html manual-pdf)
71
72 set(MANUAL_INST_SUBDIR "share/doc/pulseview")
73 install(
74 FILES ${MANUAL_OUT_HTML} ${MANUAL_OUT_PDF}
75 DESTINATION ${MANUAL_INST_SUBDIR}
76 PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
77 OPTIONAL
78 )
79 if (ASCIIDOCTOR_EXECUTABLE)
80 install(
81 DIRECTORY images
82 DESTINATION ${MANUAL_INST_SUBDIR}
83 FILE_PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
84 PATTERN "*.xcf" EXCLUDE
85 )
86 endif ()
0 == Data Acquisition
1
2 Working with PulseView follows a pattern:
3
4 image::pv_nodevice.png[]
5 <1> Open a new session
6 <2> Select the device you want to work with:
7 <3> Click "Run" to acquire signal data (waiting for a trigger first if you set one)
8
9 When you start PulseView and no sessions are restored from the last time you used it, it will
10 come up with a session that has the demo device selected. That way, you can get to know the
11 program even when you don't have any hardware to use it with.
12
13 === Device Selection
14
15 The device selector offers two methods to choose the device to use. If you click on the small
16 arrow on the side, you see a list of devices PulseView has recognized. If the device you want
17 to use it listed, you can just select it here to use it.
18
19 image::device_selector_dropdown.png[]
20
21 If it's not listed, you'll need to scan for it first. Since most serial port and Ethernet
22 devices can't be auto-detected, this is usually required for those.
23 To do so, either choose the "Connect to Device" option from the list or click on the button
24 itself. You will see the following dialog:
25
26 image::device_selector_scan.png[]
27
28 First, you'll need to pick a driver that you want to use. In order to do this, you'll need
29 to know which driver is used to talk to the device. If you're unsure, you can either try the
30 driver which you think may fit best or you can check the wiki. For every supported device there's
31 a wiki page, showing you which driver is used.
32
33 Once the driver has been chosen, you need to select the interface. Please be aware that USB
34 is only usable for devices that directly communicate over USB. Devices that use USB to emulate
35 a serial port (like the OpenBench Logic Sniffer) will have their serial port listed in the
36 serial port drop-down.
37
38 In case your device connects via Ethernet, you must supply the IP address and port. You are
39 also given the option to choose between raw TCP access and using the VXI protocol. VXI is an
40 industry standard which is mainly used in professional equipment and the device will most
41 likely let you know that it supports VXI. If your device however is more of a hobbyist grade
42 device, it's more likely that using raw TCP will be the correct choice.
43
44 After you selected the appropriate options, clicking the scan button will make PulseView try
45 to connect to the device with the given settings. If successful, any device(s) found will be
46 shown in the list box.
47
48 [NOTE]
49 When a session uses a USB device and you close Pulseview, a session with that same device
50 is re-opened when you start Pulseview again. Currently, this is however not the case for non-USB
51 devices, such as ones that connect via serial port or Ethernet.
52
53 [NOTE]
54 To avoid having to manually enter the device configuration for a serial port or Ethernet
55 device every time you want to use it and then having to scan for it, you can also use the
56 command line parameter -d to have PulseView scan for it on startup.
57
58 You may then change the device configuration and/or start the data acquisition by clicking
59 the "Run" button on the top left.
60
61 When you run the acquisition, you'll notice that the newly captured data goes off-screen.
62 This is to improve performance and let PulseView acquire the data without bogging down your
63 CPU too much. If you find this inconvenient because you'd like to see what kind of data is
64 coming in, you have three options:
65
66 * Enable "always perform zoom-to-fit" temporarily (see chapter "Data Analysis")
67 * Enable "constantly perform zoom-to-fit during acquisition" in the options
68 * Enable "always keep newest samples at the right edge during capture" in the options
69
70 Which method suits you best is up for you to decide.
71
72 === Device Configuration
73
74 In PulseView, the device configuration is done using these buttons:
75
76 image::pv_device_config.png[]
77 <1> Device-specific settings
78 <2> Channel-specific settings
79 <3> Number of samples to capture
80 <4> Sample rate at which to capture the samples
81 <5> Per-channel trigger setting (see below)
82
83 The values offered for those four elements depend on your device. Which settings you should choose
84 depends on several factors: the needs of your measurement, the device you use to capture the data
85 and the capabilities of your computer.
86
87 The sample rate you choose must at least be twice that of the highest frequency you want to
88 capture - ideally 3 to 5 times as much so that you have some margin. That way, a jittering signal
89 won't ruin your measurements.
90
91 [NOTE]
92 If you're using a device with a Cypress FX2 (most 8 channel / 24 MHz logic analyzers do) then you should
93 be aware that the 24 MHz sampling rate (12 MHz for 16 channels) can only be sustained under perfect
94 conditions. Usually, those devices are shipped with low-quality USB cables, impairing USB transfers as
95 USB traffic increases. Therefore, you can try a different USB cable if you're facing issues at higher
96 sample rates. If they persist, it's worth trying a different USB port as well.
97
98 === Triggers
99
100 The signal labels on the left side of the view (D0, D1 and so on in the picture above) allow you to
101 configure certain aspects of these signals. If the device supports it then the trigger that will be
102 used for this signal will be among them.
103
104 As of now, the trigger system is awaiting extension for advanced and complex trigger types, meaning
105 that the only triggers available to you are:
106
107 * Trigger when the signal has a "low" level
108 * Trigger when the signal has a "high" level
109 * Trigger when the signal switches from "low" to "high" level (rising edge)
110 * Trigger when the signal switches from "hig" to "low" level (falling edge)
111 * Trigger when the signal changes level in any way (any edge)
112
113 Once you choose a trigger, the icon for the type you chose becomes visible on the right side of the
114 trace view.
115
116 When you click "Run" with a trigger configured, PulseView will wait for the device to trigger and
117 send data before it can show anything. There is currently no frame limit, so if the device driver
118 supports it, PulseView will continue arming the trigger and collecting data until you either click
119 "Stop" or it runs out of memory.
120
121 === Channel Groups
122
123 Some devices share certain settings between a group of channels, which is why PulseView may show
124 the channels your device offers in groups. You can see which channels are grouped by looking at the
125 dark gray bar on the left. If there is none, no channels are grouped.
126
127 Currently, the grouping is only done for your convenience and there's no direct functional impact.
128 This means that you're free to ungroup and group channels as you please. To do so, right-click
129 on the dark gray bar and select "Ungroup".
130 If you want to create a new group, select the signals you want to group by holding down CTRL
131 and clicking on the signal labels. Once you have selected the ones you want to be grouped,
132 right-click on one of the labels you selected and choose "Group".
133
0 == Data Analysis
1
2 Once you have acquired some measurement data, it's time to have a look and see what
3 insights you can gain from it. Usually, the first step is to look at the data as a
4 whole, achieved by clicking the _Zoom to Fit_ button:
5
6 image::pv_analysis.png[]
7
8 <1> Zoom-to-Fit button
9 <2> Zoom in/zoom out buttons
10 <3> Cursors
11 <4> Time scale (used to set up and show markers, see below)
12
13 If you have located an area of interest (maybe with the help of decoders, more about
14 that later), you can zoom in on it using the _zoom in_/_zoom out_ buttons, using the
15 scroll wheel of your mouse or the pinch/expand gestures on your touch panel.
16
17 [NOTE]
18 When a data capture is ongoing, the Zoom-to-Fit button stays active if you click it,
19 meaning that PulseView automatically fits all data to the views until either the
20 capture is finished or the Zoom-to-Fit button is clicked again.
21 If you want this feature but don't want to always have to click the button, you
22 can enable the "Always Zoom-to-Fit" option in the settings.
23
24 === Cursors and Markers
25
26 Just looking at the signal data however is usually not sufficient. A lot of times,
27 you'll want to make sure that timings are honored and the bit times are like what
28 you'd expect. To do so, you'll want to use cursors and markers.
29
30 In the picture above, you can enable the cursor by clicking on the cursor button.
31 You can move both of its boundaries around by clicking on the blue flags in the
32 time scale area. The area between the two boundary lines shows the time distance
33 and its inverse (i.e. the frequency). If you can't see it, just zoom in until it
34 shows. You can also move both boundaries at the same time by dragging the label
35 where this information is shown.
36
37 image::pv_cursors_markers.png[]
38
39 <1> Cursors button, showing enabled state
40 <2> Cursor
41 <3> Marker
42
43 Markers are movable indicators that you can create wherever you like on the
44 time scale - just double-click on it and it'll create one for you where your
45 mouse cursor is at the time, or use the context menu when right-clicking on
46 the ruler or a signal trace.
47 You can click on its label and you'll have the option to change its name, or
48 drag it to reposition it.
49
50 [NOTE]
51 For timing comparison purposes, you can also enable a vertical marker line that
52 follows your mouse cursor: _Settings_ -> _Views_ -> _Highlight mouse cursor_
53
54 [NOTE]
55 There is also a special kind of marker that appears for each time the data
56 acquisition device has triggered. It cannot be moved and appears as a vertical
57 dashed line.
58
59 === Special-Purpose Decoders
60
61 There are some decoders available that analyze the data instead of decoding it.
62 You can make use of them to examine various properties of the signals that are
63 of interest to you.
64
65 Their names are:
66
67 * Counter - counts pulses and/or groups of pulses (i.e. words)
68 * Guess bitrate - guesses the bitrate when using a serial protocol
69 * Jitter - determines the jitter (variance) of a signal
70 * Timing - shows the time passing between the chosen signal edges
71
72 === Other Features
73
74 Trace Views also allow you to maximize the viewing area by minimizing the area
75 occupied by the label area on the left. To do this, simply position the mouse
76 cursor at the right edge of the label area (or left edge of the viewing area).
77 Your mouse cursor will change shape and you now can drag the border.
78
79 This way, you can give signals long, expressive names without clogging up the
80 view area.
81
82 Also, you can create multiple views by clicking on the "New View" button on
83 the very left of the toolbar. Those can be rearranged as you wish.
0 Asciidoctor styles
1 ------------------
2
3 Copyright (c) 2013 Dan Allen
4
5 MIT License
6
7 Permission is hereby granted, free of charge, to any person obtaining
8 a copy of this software and associated documentation files (the
9 "Software"), to deal in the Software without restriction, including
10 without limitation the rights to use, copy, modify, merge, publish,
11 distribute, sublicense, and/or sell copies of the Software, and to
12 permit persons to whom the Software is furnished to do so, subject to
13 the following conditions:
14
15 The above copyright notice and this permission notice shall be
16 included in all copies or substantial portions of the Software.
17
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
26
27 Other licensed work
28 -------------------
29
30 - Foundation 4 by Zurb, on which the themes are built, is licensed under the
31 Apache License, v2.0:
32
33 http://apache.org/licenses/LICENSE-2.0
34 http://foundation.zurb.com
35
36 - The riak theme is derived from the Riak documentation theme by Basho,
37 licensed under the Creative Commons Attribution 3.0 Unported License:
38
39 http://creativecommons.org/licenses/by/3.0/us
40 http://docs.basho.org
41
42 - The iconic theme is inspired by O'Reilly typography and Atlas manual.
43
44 http://oreilly.com
0 /*! normalize.css v2.1.2 | MIT License | git.io/normalize */
1 /* ========================================================================== HTML5 display definitions ========================================================================== */
2 /** Correct `block` display not defined in IE 8/9. */
3 article, aside, details, figcaption, figure, footer, header, hgroup, main, nav, section, summary { display: block; }
4
5 /** Correct `inline-block` display not defined in IE 8/9. */
6 audio, canvas, video { display: inline-block; }
7
8 /** Prevent modern browsers from displaying `audio` without controls. Remove excess height in iOS 5 devices. */
9 audio:not([controls]) { display: none; height: 0; }
10
11 /** Address `[hidden]` styling not present in IE 8/9. Hide the `template` element in IE, Safari, and Firefox < 22. */
12 [hidden], template { display: none; }
13
14 script { display: none !important; }
15
16 /* ========================================================================== Base ========================================================================== */
17 /** 1. Set default font family to sans-serif. 2. Prevent iOS text size adjust after orientation change, without disabling user zoom. */
18 html { font-family: sans-serif; /* 1 */ -ms-text-size-adjust: 100%; /* 2 */ -webkit-text-size-adjust: 100%; /* 2 */ }
19
20 /** Remove default margin. */
21 body { margin: 0; }
22
23 /* ========================================================================== Links ========================================================================== */
24 /** Remove the gray background color from active links in IE 10. */
25 a { background: transparent; }
26
27 /** Address `outline` inconsistency between Chrome and other browsers. */
28 a:focus { outline: thin dotted; }
29
30 /** Improve readability when focused and also mouse hovered in all browsers. */
31 a:active, a:hover { outline: 0; }
32
33 /* ========================================================================== Typography ========================================================================== */
34 /** Address variable `h1` font-size and margin within `section` and `article` contexts in Firefox 4+, Safari 5, and Chrome. */
35 h1 { font-size: 2em; margin: 0.67em 0; }
36
37 /** Address styling not present in IE 8/9, Safari 5, and Chrome. */
38 abbr[title] { border-bottom: 1px dotted; }
39
40 /** Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. */
41 b, strong { font-weight: bold; }
42
43 /** Address styling not present in Safari 5 and Chrome. */
44 dfn { font-style: italic; }
45
46 /** Address differences between Firefox and other browsers. */
47 hr { -moz-box-sizing: content-box; box-sizing: content-box; height: 0; }
48
49 /** Address styling not present in IE 8/9. */
50 mark { background: #ff0; color: #000; }
51
52 /** Correct font family set oddly in Safari 5 and Chrome. */
53 code, kbd, pre, samp { font-family: monospace, serif; font-size: 1em; }
54
55 /** Improve readability of pre-formatted text in all browsers. */
56 pre { white-space: pre-wrap; }
57
58 /** Set consistent quote types. */
59 q { quotes: "\201C" "\201D" "\2018" "\2019"; }
60
61 /** Address inconsistent and variable font size in all browsers. */
62 small { font-size: 80%; }
63
64 /** Prevent `sub` and `sup` affecting `line-height` in all browsers. */
65 sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
66
67 sup { top: -0.5em; }
68
69 sub { bottom: -0.25em; }
70
71 /* ========================================================================== Embedded content ========================================================================== */
72 /** Remove border when inside `a` element in IE 8/9. */
73 img { border: 0; }
74
75 /** Correct overflow displayed oddly in IE 9. */
76 svg:not(:root) { overflow: hidden; }
77
78 /* ========================================================================== Figures ========================================================================== */
79 /** Address margin not present in IE 8/9 and Safari 5. */
80 figure { margin: 0; }
81
82 /* ========================================================================== Forms ========================================================================== */
83 /** Define consistent border, margin, and padding. */
84 fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em; }
85
86 /** 1. Correct `color` not being inherited in IE 8/9. 2. Remove padding so people aren't caught out if they zero out fieldsets. */
87 legend { border: 0; /* 1 */ padding: 0; /* 2 */ }
88
89 /** 1. Correct font family not being inherited in all browsers. 2. Correct font size not being inherited in all browsers. 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. */
90 button, input, select, textarea { font-family: inherit; /* 1 */ font-size: 100%; /* 2 */ margin: 0; /* 3 */ }
91
92 /** Address Firefox 4+ setting `line-height` on `input` using `!important` in the UA stylesheet. */
93 button, input { line-height: normal; }
94
95 /** Address inconsistent `text-transform` inheritance for `button` and `select`. All other form control elements do not inherit `text-transform` values. Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+. Correct `select` style inheritance in Firefox 4+ and Opera. */
96 button, select { text-transform: none; }
97
98 /** 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` and `video` controls. 2. Correct inability to style clickable `input` types in iOS. 3. Improve usability and consistency of cursor style between image-type `input` and others. */
99 button, html input[type="button"], input[type="reset"], input[type="submit"] { -webkit-appearance: button; /* 2 */ cursor: pointer; /* 3 */ }
100
101 /** Re-set default cursor for disabled elements. */
102 button[disabled], html input[disabled] { cursor: default; }
103
104 /** 1. Address box sizing set to `content-box` in IE 8/9. 2. Remove excess padding in IE 8/9. */
105 input[type="checkbox"], input[type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ }
106
107 /** 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome (include `-moz` to future-proof). */
108 input[type="search"] { -webkit-appearance: textfield; /* 1 */ -moz-box-sizing: content-box; -webkit-box-sizing: content-box; /* 2 */ box-sizing: content-box; }
109
110 /** Remove inner padding and search cancel button in Safari 5 and Chrome on OS X. */
111 input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; }
112
113 /** Remove inner padding and border in Firefox 4+. */
114 button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; }
115
116 /** 1. Remove default vertical scrollbar in IE 8/9. 2. Improve readability and alignment in all browsers. */
117 textarea { overflow: auto; /* 1 */ vertical-align: top; /* 2 */ }
118
119 /* ========================================================================== Tables ========================================================================== */
120 /** Remove most spacing between table cells. */
121 table { border-collapse: collapse; border-spacing: 0; }
122
123 meta.foundation-mq-small { font-family: "only screen and (min-width: 768px)"; width: 768px; }
124
125 meta.foundation-mq-medium { font-family: "only screen and (min-width:1280px)"; width: 1280px; }
126
127 meta.foundation-mq-large { font-family: "only screen and (min-width:1440px)"; width: 1440px; }
128
129 *, *:before, *:after { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; }
130
131 html, body { font-size: 100%; }
132
133 body { background: #fff; color: #222; padding: 0; margin: 0; font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif; font-weight: normal; font-style: normal; line-height: 1; position: relative; cursor: auto; }
134
135 a:hover { cursor: pointer; }
136
137 img, object, embed { max-width: 100%; height: auto; }
138
139 object, embed { height: 100%; }
140
141 img { -ms-interpolation-mode: bicubic; }
142
143 #map_canvas img, #map_canvas embed, #map_canvas object, .map_canvas img, .map_canvas embed, .map_canvas object { max-width: none !important; }
144
145 .left { float: left !important; }
146
147 .right { float: right !important; }
148
149 .text-left { text-align: left !important; }
150
151 .text-right { text-align: right !important; }
152
153 .text-center { text-align: center !important; }
154
155 .text-justify { text-align: justify !important; }
156
157 .hide { display: none; }
158
159 .antialiased { -webkit-font-smoothing: antialiased; }
160
161 img { display: inline-block; vertical-align: middle; }
162
163 textarea { height: auto; min-height: 50px; }
164
165 select { width: 100%; }
166
167 object, svg { display: inline-block; vertical-align: middle; }
168
169 .center { margin-left: auto; margin-right: auto; }
170
171 .stretch { width: 100%; }
172
173 p.lead { font-size: 1.21875em; line-height: 1.6; }
174
175 .subheader, .admonitionblock td.content > .title, .audioblock > .title, .exampleblock > .title, .imageblock > .title, .listingblock > .title, .literalblock > .title, .stemblock > .title, .openblock > .title, .paragraph > .title, .quoteblock > .title, table.tableblock > .title, .verseblock > .title, .videoblock > .title, .dlist > .title, .olist > .title, .ulist > .title, .qlist > .title, .hdlist > .title { line-height: 1.4; color: #6c818f; font-weight: 300; margin-top: 0.2em; margin-bottom: 0.5em; }
176
177 /* Typography resets */
178 div, dl, dt, dd, ul, ol, li, h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6, pre, form, p, blockquote, th, td { margin: 0; padding: 0; direction: ltr; }
179
180 /* Default Link Styles */
181 a { color: #444; text-decoration: underline; line-height: inherit; }
182 a:hover, a:focus { color: #111; }
183 a img { border: none; }
184
185 /* Default paragraph styles */
186 p { font-family: inherit; font-weight: normal; font-size: 1em; line-height: 1.5; margin-bottom: 1.25em; text-rendering: optimizeLegibility; }
187 p aside { font-size: 0.875em; line-height: 1.35; font-style: italic; }
188
189 /* Default header styles */
190 h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6 { font-family: ff-meta-web-pro-1, ff-meta-web-pro-2, Arial, "Helvetica Neue", sans-serif; font-weight: bold; font-style: normal; color: #465158; text-rendering: optimizeLegibility; margin-top: 1em; margin-bottom: 0.5em; line-height: 1.2125em; }
191 h1 small, h2 small, h3 small, #toctitle small, .sidebarblock > .content > .title small, h4 small, h5 small, h6 small { font-size: 60%; color: #909ea7; line-height: 0; }
192
193 h1 { font-size: 2.125em; }
194
195 h2 { font-size: 1.6875em; }
196
197 h3, #toctitle, .sidebarblock > .content > .title { font-size: 1.375em; }
198
199 h4 { font-size: 1.125em; }
200
201 h5 { font-size: 1.125em; }
202
203 h6 { font-size: 1em; }
204
205 hr { border: solid #ddd; border-width: 1px 0 0; clear: both; margin: 1.25em 0 1.1875em; height: 0; }
206
207 /* Helpful Typography Defaults */
208 em, i { font-style: italic; line-height: inherit; }
209
210 strong, b { font-weight: bold; line-height: inherit; }
211
212 small { font-size: 60%; line-height: inherit; }
213
214 code { font-family: "Consolas", "Deja Vu Sans Mono", "Bitstream Vera Sans Mono", monospace; font-weight: normal; color: #444; }
215
216 /* Lists */
217 ul, ol, dl { font-size: 1em; line-height: 1.5; margin-bottom: 1.25em; list-style-position: outside; font-family: inherit; }
218
219 ul, ol { margin-left: 0; }
220 ul.no-bullet, ol.no-bullet { margin-left: 0; }
221
222 /* Unordered Lists */
223 ul li ul, ul li ol { margin-left: 1.25em; margin-bottom: 0; font-size: 1em; /* Override nested font-size change */ }
224 ul.square li ul, ul.circle li ul, ul.disc li ul { list-style: inherit; }
225 ul.square { list-style-type: square; }
226 ul.circle { list-style-type: circle; }
227 ul.disc { list-style-type: disc; }
228 ul.no-bullet { list-style: none; }
229
230 /* Ordered Lists */
231 ol li ul, ol li ol { margin-left: 1.25em; margin-bottom: 0; }
232
233 /* Definition Lists */
234 dl dt { margin-bottom: 0.3em; font-weight: bold; }
235 dl dd { margin-bottom: 0.75em; }
236
237 /* Abbreviations */
238 abbr, acronym { text-transform: uppercase; font-size: 90%; color: #000; border-bottom: 1px dotted #ddd; cursor: help; }
239
240 abbr { text-transform: none; }
241
242 /* Blockquotes */
243 blockquote { margin: 0 0 1.25em; padding: 0.5625em 1.25em 0 1.1875em; border-left: 1px solid #ddd; }
244 blockquote cite { display: block; font-size: 0.8125em; color: #748590; }
245 blockquote cite:before { content: "\2014 \0020"; }
246 blockquote cite a, blockquote cite a:visited { color: #748590; }
247
248 blockquote, blockquote p { line-height: 1.5; color: #909ea7; }
249
250 /* Microformats */
251 .vcard { display: inline-block; margin: 0 0 1.25em 0; border: 1px solid #ddd; padding: 0.625em 0.75em; }
252 .vcard li { margin: 0; display: block; }
253 .vcard .fn { font-weight: bold; font-size: 0.9375em; }
254
255 .vevent .summary { font-weight: bold; }
256 .vevent abbr { cursor: auto; text-decoration: none; font-weight: bold; border: none; padding: 0 0.0625em; }
257
258 @media only screen and (min-width: 768px) { h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6 { line-height: 1.4; }
259 h1 { font-size: 2.75em; }
260 h2 { font-size: 2.3125em; }
261 h3, #toctitle, .sidebarblock > .content > .title { font-size: 1.6875em; }
262 h4 { font-size: 1.4375em; } }
263 /* Tables */
264 table { background: #fff; margin-bottom: 1.25em; border: solid 0 #ddd; }
265 table thead, table tfoot { background: none; font-weight: bold; }
266 table thead tr th, table thead tr td, table tfoot tr th, table tfoot tr td { padding: 1px 8px 1px 5px; font-size: 1em; color: #222; text-align: left; }
267 table tr th, table tr td { padding: 1px 8px 1px 5px; font-size: 1em; color: #222; }
268 table tr.even, table tr.alt, table tr:nth-of-type(even) { background: none; }
269 table thead tr th, table tfoot tr th, table tbody tr td, table tr td, table tfoot tr td { display: table-cell; line-height: 1.5; }
270
271 body { -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; tab-size: 4; }
272
273 h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6 { line-height: 1.4; }
274
275 .clearfix:before, .clearfix:after, .float-group:before, .float-group:after { content: " "; display: table; }
276 .clearfix:after, .float-group:after { clear: both; }
277
278 *:not(pre) > code { font-size: 0.95em; font-style: normal !important; letter-spacing: 0; padding: 0; background-color: #f2f2f2; -webkit-border-radius: 6px; border-radius: 6px; line-height: inherit; word-wrap: break-word; }
279 *:not(pre) > code.nobreak { word-wrap: normal; }
280 *:not(pre) > code.nowrap { white-space: nowrap; }
281
282 pre, pre > code { line-height: 1.2; color: inherit; font-family: "Consolas", "Deja Vu Sans Mono", "Bitstream Vera Sans Mono", monospace; font-weight: normal; }
283
284 em em { font-style: normal; }
285
286 strong strong { font-weight: normal; }
287
288 .keyseq { color: #333333; }
289
290 kbd { font-family: "Consolas", "Deja Vu Sans Mono", "Bitstream Vera Sans Mono", monospace; display: inline-block; color: #000; font-size: 0.65em; line-height: 1.45; background-color: #f7f7f7; border: 1px solid #ccc; -webkit-border-radius: 3px; border-radius: 3px; -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 0.1em white inset; -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 0.1em white inset; box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 0.1em white inset; margin: 0 0.15em; padding: 0.2em 0.5em; vertical-align: middle; position: relative; top: -0.1em; white-space: nowrap; }
291
292 .keyseq kbd:first-child { margin-left: 0; }
293
294 .keyseq kbd:last-child { margin-right: 0; }
295
296 .menuseq, .menuref { color: #000; }
297
298 .menuseq b:not(.caret), .menuref { font-weight: inherit; }
299
300 .menuseq { word-spacing: -0.02em; }
301 .menuseq b.caret { font-size: 1.25em; line-height: 0.8; }
302 .menuseq i.caret { font-weight: bold; text-align: center; width: 0.45em; }
303
304 b.button:before, b.button:after { position: relative; top: -1px; font-weight: normal; }
305
306 b.button:before { content: "["; padding: 0 3px 0 2px; }
307
308 b.button:after { content: "]"; padding: 0 2px 0 3px; }
309
310 p a > code:hover { color: #373737; }
311
312 #header, #content, #footnotes, #footer { width: 100%; margin-left: auto; margin-right: auto; margin-top: 0; margin-bottom: 0; max-width: 62.5em; *zoom: 1; position: relative; padding-left: 0.9375em; padding-right: 0.9375em; }
313 #header:before, #header:after, #content:before, #content:after, #footnotes:before, #footnotes:after, #footer:before, #footer:after { content: " "; display: table; }
314 #header:after, #content:after, #footnotes:after, #footer:after { clear: both; }
315
316 #content { margin-top: 1.25em; }
317
318 #content:before { content: none; }
319
320 #header > h1:first-child { color: #111; margin-top: 2.25rem; margin-bottom: 0; }
321 #header > h1:first-child + #toc { margin-top: 8px; border-top: 1px solid #ddd; }
322 #header > h1:only-child, body.toc2 #header > h1:nth-last-child(2) { border-bottom: 1px solid #ddd; padding-bottom: 8px; }
323 #header .details { border-bottom: 1px solid #ddd; line-height: 1.45; padding-top: 0.25em; padding-bottom: 0.25em; padding-left: 0.25em; color: #748590; display: -ms-flexbox; display: -webkit-flex; display: flex; -ms-flex-flow: row wrap; -webkit-flex-flow: row wrap; flex-flow: row wrap; }
324 #header .details span:first-child { margin-left: -0.125em; }
325 #header .details span.email a { color: #909ea7; }
326 #header .details br { display: none; }
327 #header .details br + span:before { content: "\00a0\2013\00a0"; }
328 #header .details br + span.author:before { content: "\00a0\22c5\00a0"; color: #909ea7; }
329 #header .details br + span#revremark:before { content: "\00a0|\00a0"; }
330 #header #revnumber { text-transform: capitalize; }
331 #header #revnumber:after { content: "\00a0"; }
332
333 #content > h1:first-child:not([class]) { color: #111; border-bottom: 1px solid #ddd; padding-bottom: 8px; margin-top: 0; padding-top: 1rem; margin-bottom: 1.25rem; }
334
335 #toc { border-bottom: 1px solid #ddd; padding-bottom: 0.5em; }
336 #toc > ul { margin-left: 0.125em; }
337 #toc ul.sectlevel0 > li > a { font-style: italic; }
338 #toc ul.sectlevel0 ul.sectlevel1 { margin: 0.5em 0; }
339 #toc ul { font-family: ff-meta-web-pro-1, ff-meta-web-pro-2, Arial, "Helvetica Neue", sans-serif; list-style-type: none; }
340 #toc li { line-height: 1.3334; margin-top: 0.3334em; }
341 #toc a { text-decoration: none; }
342 #toc a:active { text-decoration: underline; }
343
344 #toctitle { color: #6c818f; font-size: 1.2em; }
345
346 @media only screen and (min-width: 768px) { #toctitle { font-size: 1.375em; }
347 body.toc2 { padding-left: 15em; padding-right: 0; }
348 #toc.toc2 { margin-top: 0 !important; background-color: #f2f2f2; position: fixed; width: 15em; left: 0; top: 0; border-right: 1px solid #ddd; border-top-width: 0 !important; border-bottom-width: 0 !important; z-index: 1000; padding: 1.25em 1em; height: 100%; overflow: auto; }
349 #toc.toc2 #toctitle { margin-top: 0; margin-bottom: 0.8rem; font-size: 1.2em; }
350 #toc.toc2 > ul { font-size: 0.9em; margin-bottom: 0; }
351 #toc.toc2 ul ul { margin-left: 0; padding-left: 1em; }
352 #toc.toc2 ul.sectlevel0 ul.sectlevel1 { padding-left: 0; margin-top: 0.5em; margin-bottom: 0.5em; }
353 body.toc2.toc-right { padding-left: 0; padding-right: 15em; }
354 body.toc2.toc-right #toc.toc2 { border-right-width: 0; border-left: 1px solid #ddd; left: auto; right: 0; } }
355 @media only screen and (min-width: 1280px) { body.toc2 { padding-left: 20em; padding-right: 0; }
356 #toc.toc2 { width: 20em; }
357 #toc.toc2 #toctitle { font-size: 1.375em; }
358 #toc.toc2 > ul { font-size: 0.95em; }
359 #toc.toc2 ul ul { padding-left: 1.25em; }
360 body.toc2.toc-right { padding-left: 0; padding-right: 20em; } }
361 #content #toc { border-style: solid; border-width: 1px; border-color: #d9d9d9; margin-bottom: 1.25em; padding: 1.25em; background: #f2f2f2; -webkit-border-radius: 6px; border-radius: 6px; }
362 #content #toc > :first-child { margin-top: 0; }
363 #content #toc > :last-child { margin-bottom: 0; }
364
365 #footer { max-width: 100%; background-color: #000; padding: 1.25em; }
366
367 #footer-text { color: white; line-height: 1.35; }
368
369 #content { margin-bottom: 0.625em; }
370
371 .sect1 { padding-bottom: 0.625em; }
372
373 @media only screen and (min-width: 768px) { #content { margin-bottom: 1.25em; }
374 .sect1 { padding-bottom: 1.25em; } }
375 .sect1:last-child { padding-bottom: 0; }
376
377 .sect1 + .sect1 { border-top: 1px solid #ddd; }
378
379 #content h1 > a.anchor, h2 > a.anchor, h3 > a.anchor, #toctitle > a.anchor, .sidebarblock > .content > .title > a.anchor, h4 > a.anchor, h5 > a.anchor, h6 > a.anchor { position: absolute; z-index: 1001; width: 1.5ex; margin-left: -1.5ex; display: block; text-decoration: none !important; visibility: hidden; text-align: center; font-weight: normal; }
380 #content h1 > a.anchor:before, h2 > a.anchor:before, h3 > a.anchor:before, #toctitle > a.anchor:before, .sidebarblock > .content > .title > a.anchor:before, h4 > a.anchor:before, h5 > a.anchor:before, h6 > a.anchor:before { content: "\00A7"; font-size: 0.85em; display: block; padding-top: 0.1em; }
381 #content h1:hover > a.anchor, #content h1 > a.anchor:hover, h2:hover > a.anchor, h2 > a.anchor:hover, h3:hover > a.anchor, #toctitle:hover > a.anchor, .sidebarblock > .content > .title:hover > a.anchor, h3 > a.anchor:hover, #toctitle > a.anchor:hover, .sidebarblock > .content > .title > a.anchor:hover, h4:hover > a.anchor, h4 > a.anchor:hover, h5:hover > a.anchor, h5 > a.anchor:hover, h6:hover > a.anchor, h6 > a.anchor:hover { visibility: visible; }
382 #content h1 > a.link, h2 > a.link, h3 > a.link, #toctitle > a.link, .sidebarblock > .content > .title > a.link, h4 > a.link, h5 > a.link, h6 > a.link { color: #465158; text-decoration: none; }
383 #content h1 > a.link:hover, h2 > a.link:hover, h3 > a.link:hover, #toctitle > a.link:hover, .sidebarblock > .content > .title > a.link:hover, h4 > a.link:hover, h5 > a.link:hover, h6 > a.link:hover { color: #3b444a; }
384
385 .audioblock, .imageblock, .literalblock, .listingblock, .stemblock, .videoblock { margin-bottom: 1.25em; }
386
387 .admonitionblock td.content > .title, .audioblock > .title, .exampleblock > .title, .imageblock > .title, .listingblock > .title, .literalblock > .title, .stemblock > .title, .openblock > .title, .paragraph > .title, .quoteblock > .title, table.tableblock > .title, .verseblock > .title, .videoblock > .title, .dlist > .title, .olist > .title, .ulist > .title, .qlist > .title, .hdlist > .title { text-rendering: optimizeLegibility; text-align: left; }
388
389 table.tableblock.fit-content > caption.title { white-space: nowrap; width: 0; }
390
391 .paragraph.lead > p, #preamble > .sectionbody > [class="paragraph"]:first-of-type p { font-size: 1.21875em; line-height: 1.6; color: #111; }
392
393 table.tableblock #preamble > .sectionbody > [class="paragraph"]:first-of-type p { font-size: inherit; }
394
395 .admonitionblock > table { border-collapse: separate; border: 0; background: none; width: 100%; }
396 .admonitionblock > table td.icon { text-align: center; width: 80px; }
397 .admonitionblock > table td.icon img { max-width: none; }
398 .admonitionblock > table td.icon .title { font-weight: bold; font-family: ff-meta-web-pro-1, ff-meta-web-pro-2, Arial, "Helvetica Neue", sans-serif; text-transform: uppercase; }
399 .admonitionblock > table td.content { padding-left: 1.125em; padding-right: 1.25em; border-left: 1px solid #ddd; color: #748590; }
400 .admonitionblock > table td.content > :last-child > :last-child { margin-bottom: 0; }
401
402 .exampleblock > .content { border-style: solid; border-width: 1px; border-color: #e6e6e6; margin-bottom: 1.25em; padding: 1.25em; background: #fff; -webkit-border-radius: 6px; border-radius: 6px; }
403 .exampleblock > .content > :first-child { margin-top: 0; }
404 .exampleblock > .content > :last-child { margin-bottom: 0; }
405
406 .sidebarblock { border-style: solid; border-width: 1px; border-color: #d9d9d9; margin-bottom: 1.25em; padding: 1.25em; background: #f2f2f2; -webkit-border-radius: 6px; border-radius: 6px; }
407 .sidebarblock > :first-child { margin-top: 0; }
408 .sidebarblock > :last-child { margin-bottom: 0; }
409 .sidebarblock > .content > .title { color: #6c818f; margin-top: 0; }
410
411 .exampleblock > .content > :last-child > :last-child, .exampleblock > .content .olist > ol > li:last-child > :last-child, .exampleblock > .content .ulist > ul > li:last-child > :last-child, .exampleblock > .content .qlist > ol > li:last-child > :last-child, .sidebarblock > .content > :last-child > :last-child, .sidebarblock > .content .olist > ol > li:last-child > :last-child, .sidebarblock > .content .ulist > ul > li:last-child > :last-child, .sidebarblock > .content .qlist > ol > li:last-child > :last-child { margin-bottom: 0; }
412
413 .literalblock pre, .listingblock pre:not(.highlight), .listingblock pre[class="highlight"], .listingblock pre[class^="highlight "], .listingblock pre.CodeRay, .listingblock pre.prettyprint { background: #eee; }
414 .sidebarblock .literalblock pre, .sidebarblock .listingblock pre:not(.highlight), .sidebarblock .listingblock pre[class="highlight"], .sidebarblock .listingblock pre[class^="highlight "], .sidebarblock .listingblock pre.CodeRay, .sidebarblock .listingblock pre.prettyprint { background: #f2f1f1; }
415
416 .literalblock pre, .literalblock pre[class], .listingblock pre, .listingblock pre[class] { border: 1px solid #ccc; -webkit-border-radius: 6px; border-radius: 6px; word-wrap: break-word; padding: 0.5em; font-size: 0.8125em; }
417 .literalblock pre.nowrap, .literalblock pre[class].nowrap, .listingblock pre.nowrap, .listingblock pre[class].nowrap { overflow-x: auto; white-space: pre; word-wrap: normal; }
418 @media only screen and (min-width: 768px) { .literalblock pre, .literalblock pre[class], .listingblock pre, .listingblock pre[class] { font-size: 0.90625em; } }
419 @media only screen and (min-width: 1280px) { .literalblock pre, .literalblock pre[class], .listingblock pre, .listingblock pre[class] { font-size: 1em; } }
420
421 .literalblock.output pre { color: #eee; background-color: inherit; }
422
423 .listingblock pre.highlightjs { padding: 0; }
424 .listingblock pre.highlightjs > code { padding: 0.5em; -webkit-border-radius: 6px; border-radius: 6px; }
425
426 .listingblock > .content { position: relative; }
427
428 .listingblock code[data-lang]:before { display: none; content: attr(data-lang); position: absolute; font-size: 0.75em; top: 0.425rem; right: 0.5rem; line-height: 1; text-transform: uppercase; color: #999; }
429
430 .listingblock:hover code[data-lang]:before { display: block; }
431
432 .listingblock.terminal pre .command:before { content: attr(data-prompt); padding-right: 0.5em; color: #999; }
433
434 .listingblock.terminal pre .command:not([data-prompt]):before { content: "$"; }
435
436 table.pyhltable { border-collapse: separate; border: 0; margin-bottom: 0; background: none; }
437
438 table.pyhltable td { vertical-align: top; padding-top: 0; padding-bottom: 0; line-height: 1.2; }
439
440 table.pyhltable td.code { padding-left: .75em; padding-right: 0; }
441
442 pre.pygments .lineno, table.pyhltable td:not(.code) { color: #999; padding-left: 0; padding-right: .5em; border-right: 1px solid #ddd; }
443
444 pre.pygments .lineno { display: inline-block; margin-right: .25em; }
445
446 table.pyhltable .linenodiv { background: none !important; padding-right: 0 !important; }
447
448 .quoteblock { margin: 0 1em 1.25em 1.5em; display: table; }
449 .quoteblock > .title { margin-left: -1.5em; margin-bottom: 0.75em; }
450 .quoteblock blockquote, .quoteblock blockquote p { color: #909ea7; font-size: 1.15rem; line-height: 1.75; word-spacing: 0.1em; letter-spacing: 0; font-style: italic; text-align: justify; }
451 .quoteblock blockquote { margin: 0; padding: 0; border: 0; }
452 .quoteblock blockquote:before { content: "\201c"; float: left; font-size: 2.75em; font-weight: bold; line-height: 0.6em; margin-left: -0.6em; color: #6c818f; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); }
453 .quoteblock blockquote > .paragraph:last-child p { margin-bottom: 0; }
454 .quoteblock .attribution { margin-top: 0.5em; margin-right: 0.5ex; text-align: right; }
455 .quoteblock .quoteblock { margin-left: 0; margin-right: 0; padding: 0.5em 0; border-left: 3px solid #748590; }
456 .quoteblock .quoteblock blockquote { padding: 0 0 0 0.75em; }
457 .quoteblock .quoteblock blockquote:before { display: none; }
458
459 .verseblock { margin: 0 1em 1.25em 1em; }
460 .verseblock pre { font-family: "Open Sans", "DejaVu Sans", sans; font-size: 1.15rem; color: #909ea7; font-weight: 300; text-rendering: optimizeLegibility; }
461 .verseblock pre strong { font-weight: 400; }
462 .verseblock .attribution { margin-top: 1.25rem; margin-left: 0.5ex; }
463
464 .quoteblock .attribution, .verseblock .attribution { font-size: 0.8125em; line-height: 1.45; font-style: italic; }
465 .quoteblock .attribution br, .verseblock .attribution br { display: none; }
466 .quoteblock .attribution cite, .verseblock .attribution cite { display: block; letter-spacing: -0.025em; color: #748590; }
467
468 .quoteblock.abstract { margin: 0 1em 1.25em 1em; display: block; }
469 .quoteblock.abstract > .title { margin: 0 0 0.375em 0; font-size: 1.15em; text-align: center; }
470 .quoteblock.abstract blockquote, .quoteblock.abstract blockquote p { word-spacing: 0; line-height: 1.6; }
471 .quoteblock.abstract blockquote:before, .quoteblock.abstract p:before { display: none; }
472
473 table.tableblock { max-width: 100%; border-collapse: separate; }
474
475 p.tableblock:last-child { margin-bottom: 0; }
476
477 td.tableblock > .content { margin-bottom: -1.25em; }
478
479 table.tableblock, th.tableblock, td.tableblock { border: 0 solid #ddd; }
480
481 table.grid-all > thead > tr > .tableblock, table.grid-all > tbody > tr > .tableblock { border-width: 0 0 0 0; }
482
483 table.grid-all > tfoot > tr > .tableblock { border-width: 0 0 0 0; }
484
485 table.grid-cols > * > tr > .tableblock { border-width: 0 0 0 0; }
486
487 table.grid-rows > thead > tr > .tableblock, table.grid-rows > tbody > tr > .tableblock { border-width: 0 0 0 0; }
488
489 table.grid-rows > tfoot > tr > .tableblock { border-width: 0 0 0 0; }
490
491 table.grid-all > * > tr > .tableblock:last-child, table.grid-cols > * > tr > .tableblock:last-child { border-right-width: 0; }
492
493 table.grid-all > tbody > tr:last-child > .tableblock, table.grid-all > thead:last-child > tr > .tableblock, table.grid-rows > tbody > tr:last-child > .tableblock, table.grid-rows > thead:last-child > tr > .tableblock { border-bottom-width: 0; }
494
495 table.frame-all { border-width: 0; }
496
497 table.frame-sides { border-width: 0 0; }
498
499 table.frame-topbot, table.frame-ends { border-width: 0 0; }
500
501 table.stripes-all tr, table.stripes-odd tr:nth-of-type(odd) { background: none; }
502
503 table.stripes-none tr, table.stripes-odd tr:nth-of-type(even) { background: none; }
504
505 th.halign-left, td.halign-left { text-align: left; }
506
507 th.halign-right, td.halign-right { text-align: right; }
508
509 th.halign-center, td.halign-center { text-align: center; }
510
511 th.valign-top, td.valign-top { vertical-align: top; }
512
513 th.valign-bottom, td.valign-bottom { vertical-align: bottom; }
514
515 th.valign-middle, td.valign-middle { vertical-align: middle; }
516
517 table thead th, table tfoot th { font-weight: bold; }
518
519 tbody tr th { display: table-cell; line-height: 1.5; background: none; }
520
521 tbody tr th, tbody tr th p, tfoot tr th, tfoot tr th p { color: #222; font-weight: bold; }
522
523 p.tableblock > code:only-child { background: none; padding: 0; }
524
525 p.tableblock { font-size: 1em; }
526
527 td > div.verse { white-space: pre; }
528
529 ol { margin-left: 0.25em; }
530
531 ul li ol { margin-left: 0; }
532
533 dl dd { margin-left: 1.125em; }
534
535 dl dd:last-child, dl dd:last-child > :last-child { margin-bottom: 0; }
536
537 ol > li p, ul > li p, ul dd, ol dd, .olist .olist, .ulist .ulist, .ulist .olist, .olist .ulist { margin-bottom: 0.625em; }
538
539 ul.checklist, ul.none, ol.none, ul.no-bullet, ol.no-bullet, ol.unnumbered, ul.unstyled, ol.unstyled { list-style-type: none; }
540
541 ul.no-bullet, ol.no-bullet, ol.unnumbered { margin-left: 0.625em; }
542
543 ul.unstyled, ol.unstyled { margin-left: 0; }
544
545 ul.checklist { margin-left: 0.625em; }
546
547 ul.checklist li > p:first-child > .fa-square-o:first-child, ul.checklist li > p:first-child > .fa-check-square-o:first-child { width: 1.25em; font-size: 0.8em; position: relative; bottom: 0.125em; }
548
549 ul.checklist li > p:first-child > input[type="checkbox"]:first-child { margin-right: 0.25em; }
550
551 ul.inline { display: -ms-flexbox; display: -webkit-box; display: flex; -ms-flex-flow: row wrap; -webkit-flex-flow: row wrap; flex-flow: row wrap; list-style: none; margin: 0 0 0.625em -1.25em; }
552
553 ul.inline > li { margin-left: 1.25em; }
554
555 .unstyled dl dt { font-weight: normal; font-style: normal; }
556
557 ol.arabic { list-style-type: decimal; }
558
559 ol.decimal { list-style-type: decimal-leading-zero; }
560
561 ol.loweralpha { list-style-type: lower-alpha; }
562
563 ol.upperalpha { list-style-type: upper-alpha; }
564
565 ol.lowerroman { list-style-type: lower-roman; }
566
567 ol.upperroman { list-style-type: upper-roman; }
568
569 ol.lowergreek { list-style-type: lower-greek; }
570
571 .hdlist > table, .colist > table { border: 0; background: none; }
572 .hdlist > table > tbody > tr, .colist > table > tbody > tr { background: none; }
573
574 td.hdlist1, td.hdlist2 { vertical-align: top; padding: 0 0.625em; }
575
576 td.hdlist1 { font-weight: bold; padding-bottom: 1.25em; }
577
578 .literalblock + .colist, .listingblock + .colist { margin-top: -0.5em; }
579
580 .colist td:not([class]):first-child { padding: 0.4em 0.75em 0 0.75em; line-height: 1; vertical-align: top; }
581 .colist td:not([class]):first-child img { max-width: none; }
582 .colist td:not([class]):last-child { padding: 0.25em 0; }
583
584 .thumb, .th { line-height: 0; display: inline-block; border: solid 4px #fff; -webkit-box-shadow: 0 0 0 1px #ddd; box-shadow: 0 0 0 1px #ddd; }
585
586 .imageblock.left { margin: 0.25em 0.625em 1.25em 0; }
587 .imageblock.right { margin: 0.25em 0 1.25em 0.625em; }
588 .imageblock > .title { margin-bottom: 0; }
589 .imageblock.thumb, .imageblock.th { border-width: 6px; }
590 .imageblock.thumb > .title, .imageblock.th > .title { padding: 0 0.125em; }
591
592 .image.left, .image.right { margin-top: 0.25em; margin-bottom: 0.25em; display: inline-block; line-height: 0; }
593 .image.left { margin-right: 0.625em; }
594 .image.right { margin-left: 0.625em; }
595
596 a.image { text-decoration: none; display: inline-block; }
597 a.image object { pointer-events: none; }
598
599 sup.footnote, sup.footnoteref { font-size: 0.875em; position: static; vertical-align: super; }
600 sup.footnote a, sup.footnoteref a { text-decoration: none; }
601 sup.footnote a:active, sup.footnoteref a:active { text-decoration: underline; }
602
603 #footnotes { padding-top: 0.75em; padding-bottom: 0.75em; margin-bottom: 0.625em; }
604 #footnotes hr { width: 20%; min-width: 6.25em; margin: -0.25em 0 0.75em 0; border-width: 1px 0 0 0; }
605 #footnotes .footnote { padding: 0 0.375em 0 0.225em; line-height: 1.3334; font-size: 0.875em; margin-left: 1.2em; margin-bottom: 0.2em; }
606 #footnotes .footnote a:first-of-type { font-weight: bold; text-decoration: none; margin-left: -1.05em; }
607 #footnotes .footnote:last-of-type { margin-bottom: 0; }
608 #content #footnotes { margin-top: -0.625em; margin-bottom: 0; padding: 0.75em 0; }
609
610 .gist .file-data > table { border: 0; background: #fff; width: 100%; margin-bottom: 0; }
611 .gist .file-data > table td.line-data { width: 99%; }
612
613 div.unbreakable { page-break-inside: avoid; }
614
615 .big { font-size: larger; }
616
617 .small { font-size: smaller; }
618
619 .underline { text-decoration: underline; }
620
621 .overline { text-decoration: overline; }
622
623 .line-through { text-decoration: line-through; }
624
625 .aqua { color: #00bfbf; }
626
627 .aqua-background { background-color: #00fafa; }
628
629 .black { color: black; }
630
631 .black-background { background-color: black; }
632
633 .blue { color: #0000bf; }
634
635 .blue-background { background-color: #0000fa; }
636
637 .fuchsia { color: #bf00bf; }
638
639 .fuchsia-background { background-color: #fa00fa; }
640
641 .gray { color: #606060; }
642
643 .gray-background { background-color: #7d7d7d; }
644
645 .green { color: #006000; }
646
647 .green-background { background-color: #007d00; }
648
649 .lime { color: #00bf00; }
650
651 .lime-background { background-color: #00fa00; }
652
653 .maroon { color: #600000; }
654
655 .maroon-background { background-color: #7d0000; }
656
657 .navy { color: #000060; }
658
659 .navy-background { background-color: #00007d; }
660
661 .olive { color: #606000; }
662
663 .olive-background { background-color: #7d7d00; }
664
665 .purple { color: #600060; }
666
667 .purple-background { background-color: #7d007d; }
668
669 .red { color: #bf0000; }
670
671 .red-background { background-color: #fa0000; }
672
673 .silver { color: #909090; }
674
675 .silver-background { background-color: #bcbcbc; }
676
677 .teal { color: #006060; }
678
679 .teal-background { background-color: #007d7d; }
680
681 .white { color: #bfbfbf; }
682
683 .white-background { background-color: #fafafa; }
684
685 .yellow { color: #bfbf00; }
686
687 .yellow-background { background-color: #fafa00; }
688
689 span.icon > .fa { cursor: default; }
690 a span.icon > .fa { cursor: inherit; }
691
692 .admonitionblock td.icon [class^="fa icon-"] { font-size: 2.5em; text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); cursor: default; }
693 .admonitionblock td.icon .icon-note:before { content: "\f05a"; color: #333333; }
694 .admonitionblock td.icon .icon-tip:before { content: "\f0eb"; text-shadow: 1px 1px 2px rgba(155, 155, 0, 0.8); color: #111; }
695 .admonitionblock td.icon .icon-warning:before { content: "\f071"; color: #bf6900; }
696 .admonitionblock td.icon .icon-caution:before { content: "\f06d"; color: #bf3400; }
697 .admonitionblock td.icon .icon-important:before { content: "\f06a"; color: #bf0000; }
698
699 .conum[data-value] { display: inline-block; color: #fff !important; background-color: #000; -webkit-border-radius: 100px; border-radius: 100px; text-align: center; font-size: 0.75em; width: 1.67em; height: 1.67em; line-height: 1.67em; font-family: "Open Sans", "DejaVu Sans", sans-serif; font-style: normal; font-weight: bold; }
700 .conum[data-value] * { color: #fff !important; }
701 .conum[data-value] + b { display: none; }
702 .conum[data-value]:after { content: attr(data-value); }
703 pre .conum[data-value] { position: relative; top: -0.125em; }
704
705 b.conum * { color: inherit !important; }
706
707 .conum:not([data-value]):empty { display: none; }
708
709 h4 { color: #6c818f; }
710
711 .literalblock > .content > pre, .listingblock > .content > pre { -webkit-border-radius: 6px; border-radius: 6px; margin-left: 2em; margin-right: 2em; }
712
713 .admonitionblock { margin-left: 2em; margin-right: 2em; }
714 .admonitionblock > table { border: 1px solid #607f90; border-top-width: 1.5em; background-color: #f0f9ff; border-collapse: separate; -webkit-border-radius: 0; border-radius: 0; }
715 .admonitionblock > table td.icon { padding-top: .5em; padding-bottom: .5em; }
716 .admonitionblock > table td.content { padding: .5em 1em; color: #000; font-size: .9em; border-left: none; }
717
718 .sidebarblock { background-color: #e8ecef; border-color: #ccc; }
719 .sidebarblock > .content > .title { color: #444; }
720
721 table.tableblock.grid-all { border-collapse: collapse; -webkit-border-radius: 0; border-radius: 0; }
722 table.tableblock.grid-all th.tableblock, table.tableblock.grid-all td.tableblock { border-bottom: 1px solid #aaa; }
723
724 #footer { background-color: #465158; padding: 2em; }
725
726 #footer-text { color: #eee; font-size: 0.8em; text-align: center; }
0 == Command-line Interface
1
2 Even though PulseView has a graphical user interface, there is also a command-line interface that
3 you can make use of. It is meant to provide functions for convenience and debug purposes, not to
4 replace the user interface itself.
5
6 Running
7
8 pulseview -h
9
10 gives you a list of these functions.
11
12 Since PulseView can't automatically scan for devices connected to a COM port (ttySx on Linux) or
13 Ethernet, you can tell it to look for a specific device using the -d or --driver parameter. Its
14 usage is the same as for sigrok-cli. For example:
15
16 pulseview -d lecroy-xstream:conn=vxi/192.168.178.20/111
17
18 Also, just as with sigrok-cli, you can specify -i / --input-file and -I / --input-format to open
19 a file on startup. Without -I, it is assumed that the file is in the native sigrok format (.sr).
20 You can also specify more than one file but they are all expected to be in the same format then.
21 Example:
22
23 pulseview -i data.csv -I csv:samplerate=3000000
24
25 The remaining parameters are mostly for debug purposes:
26
27 -V / --version Shows the release version
28 -l / --loglevel Sets the libsigrok/libsigrokdecode log level (max is 5)
29 -D / --dont-scan Don't auto-scan for devices
30 -c / --clean Don't restore previous sessions on startup
31
32 Of these, -D / --dont-scan can be useful when PulseView gets stuck during the startup device scan.
33 No such scan will be performed then, allowing the program to start up but you'll have to scan for
34 your acquisition device(s) manually before you can use them.
35
36 Another potentially useful option is -c / --clean, which can be used when PulseView doesn't start
37 up and you don't know what could cause this.
38
39 Thus, the combination of both parameters can be seen as some kind of "safe mode" for PulseView:
40
41 pulseview -c -D
0 == Decoders
1
2 Protocol decoders are one of the key elements of PulseView's functionality. They take
3 input data that you acquired and process it in a way that results in a (hopefully) much
4 easier to understand representation of that same data.
5
6 In its simplest form, a protocol decoder (PD) converts a group of 1-bit signals into a
7 stream of n-bit events. This is exactly what the parallel PD does: it takes for example
8 8 logic channels and treats them like an 8-bit parallel bus, emitting annotations that
9 show the current state of the bus at any point in time.
10
11 === Basic Operation
12
13 Another one of the protocol decoders available to you is the I²C decoder. It takes the
14 two I²C signals SCL and SDA (serial clock / serial data) and shows you the details of
15 the I²C communication without the need to evaluate the signal bit by bit yourself.
16
17 As an example, let's have look at one of the sample .sr files we keep around for
18 validation of the PD code base: https://sigrok.org/gitweb/?p=sigrok-dumps.git;a=blob_plain;f=i2c/rtc_dallas_ds1307/rtc_ds1307_200khz.sr;hb=HEAD[rtc_ds1307_200khz.sr].
19 It contains the capture of an I²C master interacting with a https://www.maximintegrated.com/en/products/digital/real-time-clocks/DS1307.html[Dallas DS1307 I²C Real-Time Clock]
20 where the master repeatedly sets and queries the time of day. After loading and using
21 "zoom-to-fit", it looks similar to this:
22
23 image::pv_decoders_1.png[]
24
25 Adding the I²C decoder by clicking on ➊ and selecting I²C from the list adds
26 a new decode signal to the view. PulseView tries to match existing signals to the signals
27 that the newly added protocol decoder needs to function, which is what happened here -
28 SCL and SDA have been automatically assigned and the PD has decoded the communication with
29 the default parameters. If you need to change the signal assignment or change the decoding
30 parameters, you can click on âž‹ to do so.
31
32 When you zoom in, you now see that PulseView has decoded the I²C messages and displays
33 these annotations as part of the decode signal (note that we have zoomed in so far that
34 PulseView shows you the individual samples):
35
36 image::pv_decoders_2.png[]
37
38 This is already very useful, and a massive improvement over counting out pulses on an
39 oscilloscope screen. However, sigrok allows us to go one step further with the use of
40 so-called stacked decoders.
41
42 === Decoder Stacking
43
44 To add a stacked decoder we open the settings of the decode signal, go to the _Stack
45 Decoder_ menu ➊, and select the DS1307 decoder:
46
47 image::pv_decoders_3.png[]
48
49 With the stacked decoder added, we can now see that PulseView has decoded the meaning
50 of the I²C commands, so that we don't need to bother searching the reference manual.
51 In this view, we can see that the I²C packet was a command to read the date and time,
52 which was then reported to be 10.03.2013 23:35:30.
53
54 There are all kinds of stacked decoders available, but keep in mind that they're not
55 shown in the decoder menu. Stacked decoders require a lower-level decoder first before
56 they become stackable. Most of the time, they require either the UART, I²C or SPI decoder.
57
58 You can check the https://sigrok.org/wiki/Protocol_decoders[List of Protocol Decoders]
59 to see which protocol decoders have been created already.
60
61 === Using Decoders on Analog Signals
62
63 If you're capturing data using an oscilloscope or import analog signal data from a file,
64 you'll quickly notice that protocol decoders don't give you the option to select analog
65 channels as inputs. That is because as of now, decoders only work on logic signals. You
66 can however convert analog signals into logic signals by choosing a conversion setting
67 from the signal setting popup.
68
69 image::pv_conversion_a2l.png[]
70
71 Here, A1 has been converted using a threshold (with default settings) and A2 has been
72 converted using a Schmitt-Trigger emulation (also with default settings). Additionally,
73 the conversion threshold display mode has been set to _Background_ in the _Views_ settings
74 dialog. This way, you can tell how PulseView decided to change the logic signal level
75 as you can now visually understand where the ranges for high and low are placed.
76
77 Aside from the default conversion threshold(s), you can choose from a few common presets
78 or enter custom values as well. They take the form "0.0V" and "0.0V/0.0V", respectively.
79
80 === Troubleshooting
81
82 In case a protocol decoder doesn't provide the expected result, there are several things
83 you can check.
84
85 The first check you should perform is whether the time unit in the ruler
86 is given as "sa". This is short for "samples" and means that the device didn't provide
87 a sample rate and so PulseView has no way of showing a time scale in seconds or
88 fractions thereof. While some decoders can run without timing information, or only
89 optionally make use of the time scale, others may not be able to interpret the
90 input data since timing information is an essential part of the very protocol.
91
92 Another issue to remain aware of is that decoders need enough samples per protocol step
93 to reliably interpret the information. In typical cases the minimum sample rate should
94 be four to five times the rate of the fastest activity in the protocol.
95
96 If a protocol decoder runs but shows you annotations that don't seem to make any sense,
97 it's worth double-checking the decoder settings. One common source of error is the
98 baud rate. For example, the CAN protocol decoder doesn't know what baud rate
99 is used on the bus that you captured, so it could be that a different baud rate is used
100 than the one you set. Also, if this is still not the reason for the malfunction, it's
101 worth checking whether any of the signals have been captured inverted. Again using the
102 CAN bus as an example, the decoder will decode the signal just fine if it's inverted but
103 it'll show data even when the signal looks "idle".
104
105 When a protocol decoder stops execution because of an unmet constraint (required input
106 not connected, essential parameter not specified) or a bug in the decoder itself, you
107 will be presented a static red message in the protocol decoder's display area.
108 In that case, you check the log output in the settings menu. There you'll find the Python
109 error description which you can use to either adjust the configuration,
110 or debug the decoder (and let us know of the fix) or you can copy that information and
111 file a bug report so that we can fix it.
112
113 Further helpful knowledge and explanations on logic analyzers can be found in our
114 https://sigrok.org/wiki/FAQ#Where_can_I_learn_more_about_logic_analyzers.3F["Learn about logic analyzers" FAQ item].
115
116 === Exporting Annotations
117
118 If you want to postprocess annotations that were generated by a protocol decoder, you
119 can do so by right-clicking into the area of the decode signal (not on the signal label
120 on the left). You are shown several export methods to choose from, with the last one
121 being only available if the cursor is enabled.
122
123 After you chose a method that suits your needs, you are prompted for a file to export
124 the annotations to. The contents of the file very much depend on the option you chose
125 but also on the annotation export format string that you can define in the _Decoders_
126 menu of the settings dialog. If the default output isn't useful to you, you can
127 customize it there.
128
129 === Creating a Protocol Decoder
130
131 Protocol decoders are written in Python and can be created using nothing more than a
132 text editor. You, too, can write one!
133
134 To find out how to go about it, please see our https://sigrok.org/wiki/Protocol_decoder_HOWTO[Protocol Decoder How-To]
135 and the https://sigrok.org/wiki/Protocol_decoder_API[Protocol Decoder API Reference].
136
137 If you do write one, we'd appreciate if you'd contribute to our project so that everyone
138 can benefit from your work.
139
Binary diff not shown
0 == Data Import/Export
1
2 In order to facilitate versatile use of the sigrok suite, libsigrok allows users to import
3 and export data from files in various formats - some of them as generic as possible, others
4 very specific. For a list and details, make sure to check https://sigrok.org/wiki/Input_output_formats[the wiki].
5
6 === Import
7
8 The first step to importing data from a file is to know what format the data in the file is
9 encoded in. There are common, not-so-common and outright exotic ways to represent data and sigrok
10 tries to suit as many needs as it can. To see which formats your version of PulseView supports,
11 just click on the small arrow next to the _Open_ button:
12
13 image::pv_import.png[]
14
15 After choosing the format that you want to use, PulseView will ask for the file name to open.
16 Once you picked the file, you may be asked to specify the details of the format, if the input
17 module requires them.
18
19 For example, the VCD import will ask you for these:
20
21 * Compress idle periods: Compress idle periods longer than the specified value (default 0)
22 * Downsampling factor: Downsample, i.e. divide the samplerate by the specified factor (default 1)
23 * Number of logic channels: The number of (logic) channels in the data (default 0)
24 * Skip samples until timestamp: Skip samples until the specified timestamp; < 0: Skip until first timestamp listed; 0: Don't skip (default -1)
25
26 The detailed description of each item can also be seen when clicking on the help icon on the right
27 or hovering your mouse over it. A click on _OK_ then loads the data from the selected file and you
28 can work with it.
29
30 === Export
31
32 Export works just the same as the import: clicking on the small arrow next to the _Save_ button
33 brings up the export menu. Simply choose the format you want to use and proceed.
0 [[installation,Installation]]
1 == Installation
2
3 PulseView can be run on Linux, Windows, Mac OS X or Android. For some platforms, we provide binary
4 packages, for others we provide installers and for others we provide AppImage containers that
5 you can run without the need to install anything. Check the https://sigrok.org/wiki/Downloads[sigrok download page]
6 to see which option is available for your platform.
7
8 === Linux
9
10 On Linux, the usual way to install PulseView is to install the packages provided by your distro's
11 package manager. However, sometimes only outdated packages are made available to you. In that case,
12 you have two options:
13
14 . https://sigrok.org/wiki/Downloads[Download] and use the AppImage which contains all required files and needs no installation:
15 +
16 --
17 [listing, subs="normal"]
18 chmod u+x PulseView-NIGHTLY-x86_64.AppImage
19 ./PulseView-NIGHTLY-x86_64.AppImage
20
21 Please be aware, however, that the AppImages are built every night, so they always contain
22 the latest development changes. While we do try to keep the code base in a working state, it is sometimes
23 unavoidable to introduce bugs that show up in the nightly builds. If you encounter something that is
24 odd to you, please download and install the latest nightly and check if the issue still exists. If it
25 does, feel free to https://sigrok.org/bugzilla/[file a bug].
26
27 No system files are changed, so if you decide that you no longer want to use PulseView, simply
28 delete the AppImage. If you also want the stored settings gone, delete ~/.config/sigrok as well.
29 --
30
31 . Uninstall any sigrok packages from your package manager and build PulseView from source:
32 +
33 --
34 [listing, subs="normal"]
35 _[install dependencies https://sigrok.org/wiki/Linux#Building[as listed on the wiki]]_
36 mkdir ~/sr
37 cd ~/sr
38 wget 'https://sigrok.org/gitweb/?p=sigrok-util.git;a=blob_plain;f=cross-compile/linux/sigrok-cross-linux' -O sigrok-cross-linux
39 chmod u+x sigrok-cross-linux
40 ./sigrok-cross-linux
41 export LD_LIBRARY_PATH=~/sr/lib
42 ~/sr/bin/pulseview
43
44 No system files are changed, so if you decide that you no longer want to use PulseView, simply
45 delete the ~/sr directory. If you also want the stored settings gone, delete ~/.config/sigrok
46 as well.
47 --
48
49 [WARNING]
50 --
51 If you don't install the PulseView distro packages (as is the case when using the AppImage or building
52 from source), PulseView will not be able to access USB and serial port devices unless it's run as root.
53 Since programs shouldn't be run as root unless absolutely necessary, we provide udev configuration files
54 that allows PulseView access to those devices without being root.
55
56 Here's how you install them:
57 [listing, subs="normal"]
58 sudo bash
59 cd /etc/udev/rules.d/
60 wget 'https://sigrok.org/gitweb/?p=libsigrok.git;a=blob_plain;f=contrib/60-libsigrok.rules' -O 60-libsigrok.rules
61 wget 'https://sigrok.org/gitweb/?p=libsigrok.git;a=blob_plain;f=contrib/61-libsigrok-plugdev.rules' -O 61-libsigrok-plugdev.rules
62 wget 'https://sigrok.org/gitweb/?p=libsigrok.git;a=blob_plain;f=contrib/61-libsigrok-uaccess.rules' -O 61-libsigrok-uaccess.rules
63 sudo udevadm control --reload-rules
64 --
65
66 === Windows
67
68 We offer installers for PulseView that contain everything you need to get started. Simply download
69 them from the https://sigrok.org/wiki/Downloads[sigrok download page] and run them as any other Windows
70 installer.
71 Please be aware, however, that the Windows installers are built every night, so they always contain
72 the latest development changes. While we do try to keep the code base in a working state, it is sometimes
73 unavoidable to introduce bugs that show up in the nightly builds. If you encounter something that is
74 odd to you, please download and install the latest nightly and check if the issue still exists. If it
75 does, feel free to https://sigrok.org/bugzilla/[file a bug].
76
77 After installation, you will find a program called Zadig in the start menu. By default, certain devices
78 recognized by Windows will have drivers installed for them that PulseView cannot use. The purpose of
79 Zadig is to let you change the driver Windows uses for a particular device - for most devices you'll need
80 to choose WinUSB to use them with PulseView or the original proprietary Windows driver to use it with whatever
81 other software you access the device with. More details are available https://sigrok.org/wiki/Windows[in the wiki].
82
83 In case your device doesn't show up in PulseView and you can't find it with a scan either (see next
84 chapter), check with Zadig whether the correct driver is assigned for the device.
85
86 === Mac OS X
87
88 We offer DMG installers for PulseView that contain everything you need to get started. Simply download
89 them from the https://sigrok.org/wiki/Downloads[sigrok download page] and run them.
90
91 Please be aware, however, that the DMG installers are built every night, so they always contain
92 the latest development changes. While we do try to keep the code base in a working state, it is sometimes
93 unavoidable to introduce bugs that show up in the nightly builds. If you encounter something that is
94 odd to you, please download and install the latest nightly and check if the issue still exists. If it
95 does, feel free to https://sigrok.org/bugzilla/[file a bug].
96
97 No system files are changed, so if you decide that you no longer want to use PulseView, simply
98 delete the DMG file. If you also want the stored settings gone, delete
99 ~/Library/Preferences/pulseview.plist as well.
0 == License
1
2 This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
0 PulseView User Manual
1 =====================
2 0.4.1, dated 2018-10-29
3 :doctype: book
4 :imagesdir: ./images
5 :sectnums:
6 :toc:
7 :toclevels: 2
8 :icons: font
9 :figure-caption: Image
10
11 ifdef::ebook-format[:leveloffset: -1]
12
13 include::license.txt[]
14
15 include::overview.txt[]
16
17 include::installation.txt[]
18
19 include::acquisition.txt[]
20
21 include::analysis.txt[]
22
23 include::decoders.txt[]
24
25 include::import_export.txt[]
26
27 include::cli.txt[]
0 [[overview,Overview]]
1 == Overview
2
3 PulseView is a graphical frontend for the libsigrok and libsigrokdecode libraries, permitting
4 access to a wide range of devices and protocol decoders to let you record, analyze, process
5 and export analog and logic data. It is part of the sigrok suite, just like the libraries that
6 it makes use of.
7
8 The sigrok suite needs some kind of hardware to interface to the signals you want to examine.
9 We found that most people are currently using logic analyzers based on the http://www.cypress.com/products/ez-usb-fx2lp[Cypress FX2 microcontroller].
10 With http://sigrok.org/wiki/Fx2lafw[fx2lafw], sigrok's open source runtime firmware, any device
11 containing an FX2 can become a powerful streaming logic analyzer.
12 A variety of compatible low cost chinese made logic analyzer products are available for as little
13 as $5. These can easily be found by searching for _24MHz Logic Analyzer_. There are also barebone
14 Cypress FX2 boards such as the Lcsoft Mini Board, which can usually be found by searching for
15 _Cypress FX2 Board_ or similar.
16
17 In addition, a good set of https://sigrok.org/wiki/Probe_comparison[quality probe hooks] is recommended.
18
19 Aside from FX2-based logic analyzers, sigrok also supports FX2-based oscilloscopes such as the
20 https://sigrok.org/wiki/Hantek_6022BE[Hantek 6022BE], non-FX2 devices like the
21 https://sigrok.org/wiki/Openbench_Logic_Sniffer[Openbench Logic Sniffer] or devices that make use
22 of the SCPI protocol, as all reasonably modern oscilloscopes do (Rigol DS1054z, LeCroy WaveRunner,
23 Yokogawa DLM and similar).
24
25 Please be aware however, that PulseView currently only supports devices that can either work as an
26 https://sigrok.org/wiki/Supported_hardware#Oscilloscopes[oscilloscope],
27 a https://sigrok.org/wiki/Supported_hardware#Logic_analyzers[logic analyzer] or
28 a https://sigrok.org/wiki/Supported_hardware#Mixed-signal_devices[mixed-signal device]. This
29 means that multimeters in particular are currently only usable with either https://sigrok.org/wiki/Sigrok-cli[sigrok-cli]
30 or https://sigrok.org/wiki/Sigrok-meter[sigrok-meter].
31
32 image::pv_after_startup.png[]
33
34 The PulseView user interface is geared towards navigation and analysis of captured waveforms, so
35 the most space is by default used up by the main trace view. From here, you can access the most
36 often used features.
37
38 Before we dive deeper into how to accomplish things, let's make PulseView available on your
39 system first.
00 <RCC>
1 <qresource prefix="/" >
1 <qresource prefix="/">
22 <file>icons/add-decoder.svg</file>
33 <file>icons/application-exit.png</file>
44 <file>icons/channels.svg</file>
88 <file>icons/document-new.png</file>
99 <file>icons/document-open.png</file>
1010 <file>icons/document-save-as.png</file>
11 <file>icons/help-browser.png</file>
1112 <file>icons/information.svg</file>
13 <file>icons/media-playback-pause.png</file>
14 <file>icons/media-playback-start.png</file>
1215 <file>icons/menu.svg</file>
1316 <file>icons/preferences-system.png</file>
17 <file>icons/settings-general.png</file>
1418 <file>icons/settings-views.svg</file>
1519 <file>icons/pulseview.png</file>
1620 <file>icons/pulseview.svg</file>
2933 <file>icons/trigger-marker-rising.svg</file>
3034 <file>icons/trigger-none.svg</file>
3135 <file>icons/trigger-rising.svg</file>
36 <file>icons/view-displaymode-last_complete_segment.svg</file>
37 <file>icons/view-displaymode-last_segment.svg</file>
38 <file>icons/view-displaymode-single_segment.svg</file>
3239 <file>icons/window-new.png</file>
3340 <file>icons/zoom-fit-best.png</file>
3441 <file>icons/zoom-in.png</file>
35 <file>icons/zoom-original.png</file>
3642 <file>icons/zoom-out.png</file>
3743 </qresource>
44
45 <qresource prefix="/">
46 <!-- QDarkStyleSheet -->
47 <file>themes/qdarkstyle/style.qss</file>
48 <file>themes/qdarkstyle/rc/up_arrow_disabled.png</file>
49 <file>themes/qdarkstyle/rc/Hmovetoolbar.png</file>
50 <file>themes/qdarkstyle/rc/stylesheet-branch-end.png</file>
51 <file>themes/qdarkstyle/rc/branch_closed-on.png</file>
52 <file>themes/qdarkstyle/rc/stylesheet-vline.png</file>
53 <file>themes/qdarkstyle/rc/branch_closed.png</file>
54 <file>themes/qdarkstyle/rc/branch_open-on.png</file>
55 <file>themes/qdarkstyle/rc/transparent.png</file>
56 <file>themes/qdarkstyle/rc/right_arrow_disabled.png</file>
57 <file>themes/qdarkstyle/rc/sizegrip.png</file>
58 <file>themes/qdarkstyle/rc/close.png</file>
59 <file>themes/qdarkstyle/rc/close-hover.png</file>
60 <file>themes/qdarkstyle/rc/close-pressed.png</file>
61 <file>themes/qdarkstyle/rc/down_arrow.png</file>
62 <file>themes/qdarkstyle/rc/Vmovetoolbar.png</file>
63 <file>themes/qdarkstyle/rc/left_arrow.png</file>
64 <file>themes/qdarkstyle/rc/stylesheet-branch-more.png</file>
65 <file>themes/qdarkstyle/rc/up_arrow.png</file>
66 <file>themes/qdarkstyle/rc/right_arrow.png</file>
67 <file>themes/qdarkstyle/rc/left_arrow_disabled.png</file>
68 <file>themes/qdarkstyle/rc/Hsepartoolbar.png</file>
69 <file>themes/qdarkstyle/rc/branch_open.png</file>
70 <file>themes/qdarkstyle/rc/Vsepartoolbar.png</file>
71 <file>themes/qdarkstyle/rc/down_arrow_disabled.png</file>
72 <file>themes/qdarkstyle/rc/undock.png</file>
73 <file>themes/qdarkstyle/rc/checkbox_checked_disabled.png</file>
74 <file>themes/qdarkstyle/rc/checkbox_checked_focus.png</file>
75 <file>themes/qdarkstyle/rc/checkbox_checked.png</file>
76 <file>themes/qdarkstyle/rc/checkbox_indeterminate.png</file>
77 <file>themes/qdarkstyle/rc/checkbox_indeterminate_focus.png</file>
78 <file>themes/qdarkstyle/rc/checkbox_unchecked_disabled.png</file>
79 <file>themes/qdarkstyle/rc/checkbox_unchecked_focus.png</file>
80 <file>themes/qdarkstyle/rc/checkbox_unchecked.png</file>
81 <file>themes/qdarkstyle/rc/radio_checked_disabled.png</file>
82 <file>themes/qdarkstyle/rc/radio_checked_focus.png</file>
83 <file>themes/qdarkstyle/rc/radio_checked.png</file>
84 <file>themes/qdarkstyle/rc/radio_unchecked_disabled.png</file>
85 <file>themes/qdarkstyle/rc/radio_unchecked_focus.png</file>
86 <file>themes/qdarkstyle/rc/radio_unchecked.png</file>
87 </qresource>
88
89 <qresource prefix="/">
90 <!-- DarkStyle -->
91 <file>themes/darkstyle/darkstyle.qss</file>
92 <file>themes/darkstyle/icon_close.png</file>
93 <file>themes/darkstyle/icon_restore.png</file>
94 <file>themes/darkstyle/icon_undock.png</file>
95 <file>themes/darkstyle/icon_branch_closed.png</file>
96 <file>themes/darkstyle/icon_branch_end.png</file>
97 <file>themes/darkstyle/icon_branch_more.png</file>
98 <file>themes/darkstyle/icon_branch_open.png</file>
99 <file>themes/darkstyle/icon_vline.png</file>
100 <file>themes/darkstyle/icon_checkbox_checked.png</file>
101 <file>themes/darkstyle/icon_checkbox_indeterminate.png</file>
102 <file>themes/darkstyle/icon_checkbox_unchecked.png</file>
103 <file>themes/darkstyle/icon_checkbox_checked_pressed.png</file>
104 <file>themes/darkstyle/icon_checkbox_indeterminate_pressed.png</file>
105 <file>themes/darkstyle/icon_checkbox_unchecked_pressed.png</file>
106 <file>themes/darkstyle/icon_checkbox_checked_disabled.png</file>
107 <file>themes/darkstyle/icon_checkbox_indeterminate_disabled.png</file>
108 <file>themes/darkstyle/icon_checkbox_unchecked_disabled.png</file>
109 <file>themes/darkstyle/icon_radiobutton_checked.png</file>
110 <file>themes/darkstyle/icon_radiobutton_unchecked.png</file>
111 <file>themes/darkstyle/icon_radiobutton_checked_pressed.png</file>
112 <file>themes/darkstyle/icon_radiobutton_unchecked_pressed.png</file>
113 <file>themes/darkstyle/icon_radiobutton_checked_disabled.png</file>
114 <file>themes/darkstyle/icon_radiobutton_unchecked_disabled.png</file>
115 </qresource>
38116 </RCC>
2020 #include "config.h"
2121
2222 #include <iostream>
23
24 using std::cerr;
23 #include <typeinfo>
24
25 #include <QDebug>
26
27 #include <boost/version.hpp>
28
29 #ifdef ENABLE_STACKTRACE
30 #include <boost/stacktrace.hpp>
31 #endif
32
33 #ifdef ENABLE_DECODE
34 #include <libsigrokdecode/libsigrokdecode.h>
35 #endif
36
37 using std::cout;
2538 using std::endl;
2639 using std::exception;
40 using std::shared_ptr;
41
42 #ifdef ENABLE_DECODE
43 static gint sort_pds(gconstpointer a, gconstpointer b)
44 {
45 const struct srd_decoder *sda, *sdb;
46
47 sda = (const struct srd_decoder *)a;
48 sdb = (const struct srd_decoder *)b;
49 return strcmp(sda->id, sdb->id);
50 }
51 #endif
2752
2853 Application::Application(int &argc, char* argv[]) :
2954 QApplication(argc, argv)
3459 setOrganizationDomain("sigrok.org");
3560 }
3661
62 void Application::collect_version_info(shared_ptr<sigrok::Context> context)
63 {
64 // Library versions and features
65 version_info_.emplace_back(applicationName(), applicationVersion());
66 version_info_.emplace_back("Qt", qVersion());
67 version_info_.emplace_back("glibmm", PV_GLIBMM_VERSION);
68 version_info_.emplace_back("Boost", BOOST_LIB_VERSION);
69
70 version_info_.emplace_back("libsigrok", QString("%1/%2 (rt: %3/%4)")
71 .arg(SR_PACKAGE_VERSION_STRING, SR_LIB_VERSION_STRING,
72 sr_package_version_string_get(), sr_lib_version_string_get()));
73
74 GSList *l_orig = sr_buildinfo_libs_get();
75 for (GSList *l = l_orig; l; l = l->next) {
76 GSList *m = (GSList *)l->data;
77 const char *lib = (const char *)m->data;
78 const char *version = (const char *)m->next->data;
79 version_info_.emplace_back(QString(" - %1").arg(QString(lib)), QString(version));
80 g_slist_free_full(m, g_free);
81 }
82 g_slist_free(l_orig);
83
84 char *host = sr_buildinfo_host_get();
85 version_info_.emplace_back(" - Host", QString(host));
86 g_free(host);
87
88 char *scpi_backends = sr_buildinfo_scpi_backends_get();
89 version_info_.emplace_back(" - SCPI backends", QString(scpi_backends));
90 g_free(scpi_backends);
91
92 #ifdef ENABLE_DECODE
93 struct srd_decoder *dec;
94
95 version_info_.emplace_back("libsigrokdecode", QString("%1/%2 (rt: %3/%4)")
96 .arg(SRD_PACKAGE_VERSION_STRING, SRD_LIB_VERSION_STRING,
97 srd_package_version_string_get(), srd_lib_version_string_get()));
98
99 l_orig = srd_buildinfo_libs_get();
100 for (GSList *l = l_orig; l; l = l->next) {
101 GSList *m = (GSList *)l->data;
102 const char *lib = (const char *)m->data;
103 const char *version = (const char *)m->next->data;
104 version_info_.emplace_back(QString(" - %1").arg(QString(lib)), QString(version));
105 g_slist_free_full(m, g_free);
106 }
107 g_slist_free(l_orig);
108
109 host = srd_buildinfo_host_get();
110 version_info_.emplace_back(" - Host", QString(host));
111 g_free(host);
112 #endif
113
114 // Firmware paths
115 l_orig = sr_resourcepaths_get(SR_RESOURCE_FIRMWARE);
116 for (GSList *l = l_orig; l; l = l->next)
117 fw_path_list_.emplace_back((char*)l->data);
118 g_slist_free_full(l_orig, g_free);
119
120 // PD paths
121 #ifdef ENABLE_DECODE
122 l_orig = srd_searchpaths_get();
123 for (GSList *l = l_orig; l; l = l->next)
124 pd_path_list_.emplace_back((char*)l->data);
125 g_slist_free_full(l_orig, g_free);
126 #endif
127
128 // Device drivers
129 for (auto& entry : context->drivers())
130 driver_list_.emplace_back(QString::fromUtf8(entry.first.c_str()),
131 QString::fromUtf8(entry.second->long_name().c_str()));
132
133 // Input formats
134 for (auto& entry : context->input_formats())
135 input_format_list_.emplace_back(QString::fromUtf8(entry.first.c_str()),
136 QString::fromUtf8(entry.second->description().c_str()));
137
138 // Output formats
139 for (auto& entry : context->output_formats())
140 output_format_list_.emplace_back(QString::fromUtf8(entry.first.c_str()),
141 QString::fromUtf8(entry.second->description().c_str()));
142
143 // Protocol decoders
144 #ifdef ENABLE_DECODE
145 GSList *sl = g_slist_copy((GSList *)srd_decoder_list());
146 sl = g_slist_sort(sl, sort_pds);
147 for (const GSList *l = sl; l; l = l->next) {
148 dec = (struct srd_decoder *)l->data;
149 pd_list_.emplace_back(QString::fromUtf8(dec->id),
150 QString::fromUtf8(dec->longname));
151 }
152 g_slist_free(sl);
153 #endif
154 }
155
156 void Application::print_version_info()
157 {
158 cout << PV_TITLE << " " << PV_VERSION_STRING << endl;
159
160 cout << endl << "Libraries and features:" << endl;
161 for (pair<QString, QString>& entry : version_info_)
162 cout << " " << entry.first.toStdString() << " " << entry.second.toStdString() << endl;
163
164 cout << endl << "Firmware search paths:" << endl;
165 for (QString& entry : fw_path_list_)
166 cout << " " << entry.toStdString() << endl;
167
168 cout << endl << "Protocol decoder search paths:" << endl;
169 for (QString& entry : pd_path_list_)
170 cout << " " << entry.toStdString() << endl;
171
172 cout << endl << "Supported hardware drivers:" << endl;
173 for (pair<QString, QString>& entry : driver_list_)
174 cout << " " << entry.first.leftJustified(21, ' ').toStdString() <<
175 entry.second.toStdString() << endl;
176
177 cout << endl << "Supported input formats:" << endl;
178 for (pair<QString, QString>& entry : input_format_list_)
179 cout << " " << entry.first.leftJustified(21, ' ').toStdString() <<
180 entry.second.toStdString() << endl;
181
182 cout << endl << "Supported output formats:" << endl;
183 for (pair<QString, QString>& entry : output_format_list_)
184 cout << " " << entry.first.leftJustified(21, ' ').toStdString() <<
185 entry.second.toStdString() << endl;
186
187 #ifdef ENABLE_DECODE
188 cout << endl << "Supported protocol decoders:" << endl;
189 for (pair<QString, QString>& entry : pd_list_)
190 cout << " " << entry.first.leftJustified(21, ' ').toStdString() <<
191 entry.second.toStdString() << endl;
192 #endif
193 }
194
195 vector< pair<QString, QString> > Application::get_version_info() const
196 {
197 return version_info_;
198 }
199
200 vector<QString> Application::get_fw_path_list() const
201 {
202 return fw_path_list_;
203 }
204
205 vector<QString> Application::get_pd_path_list() const
206 {
207 return pd_path_list_;
208 }
209
210 vector< pair<QString, QString> > Application::get_driver_list() const
211 {
212 return driver_list_;
213 }
214
215 vector< pair<QString, QString> > Application::get_input_format_list() const
216 {
217 return input_format_list_;
218 }
219
220 vector< pair<QString, QString> > Application::get_output_format_list() const
221 {
222 return output_format_list_;
223 }
224
225 vector< pair<QString, QString> > Application::get_pd_list() const
226 {
227 return pd_list_;
228 }
229
37230 bool Application::notify(QObject *receiver, QEvent *event)
38231 {
39232 try {
40233 return QApplication::notify(receiver, event);
41234 } catch (exception& e) {
42 cerr << "Caught exception: " << e.what() << endl;
235 qDebug().nospace() << "Caught exception of type " << \
236 typeid(e).name() << " (" << e.what() << ")";
237 #ifdef ENABLE_STACKTRACE
238 throw e;
239 #else
43240 exit(1);
241 #endif
44242 return false;
45243 }
46244 }
1919 #ifndef PULSEVIEW_PV_APPLICATION_HPP
2020 #define PULSEVIEW_PV_APPLICATION_HPP
2121
22 #include <vector>
23
2224 #include <QApplication>
25
26 #include <libsigrokcxx/libsigrokcxx.hpp>
27
28 using std::shared_ptr;
29 using std::pair;
30 using std::vector;
2331
2432 class Application : public QApplication
2533 {
34 Q_OBJECT
35
2636 public:
2737 Application(int &argc, char* argv[]);
38
39 void collect_version_info(shared_ptr<sigrok::Context> context);
40 void print_version_info();
41
42 vector< pair<QString, QString> > get_version_info() const;
43 vector<QString> get_fw_path_list() const;
44 vector<QString> get_pd_path_list() const;
45 vector< pair<QString, QString> > get_driver_list() const;
46 vector< pair<QString, QString> > get_input_format_list() const;
47 vector< pair<QString, QString> > get_output_format_list() const;
48 vector< pair<QString, QString> > get_pd_list() const;
49
2850 private:
2951 bool notify(QObject *receiver, QEvent *event);
52
53 vector< pair<QString, QString> > version_info_;
54 vector<QString> fw_path_list_;
55 vector<QString> pd_path_list_;
56 vector< pair<QString, QString> > driver_list_;
57 vector< pair<QString, QString> > input_format_list_;
58 vector< pair<QString, QString> > output_format_list_;
59 vector< pair<QString, QString> > pd_list_;
3060 };
3161
3262 #endif // PULSEVIEW_PV_APPLICATION_HPP
1919 #include <cassert>
2020
2121 #include <QFormLayout>
22 #include <QHBoxLayout>
23 #include <QIcon>
2224 #include <QLabel>
25 #include <QPushButton>
2326
2427 #include <pv/prop/property.hpp>
2528
4548 }
4649 }
4750
48 void Binding::add_properties_to_form(QFormLayout *layout,
49 bool auto_commit) const
51 void Binding::add_properties_to_form(QFormLayout *layout, bool auto_commit)
5052 {
5153 assert(layout);
54
55 help_labels_.clear();
5256
5357 for (shared_ptr<pv::prop::Property> p : properties_) {
5458 assert(p);
5559
56 QWidget *const widget = p->get_widget(layout->parentWidget(),
57 auto_commit);
60 QWidget *widget;
61 QLabel *help_lbl = nullptr;
62
63 if (p->desc().isEmpty()) {
64 widget = p->get_widget(layout->parentWidget(), auto_commit);
65 } else {
66 QPushButton *help_btn = new QPushButton();
67 help_btn->setFlat(true);
68 help_btn->setIcon(QIcon(":/icons/help-browser.png"));
69 help_btn->setToolTip(p->desc());
70 connect(help_btn, SIGNAL(clicked(bool)),
71 this, SLOT(on_help_clicked()));
72
73 QHBoxLayout *layout = new QHBoxLayout();
74 layout->setContentsMargins(0, 0, 0, 0);
75 layout->addWidget(p->get_widget(layout->parentWidget(), auto_commit));
76 layout->addWidget(help_btn, 0, Qt::AlignRight);
77
78 widget = new QWidget();
79 widget->setLayout(layout);
80
81 help_lbl = new QLabel(p->desc());
82 help_lbl->setVisible(false);
83 help_lbl->setWordWrap(true);
84 help_labels_[help_btn] = help_lbl;
85 }
86
5887 if (p->labeled_widget()) {
5988 layout->addRow(widget);
6089 } else {
6190 auto *lbl = new QLabel(p->name());
62 lbl->setToolTip(p->desc());
91 lbl->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
6392 layout->addRow(lbl, widget);
6493 }
94
95 if (help_lbl)
96 layout->addRow(help_lbl);
6597 }
6698 }
6799
68 QWidget* Binding::get_property_form(QWidget *parent,
69 bool auto_commit) const
100 QWidget* Binding::get_property_form(QWidget *parent, bool auto_commit)
70101 {
71102 QWidget *const form = new QWidget(parent);
72103 QFormLayout *const layout = new QFormLayout(form);
73104 form->setLayout(layout);
74105 add_properties_to_form(layout, auto_commit);
75106 return form;
107 }
108
109 void Binding::update_property_widgets()
110 {
111 for (shared_ptr<pv::prop::Property> p : properties_) {
112 assert(p);
113 p->update_widget();
114 }
76115 }
77116
78117 QString Binding::print_gvariant(Glib::VariantBase gvar)
83122 s = QString::fromStdString("(null)");
84123 else if (gvar.is_of_type(Glib::VariantType("s")))
85124 s = QString::fromStdString(
86 Glib::VariantBase::cast_dynamic<Glib::Variant<string>>(
87 gvar).get());
125 Glib::VariantBase::cast_dynamic<Glib::Variant<string>>(gvar).get());
88126 else
89127 s = QString::fromStdString(gvar.print());
90128
91129 return s;
92130 }
93131
132 void Binding::on_help_clicked()
133 {
134 QPushButton *btn = qobject_cast<QPushButton*>(QObject::sender());
135 assert(btn);
136
137 QLabel *lbl = help_labels_.at(btn);
138 lbl->setVisible(!lbl->isVisible());
139 }
140
94141 } // namespace binding
95142 } // namespace pv
2525 #include <glibmm.h>
2626 G_GNUC_END_IGNORE_DEPRECATIONS
2727
28 #include <map>
2829 #include <memory>
2930 #include <vector>
3031
32 #include <QObject>
3133 #include <QString>
3234
35 using std::map;
3336 using std::shared_ptr;
3437 using std::vector;
3538
3639 class QFormLayout;
40 class QLabel;
3741 class QWidget;
3842
3943 namespace pv {
4448
4549 namespace binding {
4650
47 class Binding
51 class Binding: public QObject
4852 {
53 Q_OBJECT
54
4955 public:
5056 const vector< shared_ptr<prop::Property> >& properties();
5157
5258 void commit();
5359
54 void add_properties_to_form(QFormLayout *layout,
55 bool auto_commit = false) const;
60 void add_properties_to_form(QFormLayout *layout, bool auto_commit = false);
5661
57 QWidget* get_property_form(QWidget *parent,
58 bool auto_commit = false) const;
62 QWidget* get_property_form(QWidget *parent, bool auto_commit = false);
63
64 void update_property_widgets();
5965
6066 static QString print_gvariant(Glib::VariantBase gvar);
6167
68 protected Q_SLOTS:
69 void on_help_clicked();
70
6271 protected:
6372 vector< shared_ptr<prop::Property> > properties_;
73 map<QWidget*, QLabel*> help_labels_;
6474 };
6575
6676 } // namespace binding
2323 #include <boost/none_t.hpp>
2424
2525 #include <pv/data/decode/decoder.hpp>
26 #include <pv/data/decoderstack.hpp>
26 #include <pv/data/decodesignal.hpp>
2727 #include <pv/prop/double.hpp>
2828 #include <pv/prop/enum.hpp>
2929 #include <pv/prop/int.hpp>
3030 #include <pv/prop/string.hpp>
3131
3232 using boost::none;
33 using std::make_pair;
3433 using std::map;
3534 using std::pair;
3635 using std::shared_ptr;
4746 namespace binding {
4847
4948 Decoder::Decoder(
50 shared_ptr<pv::data::DecoderStack> decoder_stack,
49 shared_ptr<pv::data::DecodeSignal> decode_signal,
5150 shared_ptr<data::decode::Decoder> decoder) :
52 decoder_stack_(decoder_stack),
51 decode_signal_(decode_signal),
5352 decoder_(decoder)
5453 {
5554 assert(decoder_);
9695 vector< pair<Glib::VariantBase, QString> > values;
9796 for (GSList *l = option->values; l; l = l->next) {
9897 Glib::VariantBase var = Glib::VariantBase((GVariant*)l->data, true);
99 values.push_back(make_pair(var, print_gvariant(var)));
98 values.emplace_back(var, print_gvariant(var));
10099 }
101100
102101 return shared_ptr<Property>(new Enum(name, desc, values, getter, setter));
136135 assert(decoder_);
137136 decoder_->set_option(id, value.gobj());
138137
139 assert(decoder_stack_);
140 decoder_stack_->begin_decode();
138 assert(decode_signal_);
139 decode_signal_->begin_decode();
141140 }
142141
143142 } // namespace binding
3030 namespace pv {
3131
3232 namespace data {
33 class DecoderStack;
33 class DecodeSignal;
3434 namespace decode {
3535 class Decoder;
3636 }
4141 class Decoder : public Binding
4242 {
4343 public:
44 Decoder(shared_ptr<pv::data::DecoderStack> decoder_stack,
44 Decoder(shared_ptr<pv::data::DecodeSignal> decode_signal,
4545 shared_ptr<pv::data::decode::Decoder> decoder);
4646
4747 private:
5454 void setter(const char *id, Glib::VariantBase value);
5555
5656 private:
57 shared_ptr<pv::data::DecoderStack> decoder_stack_;
57 shared_ptr<pv::data::DecodeSignal> decode_signal_;
5858 shared_ptr<pv::data::decode::Decoder> decoder_;
5959 };
6060
3131 using boost::optional;
3232
3333 using std::function;
34 using std::make_pair;
3534 using std::pair;
3635 using std::set;
3736 using std::shared_ptr;
6867 string name_str;
6968 try {
7069 name_str = key->description();
71 } catch (Error e) {
70 } catch (Error& e) {
7271 name_str = key->name();
7372 }
7473
9796 case SR_CONF_TRIGGER_SLOPE:
9897 case SR_CONF_COUPLING:
9998 case SR_CONF_CLOCK_EDGE:
99 case SR_CONF_DATA_SOURCE:
100 case SR_CONF_EXTERNAL_CLOCK_SOURCE:
100101 bind_enum(name, "", key, capabilities, get, set);
101102 break;
102103
104105 case SR_CONF_EXTERNAL_CLOCK:
105106 case SR_CONF_RLE:
106107 case SR_CONF_POWER_OFF:
108 case SR_CONF_AVERAGING:
107109 bind_bool(name, "", get, set);
108110 break;
109111
126128 bind_int(name, "", "", pair<int64_t, int64_t>(1, 500), get, set);
127129 break;
128130
131 case SR_CONF_AVG_SAMPLES:
132 if (capabilities.count(Capability::LIST))
133 bind_enum(name, "", key, capabilities, get, set, print_averages);
134 else
135 bind_int(name, "", "", pair<int64_t, int64_t>(0, INT32_MAX), get, set);
136 break;
137
129138 default:
130139 break;
131140 }
156165
157166 vector< pair<Glib::VariantBase, QString> > values;
158167 while ((iter.next_value(gvar)))
159 values.push_back(make_pair(gvar, printer(gvar)));
168 values.emplace_back(gvar, printer(gvar));
160169
161170 properties_.push_back(shared_ptr<Property>(new Enum(name, desc, values,
162171 getter, setter)));
205214 return QString("%1x").arg(factor);
206215 }
207216
217 QString Device::print_averages(Glib::VariantBase gvar)
218 {
219 uint64_t avg;
220 avg = g_variant_get_uint64(gvar.gobj());
221 return QString("%1").arg(avg);
222 }
223
208224 } // namespace binding
209225 } // namespace pv
1919 #ifndef PULSEVIEW_PV_BINDING_DEVICE_HPP
2020 #define PULSEVIEW_PV_BINDING_DEVICE_HPP
2121
22 #include <functional>
23
2224 #include <boost/optional.hpp>
2325
2426 #include <QObject>
3941
4042 namespace binding {
4143
42 class Device : public QObject, public Binding
44 class Device : public Binding
4345 {
4446 Q_OBJECT
4547
6567 static QString print_vdiv(Glib::VariantBase gvar);
6668 static QString print_voltage_threshold(Glib::VariantBase gvar);
6769 static QString print_probe_factor(Glib::VariantBase gvar);
70 static QString print_averages(Glib::VariantBase gvar);
6871
6972 protected:
7073 shared_ptr<sigrok::Configurable> configurable_;
3434
3535 using boost::none;
3636
37 using std::make_pair;
3837 using std::map;
3938 using std::pair;
4039 using std::shared_ptr;
5958 InputOutput::InputOutput(
6059 const map<string, shared_ptr<Option>> &options)
6160 {
62 for (pair<string, shared_ptr<Option>> o : options) {
61 for (const pair<string, shared_ptr<Option>>& o : options) {
6362 const shared_ptr<Option> &opt = o.second;
6463 assert(opt);
6564
6665 const QString name = QString::fromStdString(opt->name());
6766 const QString desc = QString::fromStdString(opt->description());
67
6868 const VariantBase def_val = opt->default_value();
6969 const vector<VariantBase> values = opt->values();
7070
110110 {
111111 vector< pair<VariantBase, QString> > enum_vals;
112112 for (VariantBase var : values)
113 enum_vals.push_back(make_pair(var, print_gvariant(var)));
113 enum_vals.emplace_back(var, print_gvariant(var));
114114 return shared_ptr<Property>(new Enum(name, desc, enum_vals, getter, setter));
115115 }
116116
3636
3737 void Analog::push_segment(shared_ptr<AnalogSegment> &segment)
3838 {
39 segments_.push_front(segment);
39 segments_.push_back(segment);
4040 }
4141
4242 const deque< shared_ptr<AnalogSegment> >& Analog::analog_segments() const
5050 segments_.begin(), segments_.end());
5151 }
5252
53 uint32_t Analog::get_segment_count() const
54 {
55 return (uint32_t)segments_.size();
56 }
57
5358 void Analog::clear()
5459 {
5560 segments_.clear();
5762 samples_cleared();
5863 }
5964
65 double Analog::get_samplerate() const
66 {
67 if (segments_.empty())
68 return 1.0;
69
70 return segments_.front()->samplerate();
71 }
72
6073 uint64_t Analog::max_sample_count() const
6174 {
6275 uint64_t l = 0;
63 for (const shared_ptr<AnalogSegment> s : segments_) {
76 for (const shared_ptr<AnalogSegment>& s : segments_) {
6477 assert(s);
6578 l = max(l, s->get_sample_count());
6679 }
7386 samples_added(segment, start_sample, end_sample);
7487 }
7588
89 void Analog::notify_min_max_changed(float min, float max)
90 {
91 min_max_changed(min, max);
92 }
93
7694 } // namespace data
7795 } // namespace pv
4848
4949 vector< shared_ptr<Segment> > segments() const;
5050
51 uint32_t get_segment_count() const;
52
5153 void clear();
54
55 double get_samplerate() const;
5256
5357 uint64_t max_sample_count() const;
5458
5559 void notify_samples_added(QObject* segment, uint64_t start_sample,
5660 uint64_t end_sample);
5761
62 void notify_min_max_changed(float min, float max);
63
5864 Q_SIGNALS:
5965 void samples_cleared();
6066
6167 void samples_added(QObject* segment, uint64_t start_sample,
6268 uint64_t end_sample);
69
70 void min_max_changed(float min, float max);
6371
6472 private:
6573 deque< shared_ptr<AnalogSegment> > segments_;
4747 const float AnalogSegment::LogEnvelopeScaleFactor = logf(EnvelopeScaleFactor);
4848 const uint64_t AnalogSegment::EnvelopeDataUnit = 64 * 1024; // bytes
4949
50 AnalogSegment::AnalogSegment(Analog& owner, uint64_t samplerate) :
51 Segment(samplerate, sizeof(float)),
50 AnalogSegment::AnalogSegment(Analog& owner, uint32_t segment_id, uint64_t samplerate) :
51 Segment(segment_id, samplerate, sizeof(float)),
5252 owner_(owner),
5353 min_value_(0),
5454 max_value_(0)
7474 uint64_t prev_sample_count = sample_count_;
7575
7676 // Deinterleave the samples and add them
77 unique_ptr<float> deint_data(new float[sample_count]);
77 unique_ptr<float[]> deint_data(new float[sample_count]);
7878 float *deint_data_ptr = deint_data.get();
7979 for (uint32_t i = 0; i < sample_count; i++) {
8080 *deint_data_ptr = (float)(*data);
9595 prev_sample_count + 1);
9696 }
9797
98 const float* AnalogSegment::get_samples(
99 int64_t start_sample, int64_t end_sample) const
98 void AnalogSegment::get_samples(int64_t start_sample, int64_t end_sample,
99 float* dest) const
100100 {
101101 assert(start_sample >= 0);
102102 assert(start_sample < (int64_t)sample_count_);
103103 assert(end_sample >= 0);
104 assert(end_sample < (int64_t)sample_count_);
104 assert(end_sample <= (int64_t)sample_count_);
105105 assert(start_sample <= end_sample);
106
107 lock_guard<recursive_mutex> lock(mutex_);
108
109 return (float*)get_raw_samples(start_sample, (end_sample - start_sample));
106 assert(dest != nullptr);
107
108 lock_guard<recursive_mutex> lock(mutex_);
109
110 get_raw_samples(start_sample, (end_sample - start_sample), (uint8_t*)dest);
110111 }
111112
112113 const pair<float, float> AnalogSegment::get_min_max() const
114115 return make_pair(min_value_, max_value_);
115116 }
116117
117 SegmentAnalogDataIterator* AnalogSegment::begin_sample_iteration(uint64_t start)
118 {
119 return (SegmentAnalogDataIterator*)begin_raw_sample_iteration(start);
120 }
121
122 void AnalogSegment::continue_sample_iteration(SegmentAnalogDataIterator* it, uint64_t increase)
123 {
124 Segment::continue_raw_sample_iteration((SegmentRawDataIterator*)it, increase);
125 }
126
127 void AnalogSegment::end_sample_iteration(SegmentAnalogDataIterator* it)
128 {
129 Segment::end_raw_sample_iteration((SegmentRawDataIterator*)it);
118 float* AnalogSegment::get_iterator_value_ptr(SegmentDataIterator* it)
119 {
120 assert(it->sample_index <= (sample_count_ - 1));
121
122 return (float*)(it->chunk + it->chunk_offs);
130123 }
131124
132125 void AnalogSegment::get_envelope_section(EnvelopeSection &s,
169162 Envelope &e0 = envelope_levels_[0];
170163 uint64_t prev_length;
171164 EnvelopeSample *dest_ptr;
172 SegmentRawDataIterator* it;
165 SegmentDataIterator* it;
173166
174167 // Expand the data buffer to fit the new samples
175168 prev_length = e0.length;
176169 e0.length = sample_count_ / EnvelopeScaleFactor;
177170
178171 // Calculate min/max values in case we have too few samples for an envelope
172 const float old_min_value = min_value_, old_max_value = max_value_;
179173 if (sample_count_ < EnvelopeScaleFactor) {
180 it = begin_raw_sample_iteration(0);
174 it = begin_sample_iteration(0);
181175 for (uint64_t i = 0; i < sample_count_; i++) {
182 const float sample = *((float*)it->value);
176 const float sample = *get_iterator_value_ptr(it);
183177 if (sample < min_value_)
184178 min_value_ = sample;
185179 if (sample > max_value_)
186180 max_value_ = sample;
187 continue_raw_sample_iteration(it, 1);
181 continue_sample_iteration(it, 1);
188182 }
189 end_raw_sample_iteration(it);
183 end_sample_iteration(it);
190184 }
191185
192186 // Break off if there are no new samples to compute
201195 uint64_t start_sample = prev_length * EnvelopeScaleFactor;
202196 uint64_t end_sample = e0.length * EnvelopeScaleFactor;
203197
204 it = begin_raw_sample_iteration(start_sample);
198 it = begin_sample_iteration(start_sample);
205199 for (uint64_t i = start_sample; i < end_sample; i += EnvelopeScaleFactor) {
206 const float* samples = (float*)it->value;
200 const float* samples = get_iterator_value_ptr(it);
207201
208202 const EnvelopeSample sub_sample = {
209203 *min_element(samples, samples + EnvelopeScaleFactor),
215209 if (sub_sample.max > max_value_)
216210 max_value_ = sub_sample.max;
217211
218 continue_raw_sample_iteration(it, EnvelopeScaleFactor);
212 continue_sample_iteration(it, EnvelopeScaleFactor);
219213 *dest_ptr++ = sub_sample;
220214 }
221 end_raw_sample_iteration(it);
215 end_sample_iteration(it);
222216
223217 // Compute higher level mipmaps
224218 for (unsigned int level = 1; level < ScaleStepCount; level++) {
255249 *dest_ptr = sub_sample;
256250 }
257251 }
252
253 // Notify if the min or max value changed
254 if ((old_min_value != min_value_) || (old_max_value != max_value_))
255 owner_.min_max_changed(min_value_, max_value_);
258256 }
259257
260258 } // namespace data
3737
3838 class Analog;
3939
40 typedef struct {
41 uint64_t sample_index, chunk_num, chunk_offs;
42 uint8_t* chunk;
43 float* value;
44 } SegmentAnalogDataIterator;
45
46 class AnalogSegment : public QObject, public Segment
40 class AnalogSegment : public Segment
4741 {
4842 Q_OBJECT
4943
7872 static const uint64_t EnvelopeDataUnit;
7973
8074 public:
81 AnalogSegment(Analog& owner, uint64_t samplerate);
75 AnalogSegment(Analog& owner, uint32_t segment_id, uint64_t samplerate);
8276
8377 virtual ~AnalogSegment();
8478
8579 void append_interleaved_samples(const float *data,
8680 size_t sample_count, size_t stride);
8781
88 const float* get_samples(int64_t start_sample,
89 int64_t end_sample) const;
82 void get_samples(int64_t start_sample, int64_t end_sample, float* dest) const;
9083
9184 const pair<float, float> get_min_max() const;
9285
93 SegmentAnalogDataIterator* begin_sample_iteration(uint64_t start);
94 void continue_sample_iteration(SegmentAnalogDataIterator* it, uint64_t increase);
95 void end_sample_iteration(SegmentAnalogDataIterator* it);
86 float* get_iterator_value_ptr(SegmentDataIterator* it);
9687
9788 void get_envelope_section(EnvelopeSection &s,
9889 uint64_t start, uint64_t end, float min_length) const;
3131 namespace data {
3232 namespace decode {
3333
34 Annotation::Annotation(const srd_proto_data *const pdata) :
34 Annotation::Annotation(const srd_proto_data *const pdata, const Row *row) :
3535 start_sample_(pdata->start_sample),
36 end_sample_(pdata->end_sample)
36 end_sample_(pdata->end_sample),
37 row_(row)
3738 {
3839 assert(pdata);
3940 const srd_proto_data_annotation *const pda =
4041 (const srd_proto_data_annotation*)pdata->data;
4142 assert(pda);
4243
43 format_ = pda->ann_class;
44 ann_class_ = (Class)(pda->ann_class);
4445
4546 const char *const *annotations = (char**)pda->ann_text;
4647 while (*annotations) {
5960 return end_sample_;
6061 }
6162
62 int Annotation::format() const
63 Annotation::Class Annotation::ann_class() const
6364 {
64 return format_;
65 return ann_class_;
6566 }
6667
6768 const vector<QString>& Annotation::annotations() const
6970 return annotations_;
7071 }
7172
73 const Row* Annotation::row() const
74 {
75 return row_;
76 }
77
78 bool Annotation::operator<(const Annotation &other) const
79 {
80 return (start_sample_ < other.start_sample_);
81 }
82
7283 } // namespace decode
7384 } // namespace data
7485 } // namespace pv
1919 #ifndef PULSEVIEW_PV_VIEW_DECODE_ANNOTATION_HPP
2020 #define PULSEVIEW_PV_VIEW_DECODE_ANNOTATION_HPP
2121
22 #include <stdint.h>
22 #include <cstdint>
23 #include <vector>
2324
2425 #include <QString>
2526
3132 namespace data {
3233 namespace decode {
3334
35 class Row;
36
3437 class Annotation
3538 {
3639 public:
37 Annotation(const srd_proto_data *const pdata);
40 typedef uint32_t Class;
41
42 public:
43 Annotation(const srd_proto_data *const pdata, const Row *row);
3844
3945 uint64_t start_sample() const;
4046 uint64_t end_sample() const;
41 int format() const;
47 Class ann_class() const;
4248 const vector<QString>& annotations() const;
49 const Row* row() const;
50
51 bool operator<(const Annotation &other) const;
4352
4453 private:
4554 uint64_t start_sample_;
4655 uint64_t end_sample_;
47 int format_;
56 Class ann_class_;
4857 vector<QString> annotations_;
58 const Row *row_;
4959 };
5060
5161 } // namespace decode
1818
1919 #include <cassert>
2020
21 #include <QDebug>
22
2123 #include <libsigrokcxx/libsigrokcxx.hpp>
2224 #include <libsigrokdecode/libsigrokdecode.h>
2325
2426 #include "decoder.hpp"
2527
2628 #include <pv/data/signalbase.hpp>
29 #include <pv/data/decodesignal.hpp>
2730
28 using std::set;
31 using pv::data::DecodeChannel;
2932 using std::map;
30 using std::shared_ptr;
3133 using std::string;
3234
3335 namespace pv {
3739 Decoder::Decoder(const srd_decoder *const dec) :
3840 decoder_(dec),
3941 shown_(true),
40 initial_pins_(nullptr)
42 decoder_inst_(nullptr)
4143 {
4244 }
4345
6264 shown_ = show;
6365 }
6466
65 const map<const srd_channel*, shared_ptr<data::SignalBase> >&
66 Decoder::channels() const
67 const vector<DecodeChannel*>& Decoder::channels() const
6768 {
6869 return channels_;
6970 }
7071
71 void Decoder::set_channels(map<const srd_channel*,
72 shared_ptr<data::SignalBase> > channels)
72 void Decoder::set_channels(vector<DecodeChannel*> channels)
7373 {
7474 channels_ = channels;
75 }
76
77 void Decoder::set_initial_pins(GArray *initial_pins)
78 {
79 if (initial_pins_)
80 g_array_free(initial_pins_, TRUE);
81 initial_pins_ = initial_pins;
82 }
83
84 GArray *Decoder::initial_pins() const
85 {
86 return initial_pins_;
8775 }
8876
8977 const map<string, GVariant*>& Decoder::options() const
9684 assert(value);
9785 g_variant_ref(value);
9886 options_[id] = value;
87
88 // If we have a decoder instance, apply option value immediately
89 apply_all_options();
90 }
91
92 void Decoder::apply_all_options()
93 {
94 if (decoder_inst_) {
95 GHashTable *const opt_hash = g_hash_table_new_full(g_str_hash,
96 g_str_equal, g_free, (GDestroyNotify)g_variant_unref);
97
98 for (const auto& option : options_) {
99 GVariant *const value = option.second;
100 g_variant_ref(value);
101 g_hash_table_replace(opt_hash, (void*)g_strdup(
102 option.first.c_str()), value);
103 }
104
105 srd_inst_option_set(decoder_inst_, opt_hash);
106 g_hash_table_destroy(opt_hash);
107 }
99108 }
100109
101110 bool Decoder::have_required_channels() const
102111 {
103 for (GSList *l = decoder_->channels; l; l = l->next) {
104 const srd_channel *const pdch = (const srd_channel*)l->data;
105 assert(pdch);
106 if (channels_.find(pdch) == channels_.end())
112 for (DecodeChannel *ch : channels_)
113 if (!ch->assigned_signal && !ch->is_optional)
107114 return false;
108 }
109115
110116 return true;
111117 }
112118
113 set< shared_ptr<pv::data::Logic> > Decoder::get_data()
114 {
115 set< shared_ptr<pv::data::Logic> > data;
116 for (const auto& channel : channels_) {
117 shared_ptr<data::SignalBase> b(channel.second);
118 assert(b);
119 data.insert(b->logic_data());
120 }
121
122 return data;
123 }
124
125 srd_decoder_inst* Decoder::create_decoder_inst(srd_session *session) const
119 srd_decoder_inst* Decoder::create_decoder_inst(srd_session *session)
126120 {
127121 GHashTable *const opt_hash = g_hash_table_new_full(g_str_hash,
128122 g_str_equal, g_free, (GDestroyNotify)g_variant_unref);
134128 option.first.c_str()), value);
135129 }
136130
137 srd_decoder_inst *const decoder_inst = srd_inst_new(
138 session, decoder_->id, opt_hash);
131 if (decoder_inst_)
132 qDebug() << "WARNING: previous decoder instance" << decoder_inst_ << "exists";
133
134 decoder_inst_ = srd_inst_new(session, decoder_->id, opt_hash);
139135 g_hash_table_destroy(opt_hash);
140136
141 if (!decoder_inst)
137 if (!decoder_inst_)
142138 return nullptr;
143139
144140 // Setup the channels
141 GArray *const init_pin_states = g_array_sized_new(false, true,
142 sizeof(uint8_t), channels_.size());
143
144 g_array_set_size(init_pin_states, channels_.size());
145
145146 GHashTable *const channels = g_hash_table_new_full(g_str_hash,
146147 g_str_equal, g_free, (GDestroyNotify)g_variant_unref);
147148
148 for (const auto& channel : channels_) {
149 shared_ptr<data::SignalBase> b(channel.second);
150 GVariant *const gvar = g_variant_new_int32(b->index());
149 for (DecodeChannel *ch : channels_) {
150 if (!ch->assigned_signal)
151 continue;
152
153 init_pin_states->data[ch->id] = ch->initial_pin_state;
154
155 GVariant *const gvar = g_variant_new_int32(ch->bit_id); // bit_id = bit position
151156 g_variant_ref_sink(gvar);
152 g_hash_table_insert(channels, channel.first->id, gvar);
157 // key is channel name (pdch->id), value is bit position in each sample (gvar)
158 g_hash_table_insert(channels, ch->pdch_->id, gvar);
153159 }
154160
155 srd_inst_channel_set_all(decoder_inst, channels);
161 srd_inst_channel_set_all(decoder_inst_, channels);
156162
157 srd_inst_initial_pins_set_all(decoder_inst, initial_pins_);
163 srd_inst_initial_pins_set_all(decoder_inst_, init_pin_states);
164 g_array_free(init_pin_states, true);
158165
159 return decoder_inst;
166 return decoder_inst_;
167 }
168
169 void Decoder::invalidate_decoder_inst()
170 {
171 decoder_inst_ = nullptr;
160172 }
161173
162174 } // namespace decode
2222 #include <map>
2323 #include <memory>
2424 #include <set>
25 #include <vector>
2526
2627 #include <glib.h>
2728
2829 using std::map;
29 using std::set;
30 using std::shared_ptr;
3130 using std::string;
31 using std::vector;
3232
3333 struct srd_decoder;
3434 struct srd_decoder_inst;
3939
4040 namespace data {
4141
42 struct DecodeChannel;
4243 class Logic;
4344 class SignalBase;
4445
5657 bool shown() const;
5758 void show(bool show = true);
5859
59 const map<const srd_channel*,
60 shared_ptr<data::SignalBase> >& channels() const;
61 void set_channels(map<const srd_channel*,
62 shared_ptr<data::SignalBase> > channels);
63
64 void set_initial_pins(GArray *initial_pins);
65
66 GArray *initial_pins() const;
60 const vector<data::DecodeChannel*>& channels() const;
61 void set_channels(vector<data::DecodeChannel*> channels);
6762
6863 const map<string, GVariant*>& options() const;
6964
7065 void set_option(const char *id, GVariant *value);
7166
67 void apply_all_options();
68
7269 bool have_required_channels() const;
7370
74 srd_decoder_inst* create_decoder_inst(srd_session *session) const;
75
76 set< shared_ptr<pv::data::Logic> > get_data();
71 srd_decoder_inst* create_decoder_inst(srd_session *session);
72 void invalidate_decoder_inst();
7773
7874 private:
7975 const srd_decoder *const decoder_;
8076
8177 bool shown_;
8278
83 map<const srd_channel*, shared_ptr<pv::data::SignalBase> > channels_;
84 GArray *initial_pins_;
79 vector<data::DecodeChannel*> channels_;
8580 map<string, GVariant*> options_;
81 srd_decoder_inst *decoder_inst_;
8682 };
8783
8884 } // namespace decode
3030 {
3131 }
3232
33 Row::Row(const srd_decoder *decoder, const srd_decoder_annotation_row *row) :
33 Row::Row(int index, const srd_decoder *decoder, const srd_decoder_annotation_row *row) :
34 index_(index),
3435 decoder_(decoder),
3536 row_(row)
3637 {
5960 return QString();
6061 }
6162
63 const QString Row::class_name() const
64 {
65 if (row_ && row_->desc)
66 return QString::fromUtf8(row_->desc);
67 return QString();
68 }
69
70 int Row::index() const
71 {
72 return index_;
73 }
74
6275 bool Row::operator<(const Row &other) const
6376 {
6477 return (decoder_ < other.decoder_) ||
3535 public:
3636 Row();
3737
38 Row(const srd_decoder *decoder,
38 Row(int index, const srd_decoder *decoder,
3939 const srd_decoder_annotation_row *row = nullptr);
4040
4141 const srd_decoder* decoder() const;
4242 const srd_decoder_annotation_row* row() const;
4343
4444 const QString title() const;
45 const QString class_name() const;
46 int index() const;
4547
4648 bool operator<(const Row &other) const;
4749
4850 private:
51 int index_;
4952 const srd_decoder *decoder_;
5053 const srd_decoder_annotation_row *row_;
5154 };
4141 dest.push_back(annotation);
4242 }
4343
44 void RowData::push_annotation(const Annotation &a)
44 void RowData::emplace_annotation(srd_proto_data *pdata, const Row *row)
4545 {
46 annotations_.push_back(a);
46 annotations_.emplace_back(pdata, row);
4747 }
4848
4949 } // namespace decode
2121
2222 #include <vector>
2323
24 #include <libsigrokdecode/libsigrokdecode.h>
25
2426 #include "annotation.hpp"
2527
2628 using std::vector;
2830 namespace pv {
2931 namespace data {
3032 namespace decode {
33
34 class Row;
3135
3236 class RowData
3337 {
3842 uint64_t get_max_sample() const;
3943
4044 /**
41 * Extracts sorted annotations between two period into a vector.
45 * Extracts annotations between the given sample range into a vector.
46 * Note: The annotations are unsorted and only annotations that fully
47 * fit into the sample range are considered.
4248 */
4349 void get_annotation_subset(
4450 vector<pv::data::decode::Annotation> &dest,
4551 uint64_t start_sample, uint64_t end_sample) const;
4652
47 void push_annotation(const Annotation &a);
53 void emplace_annotation(srd_proto_data *pdata, const Row *row);
4854
4955 private:
5056 vector<Annotation> annotations_;
+0
-460
pv/data/decoderstack.cpp less more
0 /*
1 * This file is part of the PulseView project.
2 *
3 * Copyright (C) 2012 Joel Holdsworth <joel@airwebreathe.org.uk>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <libsigrokdecode/libsigrokdecode.h>
20
21 #include <stdexcept>
22
23 #include <QDebug>
24
25 #include "decoderstack.hpp"
26
27 #include <pv/data/decode/annotation.hpp>
28 #include <pv/data/decode/decoder.hpp>
29 #include <pv/data/logic.hpp>
30 #include <pv/data/logicsegment.hpp>
31 #include <pv/session.hpp>
32 #include <pv/views/trace/logicsignal.hpp>
33
34 using std::lock_guard;
35 using std::mutex;
36 using std::unique_lock;
37 using std::deque;
38 using std::make_pair;
39 using std::max;
40 using std::min;
41 using std::list;
42 using std::shared_ptr;
43 using std::make_shared;
44 using std::vector;
45
46 using boost::optional;
47
48 using namespace pv::data::decode;
49
50 namespace pv {
51 namespace data {
52
53 const double DecoderStack::DecodeMargin = 1.0;
54 const double DecoderStack::DecodeThreshold = 0.2;
55 const int64_t DecoderStack::DecodeChunkLength = 10 * 1024 * 1024;
56 const unsigned int DecoderStack::DecodeNotifyPeriod = 1024;
57
58 mutex DecoderStack::global_srd_mutex_;
59
60 DecoderStack::DecoderStack(pv::Session &session,
61 const srd_decoder *const dec) :
62 session_(session),
63 start_time_(0),
64 samplerate_(0),
65 sample_count_(0),
66 frame_complete_(false),
67 samples_decoded_(0)
68 {
69 connect(&session_, SIGNAL(frame_began()),
70 this, SLOT(on_new_frame()));
71 connect(&session_, SIGNAL(data_received()),
72 this, SLOT(on_data_received()));
73 connect(&session_, SIGNAL(frame_ended()),
74 this, SLOT(on_frame_ended()));
75
76 stack_.push_back(make_shared<decode::Decoder>(dec));
77 }
78
79 DecoderStack::~DecoderStack()
80 {
81 if (decode_thread_.joinable()) {
82 interrupt_ = true;
83 input_cond_.notify_one();
84 decode_thread_.join();
85 }
86 }
87
88 const list< shared_ptr<decode::Decoder> >& DecoderStack::stack() const
89 {
90 return stack_;
91 }
92
93 void DecoderStack::push(shared_ptr<decode::Decoder> decoder)
94 {
95 assert(decoder);
96 stack_.push_back(decoder);
97 }
98
99 void DecoderStack::remove(int index)
100 {
101 assert(index >= 0);
102 assert(index < (int)stack_.size());
103
104 // Find the decoder in the stack
105 auto iter = stack_.begin();
106 for (int i = 0; i < index; i++, iter++)
107 assert(iter != stack_.end());
108
109 // Delete the element
110 stack_.erase(iter);
111 }
112
113 double DecoderStack::samplerate() const
114 {
115 return samplerate_;
116 }
117
118 const pv::util::Timestamp& DecoderStack::start_time() const
119 {
120 return start_time_;
121 }
122
123 int64_t DecoderStack::samples_decoded() const
124 {
125 lock_guard<mutex> decode_lock(output_mutex_);
126 return samples_decoded_;
127 }
128
129 vector<Row> DecoderStack::get_visible_rows() const
130 {
131 lock_guard<mutex> lock(output_mutex_);
132
133 vector<Row> rows;
134
135 for (const shared_ptr<decode::Decoder> &dec : stack_) {
136 assert(dec);
137 if (!dec->shown())
138 continue;
139
140 const srd_decoder *const decc = dec->decoder();
141 assert(dec->decoder());
142
143 // Add a row for the decoder if it doesn't have a row list
144 if (!decc->annotation_rows)
145 rows.emplace_back(decc);
146
147 // Add the decoder rows
148 for (const GSList *l = decc->annotation_rows; l; l = l->next) {
149 const srd_decoder_annotation_row *const ann_row =
150 (srd_decoder_annotation_row *)l->data;
151 assert(ann_row);
152 rows.emplace_back(decc, ann_row);
153 }
154 }
155
156 return rows;
157 }
158
159 void DecoderStack::get_annotation_subset(
160 vector<pv::data::decode::Annotation> &dest,
161 const Row &row, uint64_t start_sample,
162 uint64_t end_sample) const
163 {
164 lock_guard<mutex> lock(output_mutex_);
165
166 const auto iter = rows_.find(row);
167 if (iter != rows_.end())
168 (*iter).second.get_annotation_subset(dest,
169 start_sample, end_sample);
170 }
171
172 QString DecoderStack::error_message()
173 {
174 lock_guard<mutex> lock(output_mutex_);
175 return error_message_;
176 }
177
178 void DecoderStack::clear()
179 {
180 sample_count_ = 0;
181 frame_complete_ = false;
182 samples_decoded_ = 0;
183 error_message_ = QString();
184 rows_.clear();
185 class_rows_.clear();
186 }
187
188 void DecoderStack::begin_decode()
189 {
190 if (decode_thread_.joinable()) {
191 interrupt_ = true;
192 input_cond_.notify_one();
193 decode_thread_.join();
194 }
195
196 clear();
197
198 // Check that all decoders have the required channels
199 for (const shared_ptr<decode::Decoder> &dec : stack_)
200 if (!dec->have_required_channels()) {
201 error_message_ = tr("One or more required channels "
202 "have not been specified");
203 return;
204 }
205
206 // Add classes
207 for (const shared_ptr<decode::Decoder> &dec : stack_) {
208 assert(dec);
209 const srd_decoder *const decc = dec->decoder();
210 assert(dec->decoder());
211
212 // Add a row for the decoder if it doesn't have a row list
213 if (!decc->annotation_rows)
214 rows_[Row(decc)] = decode::RowData();
215
216 // Add the decoder rows
217 for (const GSList *l = decc->annotation_rows; l; l = l->next) {
218 const srd_decoder_annotation_row *const ann_row =
219 (srd_decoder_annotation_row *)l->data;
220 assert(ann_row);
221
222 const Row row(decc, ann_row);
223
224 // Add a new empty row data object
225 rows_[row] = decode::RowData();
226
227 // Map out all the classes
228 for (const GSList *ll = ann_row->ann_classes;
229 ll; ll = ll->next)
230 class_rows_[make_pair(decc,
231 GPOINTER_TO_INT(ll->data))] = row;
232 }
233 }
234
235 // We get the logic data of the first channel in the list.
236 // This works because we are currently assuming all
237 // logic signals have the same data/segment
238 pv::data::SignalBase *signalbase;
239 pv::data::Logic *data = nullptr;
240
241 for (const shared_ptr<decode::Decoder> &dec : stack_)
242 if (dec && !dec->channels().empty() &&
243 ((signalbase = (*dec->channels().begin()).second.get())) &&
244 ((data = signalbase->logic_data().get())))
245 break;
246
247 if (!data)
248 return;
249
250 // Check we have a segment of data
251 const deque< shared_ptr<pv::data::LogicSegment> > &segments =
252 data->logic_segments();
253 if (segments.empty())
254 return;
255 segment_ = segments.front();
256
257 // Get the samplerate and start time
258 start_time_ = segment_->start_time();
259 samplerate_ = segment_->samplerate();
260 if (samplerate_ == 0.0)
261 samplerate_ = 1.0;
262
263 interrupt_ = false;
264 decode_thread_ = std::thread(&DecoderStack::decode_proc, this);
265 }
266
267 uint64_t DecoderStack::max_sample_count() const
268 {
269 uint64_t max_sample_count = 0;
270
271 for (const auto& row : rows_)
272 max_sample_count = max(max_sample_count,
273 row.second.get_max_sample());
274
275 return max_sample_count;
276 }
277
278 optional<int64_t> DecoderStack::wait_for_data() const
279 {
280 unique_lock<mutex> input_lock(input_mutex_);
281
282 // Do wait if we decoded all samples but we're still capturing
283 // Do not wait if we're done capturing
284 while (!interrupt_ && !frame_complete_ &&
285 (samples_decoded_ >= sample_count_) &&
286 (session_.get_capture_state() != Session::Stopped)) {
287
288 input_cond_.wait(input_lock);
289 }
290
291 // Return value is valid if we're not aborting the decode,
292 return boost::make_optional(!interrupt_ &&
293 // and there's more work to do...
294 (samples_decoded_ < sample_count_ || !frame_complete_) &&
295 // and if the end of the data hasn't been reached yet
296 (!((samples_decoded_ >= sample_count_) && (session_.get_capture_state() == Session::Stopped))),
297 sample_count_);
298 }
299
300 void DecoderStack::decode_data(
301 const int64_t abs_start_samplenum, const int64_t sample_count, const unsigned int unit_size,
302 srd_session *const session)
303 {
304 const unsigned int chunk_sample_count =
305 DecodeChunkLength / segment_->unit_size();
306
307 for (int64_t i = abs_start_samplenum; !interrupt_ && i < sample_count;
308 i += chunk_sample_count) {
309
310 const int64_t chunk_end = min(
311 i + chunk_sample_count, sample_count);
312 const uint8_t* chunk = segment_->get_samples(i, chunk_end);
313
314 if (srd_session_send(session, i, chunk_end, chunk,
315 (chunk_end - i) * unit_size, unit_size) != SRD_OK) {
316 error_message_ = tr("Decoder reported an error");
317 delete[] chunk;
318 break;
319 }
320 delete[] chunk;
321
322 {
323 lock_guard<mutex> lock(output_mutex_);
324 samples_decoded_ = chunk_end;
325 }
326
327 if (i % DecodeNotifyPeriod == 0)
328 new_decode_data();
329 }
330
331 new_decode_data();
332 }
333
334 void DecoderStack::decode_proc()
335 {
336 optional<int64_t> sample_count;
337 srd_session *session;
338 srd_decoder_inst *prev_di = nullptr;
339
340 assert(segment_);
341
342 // Prevent any other decode threads from accessing libsigrokdecode
343 lock_guard<mutex> srd_lock(global_srd_mutex_);
344
345 // Create the session
346 srd_session_new(&session);
347 assert(session);
348
349 // Create the decoders
350 const unsigned int unit_size = segment_->unit_size();
351
352 for (const shared_ptr<decode::Decoder> &dec : stack_) {
353 srd_decoder_inst *const di = dec->create_decoder_inst(session);
354
355 if (!di) {
356 error_message_ = tr("Failed to create decoder instance");
357 srd_session_destroy(session);
358 return;
359 }
360
361 if (prev_di)
362 srd_inst_stack (session, prev_di, di);
363
364 prev_di = di;
365 }
366
367 // Get the intial sample count
368 {
369 unique_lock<mutex> input_lock(input_mutex_);
370 sample_count = sample_count_ = segment_->get_sample_count();
371 }
372
373 // Start the session
374 srd_session_metadata_set(session, SRD_CONF_SAMPLERATE,
375 g_variant_new_uint64((uint64_t)samplerate_));
376
377 srd_pd_output_callback_add(session, SRD_OUTPUT_ANN,
378 DecoderStack::annotation_callback, this);
379
380 srd_session_start(session);
381
382 int64_t abs_start_samplenum = 0;
383 do {
384 decode_data(abs_start_samplenum, *sample_count, unit_size, session);
385 abs_start_samplenum = *sample_count;
386 } while (error_message_.isEmpty() && (sample_count = wait_for_data()));
387
388 // Destroy the session
389 srd_session_destroy(session);
390 }
391
392 void DecoderStack::annotation_callback(srd_proto_data *pdata, void *decoder)
393 {
394 assert(pdata);
395 assert(decoder);
396
397 DecoderStack *const d = (DecoderStack*)decoder;
398 assert(d);
399
400 lock_guard<mutex> lock(d->output_mutex_);
401
402 const Annotation a(pdata);
403
404 // Find the row
405 assert(pdata->pdo);
406 assert(pdata->pdo->di);
407 const srd_decoder *const decc = pdata->pdo->di->decoder;
408 assert(decc);
409
410 auto row_iter = d->rows_.end();
411
412 // Try looking up the sub-row of this class
413 const auto r = d->class_rows_.find(make_pair(decc, a.format()));
414 if (r != d->class_rows_.end())
415 row_iter = d->rows_.find((*r).second);
416 else {
417 // Failing that, use the decoder as a key
418 row_iter = d->rows_.find(Row(decc));
419 }
420
421 assert(row_iter != d->rows_.end());
422 if (row_iter == d->rows_.end()) {
423 qDebug() << "Unexpected annotation: decoder = " << decc <<
424 ", format = " << a.format();
425 assert(false);
426 return;
427 }
428
429 // Add the annotation
430 (*row_iter).second.push_annotation(a);
431 }
432
433 void DecoderStack::on_new_frame()
434 {
435 begin_decode();
436 }
437
438 void DecoderStack::on_data_received()
439 {
440 {
441 unique_lock<mutex> lock(input_mutex_);
442 if (segment_)
443 sample_count_ = segment_->get_sample_count();
444 }
445 input_cond_.notify_one();
446 }
447
448 void DecoderStack::on_frame_ended()
449 {
450 {
451 unique_lock<mutex> lock(input_mutex_);
452 if (segment_)
453 frame_complete_ = true;
454 }
455 input_cond_.notify_one();
456 }
457
458 } // namespace data
459 } // namespace pv
+0
-183
pv/data/decoderstack.hpp less more
0 /*
1 * This file is part of the PulseView project.
2 *
3 * Copyright (C) 2012 Joel Holdsworth <joel@airwebreathe.org.uk>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #ifndef PULSEVIEW_PV_DATA_DECODERSTACK_HPP
20 #define PULSEVIEW_PV_DATA_DECODERSTACK_HPP
21
22 #include "signaldata.hpp"
23
24 #include <atomic>
25 #include <condition_variable>
26 #include <list>
27 #include <map>
28 #include <memory>
29 #include <thread>
30
31 #include <boost/optional.hpp>
32
33 #include <QObject>
34 #include <QString>
35
36 #include <pv/data/decode/row.hpp>
37 #include <pv/data/decode/rowdata.hpp>
38 #include <pv/util.hpp>
39
40 using std::atomic;
41 using std::condition_variable;
42 using std::list;
43 using std::map;
44 using std::mutex;
45 using std::pair;
46 using std::shared_ptr;
47 using std::vector;
48
49 struct srd_decoder;
50 struct srd_decoder_annotation_row;
51 struct srd_channel;
52 struct srd_proto_data;
53 struct srd_session;
54
55 namespace DecoderStackTest {
56 struct TwoDecoderStack;
57 }
58
59 namespace pv {
60
61 class Session;
62
63 namespace view {
64 class LogicSignal;
65 }
66
67 namespace data {
68
69 class LogicSegment;
70
71 namespace decode {
72 class Annotation;
73 class Decoder;
74 }
75
76 class Logic;
77
78 class DecoderStack : public QObject
79 {
80 Q_OBJECT
81
82 private:
83 static const double DecodeMargin;
84 static const double DecodeThreshold;
85 static const int64_t DecodeChunkLength;
86 static const unsigned int DecodeNotifyPeriod;
87
88 public:
89 DecoderStack(pv::Session &session, const srd_decoder *const dec);
90
91 virtual ~DecoderStack();
92
93 const list< shared_ptr<decode::Decoder> >& stack() const;
94 void push(shared_ptr<decode::Decoder> decoder);
95 void remove(int index);
96
97 double samplerate() const;
98
99 const pv::util::Timestamp& start_time() const;
100
101 int64_t samples_decoded() const;
102
103 vector<decode::Row> get_visible_rows() const;
104
105 /**
106 * Extracts sorted annotations between two period into a vector.
107 */
108 void get_annotation_subset(
109 vector<pv::data::decode::Annotation> &dest,
110 const decode::Row &row, uint64_t start_sample,
111 uint64_t end_sample) const;
112
113 QString error_message();
114
115 void clear();
116
117 uint64_t max_sample_count() const;
118
119 void begin_decode();
120
121 private:
122 boost::optional<int64_t> wait_for_data() const;
123
124 void decode_data(const int64_t abs_start_samplenum, const int64_t sample_count,
125 const unsigned int unit_size, srd_session *const session);
126
127 void decode_proc();
128
129 static void annotation_callback(srd_proto_data *pdata, void *decoder);
130
131 private Q_SLOTS:
132 void on_new_frame();
133
134 void on_data_received();
135
136 void on_frame_ended();
137
138 Q_SIGNALS:
139 void new_decode_data();
140
141 private:
142 pv::Session &session_;
143
144 pv::util::Timestamp start_time_;
145 double samplerate_;
146
147 /**
148 * This mutex prevents more than one thread from accessing
149 * libsigrokdecode concurrently.
150 * @todo A proper solution should be implemented to allow multiple
151 * decode operations in parallel.
152 */
153 static mutex global_srd_mutex_;
154
155 list< shared_ptr<decode::Decoder> > stack_;
156
157 shared_ptr<pv::data::LogicSegment> segment_;
158
159 mutable mutex input_mutex_;
160 mutable condition_variable input_cond_;
161 int64_t sample_count_;
162 bool frame_complete_;
163
164 mutable mutex output_mutex_;
165 int64_t samples_decoded_;
166
167 map<const decode::Row, decode::RowData> rows_;
168
169 map<pair<const srd_decoder*, int>, decode::Row> class_rows_;
170
171 QString error_message_;
172
173 std::thread decode_thread_;
174 atomic<bool> interrupt_;
175
176 friend struct DecoderStackTest::TwoDecoderStack;
177 };
178
179 } // namespace data
180 } // namespace pv
181
182 #endif // PULSEVIEW_PV_DATA_DECODERSTACK_HPP
0 /*
1 * This file is part of the PulseView project.
2 *
3 * Copyright (C) 2017 Soeren Apel <soeren@apelpie.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <forward_list>
20 #include <limits>
21
22 #include <QDebug>
23
24 #include "logic.hpp"
25 #include "logicsegment.hpp"
26 #include "decodesignal.hpp"
27 #include "signaldata.hpp"
28
29 #include <pv/binding/decoder.hpp>
30 #include <pv/data/decode/decoder.hpp>
31 #include <pv/data/decode/row.hpp>
32 #include <pv/globalsettings.hpp>
33 #include <pv/session.hpp>
34
35 using std::forward_list;
36 using std::lock_guard;
37 using std::make_pair;
38 using std::make_shared;
39 using std::min;
40 using std::out_of_range;
41 using std::shared_ptr;
42 using std::unique_lock;
43 using pv::data::decode::Annotation;
44 using pv::data::decode::Decoder;
45 using pv::data::decode::Row;
46
47 namespace pv {
48 namespace data {
49
50 const double DecodeSignal::DecodeMargin = 1.0;
51 const double DecodeSignal::DecodeThreshold = 0.2;
52 const int64_t DecodeSignal::DecodeChunkLength = 256 * 1024;
53
54
55 DecodeSignal::DecodeSignal(pv::Session &session) :
56 SignalBase(nullptr, SignalBase::DecodeChannel),
57 session_(session),
58 srd_session_(nullptr),
59 logic_mux_data_invalid_(false),
60 stack_config_changed_(true),
61 current_segment_id_(0)
62 {
63 connect(&session_, SIGNAL(capture_state_changed(int)),
64 this, SLOT(on_capture_state_changed(int)));
65 }
66
67 DecodeSignal::~DecodeSignal()
68 {
69 reset_decode(true);
70 }
71
72 const vector< shared_ptr<Decoder> >& DecodeSignal::decoder_stack() const
73 {
74 return stack_;
75 }
76
77 void DecodeSignal::stack_decoder(const srd_decoder *decoder)
78 {
79 assert(decoder);
80 const shared_ptr<Decoder> dec = make_shared<decode::Decoder>(decoder);
81
82 stack_.push_back(dec);
83
84 // Set name if this decoder is the first in the list
85 if (stack_.size() == 1)
86 set_name(QString::fromUtf8(decoder->name));
87
88 // Include the newly created decode channels in the channel lists
89 update_channel_list();
90
91 stack_config_changed_ = true;
92 auto_assign_signals(dec);
93 commit_decoder_channels();
94 begin_decode();
95 }
96
97 void DecodeSignal::remove_decoder(int index)
98 {
99 assert(index >= 0);
100 assert(index < (int)stack_.size());
101
102 // Find the decoder in the stack
103 auto iter = stack_.begin();
104 for (int i = 0; i < index; i++, iter++)
105 assert(iter != stack_.end());
106
107 // Delete the element
108 stack_.erase(iter);
109
110 // Update channels and decoded data
111 stack_config_changed_ = true;
112 update_channel_list();
113 begin_decode();
114 }
115
116 bool DecodeSignal::toggle_decoder_visibility(int index)
117 {
118 auto iter = stack_.cbegin();
119 for (int i = 0; i < index; i++, iter++)
120 assert(iter != stack_.end());
121
122 shared_ptr<Decoder> dec = *iter;
123
124 // Toggle decoder visibility
125 bool state = false;
126 if (dec) {
127 state = !dec->shown();
128 dec->show(state);
129 }
130
131 return state;
132 }
133
134 void DecodeSignal::reset_decode(bool shutting_down)
135 {
136 if (stack_config_changed_ || shutting_down)
137 stop_srd_session();
138 else
139 terminate_srd_session();
140
141 if (decode_thread_.joinable()) {
142 decode_interrupt_ = true;
143 decode_input_cond_.notify_one();
144 decode_thread_.join();
145 }
146
147 if (logic_mux_thread_.joinable()) {
148 logic_mux_interrupt_ = true;
149 logic_mux_cond_.notify_one();
150 logic_mux_thread_.join();
151 }
152
153 resume_decode(); // Make sure the decode thread isn't blocked by pausing
154
155 class_rows_.clear();
156 current_segment_id_ = 0;
157 segments_.clear();
158
159 logic_mux_data_.reset();
160 logic_mux_data_invalid_ = true;
161
162 if (!error_message_.isEmpty()) {
163 error_message_ = QString();
164 // TODO Emulate noquote()
165 qDebug().nospace() << name() << ": Error cleared";
166 }
167
168 decode_reset();
169 }
170
171 void DecodeSignal::begin_decode()
172 {
173 if (decode_thread_.joinable()) {
174 decode_interrupt_ = true;
175 decode_input_cond_.notify_one();
176 decode_thread_.join();
177 }
178
179 if (logic_mux_thread_.joinable()) {
180 logic_mux_interrupt_ = true;
181 logic_mux_cond_.notify_one();
182 logic_mux_thread_.join();
183 }
184
185 reset_decode();
186
187 if (stack_.size() == 0) {
188 set_error_message(tr("No decoders"));
189 return;
190 }
191
192 assert(channels_.size() > 0);
193
194 if (get_assigned_signal_count() == 0) {
195 set_error_message(tr("There are no channels assigned to this decoder"));
196 return;
197 }
198
199 // Make sure that all assigned channels still provide logic data
200 // (can happen when a converted signal was assigned but the
201 // conversion removed in the meanwhile)
202 for (data::DecodeChannel& ch : channels_)
203 if (ch.assigned_signal && !(ch.assigned_signal->logic_data() != nullptr))
204 ch.assigned_signal = nullptr;
205
206 // Check that all decoders have the required channels
207 for (const shared_ptr<decode::Decoder>& dec : stack_)
208 if (!dec->have_required_channels()) {
209 set_error_message(tr("One or more required channels "
210 "have not been specified"));
211 return;
212 }
213
214 // Map out all the annotation classes
215 int row_index = 0;
216 for (const shared_ptr<decode::Decoder>& dec : stack_) {
217 assert(dec);
218 const srd_decoder *const decc = dec->decoder();
219 assert(dec->decoder());
220
221 for (const GSList *l = decc->annotation_rows; l; l = l->next) {
222 const srd_decoder_annotation_row *const ann_row =
223 (srd_decoder_annotation_row *)l->data;
224 assert(ann_row);
225
226 const Row row(row_index++, decc, ann_row);
227
228 for (const GSList *ll = ann_row->ann_classes;
229 ll; ll = ll->next)
230 class_rows_[make_pair(decc,
231 GPOINTER_TO_INT(ll->data))] = row;
232 }
233 }
234
235 // Free the logic data and its segment(s) if it needs to be updated
236 if (logic_mux_data_invalid_)
237 logic_mux_data_.reset();
238
239 if (!logic_mux_data_) {
240 const uint32_t ch_count = get_assigned_signal_count();
241 logic_mux_unit_size_ = (ch_count + 7) / 8;
242 logic_mux_data_ = make_shared<Logic>(ch_count);
243 }
244
245 // Receive notifications when new sample data is available
246 connect_input_notifiers();
247
248 if (get_input_segment_count() == 0) {
249 set_error_message(tr("No input data"));
250 return;
251 }
252
253 // Make sure the logic output data is complete and up-to-date
254 logic_mux_interrupt_ = false;
255 logic_mux_thread_ = std::thread(&DecodeSignal::logic_mux_proc, this);
256
257 // Decode the muxed logic data
258 decode_interrupt_ = false;
259 decode_thread_ = std::thread(&DecodeSignal::decode_proc, this);
260 }
261
262 void DecodeSignal::pause_decode()
263 {
264 decode_paused_ = true;
265 }
266
267 void DecodeSignal::resume_decode()
268 {
269 // Manual unlocking is done before notifying, to avoid waking up the
270 // waiting thread only to block again (see notify_one for details)
271 decode_pause_mutex_.unlock();
272 decode_pause_cond_.notify_one();
273 decode_paused_ = false;
274 }
275
276 bool DecodeSignal::is_paused() const
277 {
278 return decode_paused_;
279 }
280
281 QString DecodeSignal::error_message() const
282 {
283 lock_guard<mutex> lock(output_mutex_);
284 return error_message_;
285 }
286
287 const vector<data::DecodeChannel> DecodeSignal::get_channels() const
288 {
289 return channels_;
290 }
291
292 void DecodeSignal::auto_assign_signals(const shared_ptr<Decoder> dec)
293 {
294 bool new_assignment = false;
295
296 // Try to auto-select channels that don't have signals assigned yet
297 for (data::DecodeChannel& ch : channels_) {
298 // If a decoder is given, auto-assign only its channels
299 if (dec && (ch.decoder_ != dec))
300 continue;
301
302 if (ch.assigned_signal)
303 continue;
304
305 QString ch_name = ch.name.toLower();
306 ch_name = ch_name.replace(QRegExp("[-_.]"), " ");
307
308 shared_ptr<data::SignalBase> match;
309 for (const shared_ptr<data::SignalBase>& s : session_.signalbases()) {
310 if (!s->enabled())
311 continue;
312
313 QString s_name = s->name().toLower();
314 s_name = s_name.replace(QRegExp("[-_.]"), " ");
315
316 if (s->logic_data() &&
317 ((ch_name.contains(s_name)) || (s_name.contains(ch_name)))) {
318 if (!match)
319 match = s;
320 else {
321 // Only replace an existing match if it matches more characters
322 int old_unmatched = ch_name.length() - match->name().length();
323 int new_unmatched = ch_name.length() - s->name().length();
324 if (abs(new_unmatched) < abs(old_unmatched))
325 match = s;
326 }
327 }
328 }
329
330 if (match) {
331 ch.assigned_signal = match.get();
332 new_assignment = true;
333 }
334 }
335
336 if (new_assignment) {
337 logic_mux_data_invalid_ = true;
338 stack_config_changed_ = true;
339 commit_decoder_channels();
340 channels_updated();
341 }
342 }
343
344 void DecodeSignal::assign_signal(const uint16_t channel_id, const SignalBase *signal)
345 {
346 for (data::DecodeChannel& ch : channels_)
347 if (ch.id == channel_id) {
348 ch.assigned_signal = signal;
349 logic_mux_data_invalid_ = true;
350 }
351
352 stack_config_changed_ = true;
353 commit_decoder_channels();
354 channels_updated();
355 begin_decode();
356 }
357
358 int DecodeSignal::get_assigned_signal_count() const
359 {
360 // Count all channels that have a signal assigned to them
361 return count_if(channels_.begin(), channels_.end(),
362 [](data::DecodeChannel ch) { return ch.assigned_signal; });
363 }
364
365 void DecodeSignal::set_initial_pin_state(const uint16_t channel_id, const int init_state)
366 {
367 for (data::DecodeChannel& ch : channels_)
368 if (ch.id == channel_id)
369 ch.initial_pin_state = init_state;
370
371 stack_config_changed_ = true;
372 channels_updated();
373 begin_decode();
374 }
375
376 double DecodeSignal::samplerate() const
377 {
378 double result = 0;
379
380 // TODO For now, we simply return the first samplerate that we have
381 if (segments_.size() > 0)
382 result = segments_.front().samplerate;
383
384 return result;
385 }
386
387 const pv::util::Timestamp DecodeSignal::start_time() const
388 {
389 pv::util::Timestamp result;
390
391 // TODO For now, we simply return the first start time that we have
392 if (segments_.size() > 0)
393 result = segments_.front().start_time;
394
395 return result;
396 }
397
398 int64_t DecodeSignal::get_working_sample_count(uint32_t segment_id) const
399 {
400 // The working sample count is the highest sample number for
401 // which all used signals have data available, so go through all
402 // channels and use the lowest overall sample count of the segment
403
404 int64_t count = std::numeric_limits<int64_t>::max();
405 bool no_signals_assigned = true;
406
407 for (const data::DecodeChannel& ch : channels_)
408 if (ch.assigned_signal) {
409 no_signals_assigned = false;
410
411 const shared_ptr<Logic> logic_data = ch.assigned_signal->logic_data();
412 if (!logic_data || logic_data->logic_segments().empty())
413 return 0;
414
415 try {
416 const shared_ptr<LogicSegment> segment = logic_data->logic_segments().at(segment_id);
417 count = min(count, (int64_t)segment->get_sample_count());
418 } catch (out_of_range&) {
419 return 0;
420 }
421 }
422
423 return (no_signals_assigned ? 0 : count);
424 }
425
426 int64_t DecodeSignal::get_decoded_sample_count(uint32_t segment_id,
427 bool include_processing) const
428 {
429 lock_guard<mutex> decode_lock(output_mutex_);
430
431 int64_t result = 0;
432
433 try {
434 const DecodeSegment *segment = &(segments_.at(segment_id));
435 if (include_processing)
436 result = segment->samples_decoded_incl;
437 else
438 result = segment->samples_decoded_excl;
439 } catch (out_of_range&) {
440 // Do nothing
441 }
442
443 return result;
444 }
445
446 vector<Row> DecodeSignal::visible_rows() const
447 {
448 lock_guard<mutex> lock(output_mutex_);
449
450 vector<Row> rows;
451
452 for (const shared_ptr<decode::Decoder>& dec : stack_) {
453 assert(dec);
454 if (!dec->shown())
455 continue;
456
457 const srd_decoder *const decc = dec->decoder();
458 assert(dec->decoder());
459
460 int row_index = 0;
461 // Add a row for the decoder if it doesn't have a row list
462 if (!decc->annotation_rows)
463 rows.emplace_back(row_index++, decc);
464
465 // Add the decoder rows
466 for (const GSList *l = decc->annotation_rows; l; l = l->next) {
467 const srd_decoder_annotation_row *const ann_row =
468 (srd_decoder_annotation_row *)l->data;
469 assert(ann_row);
470 rows.emplace_back(row_index++, decc, ann_row);
471 }
472 }
473
474 return rows;
475 }
476
477 void DecodeSignal::get_annotation_subset(
478 vector<pv::data::decode::Annotation> &dest,
479 const decode::Row &row, uint32_t segment_id, uint64_t start_sample,
480 uint64_t end_sample) const
481 {
482 lock_guard<mutex> lock(output_mutex_);
483
484 try {
485 const DecodeSegment *segment = &(segments_.at(segment_id));
486 const map<const decode::Row, decode::RowData> *rows =
487 &(segment->annotation_rows);
488
489 const auto iter = rows->find(row);
490 if (iter != rows->end())
491 (*iter).second.get_annotation_subset(dest,
492 start_sample, end_sample);
493 } catch (out_of_range&) {
494 // Do nothing
495 }
496 }
497
498 void DecodeSignal::get_annotation_subset(
499 vector<pv::data::decode::Annotation> &dest,
500 uint32_t segment_id, uint64_t start_sample, uint64_t end_sample) const
501 {
502 // Note: We put all vectors and lists on the heap, not the stack
503
504 const vector<Row> rows = visible_rows();
505
506 // Use forward_lists for faster merging
507 forward_list<Annotation> *all_ann_list = new forward_list<Annotation>();
508
509 for (const Row& row : rows) {
510 vector<Annotation> *ann_vector = new vector<Annotation>();
511 get_annotation_subset(*ann_vector, row, segment_id, start_sample, end_sample);
512
513 forward_list<Annotation> *ann_list =
514 new forward_list<Annotation>(ann_vector->begin(), ann_vector->end());
515 delete ann_vector;
516
517 all_ann_list->merge(*ann_list);
518 delete ann_list;
519 }
520
521 move(all_ann_list->begin(), all_ann_list->end(), back_inserter(dest));
522 delete all_ann_list;
523 }
524
525 void DecodeSignal::save_settings(QSettings &settings) const
526 {
527 SignalBase::save_settings(settings);
528
529 settings.setValue("decoders", (int)(stack_.size()));
530
531 // Save decoder stack
532 int decoder_idx = 0;
533 for (const shared_ptr<decode::Decoder>& decoder : stack_) {
534 settings.beginGroup("decoder" + QString::number(decoder_idx++));
535
536 settings.setValue("id", decoder->decoder()->id);
537
538 // Save decoder options
539 const map<string, GVariant*>& options = decoder->options();
540
541 settings.setValue("options", (int)options.size());
542
543 // Note: decode::Decoder::options() returns only the options
544 // that differ from the default. See binding::Decoder::getter()
545 int i = 0;
546 for (auto& option : options) {
547 settings.beginGroup("option" + QString::number(i));
548 settings.setValue("name", QString::fromStdString(option.first));
549 GlobalSettings::store_gvariant(settings, option.second);
550 settings.endGroup();
551 i++;
552 }
553
554 settings.endGroup();
555 }
556
557 // Save channel mapping
558 settings.setValue("channels", (int)channels_.size());
559
560 for (unsigned int channel_id = 0; channel_id < channels_.size(); channel_id++) {
561 auto channel = find_if(channels_.begin(), channels_.end(),
562 [&](data::DecodeChannel ch) { return ch.id == channel_id; });
563
564 if (channel == channels_.end()) {
565 qDebug() << "ERROR: Gap in channel index:" << channel_id;
566 continue;
567 }
568
569 settings.beginGroup("channel" + QString::number(channel_id));
570
571 settings.setValue("name", channel->name); // Useful for debugging
572 settings.setValue("initial_pin_state", channel->initial_pin_state);
573
574 if (channel->assigned_signal)
575 settings.setValue("assigned_signal_name", channel->assigned_signal->name());
576
577 settings.endGroup();
578 }
579 }
580
581 void DecodeSignal::restore_settings(QSettings &settings)
582 {
583 SignalBase::restore_settings(settings);
584
585 // Restore decoder stack
586 GSList *dec_list = g_slist_copy((GSList*)srd_decoder_list());
587
588 int decoders = settings.value("decoders").toInt();
589
590 for (int decoder_idx = 0; decoder_idx < decoders; decoder_idx++) {
591 settings.beginGroup("decoder" + QString::number(decoder_idx));
592
593 QString id = settings.value("id").toString();
594
595 for (GSList *entry = dec_list; entry; entry = entry->next) {
596 const srd_decoder *dec = (srd_decoder*)entry->data;
597 if (!dec)
598 continue;
599
600 if (QString::fromUtf8(dec->id) == id) {
601 shared_ptr<decode::Decoder> decoder =
602 make_shared<decode::Decoder>(dec);
603
604 stack_.push_back(decoder);
605
606 // Restore decoder options that differ from their default
607 int options = settings.value("options").toInt();
608
609 for (int i = 0; i < options; i++) {
610 settings.beginGroup("option" + QString::number(i));
611 QString name = settings.value("name").toString();
612 GVariant *value = GlobalSettings::restore_gvariant(settings);
613 decoder->set_option(name.toUtf8(), value);
614 settings.endGroup();
615 }
616
617 // Include the newly created decode channels in the channel lists
618 update_channel_list();
619 break;
620 }
621 }
622
623 settings.endGroup();
624 channels_updated();
625 }
626
627 // Restore channel mapping
628 unsigned int channels = settings.value("channels").toInt();
629
630 const unordered_set< shared_ptr<data::SignalBase> > signalbases =
631 session_.signalbases();
632
633 for (unsigned int channel_id = 0; channel_id < channels; channel_id++) {
634 auto channel = find_if(channels_.begin(), channels_.end(),
635 [&](data::DecodeChannel ch) { return ch.id == channel_id; });
636
637 if (channel == channels_.end()) {
638 qDebug() << "ERROR: Non-existant channel index:" << channel_id;
639 continue;
640 }
641
642 settings.beginGroup("channel" + QString::number(channel_id));
643
644 QString assigned_signal_name = settings.value("assigned_signal_name").toString();
645
646 for (const shared_ptr<data::SignalBase>& signal : signalbases)
647 if (signal->name() == assigned_signal_name)
648 channel->assigned_signal = signal.get();
649
650 channel->initial_pin_state = settings.value("initial_pin_state").toInt();
651
652 settings.endGroup();
653 }
654
655 // Update the internal structures
656 stack_config_changed_ = true;
657 update_channel_list();
658 commit_decoder_channels();
659
660 begin_decode();
661 }
662
663 void DecodeSignal::set_error_message(QString msg)
664 {
665 error_message_ = msg;
666 // TODO Emulate noquote()
667 qDebug().nospace() << name() << ": " << msg;
668 }
669
670 uint32_t DecodeSignal::get_input_segment_count() const
671 {
672 uint64_t count = std::numeric_limits<uint64_t>::max();
673 bool no_signals_assigned = true;
674
675 for (const data::DecodeChannel& ch : channels_)
676 if (ch.assigned_signal) {
677 no_signals_assigned = false;
678
679 const shared_ptr<Logic> logic_data = ch.assigned_signal->logic_data();
680 if (!logic_data || logic_data->logic_segments().empty())
681 return 0;
682
683 // Find the min value of all segment counts
684 if ((uint64_t)(logic_data->logic_segments().size()) < count)
685 count = logic_data->logic_segments().size();
686 }
687
688 return (no_signals_assigned ? 0 : count);
689 }
690
691 uint32_t DecodeSignal::get_input_samplerate(uint32_t segment_id) const
692 {
693 double samplerate = 0;
694
695 for (const data::DecodeChannel& ch : channels_)
696 if (ch.assigned_signal) {
697 const shared_ptr<Logic> logic_data = ch.assigned_signal->logic_data();
698 if (!logic_data || logic_data->logic_segments().empty())
699 continue;
700
701 try {
702 const shared_ptr<LogicSegment> segment = logic_data->logic_segments().at(segment_id);
703 samplerate = segment->samplerate();
704 } catch (out_of_range&) {
705 // Do nothing
706 }
707 break;
708 }
709
710 return samplerate;
711 }
712
713 void DecodeSignal::update_channel_list()
714 {
715 vector<data::DecodeChannel> prev_channels = channels_;
716 channels_.clear();
717
718 uint16_t id = 0;
719
720 // Copy existing entries, create new as needed
721 for (shared_ptr<Decoder>& decoder : stack_) {
722 const srd_decoder* srd_d = decoder->decoder();
723 const GSList *l;
724
725 // Mandatory channels
726 for (l = srd_d->channels; l; l = l->next) {
727 const struct srd_channel *const pdch = (struct srd_channel *)l->data;
728 bool ch_added = false;
729
730 // Copy but update ID if this channel was in the list before
731 for (data::DecodeChannel& ch : prev_channels)
732 if (ch.pdch_ == pdch) {
733 ch.id = id++;
734 channels_.push_back(ch);
735 ch_added = true;
736 break;
737 }
738
739 if (!ch_added) {
740 // Create new entry without a mapped signal
741 data::DecodeChannel ch = {id++, 0, false, nullptr,
742 QString::fromUtf8(pdch->name), QString::fromUtf8(pdch->desc),
743 SRD_INITIAL_PIN_SAME_AS_SAMPLE0, decoder, pdch};
744 channels_.push_back(ch);
745 }
746 }
747
748 // Optional channels
749 for (l = srd_d->opt_channels; l; l = l->next) {
750 const struct srd_channel *const pdch = (struct srd_channel *)l->data;
751 bool ch_added = false;
752
753 // Copy but update ID if this channel was in the list before
754 for (data::DecodeChannel& ch : prev_channels)
755 if (ch.pdch_ == pdch) {
756 ch.id = id++;
757 channels_.push_back(ch);
758 ch_added = true;
759 break;
760 }
761
762 if (!ch_added) {
763 // Create new entry without a mapped signal
764 data::DecodeChannel ch = {id++, 0, true, nullptr,
765 QString::fromUtf8(pdch->name), QString::fromUtf8(pdch->desc),
766 SRD_INITIAL_PIN_SAME_AS_SAMPLE0, decoder, pdch};
767 channels_.push_back(ch);
768 }
769 }
770 }
771
772 // Invalidate the logic output data if the channel assignment changed
773 if (prev_channels.size() != channels_.size()) {
774 // The number of channels changed, there's definitely a difference
775 logic_mux_data_invalid_ = true;
776 } else {
777 // Same number but assignment may still differ, so compare all channels
778 for (size_t i = 0; i < channels_.size(); i++) {
779 const data::DecodeChannel& p_ch = prev_channels[i];
780 const data::DecodeChannel& ch = channels_[i];
781
782 if ((p_ch.pdch_ != ch.pdch_) ||
783 (p_ch.assigned_signal != ch.assigned_signal)) {
784 logic_mux_data_invalid_ = true;
785 break;
786 }
787 }
788
789 }
790
791 channels_updated();
792 }
793
794 void DecodeSignal::commit_decoder_channels()
795 {
796 // Submit channel list to every decoder, containing only the relevant channels
797 for (shared_ptr<decode::Decoder> dec : stack_) {
798 vector<data::DecodeChannel*> channel_list;
799
800 for (data::DecodeChannel& ch : channels_)
801 if (ch.decoder_ == dec)
802 channel_list.push_back(&ch);
803
804 dec->set_channels(channel_list);
805 }
806
807 // Channel bit IDs must be in sync with the channel's apperance in channels_
808 int id = 0;
809 for (data::DecodeChannel& ch : channels_)
810 if (ch.assigned_signal)
811 ch.bit_id = id++;
812 }
813
814 void DecodeSignal::mux_logic_samples(uint32_t segment_id, const int64_t start, const int64_t end)
815 {
816 // Enforce end to be greater than start
817 if (end <= start)
818 return;
819
820 // Fetch the channel segments and their data
821 vector<shared_ptr<LogicSegment> > segments;
822 vector<const uint8_t*> signal_data;
823 vector<uint8_t> signal_in_bytepos;
824 vector<uint8_t> signal_in_bitpos;
825
826 for (data::DecodeChannel& ch : channels_)
827 if (ch.assigned_signal) {
828 const shared_ptr<Logic> logic_data = ch.assigned_signal->logic_data();
829
830 shared_ptr<LogicSegment> segment;
831 try {
832 segment = logic_data->logic_segments().at(segment_id);
833 } catch (out_of_range&) {
834 qDebug() << "Muxer error for" << name() << ":" << ch.assigned_signal->name() \
835 << "has no logic segment" << segment_id;
836 return;
837 }
838 segments.push_back(segment);
839
840 uint8_t* data = new uint8_t[(end - start) * segment->unit_size()];
841 segment->get_samples(start, end, data);
842 signal_data.push_back(data);
843
844 const int bitpos = ch.assigned_signal->logic_bit_index();
845 signal_in_bytepos.push_back(bitpos / 8);
846 signal_in_bitpos.push_back(bitpos % 8);
847 }
848
849
850 shared_ptr<LogicSegment> output_segment;
851 try {
852 output_segment = logic_mux_data_->logic_segments().at(segment_id);
853 } catch (out_of_range&) {
854 qDebug() << "Muxer error for" << name() << ": no logic mux segment" \
855 << segment_id << "in mux_logic_samples(), mux segments size is" \
856 << logic_mux_data_->logic_segments().size();
857 return;
858 }
859
860 // Perform the muxing of signal data into the output data
861 uint8_t* output = new uint8_t[(end - start) * output_segment->unit_size()];
862 unsigned int signal_count = signal_data.size();
863
864 for (int64_t sample_cnt = 0; !logic_mux_interrupt_ && (sample_cnt < (end - start));
865 sample_cnt++) {
866
867 int bitpos = 0;
868 uint8_t bytepos = 0;
869
870 const int out_sample_pos = sample_cnt * output_segment->unit_size();
871 for (unsigned int i = 0; i < output_segment->unit_size(); i++)
872 output[out_sample_pos + i] = 0;
873
874 for (unsigned int i = 0; i < signal_count; i++) {
875 const int in_sample_pos = sample_cnt * segments[i]->unit_size();
876 const uint8_t in_sample = 1 &
877 ((signal_data[i][in_sample_pos + signal_in_bytepos[i]]) >> (signal_in_bitpos[i]));
878
879 const uint8_t out_sample = output[out_sample_pos + bytepos];
880
881 output[out_sample_pos + bytepos] = out_sample | (in_sample << bitpos);
882
883 bitpos++;
884 if (bitpos > 7) {
885 bitpos = 0;
886 bytepos++;
887 }
888 }
889 }
890
891 output_segment->append_payload(output, (end - start) * output_segment->unit_size());
892 delete[] output;
893
894 for (const uint8_t* data : signal_data)
895 delete[] data;
896 }
897
898 void DecodeSignal::logic_mux_proc()
899 {
900 uint32_t segment_id = 0;
901
902 assert(logic_mux_data_);
903
904 // Create initial logic mux segment
905 shared_ptr<LogicSegment> output_segment =
906 make_shared<LogicSegment>(*logic_mux_data_, segment_id,
907 logic_mux_unit_size_, 0);
908 logic_mux_data_->push_segment(output_segment);
909
910 output_segment->set_samplerate(get_input_samplerate(0));
911
912 do {
913 const uint64_t input_sample_count = get_working_sample_count(segment_id);
914 const uint64_t output_sample_count = output_segment->get_sample_count();
915
916 const uint64_t samples_to_process =
917 (input_sample_count > output_sample_count) ?
918 (input_sample_count - output_sample_count) : 0;
919
920 // Process the samples if necessary...
921 if (samples_to_process > 0) {
922 const uint64_t unit_size = output_segment->unit_size();
923 const uint64_t chunk_sample_count = DecodeChunkLength / unit_size;
924
925 uint64_t processed_samples = 0;
926 do {
927 const uint64_t start_sample = output_sample_count + processed_samples;
928 const uint64_t sample_count =
929 min(samples_to_process - processed_samples, chunk_sample_count);
930
931 mux_logic_samples(segment_id, start_sample, start_sample + sample_count);
932 processed_samples += sample_count;
933
934 // ...and process the newly muxed logic data
935 decode_input_cond_.notify_one();
936 } while (!logic_mux_interrupt_ && (processed_samples < samples_to_process));
937 }
938
939 if (samples_to_process == 0) {
940 // TODO Optimize this by caching the input segment count and only
941 // querying it when the cached value was reached
942 if (segment_id < get_input_segment_count() - 1) {
943 // Process next segment
944 segment_id++;
945
946 output_segment =
947 make_shared<LogicSegment>(*logic_mux_data_, segment_id,
948 logic_mux_unit_size_, 0);
949 logic_mux_data_->push_segment(output_segment);
950
951 output_segment->set_samplerate(get_input_samplerate(segment_id));
952
953 } else {
954 // All segments have been processed
955 logic_mux_data_invalid_ = false;
956
957 // Wait for more input
958 unique_lock<mutex> logic_mux_lock(logic_mux_mutex_);
959 logic_mux_cond_.wait(logic_mux_lock);
960 }
961 }
962
963 } while (!logic_mux_interrupt_);
964 }
965
966 void DecodeSignal::decode_data(
967 const int64_t abs_start_samplenum, const int64_t sample_count,
968 const shared_ptr<LogicSegment> input_segment)
969 {
970 const int64_t unit_size = input_segment->unit_size();
971 const int64_t chunk_sample_count = DecodeChunkLength / unit_size;
972
973 for (int64_t i = abs_start_samplenum;
974 error_message_.isEmpty() && !decode_interrupt_ &&
975 (i < (abs_start_samplenum + sample_count));
976 i += chunk_sample_count) {
977
978 const int64_t chunk_end = min(i + chunk_sample_count,
979 abs_start_samplenum + sample_count);
980
981 {
982 lock_guard<mutex> lock(output_mutex_);
983 // Update the sample count showing the samples including currently processed ones
984 segments_.at(current_segment_id_).samples_decoded_incl = chunk_end;
985 }
986
987 int64_t data_size = (chunk_end - i) * unit_size;
988 uint8_t* chunk = new uint8_t[data_size];
989 input_segment->get_samples(i, chunk_end, chunk);
990
991 if (srd_session_send(srd_session_, i, chunk_end, chunk,
992 data_size, unit_size) != SRD_OK)
993 set_error_message(tr("Decoder reported an error"));
994
995 delete[] chunk;
996
997 {
998 lock_guard<mutex> lock(output_mutex_);
999 // Now that all samples are processed, the exclusive sample count catches up
1000 segments_.at(current_segment_id_).samples_decoded_excl = chunk_end;
1001 }
1002
1003 // Notify the frontend that we processed some data and
1004 // possibly have new annotations as well
1005 new_annotations();
1006
1007 if (decode_paused_) {
1008 unique_lock<mutex> pause_wait_lock(decode_pause_mutex_);
1009 decode_pause_cond_.wait(pause_wait_lock);
1010 }
1011 }
1012 }
1013
1014 void DecodeSignal::decode_proc()
1015 {
1016 current_segment_id_ = 0;
1017
1018 // If there is no input data available yet, wait until it is or we're interrupted
1019 if (logic_mux_data_->logic_segments().size() == 0) {
1020 unique_lock<mutex> input_wait_lock(input_mutex_);
1021 decode_input_cond_.wait(input_wait_lock);
1022 }
1023
1024 if (decode_interrupt_)
1025 return;
1026
1027 shared_ptr<LogicSegment> input_segment = logic_mux_data_->logic_segments().front();
1028 assert(input_segment);
1029
1030 // Create the initial segment and set its sample rate so that we can pass it to SRD
1031 create_decode_segment();
1032 segments_.at(current_segment_id_).samplerate = input_segment->samplerate();
1033 segments_.at(current_segment_id_).start_time = input_segment->start_time();
1034
1035 start_srd_session();
1036
1037 uint64_t sample_count = 0;
1038 uint64_t abs_start_samplenum = 0;
1039 do {
1040 // Keep processing new samples until we exhaust the input data
1041 do {
1042 lock_guard<mutex> input_lock(input_mutex_);
1043 sample_count = input_segment->get_sample_count() - abs_start_samplenum;
1044
1045 if (sample_count > 0) {
1046 decode_data(abs_start_samplenum, sample_count, input_segment);
1047 abs_start_samplenum += sample_count;
1048 }
1049 } while (error_message_.isEmpty() && (sample_count > 0) && !decode_interrupt_);
1050
1051 if (error_message_.isEmpty() && !decode_interrupt_ && sample_count == 0) {
1052 if (current_segment_id_ < logic_mux_data_->logic_segments().size() - 1) {
1053 // Process next segment
1054 current_segment_id_++;
1055
1056 try {
1057 input_segment = logic_mux_data_->logic_segments().at(current_segment_id_);
1058 } catch (out_of_range&) {
1059 qDebug() << "Decode error for" << name() << ": no logic mux segment" \
1060 << current_segment_id_ << "in decode_proc(), mux segments size is" \
1061 << logic_mux_data_->logic_segments().size();
1062 return;
1063 }
1064 abs_start_samplenum = 0;
1065
1066 // Create the next segment and set its metadata
1067 create_decode_segment();
1068 segments_.at(current_segment_id_).samplerate = input_segment->samplerate();
1069 segments_.at(current_segment_id_).start_time = input_segment->start_time();
1070
1071 // Reset decoder state but keep the decoder stack intact
1072 terminate_srd_session();
1073 } else {
1074 // All segments have been processed
1075 decode_finished();
1076
1077 // Wait for new input data or an interrupt was requested
1078 unique_lock<mutex> input_wait_lock(input_mutex_);
1079 decode_input_cond_.wait(input_wait_lock);
1080 }
1081 }
1082 } while (error_message_.isEmpty() && !decode_interrupt_);
1083
1084 // Potentially reap decoders when the application no longer is
1085 // interested in their (pending) results.
1086 if (decode_interrupt_)
1087 terminate_srd_session();
1088 }
1089
1090 void DecodeSignal::start_srd_session()
1091 {
1092 // If there were stack changes, the session has been destroyed by now, so if
1093 // it hasn't been destroyed, we can just reset and re-use it
1094 if (srd_session_) {
1095 // When a decoder stack was created before, re-use it
1096 // for the next stream of input data, after terminating
1097 // potentially still executing operations, and resetting
1098 // internal state. Skip the rather expensive (teardown
1099 // and) construction of another decoder stack.
1100
1101 // TODO Reduce redundancy, use a common code path for
1102 // the meta/start sequence?
1103 terminate_srd_session();
1104
1105 // Metadata is cleared also, so re-set it
1106 uint64_t samplerate = 0;
1107 if (segments_.size() > 0)
1108 samplerate = segments_.at(current_segment_id_).samplerate;
1109 if (samplerate)
1110 srd_session_metadata_set(srd_session_, SRD_CONF_SAMPLERATE,
1111 g_variant_new_uint64(samplerate));
1112 for (const shared_ptr<decode::Decoder>& dec : stack_)
1113 dec->apply_all_options();
1114 srd_session_start(srd_session_);
1115
1116 return;
1117 }
1118
1119 // Create the session
1120 srd_session_new(&srd_session_);
1121 assert(srd_session_);
1122
1123 // Create the decoders
1124 srd_decoder_inst *prev_di = nullptr;
1125 for (const shared_ptr<decode::Decoder>& dec : stack_) {
1126 srd_decoder_inst *const di = dec->create_decoder_inst(srd_session_);
1127
1128 if (!di) {
1129 set_error_message(tr("Failed to create decoder instance"));
1130 srd_session_destroy(srd_session_);
1131 srd_session_ = nullptr;
1132 return;
1133 }
1134
1135 if (prev_di)
1136 srd_inst_stack(srd_session_, prev_di, di);
1137
1138 prev_di = di;
1139 }
1140
1141 // Start the session
1142 if (segments_.size() > 0)
1143 srd_session_metadata_set(srd_session_, SRD_CONF_SAMPLERATE,
1144 g_variant_new_uint64(segments_.at(current_segment_id_).samplerate));
1145
1146 srd_pd_output_callback_add(srd_session_, SRD_OUTPUT_ANN,
1147 DecodeSignal::annotation_callback, this);
1148
1149 srd_session_start(srd_session_);
1150
1151 // We just recreated the srd session, so all stack changes are applied now
1152 stack_config_changed_ = false;
1153 }
1154
1155 void DecodeSignal::terminate_srd_session()
1156 {
1157 // Call the "terminate and reset" routine for the decoder stack
1158 // (if available). This does not harm those stacks which already
1159 // have completed their operation, and reduces response time for
1160 // those stacks which still are processing data while the
1161 // application no longer wants them to.
1162 if (srd_session_) {
1163 srd_session_terminate_reset(srd_session_);
1164
1165 // Metadata is cleared also, so re-set it
1166 uint64_t samplerate = 0;
1167 if (segments_.size() > 0)
1168 samplerate = segments_.at(current_segment_id_).samplerate;
1169 if (samplerate)
1170 srd_session_metadata_set(srd_session_, SRD_CONF_SAMPLERATE,
1171 g_variant_new_uint64(samplerate));
1172 for (const shared_ptr<decode::Decoder>& dec : stack_)
1173 dec->apply_all_options();
1174 }
1175 }
1176
1177 void DecodeSignal::stop_srd_session()
1178 {
1179 if (srd_session_) {
1180 // Destroy the session
1181 srd_session_destroy(srd_session_);
1182 srd_session_ = nullptr;
1183
1184 // Mark the decoder instances as non-existant since they were deleted
1185 for (const shared_ptr<decode::Decoder>& dec : stack_)
1186 dec->invalidate_decoder_inst();
1187 }
1188 }
1189
1190 void DecodeSignal::connect_input_notifiers()
1191 {
1192 // Disconnect the notification slot from the previous set of signals
1193 disconnect(this, SLOT(on_data_cleared()));
1194 disconnect(this, SLOT(on_data_received()));
1195
1196 // Connect the currently used signals to our slot
1197 for (data::DecodeChannel& ch : channels_) {
1198 if (!ch.assigned_signal)
1199 continue;
1200
1201 const data::SignalBase *signal = ch.assigned_signal;
1202 connect(signal, SIGNAL(samples_cleared()),
1203 this, SLOT(on_data_cleared()));
1204 connect(signal, SIGNAL(samples_added(uint64_t, uint64_t, uint64_t)),
1205 this, SLOT(on_data_received()));
1206 }
1207 }
1208
1209 void DecodeSignal::create_decode_segment()
1210 {
1211 // Create annotation segment
1212 segments_.emplace_back(DecodeSegment());
1213
1214 // Add annotation classes
1215 for (const shared_ptr<decode::Decoder>& dec : stack_) {
1216 assert(dec);
1217 const srd_decoder *const decc = dec->decoder();
1218 assert(dec->decoder());
1219
1220 int row_index = 0;
1221 // Add a row for the decoder if it doesn't have a row list
1222 if (!decc->annotation_rows)
1223 (segments_.back().annotation_rows)[Row(row_index++, decc)] =
1224 decode::RowData();
1225
1226 // Add the decoder rows
1227 for (const GSList *l = decc->annotation_rows; l; l = l->next) {
1228 const srd_decoder_annotation_row *const ann_row =
1229 (srd_decoder_annotation_row *)l->data;
1230 assert(ann_row);
1231
1232 const Row row(row_index++, decc, ann_row);
1233
1234 // Add a new empty row data object
1235 (segments_.back().annotation_rows)[row] =
1236 decode::RowData();
1237 }
1238 }
1239 }
1240
1241 void DecodeSignal::annotation_callback(srd_proto_data *pdata, void *decode_signal)
1242 {
1243 assert(pdata);
1244 assert(decode_signal);
1245
1246 DecodeSignal *const ds = (DecodeSignal*)decode_signal;
1247 assert(ds);
1248
1249 if (ds->decode_interrupt_)
1250 return;
1251
1252 lock_guard<mutex> lock(ds->output_mutex_);
1253
1254 // Find the row
1255 assert(pdata->pdo);
1256 assert(pdata->pdo->di);
1257 const srd_decoder *const decc = pdata->pdo->di->decoder;
1258 assert(decc);
1259
1260 const srd_proto_data_annotation *const pda =
1261 (const srd_proto_data_annotation*)pdata->data;
1262 assert(pda);
1263
1264 auto row_iter = ds->segments_.at(ds->current_segment_id_).annotation_rows.end();
1265
1266 // Try looking up the sub-row of this class
1267 const auto format = pda->ann_class;
1268 const auto r = ds->class_rows_.find(make_pair(decc, format));
1269 if (r != ds->class_rows_.end())
1270 row_iter = ds->segments_.at(ds->current_segment_id_).annotation_rows.find((*r).second);
1271 else {
1272 // Failing that, use the decoder as a key
1273 row_iter = ds->segments_.at(ds->current_segment_id_).annotation_rows.find(Row(0, decc));
1274 }
1275
1276 if (row_iter == ds->segments_.at(ds->current_segment_id_).annotation_rows.end()) {
1277 qDebug() << "Unexpected annotation: decoder = " << decc <<
1278 ", format = " << format;
1279 assert(false);
1280 return;
1281 }
1282
1283 // Add the annotation
1284 (*row_iter).second.emplace_annotation(pdata, &((*row_iter).first));
1285 }
1286
1287 void DecodeSignal::on_capture_state_changed(int state)
1288 {
1289 // If a new acquisition was started, we need to start decoding from scratch
1290 if (state == Session::Running) {
1291 logic_mux_data_invalid_ = true;
1292 begin_decode();
1293 }
1294 }
1295
1296 void DecodeSignal::on_data_cleared()
1297 {
1298 reset_decode();
1299 }
1300
1301 void DecodeSignal::on_data_received()
1302 {
1303 // If we detected a lack of input data when trying to start decoding,
1304 // we have set an error message. Only try again if we now have data
1305 // to work with
1306 if ((!error_message_.isEmpty()) && (get_input_segment_count() == 0))
1307 return;
1308
1309 if (!logic_mux_thread_.joinable())
1310 begin_decode();
1311 else
1312 logic_mux_cond_.notify_one();
1313 }
1314
1315 } // namespace data
1316 } // namespace pv
0 /*
1 * This file is part of the PulseView project.
2 *
3 * Copyright (C) 2017 Soeren Apel <soeren@apelpie.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #ifndef PULSEVIEW_PV_DATA_DECODESIGNAL_HPP
20 #define PULSEVIEW_PV_DATA_DECODESIGNAL_HPP
21
22 #include <atomic>
23 #include <condition_variable>
24 #include <unordered_set>
25 #include <vector>
26
27 #include <QSettings>
28 #include <QString>
29
30 #include <libsigrokdecode/libsigrokdecode.h>
31
32 #include <pv/data/decode/row.hpp>
33 #include <pv/data/decode/rowdata.hpp>
34 #include <pv/data/signalbase.hpp>
35 #include <pv/util.hpp>
36
37 using std::atomic;
38 using std::condition_variable;
39 using std::map;
40 using std::mutex;
41 using std::pair;
42 using std::vector;
43 using std::shared_ptr;
44
45 namespace pv {
46 class Session;
47
48 namespace data {
49
50 namespace decode {
51 class Annotation;
52 class Decoder;
53 class Row;
54 }
55
56 class Logic;
57 class LogicSegment;
58 class SignalBase;
59 class SignalData;
60
61 struct DecodeChannel
62 {
63 uint16_t id; ///< Global numerical ID for the decode channels in the stack
64 uint16_t bit_id; ///< Tells which bit within a sample represents this channel
65 const bool is_optional;
66 const pv::data::SignalBase *assigned_signal;
67 const QString name, desc;
68 int initial_pin_state;
69 const shared_ptr<pv::data::decode::Decoder> decoder_;
70 const srd_channel *pdch_;
71 };
72
73 struct DecodeSegment
74 {
75 map<const decode::Row, decode::RowData> annotation_rows;
76 pv::util::Timestamp start_time;
77 double samplerate;
78 int64_t samples_decoded_incl, samples_decoded_excl;
79 };
80
81 class DecodeSignal : public SignalBase
82 {
83 Q_OBJECT
84
85 private:
86 static const double DecodeMargin;
87 static const double DecodeThreshold;
88 static const int64_t DecodeChunkLength;
89
90 public:
91 DecodeSignal(pv::Session &session);
92 virtual ~DecodeSignal();
93
94 bool is_decode_signal() const;
95 const vector< shared_ptr<data::decode::Decoder> >& decoder_stack() const;
96
97 void stack_decoder(const srd_decoder *decoder);
98 void remove_decoder(int index);
99 bool toggle_decoder_visibility(int index);
100
101 void reset_decode(bool shutting_down = false);
102 void begin_decode();
103 void pause_decode();
104 void resume_decode();
105 bool is_paused() const;
106 QString error_message() const;
107
108 const vector<data::DecodeChannel> get_channels() const;
109 void auto_assign_signals(const shared_ptr<pv::data::decode::Decoder> dec);
110 void assign_signal(const uint16_t channel_id, const SignalBase *signal);
111 int get_assigned_signal_count() const;
112
113 void set_initial_pin_state(const uint16_t channel_id, const int init_state);
114
115 double samplerate() const;
116 const pv::util::Timestamp start_time() const;
117
118 /**
119 * Returns the number of samples that can be worked on,
120 * i.e. the number of samples where samples are available
121 * for all connected channels.
122 */
123 int64_t get_working_sample_count(uint32_t segment_id) const;
124
125 /**
126 * Returns the number of processed samples. Newly generated annotations will
127 * have sample numbers greater than this.
128 *
129 * If include_processing is true, this number will include the ones being
130 * currently processed (in case the decoder stack is running). In this case,
131 * newly generated annotations will have sample numbers smaller than this.
132 */
133 int64_t get_decoded_sample_count(uint32_t segment_id,
134 bool include_processing) const;
135
136 vector<decode::Row> visible_rows() const;
137
138 /**
139 * Extracts annotations from a single row into a vector.
140 * Note: The annotations may be unsorted and only annotations that fully
141 * fit into the sample range are considered.
142 */
143 void get_annotation_subset(
144 vector<pv::data::decode::Annotation> &dest,
145 const decode::Row &row, uint32_t segment_id, uint64_t start_sample,
146 uint64_t end_sample) const;
147
148 /**
149 * Extracts annotations from all rows into a vector.
150 * Note: The annotations may be unsorted and only annotations that fully
151 * fit into the sample range are considered.
152 */
153 void get_annotation_subset(
154 vector<pv::data::decode::Annotation> &dest,
155 uint32_t segment_id, uint64_t start_sample, uint64_t end_sample) const;
156
157 virtual void save_settings(QSettings &settings) const;
158
159 virtual void restore_settings(QSettings &settings);
160
161 private:
162 void set_error_message(QString msg);
163
164 uint32_t get_input_segment_count() const;
165
166 uint32_t get_input_samplerate(uint32_t segment_id) const;
167
168 void update_channel_list();
169
170 void commit_decoder_channels();
171
172 void mux_logic_samples(uint32_t segment_id, const int64_t start, const int64_t end);
173
174 void logic_mux_proc();
175
176 void decode_data(const int64_t abs_start_samplenum, const int64_t sample_count,
177 const shared_ptr<LogicSegment> input_segment);
178
179 void decode_proc();
180
181 void start_srd_session();
182 void terminate_srd_session();
183 void stop_srd_session();
184
185 void connect_input_notifiers();
186
187 void create_decode_segment();
188
189 static void annotation_callback(srd_proto_data *pdata, void *decode_signal);
190
191 Q_SIGNALS:
192 void new_annotations(); // TODO Supply segment for which they belong to
193 void decode_reset();
194 void decode_finished();
195 void channels_updated();
196
197 private Q_SLOTS:
198 void on_capture_state_changed(int state);
199 void on_data_cleared();
200 void on_data_received();
201
202 private:
203 pv::Session &session_;
204
205 vector<data::DecodeChannel> channels_;
206
207 struct srd_session *srd_session_;
208
209 shared_ptr<Logic> logic_mux_data_;
210 uint32_t logic_mux_unit_size_;
211 bool logic_mux_data_invalid_;
212
213 vector< shared_ptr<decode::Decoder> > stack_;
214 bool stack_config_changed_;
215 map<pair<const srd_decoder*, int>, decode::Row> class_rows_;
216
217 vector<DecodeSegment> segments_;
218 uint32_t current_segment_id_;
219
220 mutable mutex input_mutex_, output_mutex_, decode_pause_mutex_, logic_mux_mutex_;
221 mutable condition_variable decode_input_cond_, decode_pause_cond_,
222 logic_mux_cond_;
223
224 std::thread decode_thread_, logic_mux_thread_;
225 atomic<bool> decode_interrupt_, logic_mux_interrupt_;
226
227 bool decode_paused_;
228
229 QString error_message_;
230 };
231
232 } // namespace data
233 } // namespace pv
234
235 #endif // PULSEVIEW_PV_DATA_DECODESIGNAL_HPP
4343
4444 void Logic::push_segment(shared_ptr<LogicSegment> &segment)
4545 {
46 segments_.push_front(segment);
46 segments_.push_back(segment);
4747 }
4848
4949 const deque< shared_ptr<LogicSegment> >& Logic::logic_segments() const
5656 return vector< shared_ptr<Segment> >(segments_.begin(), segments_.end());
5757 }
5858
59 uint32_t Logic::get_segment_count() const
60 {
61 return (uint32_t)segments_.size();
62 }
63
5964 void Logic::clear()
6065 {
6166 segments_.clear();
6368 samples_cleared();
6469 }
6570
71 double Logic::get_samplerate() const
72 {
73 if (segments_.empty())
74 return 1.0;
75
76 return segments_.front()->samplerate();
77 }
78
6679 uint64_t Logic::max_sample_count() const
6780 {
6881 uint64_t l = 0;
69 for (shared_ptr<LogicSegment> s : segments_) {
82 for (const shared_ptr<LogicSegment>& s : segments_) {
7083 assert(s);
7184 l = max(l, s->get_sample_count());
7285 }
4949
5050 vector< shared_ptr<Segment> > segments() const;
5151
52 uint32_t get_segment_count() const;
53
5254 void clear();
55
56 double get_samplerate() const;
5357
5458 uint64_t max_sample_count() const;
5559
1616 * along with this program; if not, see <http://www.gnu.org/licenses/>.
1717 */
1818
19 #include "config.h" // For HAVE_UNALIGNED_LITTLE_ENDIAN_ACCESS
20
1921 #include <extdef.h>
2022
2123 #include <cassert>
2224 #include <cmath>
2325 #include <cstdlib>
2426 #include <cstring>
27 #include <cstdint>
2528
2629 #include "logic.hpp"
2730 #include "logicsegment.hpp"
4548 const float LogicSegment::LogMipMapScaleFactor = logf(MipMapScaleFactor);
4649 const uint64_t LogicSegment::MipMapDataUnit = 64 * 1024; // bytes
4750
48 LogicSegment::LogicSegment(pv::data::Logic& owner, unsigned int unit_size,
49 uint64_t samplerate) :
50 Segment(samplerate, unit_size),
51 LogicSegment::LogicSegment(pv::data::Logic& owner, uint32_t segment_id,
52 unsigned int unit_size, uint64_t samplerate) :
53 Segment(segment_id, samplerate, unit_size),
5154 owner_(owner),
52 last_append_sample_(0)
55 last_append_sample_(0),
56 last_append_accumulator_(0),
57 last_append_extra_(0)
5358 {
5459 memset(mip_map_, 0, sizeof(mip_map_));
5560 }
6166 free(l.data);
6267 }
6368
64 uint64_t LogicSegment::unpack_sample(const uint8_t *ptr) const
69 template <class T>
70 void LogicSegment::downsampleTmain(const T*&in, T &acc, T &prev)
71 {
72 // Accumulate one sample at a time
73 for (uint64_t i = 0; i < MipMapScaleFactor; i++) {
74 T sample = *in++;
75 acc |= prev ^ sample;
76 prev = sample;
77 }
78 }
79
80 template <>
81 void LogicSegment::downsampleTmain<uint8_t>(const uint8_t*&in, uint8_t &acc, uint8_t &prev)
82 {
83 // Handle 8 bit samples in 32 bit steps
84 uint32_t prev32 = prev | prev << 8 | prev << 16 | prev << 24;
85 uint32_t acc32 = acc;
86 const uint32_t *in32 = (const uint32_t*)in;
87 for (uint64_t i = 0; i < MipMapScaleFactor; i += 4) {
88 uint32_t sample32 = *in32++;
89 acc32 |= prev32 ^ sample32;
90 prev32 = sample32;
91 }
92 // Reduce result back to uint8_t
93 #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
94 prev = (prev32 >> 24) & 0xff; // MSB is last
95 #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
96 prev = prev32 & 0xff; // LSB is last
97 #else
98 #error Endianness unknown
99 #endif
100 acc |= acc32 & 0xff;
101 acc |= (acc32 >> 8) & 0xff;
102 acc |= (acc32 >> 16) & 0xff;
103 acc |= (acc32 >> 24) & 0xff;
104 in = (const uint8_t*)in32;
105 }
106
107 template <>
108 void LogicSegment::downsampleTmain<uint16_t>(const uint16_t*&in, uint16_t &acc, uint16_t &prev)
109 {
110 // Handle 16 bit samples in 32 bit steps
111 uint32_t prev32 = prev | prev << 16;
112 uint32_t acc32 = acc;
113 const uint32_t *in32 = (const uint32_t*)in;
114 for (uint64_t i = 0; i < MipMapScaleFactor; i += 2) {
115 uint32_t sample32 = *in32++;
116 acc32 |= prev32 ^ sample32;
117 prev32 = sample32;
118 }
119 // Reduce result back to uint16_t
120 #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
121 prev = (prev32 >> 16) & 0xffff; // MSB is last
122 #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
123 prev = prev32 & 0xffff; // LSB is last
124 #else
125 #error Endian unknown
126 #endif
127 acc |= acc32 & 0xffff;
128 acc |= (acc32 >> 16) & 0xffff;
129 in = (const uint16_t*)in32;
130 }
131
132 template <class T>
133 void LogicSegment::downsampleT(const uint8_t *in_, uint8_t *&out_, uint64_t len)
134 {
135 const T *in = (const T*)in_;
136 T *out = (T*)out_;
137 T prev = last_append_sample_;
138 T acc = last_append_accumulator_;
139
140 // Try to complete the previous downsample
141 if (last_append_extra_) {
142 while (last_append_extra_ < MipMapScaleFactor && len > 0) {
143 T sample = *in++;
144 acc |= prev ^ sample;
145 prev = sample;
146 last_append_extra_++;
147 len--;
148 }
149 if (!len) {
150 // Not enough samples available to complete downsample
151 last_append_sample_ = prev;
152 last_append_accumulator_ = acc;
153 return;
154 }
155 // We have a complete downsample
156 *out++ = acc;
157 acc = 0;
158 last_append_extra_ = 0;
159 }
160
161 // Handle complete blocks of MipMapScaleFactor samples
162 while (len >= MipMapScaleFactor) {
163 downsampleTmain<T>(in, acc, prev);
164 len -= MipMapScaleFactor;
165 // Output downsample
166 *out++ = acc;
167 acc = 0;
168 }
169
170 // Process remainder, not enough for a complete sample
171 while (len > 0) {
172 T sample = *in++;
173 acc |= prev ^ sample;
174 prev = sample;
175 last_append_extra_++;
176 len--;
177 }
178
179 // Update context
180 last_append_sample_ = prev;
181 last_append_accumulator_ = acc;
182 out_ = (uint8_t *)out;
183 }
184
185 void LogicSegment::downsampleGeneric(const uint8_t *in, uint8_t *&out, uint64_t len)
186 {
187 // Downsample using the generic unpack_sample()
188 // which can handle any width between 1 and 8 bytes
189 uint64_t prev = last_append_sample_;
190 uint64_t acc = last_append_accumulator_;
191
192 // Try to complete the previous downsample
193 if (last_append_extra_) {
194 while (last_append_extra_ < MipMapScaleFactor && len > 0) {
195 const uint64_t sample = unpack_sample(in);
196 in += unit_size_;
197 acc |= prev ^ sample;
198 prev = sample;
199 last_append_extra_++;
200 len--;
201 }
202 if (!len) {
203 // Not enough samples available to complete downsample
204 last_append_sample_ = prev;
205 last_append_accumulator_ = acc;
206 return;
207 }
208 // We have a complete downsample
209 pack_sample(out, acc);
210 out += unit_size_;
211 acc = 0;
212 last_append_extra_ = 0;
213 }
214
215 // Handle complete blocks of MipMapScaleFactor samples
216 while (len >= MipMapScaleFactor) {
217 // Accumulate one sample at a time
218 for (uint64_t i = 0; i < MipMapScaleFactor; i++) {
219 const uint64_t sample = unpack_sample(in);
220 in += unit_size_;
221 acc |= prev ^ sample;
222 prev = sample;
223 }
224 len -= MipMapScaleFactor;
225 // Output downsample
226 pack_sample(out, acc);
227 out += unit_size_;
228 acc = 0;
229 }
230
231 // Process remainder, not enough for a complete sample
232 while (len > 0) {
233 const uint64_t sample = unpack_sample(in);
234 in += unit_size_;
235 acc |= prev ^ sample;
236 prev = sample;
237 last_append_extra_++;
238 len--;
239 }
240
241 // Update context
242 last_append_sample_ = prev;
243 last_append_accumulator_ = acc;
244 }
245
246 inline uint64_t LogicSegment::unpack_sample(const uint8_t *ptr) const
65247 {
66248 #ifdef HAVE_UNALIGNED_LITTLE_ENDIAN_ACCESS
67249 return *(uint64_t*)ptr;
99281 #endif
100282 }
101283
102 void LogicSegment::pack_sample(uint8_t *ptr, uint64_t value)
284 inline void LogicSegment::pack_sample(uint8_t *ptr, uint64_t value)
103285 {
104286 #ifdef HAVE_UNALIGNED_LITTLE_ENDIAN_ACCESS
105287 *(uint64_t*)ptr = value;
149331
150332 lock_guard<recursive_mutex> lock(mutex_);
151333
152 uint64_t prev_sample_count = sample_count_;
153 uint64_t sample_count = data_size / unit_size_;
334 const uint64_t prev_sample_count = sample_count_;
335 const uint64_t sample_count = data_size / unit_size_;
154336
155337 append_samples(data, sample_count);
156338
165347 prev_sample_count + 1);
166348 }
167349
168 const uint8_t* LogicSegment::get_samples(int64_t start_sample,
169 int64_t end_sample) const
350 void LogicSegment::get_samples(int64_t start_sample,
351 int64_t end_sample, uint8_t* dest) const
170352 {
171353 assert(start_sample >= 0);
172354 assert(start_sample <= (int64_t)sample_count_);
173355 assert(end_sample >= 0);
174356 assert(end_sample <= (int64_t)sample_count_);
175357 assert(start_sample <= end_sample);
358 assert(dest != nullptr);
176359
177360 lock_guard<recursive_mutex> lock(mutex_);
178361
179 return get_raw_samples(start_sample, (end_sample - start_sample));
180 }
181
182 SegmentLogicDataIterator* LogicSegment::begin_sample_iteration(uint64_t start)
183 {
184 return (SegmentLogicDataIterator*)begin_raw_sample_iteration(start);
185 }
186
187 void LogicSegment::continue_sample_iteration(SegmentLogicDataIterator* it, uint64_t increase)
188 {
189 Segment::continue_raw_sample_iteration((SegmentRawDataIterator*)it, increase);
190 }
191
192 void LogicSegment::end_sample_iteration(SegmentLogicDataIterator* it)
193 {
194 Segment::end_raw_sample_iteration((SegmentRawDataIterator*)it);
362 get_raw_samples(start_sample, (end_sample - start_sample), dest);
363 }
364
365 void LogicSegment::get_subsampled_edges(
366 vector<EdgePair> &edges,
367 uint64_t start, uint64_t end,
368 float min_length, int sig_index, bool first_change_only)
369 {
370 uint64_t index = start;
371 unsigned int level;
372 bool last_sample;
373 bool fast_forward;
374
375 assert(start <= end);
376 assert(min_length > 0);
377 assert(sig_index >= 0);
378 assert(sig_index < 64);
379
380 lock_guard<recursive_mutex> lock(mutex_);
381
382 // Make sure we only process as many samples as we have
383 if (end > get_sample_count())
384 end = get_sample_count();
385
386 const uint64_t block_length = (uint64_t)max(min_length, 1.0f);
387 const unsigned int min_level = max((int)floorf(logf(min_length) /
388 LogMipMapScaleFactor) - 1, 0);
389 const uint64_t sig_mask = 1ULL << sig_index;
390
391 // Store the initial state
392 last_sample = (get_unpacked_sample(start) & sig_mask) != 0;
393 if (!first_change_only)
394 edges.emplace_back(index++, last_sample);
395
396 while (index + block_length <= end) {
397 //----- Continue to search -----//
398 level = min_level;
399
400 // We cannot fast-forward if there is no mip-map data at
401 // the minimum level.
402 fast_forward = (mip_map_[level].data != nullptr);
403
404 if (min_length < MipMapScaleFactor) {
405 // Search individual samples up to the beginning of
406 // the next first level mip map block
407 const uint64_t final_index = min(end, pow2_ceil(index, MipMapScalePower));
408
409 for (; index < final_index &&
410 (index & ~((uint64_t)(~0) << MipMapScalePower)) != 0;
411 index++) {
412
413 const bool sample = (get_unpacked_sample(index) & sig_mask) != 0;
414
415 // If there was a change we cannot fast forward
416 if (sample != last_sample) {
417 fast_forward = false;
418 break;
419 }
420 }
421 } else {
422 // If resolution is less than a mip map block,
423 // round up to the beginning of the mip-map block
424 // for this level of detail
425 const int min_level_scale_power = (level + 1) * MipMapScalePower;
426 index = pow2_ceil(index, min_level_scale_power);
427 if (index >= end)
428 break;
429
430 // We can fast forward only if there was no change
431 const bool sample = (get_unpacked_sample(index) & sig_mask) != 0;
432 if (last_sample != sample)
433 fast_forward = false;
434 }
435
436 if (fast_forward) {
437
438 // Fast forward: This involves zooming out to higher
439 // levels of the mip map searching for changes, then
440 // zooming in on them to find the point where the edge
441 // begins.
442
443 // Slide right and zoom out at the beginnings of mip-map
444 // blocks until we encounter a change
445 while (true) {
446 const int level_scale_power = (level + 1) * MipMapScalePower;
447 const uint64_t offset = index >> level_scale_power;
448
449 // Check if we reached the last block at this
450 // level, or if there was a change in this block
451 if (offset >= mip_map_[level].length ||
452 (get_subsample(level, offset) & sig_mask))
453 break;
454
455 if ((offset & ~((uint64_t)(~0) << MipMapScalePower)) == 0) {
456 // If we are now at the beginning of a
457 // higher level mip-map block ascend one
458 // level
459 if ((level + 1 >= ScaleStepCount) || (!mip_map_[level + 1].data))
460 break;
461
462 level++;
463 } else {
464 // Slide right to the beginning of the
465 // next mip map block
466 index = pow2_ceil(index + 1, level_scale_power);
467 }
468 }
469
470 // Zoom in, and slide right until we encounter a change,
471 // and repeat until we reach min_level
472 while (true) {
473 assert(mip_map_[level].data);
474
475 const int level_scale_power = (level + 1) * MipMapScalePower;
476 const uint64_t offset = index >> level_scale_power;
477
478 // Check if we reached the last block at this
479 // level, or if there was a change in this block
480 if (offset >= mip_map_[level].length ||
481 (get_subsample(level, offset) & sig_mask)) {
482 // Zoom in unless we reached the minimum
483 // zoom
484 if (level == min_level)
485 break;
486
487 level--;
488 } else {
489 // Slide right to the beginning of the
490 // next mip map block
491 index = pow2_ceil(index + 1, level_scale_power);
492 }
493 }
494
495 // If individual samples within the limit of resolution,
496 // do a linear search for the next transition within the
497 // block
498 if (min_length < MipMapScaleFactor) {
499 for (; index < end; index++) {
500 const bool sample = (get_unpacked_sample(index) & sig_mask) != 0;
501 if (sample != last_sample)
502 break;
503 }
504 }
505 }
506
507 //----- Store the edge -----//
508
509 // Take the last sample of the quanization block
510 const int64_t final_index = index + block_length;
511 if (index + block_length > end)
512 break;
513
514 // Store the final state
515 const bool final_sample = (get_unpacked_sample(final_index - 1) & sig_mask) != 0;
516 edges.emplace_back(index, final_sample);
517
518 index = final_index;
519 last_sample = final_sample;
520
521 if (first_change_only)
522 break;
523 }
524
525 // Add the final state
526 if (!first_change_only) {
527 const bool end_sample = get_unpacked_sample(end) & sig_mask;
528 if (last_sample != end_sample)
529 edges.emplace_back(end, end_sample);
530 edges.emplace_back(end + 1, end_sample);
531 }
532 }
533
534 void LogicSegment::get_surrounding_edges(vector<EdgePair> &dest,
535 uint64_t origin_sample, float min_length, int sig_index)
536 {
537 if (origin_sample >= sample_count_)
538 return;
539
540 // Put the edges vector on the heap, it can become quite big until we can
541 // use a get_subsampled_edges() implementation that searches backwards
542 vector<EdgePair>* edges = new vector<EdgePair>;
543
544 // Get all edges to the left of origin_sample
545 get_subsampled_edges(*edges, 0, origin_sample, min_length, sig_index, false);
546
547 // If we don't specify "first only", the first and last edge are the states
548 // at samples 0 and origin_sample. If only those exist, there are no edges
549 if (edges->size() == 2) {
550 delete edges;
551 return;
552 }
553
554 // Dismiss the entry for origin_sample so that back() gives us the
555 // real last entry
556 edges->pop_back();
557 dest.push_back(edges->back());
558 edges->clear();
559
560 // Get first edge to the right of origin_sample
561 get_subsampled_edges(*edges, origin_sample, sample_count_, min_length, sig_index, true);
562
563 // "first only" is specified, so nothing needs to be dismissed
564 if (edges->size() == 0) {
565 delete edges;
566 return;
567 }
568
569 dest.push_back(edges->front());
570
571 delete edges;
195572 }
196573
197574 void LogicSegment::reallocate_mipmap_level(MipMapLevel &m)
215592 MipMapLevel &m0 = mip_map_[0];
216593 uint64_t prev_length;
217594 uint8_t *dest_ptr;
218 SegmentRawDataIterator* it;
595 SegmentDataIterator* it;
219596 uint64_t accumulator;
220597 unsigned int diff_counter;
221598
232609 dest_ptr = (uint8_t*)m0.data + prev_length * unit_size_;
233610
234611 // Iterate through the samples to populate the first level mipmap
235 uint64_t start_sample = prev_length * MipMapScaleFactor;
236 uint64_t end_sample = m0.length * MipMapScaleFactor;
237
238 it = begin_raw_sample_iteration(start_sample);
239 for (uint64_t i = start_sample; i < end_sample;) {
240 // Accumulate transitions which have occurred in this sample
241 accumulator = 0;
242 diff_counter = MipMapScaleFactor;
243 while (diff_counter-- > 0) {
244 const uint64_t sample = unpack_sample(it->value);
245 accumulator |= last_append_sample_ ^ sample;
246 last_append_sample_ = sample;
247 continue_raw_sample_iteration(it, 1);
248 i++;
249 }
250
251 pack_sample(dest_ptr, accumulator);
252 dest_ptr += unit_size_;
253 }
254 end_raw_sample_iteration(it);
612 const uint64_t start_sample = prev_length * MipMapScaleFactor;
613 const uint64_t end_sample = m0.length * MipMapScaleFactor;
614 uint64_t len_sample = end_sample - start_sample;
615 it = begin_sample_iteration(start_sample);
616 while (len_sample > 0) {
617 // Number of samples available in this chunk
618 uint64_t count = get_iterator_valid_length(it);
619 // Reduce if less than asked for
620 count = std::min(count, len_sample);
621 uint8_t *src_ptr = get_iterator_value(it);
622 // Submit these contiguous samples to downsampling in bulk
623 if (unit_size_ == 1)
624 downsampleT<uint8_t>(src_ptr, dest_ptr, count);
625 else if (unit_size_ == 2)
626 downsampleT<uint16_t>(src_ptr, dest_ptr, count);
627 else if (unit_size_ == 4)
628 downsampleT<uint32_t>(src_ptr, dest_ptr, count);
629 else if (unit_size_ == 8)
630 downsampleT<uint8_t>(src_ptr, dest_ptr, count);
631 else
632 downsampleGeneric(src_ptr, dest_ptr, count);
633 len_sample -= count;
634 // Advance iterator, should move to start of next chunk
635 continue_sample_iteration(it, count);
636 }
637 end_sample_iteration(it);
255638
256639 // Compute higher level mipmaps
257640 for (unsigned int level = 1; level < ScaleStepCount; level++) {
294677 {
295678 assert(index < sample_count_);
296679
297 const uint8_t* data = get_raw_samples(index, 1);
298 uint64_t sample = unpack_sample(data);
299 delete[] data;
300
301 return sample;
302 }
303
304 void LogicSegment::get_subsampled_edges(
305 vector<EdgePair> &edges,
306 uint64_t start, uint64_t end,
307 float min_length, int sig_index)
308 {
309 uint64_t index = start;
310 unsigned int level;
311 bool last_sample;
312 bool fast_forward;
313
314 assert(end <= get_sample_count());
315 assert(start <= end);
316 assert(min_length > 0);
317 assert(sig_index >= 0);
318 assert(sig_index < 64);
319
320 lock_guard<recursive_mutex> lock(mutex_);
321
322 const uint64_t block_length = (uint64_t)max(min_length, 1.0f);
323 const unsigned int min_level = max((int)floorf(logf(min_length) /
324 LogMipMapScaleFactor) - 1, 0);
325 const uint64_t sig_mask = 1ULL << sig_index;
326
327 // Store the initial state
328 last_sample = (get_unpacked_sample(start) & sig_mask) != 0;
329 edges.emplace_back(index++, last_sample);
330
331 while (index + block_length <= end) {
332 //----- Continue to search -----//
333 level = min_level;
334
335 // We cannot fast-forward if there is no mip-map data at
336 // at the minimum level.
337 fast_forward = (mip_map_[level].data != nullptr);
338
339 if (min_length < MipMapScaleFactor) {
340 // Search individual samples up to the beginning of
341 // the next first level mip map block
342 const uint64_t final_index = min(end,
343 pow2_ceil(index, MipMapScalePower));
344
345 for (; index < final_index &&
346 (index & ~((uint64_t)(~0) << MipMapScalePower)) != 0;
347 index++) {
348 const bool sample =
349 (get_unpacked_sample(index) & sig_mask) != 0;
350
351 // If there was a change we cannot fast forward
352 if (sample != last_sample) {
353 fast_forward = false;
354 break;
355 }
356 }
357 } else {
358 // If resolution is less than a mip map block,
359 // round up to the beginning of the mip-map block
360 // for this level of detail
361 const int min_level_scale_power =
362 (level + 1) * MipMapScalePower;
363 index = pow2_ceil(index, min_level_scale_power);
364 if (index >= end)
365 break;
366
367 // We can fast forward only if there was no change
368 const bool sample =
369 (get_unpacked_sample(index) & sig_mask) != 0;
370 if (last_sample != sample)
371 fast_forward = false;
372 }
373
374 if (fast_forward) {
375
376 // Fast forward: This involves zooming out to higher
377 // levels of the mip map searching for changes, then
378 // zooming in on them to find the point where the edge
379 // begins.
380
381 // Slide right and zoom out at the beginnings of mip-map
382 // blocks until we encounter a change
383 while (true) {
384 const int level_scale_power =
385 (level + 1) * MipMapScalePower;
386 const uint64_t offset =
387 index >> level_scale_power;
388
389 // Check if we reached the last block at this
390 // level, or if there was a change in this block
391 if (offset >= mip_map_[level].length ||
392 (get_subsample(level, offset) &
393 sig_mask))
394 break;
395
396 if ((offset & ~((uint64_t)(~0) << MipMapScalePower)) == 0) {
397 // If we are now at the beginning of a
398 // higher level mip-map block ascend one
399 // level
400 if (level + 1 >= ScaleStepCount ||
401 !mip_map_[level + 1].data)
402 break;
403
404 level++;
405 } else {
406 // Slide right to the beginning of the
407 // next mip map block
408 index = pow2_ceil(index + 1,
409 level_scale_power);
410 }
411 }
412
413 // Zoom in, and slide right until we encounter a change,
414 // and repeat until we reach min_level
415 while (true) {
416 assert(mip_map_[level].data);
417
418 const int level_scale_power =
419 (level + 1) * MipMapScalePower;
420 const uint64_t offset =
421 index >> level_scale_power;
422
423 // Check if we reached the last block at this
424 // level, or if there was a change in this block
425 if (offset >= mip_map_[level].length ||
426 (get_subsample(level, offset) &
427 sig_mask)) {
428 // Zoom in unless we reached the minimum
429 // zoom
430 if (level == min_level)
431 break;
432
433 level--;
434 } else {
435 // Slide right to the beginning of the
436 // next mip map block
437 index = pow2_ceil(index + 1,
438 level_scale_power);
439 }
440 }
441
442 // If individual samples within the limit of resolution,
443 // do a linear search for the next transition within the
444 // block
445 if (min_length < MipMapScaleFactor) {
446 for (; index < end; index++) {
447 const bool sample = (get_unpacked_sample(index) &
448 sig_mask) != 0;
449 if (sample != last_sample)
450 break;
451 }
452 }
453 }
454
455 //----- Store the edge -----//
456
457 // Take the last sample of the quanization block
458 const int64_t final_index = index + block_length;
459 if (index + block_length > end)
460 break;
461
462 // Store the final state
463 const bool final_sample =
464 (get_unpacked_sample(final_index - 1) & sig_mask) != 0;
465 edges.emplace_back(index, final_sample);
466
467 index = final_index;
468 last_sample = final_sample;
469 }
470
471 // Add the final state
472 const bool end_sample = get_unpacked_sample(end) & sig_mask;
473 if (last_sample != end_sample)
474 edges.emplace_back(end, end_sample);
475 edges.emplace_back(end + 1, end_sample);
680 assert(unit_size_ <= 8); // 8 * 8 = 64 channels
681 uint8_t data[8];
682
683 get_raw_samples(index, 1, data);
684
685 return unpack_sample(data);
476686 }
477687
478688 uint64_t LogicSegment::get_subsample(int level, uint64_t offset) const
485695
486696 uint64_t LogicSegment::pow2_ceil(uint64_t x, unsigned int power)
487697 {
488 const uint64_t p = 1 << power;
698 const uint64_t p = UINT64_C(1) << power;
489699 return (x + p - 1) / p * p;
490700 }
491701
4747
4848 class Logic;
4949
50 typedef struct {
51 uint64_t sample_index, chunk_num, chunk_offs;
52 uint8_t* chunk;
53 uint8_t* value;
54 } SegmentLogicDataIterator;
55
56 class LogicSegment : public QObject, public Segment
50 class LogicSegment : public Segment
5751 {
5852 Q_OBJECT
53
54 public:
55 typedef pair<int64_t, bool> EdgePair;
56
57 static const unsigned int ScaleStepCount = 10;
58 static const int MipMapScalePower;
59 static const int MipMapScaleFactor;
60 static const float LogMipMapScaleFactor;
61 static const uint64_t MipMapDataUnit;
5962
6063 private:
6164 struct MipMapLevel
6568 void *data;
6669 };
6770
68 private:
69 static const unsigned int ScaleStepCount = 10;
70 static const int MipMapScalePower;
71 static const int MipMapScaleFactor;
72 static const float LogMipMapScaleFactor;
73 static const uint64_t MipMapDataUnit;
74
7571 public:
76 typedef pair<int64_t, bool> EdgePair;
77
78 public:
79 LogicSegment(pv::data::Logic& owner, unsigned int unit_size, uint64_t samplerate);
72 LogicSegment(pv::data::Logic& owner, uint32_t segment_id,
73 unsigned int unit_size, uint64_t samplerate);
8074
8175 virtual ~LogicSegment();
8276
8377 void append_payload(shared_ptr<sigrok::Logic> logic);
8478 void append_payload(void *data, uint64_t data_size);
8579
86 const uint8_t* get_samples(int64_t start_sample, int64_t end_sample) const;
80 void get_samples(int64_t start_sample, int64_t end_sample, uint8_t* dest) const;
8781
88 SegmentLogicDataIterator* begin_sample_iteration(uint64_t start);
89 void continue_sample_iteration(SegmentLogicDataIterator* it, uint64_t increase);
90 void end_sample_iteration(SegmentLogicDataIterator* it);
91
92 private:
93 uint64_t unpack_sample(const uint8_t *ptr) const;
94 void pack_sample(uint8_t *ptr, uint64_t value);
95
96 void reallocate_mipmap_level(MipMapLevel &m);
97
98 void append_payload_to_mipmap();
99
100 uint64_t get_unpacked_sample(uint64_t index) const;
101
102 public:
10382 /**
10483 * Parses a logic data segment to generate a list of transitions
10584 * in a time interval to a given level of detail.
11291 */
11392 void get_subsampled_edges(vector<EdgePair> &edges,
11493 uint64_t start, uint64_t end,
115 float min_length, int sig_index);
94 float min_length, int sig_index, bool first_change_only = false);
95
96 void get_surrounding_edges(vector<EdgePair> &dest,
97 uint64_t origin_sample, float min_length, int sig_index);
98
99 private:
100 uint64_t unpack_sample(const uint8_t *ptr) const;
101 void pack_sample(uint8_t *ptr, uint64_t value);
102
103 void reallocate_mipmap_level(MipMapLevel &m);
104
105 void append_payload_to_mipmap();
106
107 uint64_t get_unpacked_sample(uint64_t index) const;
108
109 template <class T> void downsampleTmain(const T*&in, T &acc, T &prev);
110 template <class T> void downsampleT(const uint8_t *in, uint8_t *&out, uint64_t len);
111 void downsampleGeneric(const uint8_t *in, uint8_t *&out, uint64_t len);
116112
117113 private:
118114 uint64_t get_subsample(int level, uint64_t offset) const;
124120
125121 struct MipMapLevel mip_map_[ScaleStepCount];
126122 uint64_t last_append_sample_;
123 uint64_t last_append_accumulator_;
124 uint64_t last_append_extra_;
127125
128126 friend struct LogicSegmentTest::Pow2;
129127 friend struct LogicSegmentTest::Basic;
2323 #include <cstdlib>
2424 #include <cstring>
2525
26 #include <QDebug>
27
28 using std::bad_alloc;
2629 using std::lock_guard;
2730 using std::min;
2831 using std::recursive_mutex;
3235
3336 const uint64_t Segment::MaxChunkSize = 10 * 1024 * 1024; /* 10MiB */
3437
35 Segment::Segment(uint64_t samplerate, unsigned int unit_size) :
38 Segment::Segment(uint32_t segment_id, uint64_t samplerate, unsigned int unit_size) :
39 segment_id_(segment_id),
3640 sample_count_(0),
3741 start_time_(0),
3842 samplerate_(samplerate),
3943 unit_size_(unit_size),
4044 iterator_count_(0),
41 mem_optimization_requested_(false)
45 mem_optimization_requested_(false),
46 is_complete_(false)
4247 {
4348 lock_guard<recursive_mutex> lock(mutex_);
4449 assert(unit_size_ > 0);
4853 chunk_size_ = min(MaxChunkSize, (MaxChunkSize / unit_size_) * unit_size_);
4954
5055 // Create the initial chunk
51 current_chunk_ = new uint8_t[chunk_size_];
56 current_chunk_ = new uint8_t[chunk_size_ + 7]; /* FIXME +7 is workaround for #1284 */
5257 data_chunks_.push_back(current_chunk_);
5358 used_samples_ = 0;
5459 unused_samples_ = chunk_size_ / unit_size_;
8691 unsigned int Segment::unit_size() const
8792 {
8893 return unit_size_;
94 }
95
96 uint32_t Segment::segment_id() const
97 {
98 return segment_id_;
99 }
100
101 void Segment::set_complete()
102 {
103 is_complete_ = true;
104 }
105
106 bool Segment::is_complete() const
107 {
108 return is_complete_;
89109 }
90110
91111 void Segment::free_unused_memory()
98118 return;
99119 }
100120
101 // No more data will come in, so re-create the last chunk accordingly
102 uint8_t* resized_chunk = new uint8_t[used_samples_ * unit_size_];
103 memcpy(resized_chunk, current_chunk_, used_samples_ * unit_size_);
104
105 delete[] current_chunk_;
106 current_chunk_ = resized_chunk;
107
108 data_chunks_.pop_back();
109 data_chunks_.push_back(resized_chunk);
121 if (current_chunk_) {
122 // No more data will come in, so re-create the last chunk accordingly
123 uint8_t* resized_chunk = new uint8_t[used_samples_ * unit_size_ + 7]; /* FIXME +7 is workaround for #1284 */
124 memcpy(resized_chunk, current_chunk_, used_samples_ * unit_size_);
125
126 delete[] current_chunk_;
127 current_chunk_ = resized_chunk;
128
129 data_chunks_.pop_back();
130 data_chunks_.push_back(resized_chunk);
131 }
110132 }
111133
112134 void Segment::append_single_sample(void *data)
121143 unused_samples_--;
122144
123145 if (unused_samples_ == 0) {
124 current_chunk_ = new uint8_t[chunk_size_];
146 current_chunk_ = new uint8_t[chunk_size_ + 7]; /* FIXME +7 is workaround for #1284 */
125147 data_chunks_.push_back(current_chunk_);
126148 used_samples_ = 0;
127149 unused_samples_ = chunk_size_ / unit_size_;
159181 data_offset += (copy_count * unit_size_);
160182
161183 if (unused_samples_ == 0) {
162 // If we're out of memory, this will throw std::bad_alloc
163 current_chunk_ = new uint8_t[chunk_size_];
184 try {
185 // If we're out of memory, allocating a chunk will throw
186 // std::bad_alloc. To give the application some usable memory
187 // to work with in case chunk allocation fails, we allocate
188 // extra memory and throw it away if it all succeeded.
189 // This way, memory allocation will fail early enough to let
190 // PV remain alive. Otherwise, PV will crash in a random
191 // memory-allocating part of the application.
192 current_chunk_ = new uint8_t[chunk_size_ + 7]; /* FIXME +7 is workaround for #1284 */
193
194 const int dummy_size = 2 * chunk_size_;
195 auto dummy_chunk = new uint8_t[dummy_size];
196 memset(dummy_chunk, 0xFF, dummy_size);
197 delete[] dummy_chunk;
198 } catch (bad_alloc&) {
199 delete[] current_chunk_; // The new may have succeeded
200 current_chunk_ = nullptr;
201 throw;
202 }
203
164204 data_chunks_.push_back(current_chunk_);
165205 used_samples_ = 0;
166206 unused_samples_ = chunk_size_ / unit_size_;
170210 sample_count_ += samples;
171211 }
172212
173 uint8_t* Segment::get_raw_samples(uint64_t start, uint64_t count) const
213 void Segment::get_raw_samples(uint64_t start, uint64_t count,
214 uint8_t* dest) const
174215 {
175216 assert(start < sample_count_);
176217 assert(start + count <= sample_count_);
177218 assert(count > 0);
178
179 lock_guard<recursive_mutex> lock(mutex_);
180
181 uint8_t* dest = new uint8_t[count * unit_size_];
219 assert(dest != nullptr);
220
221 lock_guard<recursive_mutex> lock(mutex_);
222
182223 uint8_t* dest_ptr = dest;
183224
184225 uint64_t chunk_num = (start * unit_size_) / chunk_size_;
198239 chunk_num++;
199240 chunk_offs = 0;
200241 }
201
202 return dest;
203 }
204
205 SegmentRawDataIterator* Segment::begin_raw_sample_iteration(uint64_t start)
206 {
207 SegmentRawDataIterator* it = new SegmentRawDataIterator;
242 }
243
244 SegmentDataIterator* Segment::begin_sample_iteration(uint64_t start)
245 {
246 SegmentDataIterator* it = new SegmentDataIterator;
208247
209248 assert(start < sample_count_);
210249
214253 it->chunk_num = (start * unit_size_) / chunk_size_;
215254 it->chunk_offs = (start * unit_size_) % chunk_size_;
216255 it->chunk = data_chunks_[it->chunk_num];
217 it->value = it->chunk + it->chunk_offs;
218256
219257 return it;
220258 }
221259
222 void Segment::continue_raw_sample_iteration(SegmentRawDataIterator* it, uint64_t increase)
223 {
224 // Fail gracefully if we are asked to deliver data we don't have
225 if (it->sample_index > sample_count_)
226 return;
227
260 void Segment::continue_sample_iteration(SegmentDataIterator* it, uint64_t increase)
261 {
228262 it->sample_index += increase;
229263 it->chunk_offs += (increase * unit_size_);
230264
233267 it->chunk_offs -= chunk_size_;
234268 it->chunk = data_chunks_[it->chunk_num];
235269 }
236
237 it->value = it->chunk + it->chunk_offs;
238 }
239
240 void Segment::end_raw_sample_iteration(SegmentRawDataIterator* it)
270 }
271
272 void Segment::end_sample_iteration(SegmentDataIterator* it)
241273 {
242274 delete it;
243275
249281 }
250282 }
251283
284 uint8_t* Segment::get_iterator_value(SegmentDataIterator* it)
285 {
286 assert(it->sample_index <= (sample_count_ - 1));
287
288 return (it->chunk + it->chunk_offs);
289 }
290
291 uint64_t Segment::get_iterator_valid_length(SegmentDataIterator* it)
292 {
293 assert(it->sample_index <= (sample_count_ - 1));
294
295 return ((chunk_size_ - it->chunk_offs) / unit_size_);
296 }
297
252298 } // namespace data
253299 } // namespace pv
2626 #include <thread>
2727 #include <vector>
2828
29 #include <QObject>
30
2931 using std::recursive_mutex;
3032 using std::vector;
3133
4850 typedef struct {
4951 uint64_t sample_index, chunk_num, chunk_offs;
5052 uint8_t* chunk;
51 uint8_t* value;
52 } SegmentRawDataIterator;
53 } SegmentDataIterator;
5354
54 class Segment
55 class Segment : public QObject
5556 {
57 Q_OBJECT
58
5659 private:
5760 static const uint64_t MaxChunkSize;
5861
5962 public:
60 Segment(uint64_t samplerate, unsigned int unit_size);
63 Segment(uint32_t segment_id, uint64_t samplerate, unsigned int unit_size);
6164
6265 virtual ~Segment();
6366
7073
7174 unsigned int unit_size() const;
7275
76 uint32_t segment_id() const;
77
78 void set_complete();
79 bool is_complete() const;
80
7381 void free_unused_memory();
7482
7583 protected:
7684 void append_single_sample(void *data);
7785 void append_samples(void *data, uint64_t samples);
78 uint8_t* get_raw_samples(uint64_t start, uint64_t count) const;
86 void get_raw_samples(uint64_t start, uint64_t count, uint8_t *dest) const;
7987
80 SegmentRawDataIterator* begin_raw_sample_iteration(uint64_t start);
81 void continue_raw_sample_iteration(SegmentRawDataIterator* it, uint64_t increase);
82 void end_raw_sample_iteration(SegmentRawDataIterator* it);
88 SegmentDataIterator* begin_sample_iteration(uint64_t start);
89 void continue_sample_iteration(SegmentDataIterator* it, uint64_t increase);
90 void end_sample_iteration(SegmentDataIterator* it);
91 uint8_t* get_iterator_value(SegmentDataIterator* it);
92 uint64_t get_iterator_valid_length(SegmentDataIterator* it);
8393
94 uint32_t segment_id_;
8495 mutable recursive_mutex mutex_;
8596 vector<uint8_t*> data_chunks_;
8697 uint8_t* current_chunk_;
92103 unsigned int unit_size_;
93104 int iterator_count_;
94105 bool mem_optimization_requested_;
106 bool is_complete_;
95107
96108 friend struct SegmentTest::SmallSize8Single;
97109 friend struct SegmentTest::MediumSize8Single;
2525 #include "signalbase.hpp"
2626 #include "signaldata.hpp"
2727
28 #include <QDebug>
29
2830 #include <pv/binding/decoder.hpp>
2931 #include <pv/session.hpp>
3032
3133 using std::dynamic_pointer_cast;
3234 using std::make_shared;
35 using std::out_of_range;
3336 using std::shared_ptr;
3437 using std::tie;
38 using std::unique_lock;
3539
3640 namespace pv {
3741 namespace data {
3842
39 const int SignalBase::ColourBGAlpha = 8 * 256 / 100;
43 const int SignalBase::ColorBGAlpha = 8 * 256 / 100;
44 const uint64_t SignalBase::ConversionBlockSize = 4096;
45 const uint32_t SignalBase::ConversionDelay = 1000; // 1 second
4046
4147 SignalBase::SignalBase(shared_ptr<sigrok::Channel> channel, ChannelType channel_type) :
4248 channel_(channel),
4349 channel_type_(channel_type),
44 conversion_type_(NoConversion)
50 conversion_type_(NoConversion),
51 min_value_(0),
52 max_value_(0)
4553 {
4654 if (channel_)
4755 internal_name_ = QString::fromStdString(channel_->name());
56
57 connect(&delayed_conversion_starter_, SIGNAL(timeout()),
58 this, SLOT(on_delayed_conversion_start()));
59 delayed_conversion_starter_.setSingleShot(true);
60 delayed_conversion_starter_.setInterval(ConversionDelay);
4861 }
4962
5063 SignalBase::~SignalBase()
5164 {
52 // Wait for the currently ongoing conversion to finish
53 if (conversion_thread_.joinable())
54 conversion_thread_.join();
65 stop_conversion();
5566 }
5667
5768 shared_ptr<sigrok::Channel> SignalBase::channel() const
6778 QString SignalBase::internal_name() const
6879 {
6980 return internal_name_;
81 }
82
83 QString SignalBase::display_name() const
84 {
85 if (name() != internal_name_)
86 return name() + " (" + internal_name_ + ")";
87 else
88 return name();
7089 }
7190
7291 void SignalBase::set_name(QString name)
99118
100119 unsigned int SignalBase::index() const
101120 {
102 return (channel_) ? channel_->index() : (unsigned int)-1;
103 }
104
105 QColor SignalBase::colour() const
106 {
107 return colour_;
108 }
109
110 void SignalBase::set_colour(QColor colour)
111 {
112 colour_ = colour;
113
114 bgcolour_ = colour;
115 bgcolour_.setAlpha(ColourBGAlpha);
116
117 colour_changed(colour);
118 }
119
120 QColor SignalBase::bgcolour() const
121 {
122 return bgcolour_;
121 return (channel_) ? channel_->index() : 0;
122 }
123
124 unsigned int SignalBase::logic_bit_index() const
125 {
126 if (channel_type_ == LogicChannel)
127 return channel_->index();
128 else
129 return 0;
130 }
131
132 QColor SignalBase::color() const
133 {
134 return color_;
135 }
136
137 void SignalBase::set_color(QColor color)
138 {
139 color_ = color;
140
141 bgcolor_ = color;
142 bgcolor_.setAlpha(ColorBGAlpha);
143
144 color_changed(color);
145 }
146
147 QColor SignalBase::bgcolor() const
148 {
149 return bgcolor_;
123150 }
124151
125152 void SignalBase::set_data(shared_ptr<pv::data::SignalData> data)
129156 this, SLOT(on_samples_cleared()));
130157 disconnect(data.get(), SIGNAL(samples_added(QObject*, uint64_t, uint64_t)),
131158 this, SLOT(on_samples_added(QObject*, uint64_t, uint64_t)));
159
160 if (channel_type_ == AnalogChannel) {
161 shared_ptr<Analog> analog = analog_data();
162 assert(analog);
163
164 disconnect(analog.get(), SIGNAL(min_max_changed(float, float)),
165 this, SLOT(on_min_max_changed(float, float)));
166 }
132167 }
133168
134169 data_ = data;
138173 this, SLOT(on_samples_cleared()));
139174 connect(data.get(), SIGNAL(samples_added(QObject*, uint64_t, uint64_t)),
140175 this, SLOT(on_samples_added(QObject*, uint64_t, uint64_t)));
176
177 if (channel_type_ == AnalogChannel) {
178 shared_ptr<Analog> analog = analog_data();
179 assert(analog);
180
181 connect(analog.get(), SIGNAL(min_max_changed(float, float)),
182 this, SLOT(on_min_max_changed(float, float)));
183 }
141184 }
142185 }
143186
158201 if (channel_type_ == LogicChannel)
159202 result = dynamic_pointer_cast<Logic>(data_);
160203
161 if (((conversion_type_ == A2LConversionByTreshold) ||
204 if (((conversion_type_ == A2LConversionByThreshold) ||
162205 (conversion_type_ == A2LConversionBySchmittTrigger)))
163206 result = dynamic_pointer_cast<Logic>(converted_data_);
164207
165208 return result;
166209 }
167210
211 bool SignalBase::segment_is_complete(uint32_t segment_id) const
212 {
213 bool result = true;
214
215 if (channel_type_ == AnalogChannel)
216 {
217 shared_ptr<Analog> data = dynamic_pointer_cast<Analog>(data_);
218 auto segments = data->analog_segments();
219 try {
220 result = segments.at(segment_id)->is_complete();
221 } catch (out_of_range&) {
222 // Do nothing
223 }
224 }
225
226 if (channel_type_ == LogicChannel)
227 {
228 shared_ptr<Logic> data = dynamic_pointer_cast<Logic>(data_);
229 auto segments = data->logic_segments();
230 try {
231 result = segments.at(segment_id)->is_complete();
232 } catch (out_of_range&) {
233 // Do nothing
234 }
235 }
236
237 return result;
238 }
239
240 bool SignalBase::has_samples() const
241 {
242 bool result = false;
243
244 if (channel_type_ == AnalogChannel)
245 {
246 shared_ptr<Analog> data = dynamic_pointer_cast<Analog>(data_);
247 if (data) {
248 auto segments = data->analog_segments();
249 if ((segments.size() > 0) && (segments.front()->get_sample_count() > 0))
250 result = true;
251 }
252 }
253
254 if (channel_type_ == LogicChannel)
255 {
256 shared_ptr<Logic> data = dynamic_pointer_cast<Logic>(data_);
257 if (data) {
258 auto segments = data->logic_segments();
259 if ((segments.size() > 0) && (segments.front()->get_sample_count() > 0))
260 result = true;
261 }
262 }
263
264 return result;
265 }
266
267 double SignalBase::get_samplerate() const
268 {
269 if (channel_type_ == AnalogChannel)
270 {
271 shared_ptr<Analog> data = dynamic_pointer_cast<Analog>(data_);
272 if (data)
273 return data->get_samplerate();
274 }
275
276 if (channel_type_ == LogicChannel)
277 {
278 shared_ptr<Logic> data = dynamic_pointer_cast<Logic>(data_);
279 if (data)
280 return data->get_samplerate();
281 }
282
283 // Default samplerate is 1 Hz
284 return 1.0;
285 }
286
287 SignalBase::ConversionType SignalBase::get_conversion_type() const
288 {
289 return conversion_type_;
290 }
291
168292 void SignalBase::set_conversion_type(ConversionType t)
169293 {
170294 if (conversion_type_ != NoConversion) {
171 // Wait for the currently ongoing conversion to finish
172 if (conversion_thread_.joinable())
173 conversion_thread_.join();
295 stop_conversion();
174296
175297 // Discard converted data
176298 converted_data_.reset();
299 samples_cleared();
177300 }
178301
179302 conversion_type_ = t;
180303
181 if ((channel_type_ == AnalogChannel) &&
182 ((conversion_type_ == A2LConversionByTreshold) ||
183 (conversion_type_ == A2LConversionBySchmittTrigger))) {
184
185 shared_ptr<Analog> analog_data = dynamic_pointer_cast<Analog>(data_);
186
187 if (analog_data->analog_segments().size() > 0) {
188 AnalogSegment *asegment = analog_data->analog_segments().front().get();
189
190 // Begin conversion of existing sample data
191 // TODO Support for multiple segments is missing
192 on_samples_added(asegment, 0, 0);
193 }
194 }
304 // Re-create an empty container
305 // so that the signal is recognized as providing logic data
306 // and thus can be assigned to a decoder
307 if (conversion_is_a2l())
308 if (!converted_data_)
309 converted_data_ = make_shared<Logic>(1); // Contains only one channel
310
311 start_conversion();
195312
196313 conversion_type_changed(t);
314 }
315
316 map<QString, QVariant> SignalBase::get_conversion_options() const
317 {
318 return conversion_options_;
319 }
320
321 bool SignalBase::set_conversion_option(QString key, QVariant value)
322 {
323 QVariant old_value;
324
325 auto key_iter = conversion_options_.find(key);
326 if (key_iter != conversion_options_.end())
327 old_value = key_iter->second;
328
329 conversion_options_[key] = value;
330
331 return (value != old_value);
332 }
333
334 vector<double> SignalBase::get_conversion_thresholds(const ConversionType t,
335 const bool always_custom) const
336 {
337 vector<double> result;
338 ConversionType conv_type = t;
339 ConversionPreset preset;
340
341 // Use currently active conversion if no conversion type was supplied
342 if (conv_type == NoConversion)
343 conv_type = conversion_type_;
344
345 if (always_custom)
346 preset = NoPreset;
347 else
348 preset = get_current_conversion_preset();
349
350 if (conv_type == A2LConversionByThreshold) {
351 double thr = 0;
352
353 if (preset == NoPreset) {
354 auto thr_iter = conversion_options_.find("threshold_value");
355 if (thr_iter != conversion_options_.end())
356 thr = (thr_iter->second).toDouble();
357 }
358
359 if (preset == DynamicPreset)
360 thr = (min_value_ + max_value_) * 0.5; // middle between min and max
361
362 if ((int)preset == 1) thr = 0.9;
363 if ((int)preset == 2) thr = 1.8;
364 if ((int)preset == 3) thr = 2.5;
365 if ((int)preset == 4) thr = 1.5;
366
367 result.push_back(thr);
368 }
369
370 if (conv_type == A2LConversionBySchmittTrigger) {
371 double thr_lo = 0, thr_hi = 0;
372
373 if (preset == NoPreset) {
374 auto thr_lo_iter = conversion_options_.find("threshold_value_low");
375 if (thr_lo_iter != conversion_options_.end())
376 thr_lo = (thr_lo_iter->second).toDouble();
377
378 auto thr_hi_iter = conversion_options_.find("threshold_value_high");
379 if (thr_hi_iter != conversion_options_.end())
380 thr_hi = (thr_hi_iter->second).toDouble();
381 }
382
383 if (preset == DynamicPreset) {
384 const double amplitude = max_value_ - min_value_;
385 const double center = min_value_ + (amplitude / 2);
386 thr_lo = center - (amplitude * 0.15); // 15% margin
387 thr_hi = center + (amplitude * 0.15); // 15% margin
388 }
389
390 if ((int)preset == 1) { thr_lo = 0.3; thr_hi = 1.2; }
391 if ((int)preset == 2) { thr_lo = 0.7; thr_hi = 2.5; }
392 if ((int)preset == 3) { thr_lo = 1.3; thr_hi = 3.7; }
393 if ((int)preset == 4) { thr_lo = 0.8; thr_hi = 2.0; }
394
395 result.push_back(thr_lo);
396 result.push_back(thr_hi);
397 }
398
399 return result;
400 }
401
402 vector< pair<QString, int> > SignalBase::get_conversion_presets() const
403 {
404 vector< pair<QString, int> > presets;
405
406 if (conversion_type_ == A2LConversionByThreshold) {
407 // Source: http://www.interfacebus.com/voltage_threshold.html
408 presets.emplace_back(tr("Signal average"), 0);
409 presets.emplace_back(tr("0.9V (for 1.8V CMOS)"), 1);
410 presets.emplace_back(tr("1.8V (for 3.3V CMOS)"), 2);
411 presets.emplace_back(tr("2.5V (for 5.0V CMOS)"), 3);
412 presets.emplace_back(tr("1.5V (for TTL)"), 4);
413 }
414
415 if (conversion_type_ == A2LConversionBySchmittTrigger) {
416 // Source: http://www.interfacebus.com/voltage_threshold.html
417 presets.emplace_back(tr("Signal average +/- 15%"), 0);
418 presets.emplace_back(tr("0.3V/1.2V (for 1.8V CMOS)"), 1);
419 presets.emplace_back(tr("0.7V/2.5V (for 3.3V CMOS)"), 2);
420 presets.emplace_back(tr("1.3V/3.7V (for 5.0V CMOS)"), 3);
421 presets.emplace_back(tr("0.8V/2.0V (for TTL)"), 4);
422 }
423
424 return presets;
425 }
426
427 SignalBase::ConversionPreset SignalBase::get_current_conversion_preset() const
428 {
429 auto preset = conversion_options_.find("preset");
430 if (preset != conversion_options_.end())
431 return (ConversionPreset)((preset->second).toInt());
432
433 return DynamicPreset;
434 }
435
436 void SignalBase::set_conversion_preset(ConversionPreset id)
437 {
438 conversion_options_["preset"] = (int)id;
197439 }
198440
199441 #ifdef ENABLE_DECODE
200442 bool SignalBase::is_decode_signal() const
201443 {
202 return (decoder_stack_ != nullptr);
203 }
204
205 shared_ptr<pv::data::DecoderStack> SignalBase::decoder_stack() const
206 {
207 return decoder_stack_;
208 }
209
210 void SignalBase::set_decoder_stack(shared_ptr<pv::data::DecoderStack>
211 decoder_stack)
212 {
213 decoder_stack_ = decoder_stack;
444 return (channel_type_ == DecodeChannel);
214445 }
215446 #endif
216447
218449 {
219450 settings.setValue("name", name());
220451 settings.setValue("enabled", enabled());
221 settings.setValue("colour", colour());
452 settings.setValue("color", color().rgba());
222453 settings.setValue("conversion_type", (int)conversion_type_);
454
455 settings.setValue("conv_options", (int)(conversion_options_.size()));
456 int i = 0;
457 for (auto& kvp : conversion_options_) {
458 settings.setValue(QString("conv_option%1_key").arg(i), kvp.first);
459 settings.setValue(QString("conv_option%1_value").arg(i), kvp.second);
460 i++;
461 }
223462 }
224463
225464 void SignalBase::restore_settings(QSettings &settings)
226465 {
227 set_name(settings.value("name").toString());
228 set_enabled(settings.value("enabled").toBool());
229 set_colour(settings.value("colour").value<QColor>());
230 set_conversion_type((ConversionType)settings.value("conversion_type").toInt());
231 }
232
233 uint8_t SignalBase::convert_a2l_threshold(float threshold, float value)
234 {
235 return (value >= threshold) ? 1 : 0;
236 }
237
238 uint8_t SignalBase::convert_a2l_schmitt_trigger(float lo_thr, float hi_thr,
239 float value, uint8_t &state)
240 {
241 if (value < lo_thr)
242 state = 0;
243 else if (value > hi_thr)
244 state = 1;
245
246 return state;
247 }
248
249 void SignalBase::conversion_thread_proc(QObject* segment, uint64_t start_sample,
250 uint64_t end_sample)
251 {
252 const uint64_t block_size = 4096;
253
254 // TODO Support for multiple segments is missing
255
256 if ((channel_type_ == AnalogChannel) &&
257 ((conversion_type_ == A2LConversionByTreshold) ||
258 (conversion_type_ == A2LConversionBySchmittTrigger))) {
259
260 AnalogSegment *asegment = qobject_cast<AnalogSegment*>(segment);
261
262 // Create the logic data container if needed
263 shared_ptr<Logic> logic_data;
264 if (!converted_data_) {
265 logic_data = make_shared<Logic>(1); // Contains only one channel
266 converted_data_ = logic_data;
267 } else
268 logic_data = dynamic_pointer_cast<Logic>(converted_data_);
269
270 // Create the initial logic data segment if needed
271 if (logic_data->segments().size() == 0) {
272 shared_ptr<LogicSegment> lsegment =
273 make_shared<LogicSegment>(*logic_data.get(), 1, asegment->samplerate());
274 logic_data->push_segment(lsegment);
275 }
276
277 LogicSegment *lsegment = dynamic_cast<LogicSegment*>(logic_data->segments().front().get());
278
279 // start_sample=end_sample=0 means we need to figure out the unprocessed range
280 if ((start_sample == 0) && (end_sample == 0)) {
281 start_sample = lsegment->get_sample_count();
282 end_sample = asegment->get_sample_count();
283 }
284
285 if (start_sample == end_sample)
286 return; // Nothing to do
287
288 float min_v, max_v;
289 tie(min_v, max_v) = asegment->get_min_max();
290
291 vector<uint8_t> lsamples;
292 lsamples.reserve(block_size);
293
466 if (settings.contains("name"))
467 set_name(settings.value("name").toString());
468
469 if (settings.contains("enabled"))
470 set_enabled(settings.value("enabled").toBool());
471
472 if (settings.contains("color")) {
473 QVariant value = settings.value("color");
474
475 // Workaround for Qt QColor serialization bug on OSX
476 if ((QMetaType::Type)(value.type()) == QMetaType::QColor)
477 set_color(value.value<QColor>());
478 else
479 set_color(QColor::fromRgba(value.value<uint32_t>()));
480
481 // A color with an alpha value of 0 makes the signal marker invisible
482 if (color() == QColor(0, 0, 0, 0))
483 set_color(Qt::gray);
484 }
485
486 if (settings.contains("conversion_type"))
487 set_conversion_type((ConversionType)settings.value("conversion_type").toInt());
488
489 int conv_options = 0;
490 if (settings.contains("conv_options"))
491 conv_options = settings.value("conv_options").toInt();
492
493 if (conv_options)
494 for (int i = 0; i < conv_options; i++) {
495 const QString key_id = QString("conv_option%1_key").arg(i);
496 const QString value_id = QString("conv_option%1_value").arg(i);
497
498 if (settings.contains(key_id) && settings.contains(value_id))
499 conversion_options_[settings.value(key_id).toString()] =
500 settings.value(value_id);
501 }
502 }
503
504 bool SignalBase::conversion_is_a2l() const
505 {
506 return ((channel_type_ == AnalogChannel) &&
507 ((conversion_type_ == A2LConversionByThreshold) ||
508 (conversion_type_ == A2LConversionBySchmittTrigger)));
509 }
510
511 void SignalBase::convert_single_segment_range(AnalogSegment *asegment,
512 LogicSegment *lsegment, uint64_t start_sample, uint64_t end_sample)
513 {
514 if (end_sample > start_sample) {
515 tie(min_value_, max_value_) = asegment->get_min_max();
516
517 // Create sigrok::Analog instance
518 float *asamples = new float[ConversionBlockSize];
519 uint8_t *lsamples = new uint8_t[ConversionBlockSize];
520
521 vector<shared_ptr<sigrok::Channel> > channels;
522 channels.push_back(channel_);
523
524 vector<const sigrok::QuantityFlag*> mq_flags;
525 const sigrok::Quantity * const mq = sigrok::Quantity::VOLTAGE;
526 const sigrok::Unit * const unit = sigrok::Unit::VOLT;
527
528 shared_ptr<sigrok::Packet> packet =
529 Session::sr_context->create_analog_packet(channels,
530 asamples, ConversionBlockSize, mq, unit, mq_flags);
531
532 shared_ptr<sigrok::Analog> analog =
533 dynamic_pointer_cast<sigrok::Analog>(packet->payload());
534
535 // Convert
294536 uint64_t i = start_sample;
295537
296 if (conversion_type_ == A2LConversionByTreshold) {
297 const float threshold = (min_v + max_v) * 0.5; // middle between min and max
538 if (conversion_type_ == A2LConversionByThreshold) {
539 const double threshold = get_conversion_thresholds()[0];
298540
299541 // Convert as many sample blocks as we can
300 while ((end_sample - i) > block_size) {
301 const float* asamples = asegment->get_samples(i, i + block_size);
302 for (uint32_t j = 0; j < block_size; j++)
303 lsamples.push_back(convert_a2l_threshold(threshold, asamples[j]));
304 lsegment->append_payload(lsamples.data(), lsamples.size());
305 i += block_size;
306 lsamples.clear();
307 delete[] asamples;
542 while ((end_sample - i) > ConversionBlockSize) {
543 asegment->get_samples(i, i + ConversionBlockSize, asamples);
544
545 shared_ptr<sigrok::Logic> logic =
546 analog->get_logic_via_threshold(threshold, lsamples);
547
548 lsegment->append_payload(logic->data_pointer(), logic->data_length());
549 samples_added(lsegment->segment_id(), i, i + ConversionBlockSize);
550 i += ConversionBlockSize;
308551 }
309552
310 // Convert remaining samples
311 const float* asamples = asegment->get_samples(i, end_sample);
312 for (uint32_t j = 0; j < (end_sample - i); j++)
313 lsamples.push_back(convert_a2l_threshold(threshold, asamples[j]));
314 lsegment->append_payload(lsamples.data(), lsamples.size());
315 delete[] asamples;
316
317 samples_added(lsegment, start_sample, end_sample);
553 // Re-create sigrok::Analog and convert remaining samples
554 packet = Session::sr_context->create_analog_packet(channels,
555 asamples, end_sample - i, mq, unit, mq_flags);
556
557 analog = dynamic_pointer_cast<sigrok::Analog>(packet->payload());
558
559 asegment->get_samples(i, end_sample, asamples);
560 shared_ptr<sigrok::Logic> logic =
561 analog->get_logic_via_threshold(threshold, lsamples);
562 lsegment->append_payload(logic->data_pointer(), logic->data_length());
563 samples_added(lsegment->segment_id(), i, end_sample);
318564 }
319565
320566 if (conversion_type_ == A2LConversionBySchmittTrigger) {
321 const float amplitude = max_v - min_v;
322 const float lo_thr = min_v + (amplitude * 0.1); // 10% above min
323 const float hi_thr = max_v - (amplitude * 0.1); // 10% below max
567 const vector<double> thresholds = get_conversion_thresholds();
568 const double lo_thr = thresholds[0];
569 const double hi_thr = thresholds[1];
570
324571 uint8_t state = 0; // TODO Use value of logic sample n-1 instead of 0
325572
326573 // Convert as many sample blocks as we can
327 while ((end_sample - i) > block_size) {
328 const float* asamples = asegment->get_samples(i, i + block_size);
329 for (uint32_t j = 0; j < block_size; j++)
330 lsamples.push_back(convert_a2l_schmitt_trigger(lo_thr, hi_thr, asamples[j], state));
331 lsegment->append_payload(lsamples.data(), lsamples.size());
332 i += block_size;
333 lsamples.clear();
334 delete[] asamples;
574 while ((end_sample - i) > ConversionBlockSize) {
575 asegment->get_samples(i, i + ConversionBlockSize, asamples);
576
577 shared_ptr<sigrok::Logic> logic =
578 analog->get_logic_via_schmitt_trigger(lo_thr, hi_thr,
579 &state, lsamples);
580
581 lsegment->append_payload(logic->data_pointer(), logic->data_length());
582 samples_added(lsegment->segment_id(), i, i + ConversionBlockSize);
583 i += ConversionBlockSize;
335584 }
336585
337 // Convert remaining samples
338 const float* asamples = asegment->get_samples(i, end_sample);
339 for (uint32_t j = 0; j < (end_sample - i); j++)
340 lsamples.push_back(convert_a2l_schmitt_trigger(lo_thr, hi_thr, asamples[j], state));
341 lsegment->append_payload(lsamples.data(), lsamples.size());
342 delete[] asamples;
343
344 samples_added(lsegment, start_sample, end_sample);
345 }
346 }
347 }
348
349 void SignalBase::on_samples_cleared()
350 {
586 // Re-create sigrok::Analog and convert remaining samples
587 packet = Session::sr_context->create_analog_packet(channels,
588 asamples, end_sample - i, mq, unit, mq_flags);
589
590 analog = dynamic_pointer_cast<sigrok::Analog>(packet->payload());
591
592 asegment->get_samples(i, end_sample, asamples);
593 shared_ptr<sigrok::Logic> logic =
594 analog->get_logic_via_schmitt_trigger(lo_thr, hi_thr,
595 &state, lsamples);
596 lsegment->append_payload(logic->data_pointer(), logic->data_length());
597 samples_added(lsegment->segment_id(), i, end_sample);
598 }
599
600 // If acquisition is ongoing, start-/endsample may have changed
601 end_sample = asegment->get_sample_count();
602
603 delete[] lsamples;
604 delete[] asamples;
605 }
606 }
607
608 void SignalBase::convert_single_segment(AnalogSegment *asegment, LogicSegment *lsegment)
609 {
610 uint64_t start_sample, end_sample, old_end_sample;
611 start_sample = end_sample = 0;
612 bool complete_state, old_complete_state;
613
614 start_sample = lsegment->get_sample_count();
615 end_sample = asegment->get_sample_count();
616 complete_state = asegment->is_complete();
617
618 // Don't do anything if the segment is still being filled and the sample count is too small
619 if ((!complete_state) && (end_sample - start_sample < ConversionBlockSize))
620 return;
621
622 do {
623 convert_single_segment_range(asegment, lsegment, start_sample, end_sample);
624
625 old_end_sample = end_sample;
626 old_complete_state = complete_state;
627
628 start_sample = lsegment->get_sample_count();
629 end_sample = asegment->get_sample_count();
630 complete_state = asegment->is_complete();
631
632 // If the segment has been incomplete when we were called and has been
633 // completed in the meanwhile, we convert the remaining samples as well.
634 // Also, if a sufficient number of samples was added in the meanwhile,
635 // we do another round of sample conversion.
636 } while ((complete_state != old_complete_state) ||
637 (end_sample - old_end_sample >= ConversionBlockSize));
638 }
639
640 void SignalBase::conversion_thread_proc()
641 {
642 shared_ptr<Analog> analog_data;
643
644 if (conversion_is_a2l()) {
645 analog_data = dynamic_pointer_cast<Analog>(data_);
646
647 if (analog_data->analog_segments().size() == 0) {
648 unique_lock<mutex> input_lock(conversion_input_mutex_);
649 conversion_input_cond_.wait(input_lock);
650 }
651
652 } else
653 // Currently, we only handle A2L conversions
654 return;
655
656 // If we had to wait for input data, we may have been notified to terminate
657 if (conversion_interrupt_)
658 return;
659
660 uint32_t segment_id = 0;
661
662 AnalogSegment *asegment = analog_data->analog_segments().front().get();
663 assert(asegment);
664
665 const shared_ptr<Logic> logic_data = dynamic_pointer_cast<Logic>(converted_data_);
666 assert(logic_data);
667
668 // Create the initial logic data segment if needed
669 if (logic_data->logic_segments().size() == 0) {
670 shared_ptr<LogicSegment> new_segment =
671 make_shared<LogicSegment>(*logic_data.get(), 0, 1, asegment->samplerate());
672 logic_data->push_segment(new_segment);
673 }
674
675 LogicSegment *lsegment = logic_data->logic_segments().front().get();
676 assert(lsegment);
677
678 do {
679 convert_single_segment(asegment, lsegment);
680
681 // Only advance to next segment if the current input segment is complete
682 if (asegment->is_complete() &&
683 analog_data->analog_segments().size() > logic_data->logic_segments().size()) {
684 // There are more segments to process
685 segment_id++;
686
687 try {
688 asegment = analog_data->analog_segments().at(segment_id).get();
689 } catch (out_of_range&) {
690 qDebug() << "Conversion error for" << name() << ": no analog segment" \
691 << segment_id << ", segments size is" << analog_data->analog_segments().size();
692 return;
693 }
694
695 shared_ptr<LogicSegment> new_segment = make_shared<LogicSegment>(
696 *logic_data.get(), segment_id, 1, asegment->samplerate());
697 logic_data->push_segment(new_segment);
698
699 lsegment = logic_data->logic_segments().back().get();
700 } else {
701 // No more samples/segments to process, wait for data or interrupt
702 if (!conversion_interrupt_) {
703 unique_lock<mutex> input_lock(conversion_input_mutex_);
704 conversion_input_cond_.wait(input_lock);
705 }
706 }
707 } while (!conversion_interrupt_);
708 }
709
710 void SignalBase::start_conversion(bool delayed_start)
711 {
712 if (delayed_start) {
713 delayed_conversion_starter_.start();
714 return;
715 }
716
717 stop_conversion();
718
351719 if (converted_data_)
352720 converted_data_->clear();
721 samples_cleared();
722
723 conversion_interrupt_ = false;
724 conversion_thread_ = std::thread(
725 &SignalBase::conversion_thread_proc, this);
726 }
727
728 void SignalBase::stop_conversion()
729 {
730 // Stop conversion so we can restart it from the beginning
731 conversion_interrupt_ = true;
732 conversion_input_cond_.notify_one();
733 if (conversion_thread_.joinable())
734 conversion_thread_.join();
735 }
736
737 void SignalBase::on_samples_cleared()
738 {
739 if (converted_data_)
740 converted_data_->clear();
353741
354742 samples_cleared();
355743 }
358746 uint64_t end_sample)
359747 {
360748 if (conversion_type_ != NoConversion) {
361
362 // Wait for the currently ongoing conversion to finish
363 if (conversion_thread_.joinable())
364 conversion_thread_.join();
365
366 conversion_thread_ = std::thread(
367 &SignalBase::conversion_thread_proc, this,
368 segment, start_sample, end_sample);
369 }
370
371 samples_added(segment, start_sample, end_sample);
749 if (conversion_thread_.joinable()) {
750 // Notify the conversion thread since it's running
751 conversion_input_cond_.notify_one();
752 } else {
753 // Start the conversion thread unless the delay timer is running
754 if (!delayed_conversion_starter_.isActive())
755 start_conversion();
756 }
757 }
758
759 data::Segment* s = qobject_cast<data::Segment*>(segment);
760 samples_added(s->segment_id(), start_sample, end_sample);
761 }
762
763 void SignalBase::on_min_max_changed(float min, float max)
764 {
765 // Restart conversion if one is enabled and uses a calculated threshold
766 if ((conversion_type_ != NoConversion) &&
767 (get_current_conversion_preset() == DynamicPreset))
768 start_conversion(true);
769
770 min_max_changed(min, max);
372771 }
373772
374773 void SignalBase::on_capture_state_changed(int state)
375774 {
376 return;
377 if (state == Session::Stopped) {
378 // Make sure that all data is converted
379
380 if ((channel_type_ == AnalogChannel) &&
381 ((conversion_type_ == A2LConversionByTreshold) ||
382 (conversion_type_ == A2LConversionBySchmittTrigger))) {
383
384 shared_ptr<Analog> analog_data = dynamic_pointer_cast<Analog>(data_);
385
386 if (analog_data->analog_segments().size() > 0) {
387 // TODO Support for multiple segments is missing
388 AnalogSegment *asegment = analog_data->analog_segments().front().get();
389
390 if (conversion_thread_.joinable())
391 conversion_thread_.join();
392
393 conversion_thread_ = std::thread(
394 &SignalBase::conversion_thread_proc, this, asegment, 0, 0);
395 }
396 }
397 }
775 if (state == Session::Running) {
776 // Restart conversion if one is enabled
777 if (conversion_type_ != NoConversion)
778 start_conversion();
779 }
780 }
781
782 void SignalBase::on_delayed_conversion_start()
783 {
784 start_conversion();
398785 }
399786
400787 } // namespace data
2020 #ifndef PULSEVIEW_PV_DATA_SIGNALBASE_HPP
2121 #define PULSEVIEW_PV_DATA_SIGNALBASE_HPP
2222
23 #include <atomic>
24 #include <condition_variable>
2325 #include <thread>
26 #include <vector>
2427
2528 #include <QColor>
2629 #include <QObject>
2730 #include <QSettings>
2831 #include <QString>
32 #include <QTimer>
33 #include <QVariant>
2934
3035 #include <libsigrokcxx/libsigrokcxx.hpp>
3136
37 using std::atomic;
38 using std::condition_variable;
39 using std::map;
40 using std::mutex;
41 using std::pair;
3242 using std::shared_ptr;
43 using std::vector;
3344
3445 namespace sigrok {
3546 class Channel;
3950 namespace data {
4051
4152 class Analog;
53 class AnalogSegment;
4254 class DecoderStack;
4355 class Logic;
56 class LogicSegment;
4457 class SignalData;
4558
4659 class SignalBase : public QObject
4962
5063 public:
5164 enum ChannelType {
52 AnalogChannel = 1,
53 LogicChannel,
54 DecodeChannel,
55 A2LChannel, // Analog converted to logic, joint representation
56 MathChannel
65 AnalogChannel = 1, ///< Analog data
66 LogicChannel, ///< Logic data
67 DecodeChannel, ///< Protocol Decoder channel using libsigrokdecode
68 MathChannel ///< Virtual channel generated by math operations
5769 };
5870
5971 enum ConversionType {
6072 NoConversion = 0,
61 A2LConversionByTreshold = 1,
73 A2LConversionByThreshold = 1,
6274 A2LConversionBySchmittTrigger = 2
6375 };
6476
77 /**
78 * Conversion presets range from -1 to n, where 1..n are dependent on
79 * the conversion these presets apply to. -1 and 0 have fixed meanings,
80 * however.
81 */
82 enum ConversionPreset {
83 NoPreset = -1, ///< Conversion uses custom values
84 DynamicPreset = 0 ///< Conversion uses calculated values
85 };
86
6587 private:
66 static const int ColourBGAlpha;
88 static const int ColorBGAlpha;
89 static const uint64_t ConversionBlockSize;
90 static const uint32_t ConversionDelay;
6791
6892 public:
6993 SignalBase(shared_ptr<sigrok::Channel> channel, ChannelType channel_type);
92116 ChannelType type() const;
93117
94118 /**
95 * Gets the index number of this channel.
119 * Gets the index number of this channel, i.e. a unique ID assigned by
120 * the device driver.
96121 */
97122 unsigned int index() const;
98123
99124 /**
125 * Returns which bit of a given sample for this signal represents the
126 * signal itself. This is relevant for compound signals like logic,
127 * rather meaningless for everything else but provided in case there
128 * is a conversion active that provides a digital signal using bit #0.
129 */
130 unsigned int logic_bit_index() const;
131
132 /**
100133 * Gets the name of this signal.
101134 */
102135 QString name() const;
107140 QString internal_name() const;
108141
109142 /**
143 * Produces a string for this signal that can be used for display,
144 * i.e. it contains one or both of the signal/internal names.
145 */
146 QString display_name() const;
147
148 /**
110149 * Sets the name of the signal.
111150 */
112151 virtual void set_name(QString name);
113152
114153 /**
115 * Get the colour of the signal.
116 */
117 QColor colour() const;
118
119 /**
120 * Set the colour of the signal.
121 */
122 void set_colour(QColor colour);
123
124 /**
125 * Get the background colour of the signal.
126 */
127 QColor bgcolour() const;
154 * Get the color of the signal.
155 */
156 QColor color() const;
157
158 /**
159 * Set the color of the signal.
160 */
161 void set_color(QColor color);
162
163 /**
164 * Get the background color of the signal.
165 */
166 QColor bgcolor() const;
128167
129168 /**
130169 * Sets the internal data object.
142181 shared_ptr<pv::data::Logic> logic_data() const;
143182
144183 /**
184 * Determines whether a given segment is complete (i.e. end-of-frame has
185 * been seen). It only considers the original data, not the converted data.
186 */
187 bool segment_is_complete(uint32_t segment_id) const;
188
189 /**
190 * Determines whether this signal has any sample data at all.
191 */
192 bool has_samples() const;
193
194 /**
195 * Returns the sample rate for this signal.
196 */
197 double get_samplerate() const;
198
199 /**
200 * Queries the kind of conversion performed on this channel.
201 */
202 ConversionType get_conversion_type() const;
203
204 /**
145205 * Changes the kind of conversion performed on this channel.
206 *
207 * Restarts the conversion.
146208 */
147209 void set_conversion_type(ConversionType t);
210
211 /**
212 * Returns all currently known conversion options
213 */
214 map<QString, QVariant> get_conversion_options() const;
215
216 /**
217 * Sets the value of a particular conversion option
218 * Note: it is not checked whether the option is valid for the
219 * currently conversion. If it's not, it will be silently ignored.
220 *
221 * Does not restart the conversion.
222 *
223 * @return true if the value is different from before, false otherwise
224 */
225 bool set_conversion_option(QString key, QVariant value);
226
227 /**
228 * Returns the threshold(s) used for conversions, if applicable.
229 * The resulting thresholds are given for the chosen conversion, so you
230 * can query thresholds also for conversions which aren't currently active.
231 *
232 * If you want the thresholds for the currently active conversion,
233 * call it either with NoConversion or no parameter.
234 *
235 * @param t the type of conversion to obtain the thresholds for, leave
236 * empty or use NoConversion if you want to query the currently
237 * used conversion
238 *
239 * @param always_custom ignore the currently selected preset and always
240 * return the custom values for this conversion, using 0 if those
241 * aren't set
242 *
243 * @return a list of threshold(s) used by the chosen conversion
244 */
245 vector<double> get_conversion_thresholds(
246 const ConversionType t = NoConversion, const bool always_custom=false) const;
247
248 /**
249 * Provides all conversion presets available for the currently active
250 * conversion.
251 *
252 * @return a list of description/ID pairs for each preset
253 */
254 vector<pair<QString, int> > get_conversion_presets() const;
255
256 /**
257 * Determines the ID of the currently used conversion preset, which is only
258 * valid for the currently available conversion presets. It is therefore
259 * suggested to call @ref get_conversion_presets right before calling this.
260 *
261 * @return the ID of the currently used conversion preset. -1 if no preset
262 * is used. In that case, a user setting is used instead.
263 */
264 ConversionPreset get_current_conversion_preset() const;
265
266 /**
267 * Sets the conversion preset to be used.
268 *
269 * Does not restart the conversion.
270 *
271 * @param id the id of the preset to use
272 */
273 void set_conversion_preset(ConversionPreset id);
148274
149275 #ifdef ENABLE_DECODE
150276 bool is_decode_signal() const;
151
152 shared_ptr<pv::data::DecoderStack> decoder_stack() const;
153
154 void set_decoder_stack(shared_ptr<pv::data::DecoderStack> decoder_stack);
155277 #endif
156278
157 void save_settings(QSettings &settings) const;
158
159 void restore_settings(QSettings &settings);
279 virtual void save_settings(QSettings &settings) const;
280
281 virtual void restore_settings(QSettings &settings);
282
283 void start_conversion(bool delayed_start=false);
160284
161285 private:
286 bool conversion_is_a2l() const;
287
162288 uint8_t convert_a2l_threshold(float threshold, float value);
163289 uint8_t convert_a2l_schmitt_trigger(float lo_thr, float hi_thr,
164290 float value, uint8_t &state);
165291
166 void conversion_thread_proc(QObject* segment, uint64_t start_sample,
167 uint64_t end_sample);
292 void convert_single_segment_range(AnalogSegment *asegment,
293 LogicSegment *lsegment, uint64_t start_sample, uint64_t end_sample);
294 void convert_single_segment(pv::data::AnalogSegment *asegment,
295 pv::data::LogicSegment *lsegment);
296 void conversion_thread_proc();
297
298 void stop_conversion();
168299
169300 Q_SIGNALS:
170301 void enabled_changed(const bool &value);
171302
172303 void name_changed(const QString &name);
173304
174 void colour_changed(const QColor &colour);
305 void color_changed(const QColor &color);
175306
176307 void conversion_type_changed(const ConversionType t);
177308
178309 void samples_cleared();
179310
180 void samples_added(QObject* segment, uint64_t start_sample,
311 void samples_added(uint64_t segment_id, uint64_t start_sample,
181312 uint64_t end_sample);
313
314 void min_max_changed(float min, float max);
182315
183316 private Q_SLOTS:
184317 void on_samples_cleared();
186319 void on_samples_added(QObject* segment, uint64_t start_sample,
187320 uint64_t end_sample);
188321
322 void on_min_max_changed(float min, float max);
323
189324 void on_capture_state_changed(int state);
190325
191 private:
326 void on_delayed_conversion_start();
327
328 protected:
192329 shared_ptr<sigrok::Channel> channel_;
193330 ChannelType channel_type_;
194331 shared_ptr<pv::data::SignalData> data_;
195332 shared_ptr<pv::data::SignalData> converted_data_;
196 int conversion_type_;
197
198 #ifdef ENABLE_DECODE
199 shared_ptr<pv::data::DecoderStack> decoder_stack_;
200 #endif
333 ConversionType conversion_type_;
334 map<QString, QVariant> conversion_options_;
335
336 float min_value_, max_value_;
201337
202338 std::thread conversion_thread_;
339 atomic<bool> conversion_interrupt_;
340 mutex conversion_input_mutex_;
341 condition_variable conversion_input_cond_;
342 QTimer delayed_conversion_starter_;
203343
204344 QString internal_name_, name_;
205 QColor colour_, bgcolour_;
345 QColor color_, bgcolor_;
206346 };
207347
208348 } // namespace data
4444 public:
4545 virtual vector< shared_ptr<Segment> > segments() const = 0;
4646
47 virtual uint32_t get_segment_count() const = 0;
48
4749 virtual void clear() = 0;
4850
4951 virtual uint64_t max_sample_count() const = 0;
52
53 virtual double get_samplerate() const = 0;
5054 };
5155
5256 } // namespace data
2929 #include <libsigrokcxx/libsigrokcxx.hpp>
3030
3131 #include <QApplication>
32 #include <QDebug>
3233 #include <QObject>
3334 #include <QProgressDialog>
3435
3536 #include <boost/filesystem.hpp>
3637
3738 #include <pv/devices/hardwaredevice.hpp>
39 #include <pv/util.hpp>
3840
3941 using std::bind;
4042 using std::list;
4446 using std::shared_ptr;
4547 using std::string;
4648 using std::unique_ptr;
49 using std::vector;
4750
4851 using Glib::VariantBase;
4952
5356
5457 namespace pv {
5558
56 DeviceManager::DeviceManager(shared_ptr<Context> context) :
59 DeviceManager::DeviceManager(shared_ptr<Context> context,
60 std::string driver, bool do_scan) :
5761 context_(context)
5862 {
5963 unique_ptr<QProgressDialog> progress(new QProgressDialog("",
60 QObject::tr("Cancel"), 0, context->drivers().size()));
64 QObject::tr("Cancel"), 0, context->drivers().size() + 1));
6165 progress->setWindowModality(Qt::WindowModal);
6266 progress->setMinimumDuration(1); // To show the dialog immediately
6367
6468 int entry_num = 1;
6569
66 for (auto entry : context->drivers()) {
70 /*
71 * Check the presence of an optional user spec for device scans.
72 * Determine the driver name and options (in generic format) when
73 * applicable.
74 */
75 std::string user_name;
76 vector<std::string> user_opts;
77 if (!driver.empty()) {
78 user_opts = pv::util::split_string(driver, ":");
79 user_name = user_opts.front();
80 user_opts.erase(user_opts.begin());
81 }
82
83 /*
84 * Scan for devices. No specific options apply here, this is
85 * best effort auto detection.
86 */
87 for (auto& entry : context->drivers()) {
88 if (!do_scan)
89 break;
90
91 // Skip drivers we won't scan anyway
92 if (!driver_supported(entry.second))
93 continue;
94
6795 progress->setLabelText(QObject::tr("Scanning for %1...")
6896 .arg(QString::fromStdString(entry.first)));
6997
70 /**
71 * We currently only support devices that can deliver
72 * samples at a fixed samplerate i.e. oscilloscopes and
73 * logic analysers.
74 * @todo Add support for non-monotonic devices i.e. DMMs
75 * and sensors.
76 */
77 const auto keys = (entry.second)->config_keys();
78
79 bool supported_device = keys.count(ConfigKey::LOGIC_ANALYZER) |
80 keys.count(ConfigKey::OSCILLOSCOPE);
81
82 if (supported_device)
83 driver_scan(entry.second, map<const ConfigKey *, VariantBase>());
98 if (entry.first == user_name)
99 continue;
100 driver_scan(entry.second, map<const ConfigKey *, VariantBase>());
84101
85102 progress->setValue(entry_num++);
86103 QApplication::processEvents();
87104 if (progress->wasCanceled())
88105 break;
89106 }
107
108 /*
109 * Optionally run another scan with potentially more specific
110 * options when requested by the user. This is motivated by
111 * several different uses: It can find devices that are not
112 * covered by the above auto detection (UART, TCP). It can
113 * prefer one out of multiple found devices, and have this
114 * device pre-selected for new sessions upon user's request.
115 */
116 user_spec_device_.reset();
117 if (!driver.empty()) {
118 shared_ptr<sigrok::Driver> scan_drv;
119 map<const ConfigKey *, VariantBase> scan_opts;
120
121 /*
122 * Lookup the device driver name.
123 */
124 map<string, shared_ptr<Driver>> drivers = context->drivers();
125 auto entry = drivers.find(user_name);
126 scan_drv = (entry != drivers.end()) ? entry->second : nullptr;
127
128 /*
129 * Convert generic string representation of options
130 * to the driver specific data types.
131 */
132 if (scan_drv && !user_opts.empty()) {
133 auto drv_opts = scan_drv->scan_options();
134 scan_opts = drive_scan_options(user_opts, drv_opts);
135 }
136
137 /*
138 * Run another scan for the specified driver, passing
139 * user provided scan options this time.
140 */
141 list< shared_ptr<devices::HardwareDevice> > found;
142 if (scan_drv) {
143 found = driver_scan(scan_drv, scan_opts);
144 if (!found.empty())
145 user_spec_device_ = found.front();
146 }
147 }
148 progress->setValue(entry_num++);
90149 }
91150
92151 const shared_ptr<sigrok::Context>& DeviceManager::context() const
103162 DeviceManager::devices() const
104163 {
105164 return devices_;
165 }
166
167 /**
168 * Get the device that was detected with user provided scan options.
169 */
170 shared_ptr<devices::HardwareDevice>
171 DeviceManager::user_spec_device() const
172 {
173 return user_spec_device_;
174 }
175
176 /**
177 * Convert generic options to data types that are specific to Driver::scan().
178 *
179 * @param[in] user_spec Vector of tokenized words, string format.
180 * @param[in] driver_opts Driver's scan options, result of Driver::scan_options().
181 *
182 * @return Map of options suitable for Driver::scan().
183 */
184 map<const ConfigKey *, Glib::VariantBase>
185 DeviceManager::drive_scan_options(vector<string> user_spec,
186 set<const ConfigKey *> driver_opts)
187 {
188 map<const ConfigKey *, Glib::VariantBase> result;
189
190 for (auto& entry : user_spec) {
191 /*
192 * Split key=value specs. Accept entries without separator
193 * (for simplified boolean specifications).
194 */
195 string key, val;
196 size_t pos = entry.find("=");
197 if (pos == std::string::npos) {
198 key = entry;
199 val = "";
200 } else {
201 key = entry.substr(0, pos);
202 val = entry.substr(pos + 1);
203 }
204
205 /*
206 * Skip user specifications that are not a member of the
207 * driver's set of supported options. Have the text format
208 * input spec converted to the required driver specific type.
209 */
210 const ConfigKey *cfg;
211 try {
212 cfg = ConfigKey::get_by_identifier(key);
213 if (!cfg)
214 continue;
215 if (driver_opts.find(cfg) == driver_opts.end())
216 continue;
217 } catch (...) {
218 continue;
219 }
220 result[cfg] = cfg->parse_string(val);
221 }
222
223 return result;
224 }
225
226 bool DeviceManager::driver_supported(shared_ptr<Driver> driver) const
227 {
228 /*
229 * We currently only support devices that can deliver samples at
230 * a fixed samplerate (i.e. oscilloscopes and logic analysers).
231 *
232 * @todo Add support for non-monotonic devices (DMMs, sensors, etc).
233 */
234 const auto keys = driver->config_keys();
235
236 return keys.count(ConfigKey::LOGIC_ANALYZER) | keys.count(ConfigKey::OSCILLOSCOPE);
106237 }
107238
108239 list< shared_ptr<devices::HardwareDevice> >
112243 list< shared_ptr<devices::HardwareDevice> > driver_devices;
113244
114245 assert(driver);
246
247 if (!driver_supported(driver))
248 return driver_devices;
115249
116250 // Remove any device instances from this driver from the device
117251 // list. They will not be valid after the scan.
118252 devices_.remove_if([&](shared_ptr<devices::HardwareDevice> device) {
119253 return device->hardware_device()->driver() == driver; });
120254
121 // Do the scan
122 auto devices = driver->scan(drvopts);
123
124 // Add the scanned devices to the main list, set display names and sort.
125 for (shared_ptr<sigrok::HardwareDevice> device : devices) {
126 const shared_ptr<devices::HardwareDevice> d(
127 new devices::HardwareDevice(context_, device));
128 driver_devices.push_back(d);
129 }
130
131 devices_.insert(devices_.end(), driver_devices.begin(),
132 driver_devices.end());
133 devices_.sort(bind(&DeviceManager::compare_devices, this, _1, _2));
134 driver_devices.sort(bind(
135 &DeviceManager::compare_devices, this, _1, _2));
255 try {
256 // Do the scan
257 auto devices = driver->scan(drvopts);
258
259 // Add the scanned devices to the main list, set display names and sort.
260 for (shared_ptr<sigrok::HardwareDevice>& device : devices) {
261 const shared_ptr<devices::HardwareDevice> d(
262 new devices::HardwareDevice(context_, device));
263 driver_devices.push_back(d);
264 }
265
266 devices_.insert(devices_.end(), driver_devices.begin(),
267 driver_devices.end());
268 devices_.sort(bind(&DeviceManager::compare_devices, this, _1, _2));
269 driver_devices.sort(bind(
270 &DeviceManager::compare_devices, this, _1, _2));
271
272 } catch (const sigrok::Error &e) {
273 qWarning() << QApplication::tr("Error when scanning device driver '%1': %2").
274 arg(QString::fromStdString(driver->name()), e.what());
275 }
136276
137277 return driver_devices;
138278 }
2222 #include <list>
2323 #include <map>
2424 #include <memory>
25 #include <set>
2526 #include <string>
27 #include <vector>
2628
2729 using std::list;
2830 using std::map;
31 using std::set;
2932 using std::shared_ptr;
3033 using std::string;
34 using std::vector;
3135
3236 namespace Glib {
3337 class VariantBase;
3842 class Context;
3943 class Driver;
4044 }
45
46 using sigrok::ConfigKey;
4147
4248 namespace pv {
4349
5157 class DeviceManager
5258 {
5359 public:
54 DeviceManager(shared_ptr<sigrok::Context> context);
60 DeviceManager(shared_ptr<sigrok::Context> context,
61 std::string driver, bool do_scan);
5562
5663 ~DeviceManager() = default;
5764
6067 shared_ptr<sigrok::Context> context();
6168
6269 const list< shared_ptr<devices::HardwareDevice> >& devices() const;
70 shared_ptr<devices::HardwareDevice> user_spec_device() const;
71
72 bool driver_supported(shared_ptr<sigrok::Driver> driver) const;
6373
6474 list< shared_ptr<devices::HardwareDevice> > driver_scan(
6575 shared_ptr<sigrok::Driver> driver,
7585 bool compare_devices(shared_ptr<devices::Device> a,
7686 shared_ptr<devices::Device> b);
7787
88 static map<const ConfigKey *, Glib::VariantBase>
89 drive_scan_options(vector<string> user_spec,
90 set<const ConfigKey *> driver_opts);
91
7892 protected:
7993 shared_ptr<sigrok::Context> context_;
8094 list< shared_ptr<devices::HardwareDevice> > devices_;
95 shared_ptr<devices::HardwareDevice> user_spec_device_;
8196 };
8297
8398 } // namespace pv
1717 */
1818
1919 #include <cassert>
20 #include <type_traits>
21
22 #include <QApplication>
23 #include <QDebug>
24 #include <QString>
2025
2126 #include <libsigrokcxx/libsigrokcxx.hpp>
2227
2328 #include "device.hpp"
2429
30 using std::is_same;
2531 using std::shared_ptr;
2632
2733 using sigrok::ConfigKey;
4955 return device_;
5056 }
5157
52 template
53 uint64_t Device::read_config(const sigrok::ConfigKey*,
54 const uint64_t);
58 template uint64_t Device::read_config(const sigrok::ConfigKey*, const uint64_t);
5559
5660 template<typename T>
5761 T Device::read_config(const ConfigKey *key, const T default_value)
6165 if (!device_)
6266 return default_value;
6367
64 if (!device_->config_check(key, Capability::GET))
68 if (!device_->config_check(key, Capability::GET)) {
69 qWarning() << QApplication::tr("Querying config key %1 is not allowed")
70 .arg(QString::fromStdString(key->identifier()));
6571 return default_value;
72 }
6673
67 return VariantBase::cast_dynamic<Glib::Variant<guint64>>(
68 device_->config_get(ConfigKey::SAMPLERATE)).get();
74 VariantBase value;
75 try {
76 value = device_->config_get(key);
77 } catch (const sigrok::Error &e) {
78 qWarning() << QApplication::tr("Querying config key %1 resulted in %2")
79 .arg(QString::fromStdString(key->identifier()), e.what());
80 return default_value;
81 }
82
83 if (is_same<T, uint32_t>::value)
84 return VariantBase::cast_dynamic<Glib::Variant<guint32>>(value).get();
85 if (is_same<T, int32_t>::value)
86 return VariantBase::cast_dynamic<Glib::Variant<gint32>>(value).get();
87 if (is_same<T, uint64_t>::value)
88 return VariantBase::cast_dynamic<Glib::Variant<guint64>>(value).get();
89 if (is_same<T, int64_t>::value)
90 return VariantBase::cast_dynamic<Glib::Variant<gint64>>(value).get();
91
92 qWarning() << QApplication::tr("Unknown type supplied when attempting to query %1")
93 .arg(QString::fromStdString(key->identifier()));
94 return default_value;
6995 }
7096
7197 void Device::start()
3535
3636 public:
3737 /**
38 * Builds the full name. It only contains all the fields.
38 * Builds the full name. It contains all the fields.
3939 */
4040 string full_name() const;
4141
4545 string display_name(const DeviceManager&) const;
4646
4747 protected:
48 const string file_name_;
48 string file_name_;
4949 };
5050
5151 } // namespace devices
5353
5454 string HardwareDevice::full_name() const
5555 {
56 vector<string> parts = {device_->vendor(), device_->model(),
57 device_->version(), device_->serial_number()};
56 vector<string> parts = {};
57 if (device_->vendor().length() > 0)
58 parts.push_back(device_->vendor());
59 if (device_->model().length() > 0)
60 parts.push_back(device_->model());
61 if (device_->version().length() > 0)
62 parts.push_back(device_->version());
63 if (device_->serial_number().length() > 0)
64 parts.push_back("[S/N: " + device_->serial_number() + "]");
5865 if (device_->connection_id().length() > 0)
5966 parts.push_back("(" + device_->connection_id() + ")");
6067 return join(parts, " ");
8390 dev->device_ != device_;
8491 });
8592
86 vector<string> parts = {device_->vendor(), device_->model()};
93 vector<string> parts = {};
94 if (device_->vendor().length() > 0)
95 parts.push_back(device_->vendor());
96 if (device_->model().length() > 0)
97 parts.push_back(device_->model());
8798
8899 if (multiple_dev) {
89 parts.push_back(device_->version());
90 parts.push_back(device_->serial_number());
100 if (device_->version().length() > 0)
101 parts.push_back(device_->version());
102 if (device_->serial_number().length() > 0)
103 parts.push_back("[S/N: " + device_->serial_number() + "]");
91104
92105 if ((device_->serial_number().length() == 0) &&
93106 (device_->connection_id().length() > 0))
1818
1919 #include <cassert>
2020 #include <fstream>
21 #include <vector>
2122
23 #include <QDebug>
2224 #include <QString>
25
26 #include <pv/globalsettings.hpp>
2327
2428 #include "inputfile.hpp"
2529
30 using sigrok::InputFormat;
31
2632 using std::map;
33 using std::out_of_range;
34 using std::pair;
2735 using std::shared_ptr;
2836 using std::streamsize;
2937 using std::string;
3038 using std::ifstream;
3139 using std::ios;
40 using std::vector;
3241
3342 namespace pv {
3443 namespace devices {
3544
36 const streamsize InputFile::BufferSize = 16384;
45 // Use a 4MB chunk size for reading a file into memory. Larger values don't
46 // seem to provide any substancial performance improvements, but can cause
47 // UI lag and a visually "stuttering" display of the data currently loading.
48 const streamsize InputFile::BufferSize = (4 * 1024 * 1024);
3749
3850 InputFile::InputFile(const shared_ptr<sigrok::Context> &context,
3951 const string &file_name,
4759 {
4860 }
4961
62 InputFile::InputFile(const shared_ptr<sigrok::Context> &context,
63 QSettings &settings):
64 File(""),
65 context_(context),
66 interrupt_(false)
67 {
68 file_name_ = settings.value("filename").toString().toStdString();
69
70 QString format_name = settings.value("format").toString();
71
72 // Find matching format
73 const map<string, shared_ptr<InputFormat> > formats = context->input_formats();
74
75 try {
76 format_ = formats.at(format_name.toStdString());
77
78 // Restore all saved options
79 int options = settings.value("options").toInt();
80
81 for (int i = 0; i < options; i++) {
82 settings.beginGroup("option" + QString::number(i));
83 QString name = settings.value("name").toString();
84 options_[name.toStdString()] = GlobalSettings::restore_variantbase(settings);
85 settings.endGroup();
86 }
87
88 } catch (out_of_range&) {
89 qWarning() << "Could not find input format" << format_name <<
90 "needed to restore session input file";
91 }
92 }
93
94 void InputFile::save_meta_to_settings(QSettings &settings)
95 {
96 settings.setValue("filename", QString::fromStdString(file_name_));
97
98 settings.setValue("format", QString::fromStdString(format_->name()));
99
100 settings.setValue("options", (int)options_.size());
101
102 int i = 0;
103 for (const pair<string, Glib::VariantBase>& option : options_) {
104 settings.beginGroup("option" + QString::number(i));
105 settings.setValue("name", QString::fromStdString(option.first));
106 GlobalSettings::store_variantbase(settings, option.second);
107 settings.endGroup();
108 i++;
109 }
110 }
111
50112 void InputFile::open()
51113 {
52114 if (session_)
53115 close();
54116 else
55117 session_ = context_->create_session();
118
119 if (!format_)
120 return;
56121
57122 input_ = format_->create_input(options_);
58123
63128 // we can't open the device without sending some data first
64129 f = new ifstream(file_name_, ios::binary);
65130
66 char buffer[BufferSize];
67 f->read(buffer, BufferSize);
131 vector<char> buffer(BufferSize);
132
133 f->read(buffer.data(), BufferSize);
68134 const streamsize size = f->gcount();
135
69136 if (size == 0)
70 return;
137 throw QString("Failed to read file");
71138
72 input_->send(buffer, size);
139 input_->send(buffer.data(), size);
73140
74141 try {
75142 device_ = input_->device();
76 } catch (sigrok::Error) {
77 return;
143 } catch (sigrok::Error& e) {
144 throw e;
78145 }
79146
80147 session_->add_device(device_);
92159
93160 void InputFile::run()
94161 {
95 char buffer[BufferSize];
162 if (!input_)
163 return;
96164
97165 if (!f) {
98166 // Previous call to run() processed the entire file already
100168 input_->reset();
101169 }
102170
171 vector<char> buffer(BufferSize);
172
103173 interrupt_ = false;
104174 while (!interrupt_ && !f->eof()) {
105 f->read(buffer, BufferSize);
175 f->read(buffer.data(), BufferSize);
106176 const streamsize size = f->gcount();
107177 if (size == 0)
108178 break;
109179
110 input_->send(buffer, size);
180 input_->send(buffer.data(), size);
111181
112182 if (size != BufferSize)
113183 break;
2525
2626 #include "file.hpp"
2727
28 #include <QSettings>
29
2830 using std::atomic;
2931 using std::ifstream;
3032 using std::map;
4648 shared_ptr<sigrok::InputFormat> format,
4749 const map<string, Glib::VariantBase> &options);
4850
51 /**
52 * Constructor that loads a file using the metadata saved by
53 * save_meta_to_settings() before.
54 */
55 InputFile(const shared_ptr<sigrok::Context> &context,
56 QSettings &settings);
57
58 void save_meta_to_settings(QSettings &settings);
59
4960 void open();
5061
5162 void close();
5869
5970 private:
6071 const shared_ptr<sigrok::Context> context_;
61 const shared_ptr<sigrok::InputFormat> format_;
62 const map<string, Glib::VariantBase> options_;
72 shared_ptr<sigrok::InputFormat> format_;
73 map<string, Glib::VariantBase> options_;
6374 shared_ptr<sigrok::Input> input_;
6475
6576 ifstream *f;
9494 tcp_port_->setRange(1, 65535);
9595 tcp_port_->setValue(5555);
9696 tcp_config_layout->addWidget(tcp_port_);
97 tcp_use_vxi_ = new QCheckBox();
98 tcp_use_vxi_->setText(tr("Use VXI"));
97
9998 tcp_config_layout->addSpacing(30);
100 tcp_config_layout->addWidget(tcp_use_vxi_);
99 tcp_config_layout->addWidget(new QLabel(tr("Protocol:")));
100 tcp_protocol_ = new QComboBox();
101 tcp_protocol_->addItem("Raw TCP", QVariant("tcp-raw/%1/%2"));
102 tcp_protocol_->addItem("VXI", QVariant("vxi/%1/%2"));
103 tcp_config_layout->addWidget(tcp_protocol_);
101104 tcp_config_layout->setContentsMargins(0, 0, 0, 0);
102105 tcp_config_->setEnabled(false);
106
107 // Let the device list occupy only the minimum space needed
108 device_list_.setMaximumHeight(device_list_.minimumSizeHint().height());
103109
104110 QVBoxLayout *vbox_if = new QVBoxLayout;
105111 vbox_if->addWidget(radiobtn_usb);
147153
148154 void Connect::populate_drivers()
149155 {
150 for (auto entry : device_manager_.context()->drivers()) {
156 for (auto& entry : device_manager_.context()->drivers()) {
151157 auto name = entry.first;
152158 auto driver = entry.second;
153159 /**
172178 void Connect::populate_serials(shared_ptr<Driver> driver)
173179 {
174180 serial_devices_.clear();
175 for (auto serial : device_manager_.context()->serials(driver))
181 for (auto& serial : device_manager_.context()->serials(driver))
176182 serial_devices_.addItem(QString("%1 (%2)").arg(
177183 serial.first.c_str(), serial.second.c_str()),
178184 QString::fromStdString(serial.first));
214220 const int index = serial_devices_.currentIndex();
215221 if (index >= 0 && index < serial_devices_.count() &&
216222 serial_devices_.currentText() == serial_devices_.itemText(index))
217 serial = serial_devices_.itemData(index).value<QString>();
223 serial = serial_devices_.itemData(index).toString();
218224 else
219225 serial = serial_devices_.currentText();
220226 drvopts[ConfigKey::CONN] = Variant<ustring>::create(
225231 QString host = tcp_host_->text();
226232 QString port = tcp_port_->text();
227233 if (!host.isEmpty()) {
228 QString conn;
229 if (tcp_use_vxi_->isChecked())
230 conn = QString("vxi/%1/%2").arg(host, port);
231 else
232 conn = QString("tcp-raw/%1/%2").arg(host, port);
234 QString conn =
235 tcp_protocol_->itemData(tcp_protocol_->currentIndex()).toString();
236
237 conn = conn.arg(host, port);
233238
234239 drvopts[ConfigKey::CONN] = Variant<ustring>::create(
235240 conn.toUtf8().constData());
239244 const list< shared_ptr<HardwareDevice> > devices =
240245 device_manager_.driver_scan(driver, drvopts);
241246
242 for (shared_ptr<HardwareDevice> device : devices) {
247 for (const shared_ptr<HardwareDevice>& device : devices) {
243248 assert(device);
244249
245 QString text = QString::fromStdString(
246 device->display_name(device_manager_));
247 text += QString(" with %1 channels").arg(
248 device->device()->channels().size());
249
250 QListWidgetItem *const item = new QListWidgetItem(text,
251 &device_list_);
250 QString text = QString::fromStdString(device->display_name(device_manager_));
251 text += QString(" with %1 channels").arg(device->device()->channels().size());
252
253 QListWidgetItem *const item = new QListWidgetItem(text, &device_list_);
252254 item->setData(Qt::UserRole, qVariantFromValue(device));
253255 device_list_.addItem(item);
254256 }
9393 QWidget *tcp_config_;
9494 QLineEdit *tcp_host_;
9595 QSpinBox *tcp_port_;
96 QCheckBox *tcp_use_vxi_;
96 QComboBox *tcp_protocol_;
9797
9898 QPushButton scan_button_;
9999 QListWidget device_list_;
1919 #include "config.h"
2020
2121 #include <glib.h>
22 #include <boost/version.hpp>
2322
2423 #include <QApplication>
24 #include <QComboBox>
2525 #include <QDialogButtonBox>
26 #include <QFileDialog>
2627 #include <QFormLayout>
2728 #include <QGroupBox>
2829 #include <QHBoxLayout>
2930 #include <QLabel>
31 #include <QMainWindow>
32 #include <QMessageBox>
33 #include <QPushButton>
34 #include <QScrollBar>
35 #include <QSpinBox>
3036 #include <QString>
37 #include <QStyleFactory>
3138 #include <QTextBrowser>
3239 #include <QTextDocument>
40 #include <QTextStream>
3341 #include <QVBoxLayout>
3442
3543 #include "settings.hpp"
3644
45 #include "pv/application.hpp"
3746 #include "pv/devicemanager.hpp"
3847 #include "pv/globalsettings.hpp"
48 #include "pv/logging.hpp"
49 #include "pv/widgets/colorbutton.hpp"
3950
4051 #include <libsigrokcxx/libsigrokcxx.hpp>
4152
4354 #include <libsigrokdecode/libsigrokdecode.h>
4455 #endif
4556
46 using std::shared_ptr;
57 using pv::widgets::ColorButton;
4758
4859 namespace pv {
4960 namespace dialogs {
61
62 /**
63 * Special version of a QListView that has the width of the first column as minimum size.
64 *
65 * @note Inspired by https://github.com/qt-creator/qt-creator/blob/master/src/plugins/coreplugin/dialogs/settingsdialog.cpp
66 */
67 class PageListWidget: public QListWidget
68 {
69 public:
70 PageListWidget() :
71 QListWidget()
72 {
73 setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding);
74 }
75
76 QSize sizeHint() const final
77 {
78 int width = sizeHintForColumn(0) + frameWidth() * 2 + 5;
79 if (verticalScrollBar()->isVisible())
80 width += verticalScrollBar()->width();
81 return QSize(width, 100);
82 }
83 };
5084
5185 Settings::Settings(DeviceManager &device_manager, QWidget *parent) :
5286 QDialog(parent, nullptr),
5387 device_manager_(device_manager)
5488 {
55 const int icon_size = 64;
56
5789 resize(600, 400);
5890
59 page_list = new QListWidget;
60 page_list->setViewMode(QListView::IconMode);
61 page_list->setIconSize(QSize(icon_size, icon_size));
91 // Create log view
92 log_view_ = create_log_view();
93
94 // Create pages
95 page_list = new PageListWidget();
96 page_list->setViewMode(QListView::ListMode);
6297 page_list->setMovement(QListView::Static);
63 page_list->setMaximumWidth(icon_size + (icon_size / 2));
64 page_list->setSpacing(12);
98 page_list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
6599
66100 pages = new QStackedWidget;
67101 create_pages();
68102 page_list->setCurrentIndex(page_list->model()->index(0, 0));
69103
104 // Create the rest of the dialog
70105 QHBoxLayout *tab_layout = new QHBoxLayout;
71106 tab_layout->addWidget(page_list);
72107 tab_layout->addWidget(pages, Qt::AlignLeft);
90125
91126 void Settings::create_pages()
92127 {
128 // General page
129 pages->addWidget(get_general_settings_form(pages));
130
131 QListWidgetItem *generalButton = new QListWidgetItem(page_list);
132 generalButton->setIcon(QIcon(":/icons/settings-general.png"));
133 generalButton->setText(tr("General"));
134 generalButton->setTextAlignment(Qt::AlignVCenter);
135 generalButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
136
93137 // View page
94138 pages->addWidget(get_view_settings_form(pages));
95139
96140 QListWidgetItem *viewButton = new QListWidgetItem(page_list);
97141 viewButton->setIcon(QIcon(":/icons/settings-views.svg"));
98142 viewButton->setText(tr("Views"));
99 viewButton->setTextAlignment(Qt::AlignHCenter);
143 viewButton->setTextAlignment(Qt::AlignVCenter);
100144 viewButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
101145
102146 #ifdef ENABLE_DECODE
106150 QListWidgetItem *decoderButton = new QListWidgetItem(page_list);
107151 decoderButton->setIcon(QIcon(":/icons/add-decoder.svg"));
108152 decoderButton->setText(tr("Decoders"));
109 decoderButton->setTextAlignment(Qt::AlignHCenter);
153 decoderButton->setTextAlignment(Qt::AlignVCenter);
110154 decoderButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
111155 #endif
112156
116160 QListWidgetItem *aboutButton = new QListWidgetItem(page_list);
117161 aboutButton->setIcon(QIcon(":/icons/information.svg"));
118162 aboutButton->setText(tr("About"));
119 aboutButton->setTextAlignment(Qt::AlignHCenter);
163 aboutButton->setTextAlignment(Qt::AlignVCenter);
120164 aboutButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
165
166 // Logging page
167 pages->addWidget(get_logging_page(pages));
168
169 QListWidgetItem *loggingButton = new QListWidgetItem(page_list);
170 loggingButton->setIcon(QIcon(":/icons/information.svg"));
171 loggingButton->setText(tr("Logging"));
172 loggingButton->setTextAlignment(Qt::AlignVCenter);
173 loggingButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
121174 }
122175
123176 QCheckBox *Settings::create_checkbox(const QString& key, const char* slot) const
130183 return cb;
131184 }
132185
186 QPlainTextEdit *Settings::create_log_view() const
187 {
188 GlobalSettings settings;
189
190 QPlainTextEdit *log_view = new QPlainTextEdit();
191
192 log_view->setReadOnly(true);
193 log_view->setWordWrapMode(QTextOption::NoWrap);
194 log_view->setCenterOnScroll(true);
195
196 log_view->appendHtml(logging.get_log());
197 connect(&logging, SIGNAL(logged_text(QString)),
198 log_view, SLOT(appendHtml(QString)));
199
200 return log_view;
201 }
202
203 QWidget *Settings::get_general_settings_form(QWidget *parent) const
204 {
205 GlobalSettings settings;
206
207 QWidget *form = new QWidget(parent);
208 QVBoxLayout *form_layout = new QVBoxLayout(form);
209
210 // General settings
211 QGroupBox *general_group = new QGroupBox(tr("General"));
212 form_layout->addWidget(general_group);
213
214 QFormLayout *general_layout = new QFormLayout();
215 general_group->setLayout(general_layout);
216
217 QComboBox *theme_cb = new QComboBox();
218 for (const pair<QString, QString>& entry : Themes)
219 theme_cb->addItem(entry.first, entry.second);
220
221 theme_cb->setCurrentIndex(
222 settings.value(GlobalSettings::Key_General_Theme).toInt());
223 connect(theme_cb, SIGNAL(currentIndexChanged(int)),
224 this, SLOT(on_general_theme_changed_changed(int)));
225 general_layout->addRow(tr("User interface theme"), theme_cb);
226
227 QLabel *description_1 = new QLabel(tr("(You may need to restart PulseView for all UI elements to update)"));
228 description_1->setAlignment(Qt::AlignRight);
229 general_layout->addRow(description_1);
230
231 QComboBox *style_cb = new QComboBox();
232 style_cb->addItem(tr("System Default"), "");
233 for (QString& s : QStyleFactory::keys())
234 style_cb->addItem(s, s);
235
236 const QString current_style =
237 settings.value(GlobalSettings::Key_General_Style).toString();
238 if (current_style.isEmpty())
239 style_cb->setCurrentIndex(0);
240 else
241 style_cb->setCurrentIndex(style_cb->findText(current_style, 0));
242
243 connect(style_cb, SIGNAL(currentIndexChanged(int)),
244 this, SLOT(on_general_style_changed(int)));
245 general_layout->addRow(tr("Qt widget style"), style_cb);
246
247 QLabel *description_2 = new QLabel(tr("(Dark themes look best with the Fusion style)"));
248 description_2->setAlignment(Qt::AlignRight);
249 general_layout->addRow(description_2);
250
251 return form;
252 }
253
133254 QWidget *Settings::get_view_settings_form(QWidget *parent) const
134255 {
256 GlobalSettings settings;
135257 QCheckBox *cb;
136258
137259 QWidget *form = new QWidget(parent);
144266 QFormLayout *trace_view_layout = new QFormLayout();
145267 trace_view_group->setLayout(trace_view_layout);
146268
147 cb = create_checkbox(GlobalSettings::Key_View_ColouredBG,
148 SLOT(on_view_colouredBG_changed(int)));
149 trace_view_layout->addRow(tr("Use coloured trace &background"), cb);
150
151 cb = create_checkbox(GlobalSettings::Key_View_AlwaysZoomToFit,
152 SLOT(on_view_alwaysZoomToFit_changed(int)));
153 trace_view_layout->addRow(tr("Constantly perform &zoom-to-fit during capture"), cb);
269 cb = create_checkbox(GlobalSettings::Key_View_ColoredBG,
270 SLOT(on_view_coloredBG_changed(int)));
271 trace_view_layout->addRow(tr("Use colored trace &background"), cb);
272
273 cb = create_checkbox(GlobalSettings::Key_View_ZoomToFitDuringAcq,
274 SLOT(on_view_zoomToFitDuringAcq_changed(int)));
275 trace_view_layout->addRow(tr("Constantly perform &zoom-to-fit during acquisition"), cb);
276
277 cb = create_checkbox(GlobalSettings::Key_View_ZoomToFitAfterAcq,
278 SLOT(on_view_zoomToFitAfterAcq_changed(int)));
279 trace_view_layout->addRow(tr("Perform a zoom-to-&fit when acquisition stops"), cb);
280
281 cb = create_checkbox(GlobalSettings::Key_View_TriggerIsZeroTime,
282 SLOT(on_view_triggerIsZero_changed(int)));
283 trace_view_layout->addRow(tr("Show time zero at the trigger"), cb);
154284
155285 cb = create_checkbox(GlobalSettings::Key_View_StickyScrolling,
156286 SLOT(on_view_stickyScrolling_changed(int)));
160290 SLOT(on_view_showSamplingPoints_changed(int)));
161291 trace_view_layout->addRow(tr("Show data &sampling points"), cb);
162292
293 cb = create_checkbox(GlobalSettings::Key_View_FillSignalHighAreas,
294 SLOT(on_view_fillSignalHighAreas_changed(int)));
295 trace_view_layout->addRow(tr("Fill high areas of logic signals"), cb);
296
297 ColorButton* high_fill_cb = new ColorButton(parent);
298 high_fill_cb->set_color(QColor::fromRgba(
299 settings.value(GlobalSettings::Key_View_FillSignalHighAreaColor).value<uint32_t>()));
300 connect(high_fill_cb, SIGNAL(selected(QColor)),
301 this, SLOT(on_view_fillSignalHighAreaColor_changed(QColor)));
302 trace_view_layout->addRow(tr("Color to fill high areas of logic signals with"), high_fill_cb);
303
163304 cb = create_checkbox(GlobalSettings::Key_View_ShowAnalogMinorGrid,
164305 SLOT(on_view_showAnalogMinorGrid_changed(int)));
165 trace_view_layout->addRow(tr("Show analog minor grid in addition to vdiv grid"), cb);
306 trace_view_layout->addRow(tr("Show analog minor grid in addition to div grid"), cb);
307
308 cb = create_checkbox(GlobalSettings::Key_View_ShowHoverMarker,
309 SLOT(on_view_showHoverMarker_changed(int)));
310 trace_view_layout->addRow(tr("Highlight mouse cursor using a vertical marker line"), cb);
311
312 QSpinBox *snap_distance_sb = new QSpinBox();
313 snap_distance_sb->setRange(0, 1000);
314 snap_distance_sb->setSuffix(tr(" pixels"));
315 snap_distance_sb->setValue(
316 settings.value(GlobalSettings::Key_View_SnapDistance).toInt());
317 connect(snap_distance_sb, SIGNAL(valueChanged(int)), this,
318 SLOT(on_view_snapDistance_changed(int)));
319 trace_view_layout->addRow(tr("Maximum distance from edges before cursors snap to them"), snap_distance_sb);
320
321 ColorButton* cursor_fill_cb = new ColorButton(parent);
322 cursor_fill_cb->set_color(QColor::fromRgba(
323 settings.value(GlobalSettings::Key_View_CursorFillColor).value<uint32_t>()));
324 connect(cursor_fill_cb, SIGNAL(selected(QColor)),
325 this, SLOT(on_view_cursorFillColor_changed(QColor)));
326 trace_view_layout->addRow(tr("Color to fill cursor area with"), cursor_fill_cb);
327
328 QComboBox *thr_disp_mode_cb = new QComboBox();
329 thr_disp_mode_cb->addItem(tr("None"), GlobalSettings::ConvThrDispMode_None);
330 thr_disp_mode_cb->addItem(tr("Background"), GlobalSettings::ConvThrDispMode_Background);
331 thr_disp_mode_cb->addItem(tr("Dots"), GlobalSettings::ConvThrDispMode_Dots);
332 thr_disp_mode_cb->setCurrentIndex(
333 settings.value(GlobalSettings::Key_View_ConversionThresholdDispMode).toInt());
334 connect(thr_disp_mode_cb, SIGNAL(currentIndexChanged(int)),
335 this, SLOT(on_view_conversionThresholdDispMode_changed(int)));
336 trace_view_layout->addRow(tr("Conversion threshold display mode (analog traces only)"), thr_disp_mode_cb);
337
338 QSpinBox *default_div_height_sb = new QSpinBox();
339 default_div_height_sb->setRange(20, 1000);
340 default_div_height_sb->setSuffix(tr(" pixels"));
341 default_div_height_sb->setValue(
342 settings.value(GlobalSettings::Key_View_DefaultDivHeight).toInt());
343 connect(default_div_height_sb, SIGNAL(valueChanged(int)), this,
344 SLOT(on_view_defaultDivHeight_changed(int)));
345 trace_view_layout->addRow(tr("Default analog trace div height"), default_div_height_sb);
346
347 QSpinBox *default_logic_height_sb = new QSpinBox();
348 default_logic_height_sb->setRange(5, 1000);
349 default_logic_height_sb->setSuffix(tr(" pixels"));
350 default_logic_height_sb->setValue(
351 settings.value(GlobalSettings::Key_View_DefaultLogicHeight).toInt());
352 connect(default_logic_height_sb, SIGNAL(valueChanged(int)), this,
353 SLOT(on_view_defaultLogicHeight_changed(int)));
354 trace_view_layout->addRow(tr("Default logic trace height"), default_logic_height_sb);
166355
167356 return form;
168357 }
169358
170 QWidget *Settings::get_decoder_settings_form(QWidget *parent) const
359 QWidget *Settings::get_decoder_settings_form(QWidget *parent)
171360 {
172361 #ifdef ENABLE_DECODE
362 GlobalSettings settings;
173363 QCheckBox *cb;
174364
175365 QWidget *form = new QWidget(parent);
186376 SLOT(on_dec_initialStateConfigurable_changed(int)));
187377 decoder_layout->addRow(tr("Allow configuration of &initial signal state"), cb);
188378
379 // Annotation export settings
380 ann_export_format_ = new QLineEdit();
381 ann_export_format_->setText(
382 settings.value(GlobalSettings::Key_Dec_ExportFormat).toString());
383 connect(ann_export_format_, SIGNAL(textChanged(const QString&)),
384 this, SLOT(on_dec_exportFormat_changed(const QString&)));
385 decoder_layout->addRow(tr("Annotation export format"), ann_export_format_);
386 QLabel *description_1 = new QLabel(tr("%s = sample range; %d: decoder name; %c: row name; %q: use quotations marks"));
387 description_1->setAlignment(Qt::AlignRight);
388 decoder_layout->addRow(description_1);
389 QLabel *description_2 = new QLabel(tr("%1: longest annotation text; %a: all annotation texts"));
390 description_2->setAlignment(Qt::AlignRight);
391 decoder_layout->addRow(description_2);
392
189393 return form;
190394 #else
191395 (void)parent;
396 return nullptr;
192397 #endif
193398 }
194399
195 #ifdef ENABLE_DECODE
196 static gint sort_pds(gconstpointer a, gconstpointer b)
197 {
198 const struct srd_decoder *sda, *sdb;
199
200 sda = (const struct srd_decoder *)a;
201 sdb = (const struct srd_decoder *)b;
202 return strcmp(sda->id, sdb->id);
203 }
204 #endif
205
206400 QWidget *Settings::get_about_page(QWidget *parent) const
207401 {
208 #ifdef ENABLE_DECODE
209 struct srd_decoder *dec;
210 #endif
402 Application* a = qobject_cast<Application*>(QApplication::instance());
211403
212404 QLabel *icon = new QLabel();
213405 icon->setPixmap(QPixmap(QString::fromUtf8(":/icons/pulseview.svg")));
214406
215 /* Setup the version field */
216 QLabel *version_info = new QLabel();
217 version_info->setText(tr("%1 %2<br />%3<br /><a href=\"http://%4\">%4</a>")
218 .arg(QApplication::applicationName(),
219 QApplication::applicationVersion(),
407 // Setup the license field with the project homepage link
408 QLabel *gpl_home_info = new QLabel();
409 gpl_home_info->setText(tr("%1<br /><a href=\"http://%2\">%2</a>").arg(
220410 tr("GNU GPL, version 3 or later"),
221411 QApplication::organizationDomain()));
222 version_info->setOpenExternalLinks(true);
223
224 shared_ptr<sigrok::Context> context = device_manager_.context();
412 gpl_home_info->setOpenExternalLinks(true);
225413
226414 QString s;
227415
229417
230418 s.append("<table>");
231419
232 /* Library info */
233420 s.append("<tr><td colspan=\"2\"><b>" +
234 tr("Libraries and features:") + "</b></td></tr>");
235
236 s.append(QString("<tr><td><i>%1</i></td><td>%2</td></tr>")
237 .arg(QString("Qt"), qVersion()));
238 s.append(QString("<tr><td><i>%1</i></td><td>%2</td></tr>")
239 .arg(QString("glibmm"), PV_GLIBMM_VERSION));
240 s.append(QString("<tr><td><i>%1</i></td><td>%2</td></tr>")
241 .arg(QString("Boost"), BOOST_LIB_VERSION));
242
243 s.append(QString("<tr><td><i>%1</i></td><td>%2/%3 (rt: %4/%5)</td></tr>")
244 .arg(QString("libsigrok"), SR_PACKAGE_VERSION_STRING,
245 SR_LIB_VERSION_STRING, sr_package_version_string_get(),
246 sr_lib_version_string_get()));
247
248 GSList *l_orig = sr_buildinfo_libs_get();
249 for (GSList *l = l_orig; l; l = l->next) {
250 GSList *m = (GSList *)l->data;
251 const char *lib = (const char *)m->data;
252 const char *version = (const char *)m->next->data;
253 s.append(QString("<tr><td><i>- %1</i></td><td>%2</td></tr>")
254 .arg(QString(lib), QString(version)));
255 g_slist_free_full(m, g_free);
256 }
257 g_slist_free(l_orig);
258
259 char *host = sr_buildinfo_host_get();
260 s.append(QString("<tr><td><i>- Host</i></td><td>%1</td></tr>")
261 .arg(QString(host)));
262 g_free(host);
263
264 char *scpi_backends = sr_buildinfo_scpi_backends_get();
265 s.append(QString("<tr><td><i>- SCPI backends</i></td><td>%1</td></tr>")
266 .arg(QString(scpi_backends)));
267 g_free(scpi_backends);
421 tr("Versions, libraries and features:") + "</b></td></tr>");
422 for (pair<QString, QString> &entry : a->get_version_info())
423 s.append(QString("<tr><td><i>%1</i></td><td>%2</td></tr>")
424 .arg(entry.first, entry.second));
425
426 s.append("<tr><td colspan=\"2\"></td></tr>");
427 s.append("<tr><td colspan=\"2\"><b>" +
428 tr("Firmware search paths:") + "</b></td></tr>");
429 for (QString &entry : a->get_fw_path_list())
430 s.append(QString("<tr><td colspan=\"2\">%1</td></tr>").arg(entry));
268431
269432 #ifdef ENABLE_DECODE
270 s.append(QString("<tr><td><i>%1</i></td><td>%2/%3 (rt: %4/%5)</td></tr>")
271 .arg(QString("libsigrokdecode"), SRD_PACKAGE_VERSION_STRING,
272 SRD_LIB_VERSION_STRING, srd_package_version_string_get(),
273 srd_lib_version_string_get()));
274
275 l_orig = srd_buildinfo_libs_get();
276 for (GSList *l = l_orig; l; l = l->next) {
277 GSList *m = (GSList *)l->data;
278 const char *lib = (const char *)m->data;
279 const char *version = (const char *)m->next->data;
280 s.append(QString("<tr><td><i>- %1</i></td><td>%2</td></tr>")
281 .arg(QString(lib), QString(version)));
282 g_slist_free_full(m, g_free);
283 }
284 g_slist_free(l_orig);
285
286 host = srd_buildinfo_host_get();
287 s.append(QString("<tr><td><i>- Host</i></td><td>%1</td></tr>")
288 .arg(QString(host)));
289 g_free(host);
433 s.append("<tr><td colspan=\"2\"></td></tr>");
434 s.append("<tr><td colspan=\"2\"><b>" +
435 tr("Protocol decoder search paths:") + "</b></td></tr>");
436 for (QString &entry : a->get_pd_path_list())
437 s.append(QString("<tr><td colspan=\"2\">%1</td></tr>").arg(entry));
290438 #endif
291439
292 /* Set up the supported field */
293440 s.append("<tr><td colspan=\"2\"></td></tr>");
294441 s.append("<tr><td colspan=\"2\"><b>" +
295442 tr("Supported hardware drivers:") + "</b></td></tr>");
296 for (auto entry : context->drivers()) {
443 for (pair<QString, QString> &entry : a->get_driver_list())
297444 s.append(QString("<tr><td class=\"id\"><i>%1</i></td><td>%2</td></tr>")
298 .arg(QString::fromUtf8(entry.first.c_str()),
299 QString::fromUtf8(entry.second->long_name().c_str())));
300 }
445 .arg(entry.first, entry.second));
301446
302447 s.append("<tr><td colspan=\"2\"></td></tr>");
303448 s.append("<tr><td colspan=\"2\"><b>" +
304449 tr("Supported input formats:") + "</b></td></tr>");
305 for (auto entry : context->input_formats()) {
450 for (pair<QString, QString> &entry : a->get_input_format_list())
306451 s.append(QString("<tr><td class=\"id\"><i>%1</i></td><td>%2</td></tr>")
307 .arg(QString::fromUtf8(entry.first.c_str()),
308 QString::fromUtf8(entry.second->description().c_str())));
309 }
452 .arg(entry.first, entry.second));
310453
311454 s.append("<tr><td colspan=\"2\"></td></tr>");
312455 s.append("<tr><td colspan=\"2\"><b>" +
313456 tr("Supported output formats:") + "</b></td></tr>");
314 for (auto entry : context->output_formats()) {
457 for (pair<QString, QString> &entry : a->get_output_format_list())
315458 s.append(QString("<tr><td class=\"id\"><i>%1</i></td><td>%2</td></tr>")
316 .arg(QString::fromUtf8(entry.first.c_str()),
317 QString::fromUtf8(entry.second->description().c_str())));
318 }
459 .arg(entry.first, entry.second));
319460
320461 #ifdef ENABLE_DECODE
321462 s.append("<tr><td colspan=\"2\"></td></tr>");
322463 s.append("<tr><td colspan=\"2\"><b>" +
323464 tr("Supported protocol decoders:") + "</b></td></tr>");
324 GSList *sl = g_slist_copy((GSList *)srd_decoder_list());
325 sl = g_slist_sort(sl, sort_pds);
326 for (const GSList *l = sl; l; l = l->next) {
327 dec = (struct srd_decoder *)l->data;
465 for (pair<QString, QString> &entry : a->get_pd_list())
328466 s.append(QString("<tr><td class=\"id\"><i>%1</i></td><td>%2</td></tr>")
329 .arg(QString::fromUtf8(dec->id),
330 QString::fromUtf8(dec->longname)));
331 }
332 g_slist_free(sl);
467 .arg(entry.first, entry.second));
333468 #endif
334469
335470 s.append("</table>");
340475 QTextBrowser *support_list = new QTextBrowser();
341476 support_list->setDocument(supported_doc);
342477
343 QGridLayout *layout = new QGridLayout();
344 layout->addWidget(icon, 0, 0, 1, 1);
345 layout->addWidget(version_info, 0, 1, 1, 1);
346 layout->addWidget(support_list, 1, 1, 1, 1);
478 QHBoxLayout *h_layout = new QHBoxLayout();
479 h_layout->setAlignment(Qt::AlignLeft);
480 h_layout->addWidget(icon);
481 h_layout->addWidget(gpl_home_info);
482
483 QVBoxLayout *layout = new QVBoxLayout();
484 layout->addLayout(h_layout);
485 layout->addWidget(support_list);
347486
348487 QWidget *page = new QWidget(parent);
349488 page->setLayout(layout);
351490 return page;
352491 }
353492
493 QWidget *Settings::get_logging_page(QWidget *parent) const
494 {
495 GlobalSettings settings;
496
497 // Log level
498 QSpinBox *loglevel_sb = new QSpinBox();
499 loglevel_sb->setMaximum(SR_LOG_SPEW);
500 loglevel_sb->setValue(logging.get_log_level());
501 connect(loglevel_sb, SIGNAL(valueChanged(int)), this,
502 SLOT(on_log_logLevel_changed(int)));
503
504 QHBoxLayout *loglevel_layout = new QHBoxLayout();
505 loglevel_layout->addWidget(new QLabel(tr("Log level:")));
506 loglevel_layout->addWidget(loglevel_sb);
507
508 // Background buffer size
509 QSpinBox *buffersize_sb = new QSpinBox();
510 buffersize_sb->setSuffix(tr(" lines"));
511 buffersize_sb->setMinimum(Logging::MIN_BUFFER_SIZE);
512 buffersize_sb->setMaximum(Logging::MAX_BUFFER_SIZE);
513 buffersize_sb->setValue(
514 settings.value(GlobalSettings::Key_Log_BufferSize).toInt());
515 connect(buffersize_sb, SIGNAL(valueChanged(int)), this,
516 SLOT(on_log_bufferSize_changed(int)));
517
518 QHBoxLayout *buffersize_layout = new QHBoxLayout();
519 buffersize_layout->addWidget(new QLabel(tr("Length of background buffer:")));
520 buffersize_layout->addWidget(buffersize_sb);
521
522 // Save to file
523 QPushButton *save_log_pb = new QPushButton(
524 QIcon::fromTheme("document-save-as", QIcon(":/icons/document-save-as.png")),
525 tr("&Save to File"));
526 connect(save_log_pb, SIGNAL(clicked(bool)),
527 this, SLOT(on_log_saveToFile_clicked(bool)));
528
529 // Pop out
530 QPushButton *pop_out_pb = new QPushButton(
531 QIcon::fromTheme("window-new", QIcon(":/icons/window-new.png")),
532 tr("&Pop out"));
533 connect(pop_out_pb, SIGNAL(clicked(bool)),
534 this, SLOT(on_log_popOut_clicked(bool)));
535
536 QHBoxLayout *control_layout = new QHBoxLayout();
537 control_layout->addLayout(loglevel_layout);
538 control_layout->addLayout(buffersize_layout);
539 control_layout->addWidget(save_log_pb);
540 control_layout->addWidget(pop_out_pb);
541
542 QVBoxLayout *root_layout = new QVBoxLayout();
543 root_layout->addLayout(control_layout);
544 root_layout->addWidget(log_view_);
545
546 QWidget *page = new QWidget(parent);
547 page->setLayout(root_layout);
548
549 return page;
550 }
551
354552 void Settings::accept()
355553 {
356554 GlobalSettings settings;
375573 pages->setCurrentIndex(page_list->row(current));
376574 }
377575
378 void Settings::on_view_alwaysZoomToFit_changed(int state)
379 {
380 GlobalSettings settings;
381 settings.setValue(GlobalSettings::Key_View_AlwaysZoomToFit, state ? true : false);
382 }
383
384 void Settings::on_view_colouredBG_changed(int state)
385 {
386 GlobalSettings settings;
387 settings.setValue(GlobalSettings::Key_View_ColouredBG, state ? true : false);
576 void Settings::on_general_theme_changed_changed(int state)
577 {
578 GlobalSettings settings;
579 settings.setValue(GlobalSettings::Key_General_Theme, state);
580 settings.apply_theme();
581
582 QMessageBox msg(this);
583 msg.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
584 msg.setIcon(QMessageBox::Question);
585
586 if (settings.current_theme_is_dark()) {
587 msg.setText(tr("You selected a dark theme.\n" \
588 "Should I set the user-adjustable colors to better suit your choice?\n\n" \
589 "Please keep in mind that PulseView may need a restart to display correctly."));
590 if (msg.exec() == QMessageBox::Yes)
591 settings.set_dark_theme_default_colors();
592 } else {
593 msg.setText(tr("You selected a bright theme.\n" \
594 "Should I set the user-adjustable colors to better suit your choice?\n\n" \
595 "Please keep in mind that PulseView may need a restart to display correctly."));
596 if (msg.exec() == QMessageBox::Yes)
597 settings.set_bright_theme_default_colors();
598 }
599 }
600
601 void Settings::on_general_style_changed(int state)
602 {
603 GlobalSettings settings;
604
605 if (state == 0)
606 settings.setValue(GlobalSettings::Key_General_Style, "");
607 else
608 settings.setValue(GlobalSettings::Key_General_Style,
609 QStyleFactory::keys().at(state - 1));
610
611 settings.apply_theme();
612 }
613
614 void Settings::on_view_zoomToFitDuringAcq_changed(int state)
615 {
616 GlobalSettings settings;
617 settings.setValue(GlobalSettings::Key_View_ZoomToFitDuringAcq, state ? true : false);
618 }
619
620 void Settings::on_view_zoomToFitAfterAcq_changed(int state)
621 {
622 GlobalSettings settings;
623 settings.setValue(GlobalSettings::Key_View_ZoomToFitAfterAcq, state ? true : false);
624 }
625
626 void Settings::on_view_triggerIsZero_changed(int state)
627 {
628 GlobalSettings settings;
629 settings.setValue(GlobalSettings::Key_View_TriggerIsZeroTime, state ? true : false);
630 }
631
632 void Settings::on_view_coloredBG_changed(int state)
633 {
634 GlobalSettings settings;
635 settings.setValue(GlobalSettings::Key_View_ColoredBG, state ? true : false);
388636 }
389637
390638 void Settings::on_view_stickyScrolling_changed(int state)
399647 settings.setValue(GlobalSettings::Key_View_ShowSamplingPoints, state ? true : false);
400648 }
401649
650 void Settings::on_view_fillSignalHighAreas_changed(int state)
651 {
652 GlobalSettings settings;
653 settings.setValue(GlobalSettings::Key_View_FillSignalHighAreas, state ? true : false);
654 }
655
656 void Settings::on_view_fillSignalHighAreaColor_changed(QColor color)
657 {
658 GlobalSettings settings;
659 settings.setValue(GlobalSettings::Key_View_FillSignalHighAreaColor, color.rgba());
660 }
661
402662 void Settings::on_view_showAnalogMinorGrid_changed(int state)
403663 {
404664 GlobalSettings settings;
405665 settings.setValue(GlobalSettings::Key_View_ShowAnalogMinorGrid, state ? true : false);
406666 }
407667
668 void Settings::on_view_showHoverMarker_changed(int state)
669 {
670 GlobalSettings settings;
671 settings.setValue(GlobalSettings::Key_View_ShowHoverMarker, state ? true : false);
672 }
673
674 void Settings::on_view_snapDistance_changed(int value)
675 {
676 GlobalSettings settings;
677 settings.setValue(GlobalSettings::Key_View_SnapDistance, value);
678 }
679
680 void Settings::on_view_cursorFillColor_changed(QColor color)
681 {
682 GlobalSettings settings;
683 settings.setValue(GlobalSettings::Key_View_CursorFillColor, color.rgba());
684 }
685
686 void Settings::on_view_conversionThresholdDispMode_changed(int state)
687 {
688 GlobalSettings settings;
689 settings.setValue(GlobalSettings::Key_View_ConversionThresholdDispMode, state);
690 }
691
692 void Settings::on_view_defaultDivHeight_changed(int value)
693 {
694 GlobalSettings settings;
695 settings.setValue(GlobalSettings::Key_View_DefaultDivHeight, value);
696 }
697
698 void Settings::on_view_defaultLogicHeight_changed(int value)
699 {
700 GlobalSettings settings;
701 settings.setValue(GlobalSettings::Key_View_DefaultLogicHeight, value);
702 }
703
704 #ifdef ENABLE_DECODE
408705 void Settings::on_dec_initialStateConfigurable_changed(int state)
409706 {
410707 GlobalSettings settings;
411708 settings.setValue(GlobalSettings::Key_Dec_InitialStateConfigurable, state ? true : false);
709 }
710
711 void Settings::on_dec_exportFormat_changed(const QString &text)
712 {
713 GlobalSettings settings;
714 settings.setValue(GlobalSettings::Key_Dec_ExportFormat, text);
715 }
716 #endif
717
718 void Settings::on_log_logLevel_changed(int value)
719 {
720 logging.set_log_level(value);
721 }
722
723 void Settings::on_log_bufferSize_changed(int value)
724 {
725 GlobalSettings settings;
726 settings.setValue(GlobalSettings::Key_Log_BufferSize, value);
727 }
728
729 void Settings::on_log_saveToFile_clicked(bool checked)
730 {
731 (void)checked;
732
733 const QString file_name = QFileDialog::getSaveFileName(
734 this, tr("Save Log"), "", tr("Log Files (*.txt *.log);;All Files (*)"));
735
736 if (file_name.isEmpty())
737 return;
738
739 QFile file(file_name);
740 if (file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
741 QTextStream out_stream(&file);
742 out_stream << log_view_->toPlainText();
743
744 if (out_stream.status() == QTextStream::Ok) {
745 QMessageBox msg(this);
746 msg.setText(tr("Success"));
747 msg.setInformativeText(tr("Log saved to %1.").arg(file_name));
748 msg.setStandardButtons(QMessageBox::Ok);
749 msg.setIcon(QMessageBox::Information);
750 msg.exec();
751
752 return;
753 }
754 }
755
756 QMessageBox msg(this);
757 msg.setText(tr("Error"));
758 msg.setInformativeText(tr("File %1 could not be written to.").arg(file_name));
759 msg.setStandardButtons(QMessageBox::Ok);
760 msg.setIcon(QMessageBox::Warning);
761 msg.exec();
762 }
763
764 void Settings::on_log_popOut_clicked(bool checked)
765 {
766 (void)checked;
767
768 // Create the window as a sub-window so it closes when the main window closes
769 QMainWindow *window = new QMainWindow(nullptr, Qt::SubWindow);
770
771 window->setObjectName(QString::fromUtf8("Log Window"));
772 window->setWindowTitle(tr("%1 Log").arg(PV_TITLE));
773
774 // Use same width/height as the settings dialog
775 window->resize(width(), height());
776
777 window->setCentralWidget(create_log_view());
778 window->show();
412779 }
413780
414781 } // namespace dialogs
2020 #define PULSEVIEW_PV_SETTINGS_HPP
2121
2222 #include <QCheckBox>
23 #include <QColor>
2324 #include <QDialog>
2425 #include <QListWidget>
26 #include <QPlainTextEdit>
2527 #include <QStackedWidget>
28 #include <QLineEdit>
2629
2730 namespace pv {
2831
2932 class DeviceManager;
3033
3134 namespace dialogs {
35
36 class PageListWidget;
3237
3338 class Settings : public QDialog
3439 {
3944
4045 void create_pages();
4146 QCheckBox *create_checkbox(const QString& key, const char* slot) const;
47 QPlainTextEdit *create_log_view() const;
4248
49 QWidget *get_general_settings_form(QWidget *parent) const;
4350 QWidget *get_view_settings_form(QWidget *parent) const;
44 QWidget *get_decoder_settings_form(QWidget *parent) const;
51 QWidget *get_decoder_settings_form(QWidget *parent);
4552 QWidget *get_about_page(QWidget *parent) const;
53 QWidget *get_logging_page(QWidget *parent) const;
4654
4755 void accept();
4856 void reject();
4957
5058 private Q_SLOTS:
5159 void on_page_changed(QListWidgetItem *current, QListWidgetItem *previous);
52 void on_view_alwaysZoomToFit_changed(int state);
53 void on_view_colouredBG_changed(int state);
60 void on_general_theme_changed_changed(int state);
61 void on_general_style_changed(int state);
62 void on_view_zoomToFitDuringAcq_changed(int state);
63 void on_view_zoomToFitAfterAcq_changed(int state);
64 void on_view_triggerIsZero_changed(int state);
65 void on_view_coloredBG_changed(int state);
5466 void on_view_stickyScrolling_changed(int state);
5567 void on_view_showSamplingPoints_changed(int state);
68 void on_view_fillSignalHighAreas_changed(int state);
69 void on_view_fillSignalHighAreaColor_changed(QColor color);
5670 void on_view_showAnalogMinorGrid_changed(int state);
71 void on_view_showHoverMarker_changed(int state);
72 void on_view_snapDistance_changed(int value);
73 void on_view_cursorFillColor_changed(QColor color);
74 void on_view_conversionThresholdDispMode_changed(int state);
75 void on_view_defaultDivHeight_changed(int value);
76 void on_view_defaultLogicHeight_changed(int value);
77 #ifdef ENABLE_DECODE
5778 void on_dec_initialStateConfigurable_changed(int state);
79 void on_dec_exportFormat_changed(const QString &text);
80 #endif
81 void on_log_logLevel_changed(int value);
82 void on_log_bufferSize_changed(int value);
83 void on_log_saveToFile_clicked(bool checked);
84 void on_log_popOut_clicked(bool checked);
5885
5986 private:
6087 DeviceManager &device_manager_;
61 QListWidget *page_list;
88 PageListWidget *page_list;
6289 QStackedWidget *pages;
90
91 #ifdef ENABLE_DECODE
92 QLineEdit *ann_export_format_;
93 #endif
94
95 QPlainTextEdit *log_view_;
6396 };
6497
6598 } // namespace dialogs
1818
1919 #include <cassert>
2020
21 #include <QDebug>
2122 #include <QMessageBox>
2223
2324 #include "pv/session.hpp"
7778
7879 void StoreProgress::show_error()
7980 {
81 qDebug() << "Error trying to save:" << session_.error();
82
8083 QMessageBox msg(parentWidget());
8184 msg.setText(tr("Failed to save session."));
8285 msg.setInformativeText(session_.error());
1818
1919 #include "globalsettings.hpp"
2020
21 using std::function;
21 #include <QApplication>
22 #include <QColor>
23 #include <QDebug>
24 #include <QFile>
25 #include <QFontMetrics>
26 #include <QPixmapCache>
27 #include <QString>
28 #include <QStyle>
29 #include <QtGlobal>
30
2231 using std::map;
23 using std::multimap;
32 using std::pair;
33 using std::string;
34 using std::vector;
2435
2536 namespace pv {
2637
27 const QString GlobalSettings::Key_View_AlwaysZoomToFit = "View_AlwaysZoomToFit";
28 const QString GlobalSettings::Key_View_ColouredBG = "View_ColouredBG";
38 const vector< pair<QString, QString> > Themes {
39 {"None" , ""},
40 {"QDarkStyleSheet", ":/themes/qdarkstyle/style.qss"},
41 {"DarkStyle", ":/themes/darkstyle/darkstyle.qss"}
42 };
43
44 const QString GlobalSettings::Key_General_Theme = "General_Theme";
45 const QString GlobalSettings::Key_General_Style = "General_Style";
46 const QString GlobalSettings::Key_View_ZoomToFitDuringAcq = "View_ZoomToFitDuringAcq";
47 const QString GlobalSettings::Key_View_ZoomToFitAfterAcq = "View_ZoomToFitAfterAcq";
48 const QString GlobalSettings::Key_View_TriggerIsZeroTime = "View_TriggerIsZeroTime";
49 const QString GlobalSettings::Key_View_ColoredBG = "View_ColoredBG";
2950 const QString GlobalSettings::Key_View_StickyScrolling = "View_StickyScrolling";
3051 const QString GlobalSettings::Key_View_ShowSamplingPoints = "View_ShowSamplingPoints";
52 const QString GlobalSettings::Key_View_FillSignalHighAreas = "View_FillSignalHighAreas";
53 const QString GlobalSettings::Key_View_FillSignalHighAreaColor = "View_FillSignalHighAreaColor";
3154 const QString GlobalSettings::Key_View_ShowAnalogMinorGrid = "View_ShowAnalogMinorGrid";
55 const QString GlobalSettings::Key_View_ConversionThresholdDispMode = "View_ConversionThresholdDispMode";
56 const QString GlobalSettings::Key_View_DefaultDivHeight = "View_DefaultDivHeight";
57 const QString GlobalSettings::Key_View_DefaultLogicHeight = "View_DefaultLogicHeight";
58 const QString GlobalSettings::Key_View_ShowHoverMarker = "View_ShowHoverMarker";
59 const QString GlobalSettings::Key_View_SnapDistance = "View_SnapDistance";
60 const QString GlobalSettings::Key_View_CursorFillColor = "View_CursorFillColor";
3261 const QString GlobalSettings::Key_Dec_InitialStateConfigurable = "Dec_InitialStateConfigurable";
33
34 multimap< QString, function<void(QVariant)> > GlobalSettings::callbacks_;
62 const QString GlobalSettings::Key_Dec_ExportFormat = "Dec_ExportFormat";
63 const QString GlobalSettings::Key_Log_BufferSize = "Log_BufferSize";
64 const QString GlobalSettings::Key_Log_NotifyOfStacktrace = "Log_NotifyOfStacktrace";
65
66 vector<GlobalSettingsInterface*> GlobalSettings::callbacks_;
3567 bool GlobalSettings::tracking_ = false;
3668 map<QString, QVariant> GlobalSettings::tracked_changes_;
69 QString GlobalSettings::default_style_;
70 QPalette GlobalSettings::default_palette_;
3771
3872 GlobalSettings::GlobalSettings() :
39 QSettings()
73 QSettings(),
74 is_dark_theme_(false)
4075 {
4176 beginGroup("Settings");
4277 }
4378
79 void GlobalSettings::save_internal_defaults()
80 {
81 default_style_ = qApp->style()->objectName();
82 if (default_style_.isEmpty())
83 default_style_ = "fusion";
84
85 default_palette_ = QApplication::palette();
86 }
87
4488 void GlobalSettings::set_defaults_where_needed()
4589 {
46 // Enable coloured trace backgrounds by default
47 if (!contains(Key_View_ColouredBG))
48 setValue(Key_View_ColouredBG, true);
49 }
50
51 void GlobalSettings::register_change_handler(const QString key,
52 function<void(QVariant)> cb)
53 {
54 callbacks_.emplace(key, cb);
90 // Use no theme by default
91 if (!contains(Key_General_Theme))
92 setValue(Key_General_Theme, 0);
93 if (!contains(Key_General_Style))
94 setValue(Key_General_Style, "");
95
96 // Enable zoom-to-fit after acquisition by default
97 if (!contains(Key_View_ZoomToFitAfterAcq))
98 setValue(Key_View_ZoomToFitAfterAcq, true);
99
100 // Enable colored trace backgrounds by default
101 if (!contains(Key_View_ColoredBG))
102 setValue(Key_View_ColoredBG, true);
103
104 // Enable showing sampling points by default
105 if (!contains(Key_View_ShowSamplingPoints))
106 setValue(Key_View_ShowSamplingPoints, true);
107
108 // Enable filling logic signal high areas by default
109 if (!contains(Key_View_FillSignalHighAreas))
110 setValue(Key_View_FillSignalHighAreas, true);
111
112 if (!contains(Key_View_DefaultDivHeight))
113 setValue(Key_View_DefaultDivHeight,
114 3 * QFontMetrics(QApplication::font()).height());
115
116 if (!contains(Key_View_DefaultLogicHeight))
117 setValue(Key_View_DefaultLogicHeight,
118 2 * QFontMetrics(QApplication::font()).height());
119
120 if (!contains(Key_View_ShowHoverMarker))
121 setValue(Key_View_ShowHoverMarker, true);
122
123 if (!contains(Key_View_SnapDistance))
124 setValue(Key_View_SnapDistance, 15);
125
126 if (!contains(Key_Dec_ExportFormat))
127 setValue(Key_Dec_ExportFormat, "%s %d: %c: %1");
128
129 // Default to 500 lines of backlog
130 if (!contains(Key_Log_BufferSize))
131 setValue(Key_Log_BufferSize, 500);
132
133 // Notify user of existing stack trace by default
134 if (!contains(Key_Log_NotifyOfStacktrace))
135 setValue(Key_Log_NotifyOfStacktrace, true);
136
137 // Default theme is bright, so use its color scheme if undefined
138 if (!contains(Key_View_CursorFillColor))
139 set_bright_theme_default_colors();
140 }
141
142 void GlobalSettings::set_bright_theme_default_colors()
143 {
144 setValue(Key_View_FillSignalHighAreaColor,
145 QColor(0, 0, 0, 5 * 256 / 100).rgba());
146
147 setValue(Key_View_CursorFillColor,
148 QColor(220, 231, 243).rgba());
149 }
150
151 void GlobalSettings::set_dark_theme_default_colors()
152 {
153 setValue(Key_View_FillSignalHighAreaColor,
154 QColor(188, 188, 188, 9 * 256 / 100).rgba());
155
156 setValue(Key_View_CursorFillColor,
157 QColor(60, 60, 60).rgba());
158 }
159
160 bool GlobalSettings::current_theme_is_dark()
161 {
162 return is_dark_theme_;
163 }
164
165 void GlobalSettings::apply_theme()
166 {
167 QString theme_name = Themes.at(value(Key_General_Theme).toInt()).first;
168 QString resource_name = Themes.at(value(Key_General_Theme).toInt()).second;
169
170 if (!resource_name.isEmpty()) {
171 QFile file(resource_name);
172 file.open(QFile::ReadOnly | QFile::Text);
173 qApp->setStyleSheet(file.readAll());
174 } else
175 qApp->setStyleSheet("");
176
177 qApp->setPalette(default_palette_);
178
179 const QString style = value(Key_General_Style).toString();
180 if (style.isEmpty())
181 qApp->setStyle(default_style_);
182 else
183 qApp->setStyle(style);
184
185 is_dark_theme_ = false;
186
187 if (theme_name.compare("QDarkStyleSheet") == 0) {
188 QPalette dark_palette;
189 dark_palette.setColor(QPalette::Window, QColor(53, 53, 53));
190 dark_palette.setColor(QPalette::WindowText, Qt::white);
191 dark_palette.setColor(QPalette::Base, QColor(42, 42, 42));
192 dark_palette.setColor(QPalette::Dark, QColor(35, 35, 35));
193 dark_palette.setColor(QPalette::Highlight, QColor(42, 130, 218));
194 qApp->setPalette(dark_palette);
195 is_dark_theme_ = true;
196 } else if (theme_name.compare("DarkStyle") == 0) {
197 QPalette dark_palette;
198 dark_palette.setColor(QPalette::Window, QColor(53, 53, 53));
199 dark_palette.setColor(QPalette::WindowText, Qt::white);
200 dark_palette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(127, 127, 127));
201 dark_palette.setColor(QPalette::Base, QColor(42, 42, 42));
202 dark_palette.setColor(QPalette::AlternateBase, QColor(66, 66, 66));
203 dark_palette.setColor(QPalette::ToolTipBase, Qt::white);
204 dark_palette.setColor(QPalette::ToolTipText, QColor(53, 53, 53));
205 dark_palette.setColor(QPalette::Text, Qt::white);
206 dark_palette.setColor(QPalette::Disabled, QPalette::Text, QColor(127, 127, 127));
207 dark_palette.setColor(QPalette::Dark, QColor(35, 35, 35));
208 dark_palette.setColor(QPalette::Shadow, QColor(20, 20, 20));
209 dark_palette.setColor(QPalette::Button, QColor(53, 53, 53));
210 dark_palette.setColor(QPalette::ButtonText, Qt::white);
211 dark_palette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(127, 127, 127));
212 dark_palette.setColor(QPalette::BrightText, Qt::red);
213 dark_palette.setColor(QPalette::Link, QColor(42, 130, 218));
214 dark_palette.setColor(QPalette::Highlight, QColor(42, 130, 218));
215 dark_palette.setColor(QPalette::Disabled, QPalette::Highlight, QColor(80, 80, 80));
216 dark_palette.setColor(QPalette::HighlightedText, Qt::white);
217 dark_palette.setColor(QPalette::Disabled, QPalette::HighlightedText, QColor(127, 127, 127));
218 qApp->setPalette(dark_palette);
219 is_dark_theme_ = true;
220 }
221
222 QPixmapCache::clear();
223 }
224
225 void GlobalSettings::add_change_handler(GlobalSettingsInterface *cb)
226 {
227 callbacks_.push_back(cb);
228 }
229
230 void GlobalSettings::remove_change_handler(GlobalSettingsInterface *cb)
231 {
232 for (auto cb_it = callbacks_.begin(); cb_it != callbacks_.end(); cb_it++)
233 if (*cb_it == cb) {
234 callbacks_.erase(cb_it);
235 break;
236 }
55237 }
56238
57239 void GlobalSettings::setValue(const QString &key, const QVariant &value)
63245
64246 QSettings::setValue(key, value);
65247
66 // Call all registered callbacks for this key
67 auto range = callbacks_.equal_range(key);
68
69 for (auto it = range.first; it != range.second; it++)
70 it->second(value);
248 // TODO Emulate noquote()
249 qDebug() << "Setting" << key << "changed to" << value;
250
251 // Call all registered callbacks
252 for (GlobalSettingsInterface *cb : callbacks_)
253 cb->on_setting_changed(key, value);
71254 }
72255
73256 void GlobalSettings::start_tracking()
86269 {
87270 tracking_ = false;
88271
89 for (auto entry : tracked_changes_)
272 for (auto& entry : tracked_changes_)
90273 setValue(entry.first, entry.second);
91274
92275 tracked_changes_.clear();
93276 }
94277
278 void GlobalSettings::store_gvariant(QSettings &settings, GVariant *v)
279 {
280 const GVariantType *var_type = g_variant_get_type(v);
281 char *var_type_str = g_variant_type_dup_string(var_type);
282
283 QByteArray var_data = QByteArray((const char*)g_variant_get_data(v),
284 g_variant_get_size(v));
285
286 settings.setValue("value", var_data);
287 settings.setValue("type", var_type_str);
288
289 g_free(var_type_str);
290 }
291
292 GVariant* GlobalSettings::restore_gvariant(QSettings &settings)
293 {
294 QString raw_type = settings.value("type").toString();
295 GVariantType *var_type = g_variant_type_new(raw_type.toUtf8());
296
297 QByteArray data = settings.value("value").toByteArray();
298
299 gpointer var_data = g_memdup((gconstpointer)data.constData(),
300 (guint)data.size());
301
302 GVariant *value = g_variant_new_from_data(var_type, var_data,
303 data.size(), false, g_free, var_data);
304
305 g_variant_type_free(var_type);
306
307 return value;
308 }
309
310 void GlobalSettings::store_variantbase(QSettings &settings, Glib::VariantBase v)
311 {
312 const QByteArray var_data = QByteArray((const char*)v.get_data(), v.get_size());
313
314 settings.setValue("value", var_data);
315 settings.setValue("type", QString::fromStdString(v.get_type_string()));
316 }
317
318 Glib::VariantBase GlobalSettings::restore_variantbase(QSettings &settings)
319 {
320 QString raw_type = settings.value("type").toString();
321 GVariantType *var_type = g_variant_type_new(raw_type.toUtf8());
322
323 QByteArray data = settings.value("value").toByteArray();
324
325 gpointer var_data = g_memdup((gconstpointer)data.constData(),
326 (guint)data.size());
327
328 GVariant *value = g_variant_new_from_data(var_type, var_data,
329 data.size(), false, g_free, var_data);
330
331 Glib::VariantBase ret_val = Glib::VariantBase(value, true);
332
333 g_variant_type_free(var_type);
334 g_variant_unref(value);
335
336 return ret_val;
337 }
338
95339 } // namespace pv
1919 #ifndef PULSEVIEW_GLOBALSETTINGS_HPP
2020 #define PULSEVIEW_GLOBALSETTINGS_HPP
2121
22 #include <functional>
2322 #include <map>
2423
24 #include <glib.h>
25 #include <glibmm/variant.h>
26
27 #include <QPalette>
2528 #include <QSettings>
2629 #include <QString>
2730 #include <QVariant>
2831
29 using std::function;
3032 using std::map;
31 using std::multimap;
33 using std::pair;
34 using std::vector;
3235
3336 namespace pv {
37
38 extern const vector< pair<QString, QString> > Themes;
39
40
41 class GlobalSettingsInterface
42 {
43 public:
44 virtual void on_setting_changed(const QString &key, const QVariant &value) = 0;
45 };
46
3447
3548 class GlobalSettings : public QSettings
3649 {
3750 Q_OBJECT
3851
3952 public:
40 static const QString Key_View_AlwaysZoomToFit;
41 static const QString Key_View_ColouredBG;
53 static const QString Key_General_Theme;
54 static const QString Key_General_Style;
55 static const QString Key_View_ZoomToFitDuringAcq;
56 static const QString Key_View_ZoomToFitAfterAcq;
57 static const QString Key_View_TriggerIsZeroTime;
58 static const QString Key_View_ColoredBG;
4259 static const QString Key_View_StickyScrolling;
4360 static const QString Key_View_ShowSamplingPoints;
61 static const QString Key_View_FillSignalHighAreas;
62 static const QString Key_View_FillSignalHighAreaColor;
4463 static const QString Key_View_ShowAnalogMinorGrid;
64 static const QString Key_View_ConversionThresholdDispMode;
65 static const QString Key_View_DefaultDivHeight;
66 static const QString Key_View_DefaultLogicHeight;
67 static const QString Key_View_ShowHoverMarker;
68 static const QString Key_View_SnapDistance;
69 static const QString Key_View_CursorFillColor;
4570 static const QString Key_Dec_InitialStateConfigurable;
71 static const QString Key_Dec_ExportFormat;
72 static const QString Key_Log_BufferSize;
73 static const QString Key_Log_NotifyOfStacktrace;
74
75 enum ConvThrDispMode {
76 ConvThrDispMode_None = 0,
77 ConvThrDispMode_Background,
78 ConvThrDispMode_Dots
79 };
4680
4781 public:
4882 GlobalSettings();
4983
84 void save_internal_defaults();
5085 void set_defaults_where_needed();
86 void set_bright_theme_default_colors();
87 void set_dark_theme_default_colors();
5188
52 static void register_change_handler(const QString key,
53 function<void(QVariant)> cb);
89 bool current_theme_is_dark();
90 void apply_theme();
91
92 static void add_change_handler(GlobalSettingsInterface *cb);
93 static void remove_change_handler(GlobalSettingsInterface *cb);
5494
5595 void setValue(const QString& key, const QVariant& value);
5696
72112 */
73113 void undo_tracked_changes();
74114
115 static void store_gvariant(QSettings &settings, GVariant *v);
116
117 static GVariant* restore_gvariant(QSettings &settings);
118
119 static void store_variantbase(QSettings &settings, Glib::VariantBase v);
120
121 static Glib::VariantBase restore_variantbase(QSettings &settings);
122
75123 private:
76 static multimap< QString, function<void(QVariant)> > callbacks_;
124 static vector<GlobalSettingsInterface*> callbacks_;
77125
78126 static bool tracking_;
79127 static map<QString, QVariant> tracked_changes_;
128
129 static QString default_style_;
130 static QPalette default_palette_;
131
132 bool is_dark_theme_;
80133 };
81134
82135 } // namespace pv
0 /*
1 * This file is part of the PulseView project.
2 *
3 * Copyright (C) 2018 Soeren Apel <soeren@apelpie.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "logging.hpp"
20 #include "globalsettings.hpp"
21
22 #include <iostream>
23
24 #ifdef ENABLE_DECODE
25 #include <libsigrokdecode/libsigrokdecode.h> /* First, so we avoid a _POSIX_C_SOURCE warning. */
26 #endif
27
28 #include <libsigrokcxx/libsigrokcxx.hpp>
29
30 #include <QApplication>
31
32 using std::cout;
33 using std::endl;
34 using std::lock_guard;
35
36 namespace pv {
37
38 Logging logging;
39
40 const int Logging::MIN_BUFFER_SIZE = 10;
41 const int Logging::MAX_BUFFER_SIZE = 50000;
42
43 static sr_log_callback prev_sr_log_cb;
44 static void *prev_sr_log_cb_data;
45
46 #ifdef ENABLE_DECODE
47 static srd_log_callback prev_srd_log_cb;
48 static void *prev_srd_log_cb_data;
49 #endif
50
51 Logging::~Logging()
52 {
53 qInstallMessageHandler(nullptr);
54 if (prev_sr_log_cb)
55 sr_log_callback_set(prev_sr_log_cb, prev_sr_log_cb_data);
56 prev_sr_log_cb = nullptr;
57 prev_sr_log_cb_data = nullptr;
58 #ifdef ENABLE_DECODE
59 if (prev_srd_log_cb)
60 srd_log_callback_set(prev_srd_log_cb, prev_srd_log_cb_data);
61 prev_srd_log_cb = nullptr;
62 prev_srd_log_cb_data = nullptr;
63 #endif
64
65 GlobalSettings::remove_change_handler(this);
66 }
67
68 void Logging::init()
69 {
70 GlobalSettings settings;
71
72 buffer_size_ =
73 settings.value(GlobalSettings::Key_Log_BufferSize).toInt();
74
75 buffer_.reserve(buffer_size_);
76
77 qInstallMessageHandler(log_pv);
78 sr_log_callback_get(&prev_sr_log_cb, &prev_sr_log_cb_data);
79 sr_log_callback_set(log_sr, nullptr);
80 #ifdef ENABLE_DECODE
81 srd_log_callback_get(&prev_srd_log_cb, &prev_srd_log_cb_data);
82 srd_log_callback_set(log_srd, nullptr);
83 #endif
84
85 GlobalSettings::add_change_handler(this);
86 }
87
88 int Logging::get_log_level() const
89 {
90 // We assume that libsigrok and libsrd always have the same log level
91 return sr_log_loglevel_get();
92 }
93
94 void Logging::set_log_level(int level)
95 {
96 sr_log_loglevel_set(level);
97 #ifdef ENABLE_DECODE
98 srd_log_loglevel_set(level);
99 #endif
100 }
101
102 QString Logging::get_log() const
103 {
104 return buffer_.join("<br />\n");
105 }
106
107 void Logging::log(const QString &text, int source)
108 {
109 lock_guard<mutex> log_lock(log_mutex_);
110
111 if (buffer_.size() >= buffer_size_)
112 buffer_.removeFirst();
113
114 QString s;
115
116 if (text.contains("warning", Qt::CaseInsensitive)) {
117 s = QString("<font color=\"darkorange\">%1</font>").arg(text);
118 goto out;
119 }
120
121 if (text.contains("error", Qt::CaseInsensitive)) {
122 s = QString("<font color=\"darkred\">%1</font>").arg(text);
123 goto out;
124 }
125
126 switch (source) {
127 case LogSource_pv:
128 s = QString("<font color=\"darkMagenta\">pv: %1</font>").arg(text);
129 break;
130 case LogSource_sr:
131 s = QString("<font color=\"darkGreen\">sr: %1</font>").arg(text);
132 break;
133 case LogSource_srd:
134 s = QString("<font color=\"olive\">srd: %1</font>").arg(text);
135 break;
136 default:
137 s = text;
138 break;
139 }
140
141 out:
142 buffer_.append(s);
143
144 // If we're tearing down the program, sending out notifications to UI
145 // elements that can no longer function properly is a bad idea
146 if (!QApplication::closingDown())
147 logged_text(s);
148 }
149
150 void Logging::log_pv(QtMsgType type, const QMessageLogContext &context, const QString &msg)
151 {
152 (void)type;
153 (void)context;
154
155 logging.log(msg, LogSource_pv);
156
157 cout << msg.toUtf8().data() << endl;
158 }
159
160 int Logging::log_sr(void *cb_data, int loglevel, const char *format, va_list args)
161 {
162 va_list args2;
163
164 (void)cb_data;
165
166 va_copy(args2, args);
167 if (prev_sr_log_cb)
168 prev_sr_log_cb(prev_sr_log_cb_data, loglevel, format, args2);
169 va_end(args2);
170
171 char *text = g_strdup_vprintf(format, args);
172 logging.log(QString::fromUtf8(text), LogSource_sr);
173 g_free(text);
174
175 return SR_OK;
176 }
177
178 #ifdef ENABLE_DECODE
179 int Logging::log_srd(void *cb_data, int loglevel, const char *format, va_list args)
180 {
181 va_list args2;
182
183 (void)cb_data;
184
185 va_copy(args2, args);
186 if (prev_srd_log_cb)
187 prev_srd_log_cb(prev_srd_log_cb_data, loglevel, format, args2);
188 va_end(args2);
189
190 char *text = g_strdup_vprintf(format, args);
191 logging.log(QString::fromUtf8(text), LogSource_srd);
192 g_free(text);
193
194 return SR_OK;
195 }
196 #endif
197
198 void Logging::on_setting_changed(const QString &key, const QVariant &value)
199 {
200 if (key == GlobalSettings::Key_Log_BufferSize) {
201 // Truncate buffer if needed
202 const int delta = buffer_.size() - value.toInt();
203 if (delta > 0)
204 buffer_.erase(buffer_.begin(), buffer_.begin() + delta);
205
206 buffer_size_ = value.toInt();
207 buffer_.reserve(buffer_size_);
208 }
209 }
210
211 } // namespace pv
0 /*
1 * This file is part of the PulseView project.
2 *
3 * Copyright (C) 2018 Soeren Apel <soeren@apelpie.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #ifndef PULSEVIEW_PV_LOGGING_HPP
20 #define PULSEVIEW_PV_LOGGING_HPP
21
22 #include "globalsettings.hpp"
23
24 #include <mutex>
25
26 #include <QtGlobal>
27 #include <QObject>
28 #include <QString>
29 #include <QStringList>
30
31 using std::mutex;
32
33 namespace pv {
34
35 class Logging : public QObject, public GlobalSettingsInterface
36 {
37 Q_OBJECT
38
39 public:
40 enum LogSource {
41 LogSource_pv,
42 LogSource_sr,
43 LogSource_srd
44 };
45
46 static const int MIN_BUFFER_SIZE;
47 static const int MAX_BUFFER_SIZE;
48
49 public:
50 ~Logging();
51 void init();
52
53 int get_log_level() const;
54 void set_log_level(int level);
55
56 QString get_log() const;
57
58 void log(const QString &text, int source);
59
60 static void log_pv(QtMsgType type, const QMessageLogContext &context, const QString &msg);
61
62 static int log_sr(void *cb_data, int loglevel, const char *format, va_list args);
63
64 #ifdef ENABLE_DECODE
65 static int log_srd(void *cb_data, int loglevel, const char *format, va_list args);
66 #endif
67
68 private:
69 void on_setting_changed(const QString &key, const QVariant &value);
70
71 Q_SIGNALS:
72 void logged_text(QString s);
73
74 private:
75 int buffer_size_;
76 QStringList buffer_;
77 mutable mutex log_mutex_;
78 };
79
80 extern Logging logging;
81
82 } // namespace pv
83
84 #endif // PULSEVIEW_PV_LOGGING_HPP
2929 #include <QAction>
3030 #include <QApplication>
3131 #include <QCloseEvent>
32 #include <QDebug>
3233 #include <QDockWidget>
3334 #include <QHBoxLayout>
3435 #include <QMessageBox>
4950
5051 #include <libsigrokcxx/libsigrokcxx.hpp>
5152
52 using std::bind;
5353 using std::dynamic_pointer_cast;
5454 using std::make_shared;
55 using std::map;
56 using std::placeholders::_1;
5755 using std::shared_ptr;
5856 using std::string;
5957
7674 icon_green_(":/icons/status-green.svg"),
7775 icon_grey_(":/icons/status-grey.svg")
7876 {
79 qRegisterMetaType<util::Timestamp>("util::Timestamp");
80 qRegisterMetaType<uint64_t>("uint64_t");
81
82 GlobalSettings::register_change_handler(GlobalSettings::Key_View_ColouredBG,
83 bind(&MainWindow::on_settingViewColouredBg_changed, this, _1));
84
85 GlobalSettings::register_change_handler(GlobalSettings::Key_View_ShowSamplingPoints,
86 bind(&MainWindow::on_settingViewShowSamplingPoints_changed, this, _1));
87
88 GlobalSettings::register_change_handler(GlobalSettings::Key_View_ShowAnalogMinorGrid,
89 bind(&MainWindow::on_settingViewShowAnalogMinorGrid_changed, this, _1));
90
91 GlobalSettings settings;
92 settings.set_defaults_where_needed();
77 GlobalSettings::add_change_handler(this);
9378
9479 setup_ui();
9580 restore_ui_settings();
9782
9883 MainWindow::~MainWindow()
9984 {
85 GlobalSettings::remove_change_handler(this);
86
10087 while (!sessions_.empty())
10188 remove_session(sessions_.front());
89 }
90
91 void MainWindow::show_session_error(const QString text, const QString info_text)
92 {
93 // TODO Emulate noquote()
94 qDebug() << "Notifying user of session error:" << info_text;
95
96 QMessageBox msg;
97 msg.setText(text);
98 msg.setInformativeText(info_text);
99 msg.setStandardButtons(QMessageBox::Ok);
100 msg.setIcon(QMessageBox::Warning);
101 msg.exec();
102102 }
103103
104104 shared_ptr<views::ViewBase> MainWindow::get_active_view() const
119119 }
120120
121121 // Get the view contained in the dock widget
122 for (auto entry : view_docks_)
122 for (auto& entry : view_docks_)
123123 if (entry.first == dock)
124124 return entry.second;
125125
133133 shared_ptr<views::ViewBase> v;
134134
135135 QMainWindow *main_window = nullptr;
136 for (auto entry : session_windows_)
136 for (auto& entry : session_windows_)
137137 if (entry.first.get() == &session)
138138 main_window = entry.second;
139139
168168 QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable);
169169
170170 QAbstractButton *close_btn =
171 dock->findChildren<QAbstractButton*>
172 ("qt_dockwidget_closebutton").front();
171 dock->findChildren<QAbstractButton*>("qt_dockwidget_closebutton") // clazy:exclude=detaching-temporary
172 .front();
173173
174174 connect(close_btn, SIGNAL(clicked(bool)),
175175 this, SLOT(on_view_close_clicked()));
176176
177 connect(&session, SIGNAL(trigger_event(util::Timestamp)),
177 connect(&session, SIGNAL(trigger_event(int, util::Timestamp)),
178178 qobject_cast<views::ViewBase*>(v.get()),
179 SLOT(trigger_event(util::Timestamp)));
179 SLOT(trigger_event(int, util::Timestamp)));
180180
181181 if (type == views::ViewTypeTrace) {
182182 views::trace::View *tv =
183183 qobject_cast<views::trace::View*>(v.get());
184184
185 tv->enable_coloured_bg(settings.value(GlobalSettings::Key_View_ColouredBG).toBool());
185 tv->enable_colored_bg(settings.value(GlobalSettings::Key_View_ColoredBG).toBool());
186186 tv->enable_show_sampling_points(settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool());
187187 tv->enable_show_analog_minor_grid(settings.value(GlobalSettings::Key_View_ShowAnalogMinorGrid).toBool());
188188
222222 continue;
223223
224224 // Find the dock the view is contained in and remove it
225 for (auto entry : view_docks_)
225 for (auto& entry : view_docks_)
226226 if (entry.second == view) {
227227 // Remove the view from the session
228228 session->deregister_view(view);
290290 session->stop_capture();
291291 QApplication::processEvents();
292292
293 for (shared_ptr<views::ViewBase> view : session->views())
293 for (const shared_ptr<views::ViewBase>& view : session->views())
294294 remove_view(view);
295295
296296 QMainWindow *window = session_windows_.at(session);
309309 // When there are no more tabs, the height of the QTabWidget
310310 // drops to zero. We must prevent this to keep the static
311311 // widgets visible
312 for (QWidget *w : static_tab_widget_->findChildren<QWidget*>())
312 for (QWidget *w : static_tab_widget_->findChildren<QWidget*>()) // clazy:exclude=range-loop
313313 w->setMinimumHeight(h);
314314
315315 int margin = static_tab_widget_->layout()->contentsMargins().bottom();
337337
338338 shared_ptr<Session> session = add_session();
339339
340 map<string, string> dev_info;
341 shared_ptr<devices::HardwareDevice> other_device, demo_device;
342
343 // Use any available device that's not demo
344 for (shared_ptr<devices::HardwareDevice> dev : device_manager_.devices()) {
345 if (dev->hardware_device()->driver()->name() == "demo") {
340 // Check the list of available devices. Prefer the one that was
341 // found with user supplied scan specs (if applicable). Then try
342 // one of the auto detected devices that are not the demo device.
343 // Pick demo in the absence of "genuine" hardware devices.
344 shared_ptr<devices::HardwareDevice> user_device, other_device, demo_device;
345 for (const shared_ptr<devices::HardwareDevice>& dev : device_manager_.devices()) {
346 if (dev == device_manager_.user_spec_device()) {
347 user_device = dev;
348 } else if (dev->hardware_device()->driver()->name() == "demo") {
346349 demo_device = dev;
347350 } else {
348351 other_device = dev;
349352 }
350353 }
351
352 // ...and if there isn't any, just use demo then
353 session->select_device(other_device ? other_device : demo_device);
354 if (user_device)
355 session->select_device(user_device);
356 else if (other_device)
357 session->select_device(other_device);
358 else
359 session->select_device(demo_device);
354360 }
355361
356362 void MainWindow::save_sessions()
358364 QSettings settings;
359365 int id = 0;
360366
361 for (shared_ptr<Session> session : sessions_) {
367 for (shared_ptr<Session>& session : sessions_) {
362368 // Ignore sessions using the demo device or no device at all
363369 if (session->device()) {
364370 shared_ptr<devices::HardwareDevice> device =
394400 }
395401 }
396402
403 void MainWindow::on_setting_changed(const QString &key, const QVariant &value)
404 {
405 if (key == GlobalSettings::Key_View_ColoredBG)
406 on_settingViewColoredBg_changed(value);
407
408 if (key == GlobalSettings::Key_View_ShowSamplingPoints)
409 on_settingViewShowSamplingPoints_changed(value);
410
411 if (key == GlobalSettings::Key_View_ShowAnalogMinorGrid)
412 on_settingViewShowAnalogMinorGrid_changed(value);
413 }
414
397415 void MainWindow::setup_ui()
398416 {
399417 setObjectName(QString::fromUtf8("MainWindow"));
414432 view_show_analog_minor_grid_shortcut_ = new QShortcut(QKeySequence(Qt::Key_G), this, SLOT(on_view_show_analog_minor_grid_shortcut()));
415433 view_show_analog_minor_grid_shortcut_->setAutoRepeat(false);
416434
417 view_coloured_bg_shortcut_ = new QShortcut(QKeySequence(Qt::Key_B), this, SLOT(on_view_coloured_bg_shortcut()));
418 view_coloured_bg_shortcut_->setAutoRepeat(false);
435 view_colored_bg_shortcut_ = new QShortcut(QKeySequence(Qt::Key_B), this, SLOT(on_view_colored_bg_shortcut()));
436 view_colored_bg_shortcut_->setAutoRepeat(false);
419437
420438 // Set up the tab area
421439 new_session_button_ = new QToolButton();
510528 shared_ptr<Session> MainWindow::get_tab_session(int index) const
511529 {
512530 // Find the session that belongs to the tab's main window
513 for (auto entry : session_windows_)
531 for (auto& entry : session_windows_)
514532 if (entry.second == session_selector_.widget(index))
515533 return entry.first;
516534
521539 {
522540 bool data_saved = true;
523541
524 for (auto entry : session_windows_)
542 for (auto& entry : session_windows_)
525543 if (!entry.first->data_saved())
526544 data_saved = false;
527545
552570 return false;
553571 }
554572
555 void MainWindow::session_error(const QString text, const QString info_text)
556 {
557 QMetaObject::invokeMethod(this, "show_session_error",
558 Qt::QueuedConnection, Q_ARG(QString, text),
559 Q_ARG(QString, info_text));
560 }
561
562 void MainWindow::show_session_error(const QString text, const QString info_text)
563 {
564 QMessageBox msg(this);
565 msg.setText(text);
566 msg.setInformativeText(info_text);
567 msg.setStandardButtons(QMessageBox::Ok);
568 msg.setIcon(QMessageBox::Warning);
569 msg.exec();
570 }
571
572573 void MainWindow::on_add_view(const QString &title, views::ViewType type,
573574 Session *session)
574575 {
575576 // We get a pointer and need a reference
576 for (shared_ptr<Session> s : sessions_)
577 for (shared_ptr<Session>& s : sessions_)
577578 if (s.get() == session)
578579 add_view(title, type, *s);
579580 }
630631 switch (session->get_capture_state()) {
631632 case Session::Stopped:
632633 session->start_capture([&](QString message) {
633 session_error("Capture failed", message); });
634 show_session_error("Capture failed", message); });
634635 break;
635636 case Session::AwaitingTrigger:
636637 case Session::Running:
651652 Session *session = qobject_cast<Session*>(QObject::sender());
652653 assert(session);
653654
654 for (shared_ptr<views::ViewBase> view : session->views()) {
655 for (const shared_ptr<views::ViewBase>& view : session->views()) {
655656 // Get the dock that contains the view
656 for (auto entry : view_docks_)
657 for (auto& entry : view_docks_)
657658 if (entry.second == view) {
658659 entry.first->setObjectName(session->name());
659660 entry.first->setWindowTitle(session->name());
661662 }
662663
663664 // Update the tab widget by finding the main window and the tab from that
664 for (auto entry : session_windows_)
665 for (auto& entry : session_windows_)
665666 if (entry.first.get() == session) {
666667 QMainWindow *window = entry.second;
667668 const int index = session_selector_.indexOf(window);
693694 void MainWindow::on_new_view(Session *session)
694695 {
695696 // We get a pointer and need a reference
696 for (shared_ptr<Session> s : sessions_)
697 for (shared_ptr<Session>& s : sessions_)
697698 if (s.get() == session)
698699 add_view(session->name(), views::ViewTypeTrace, *s);
699700 }
714715 // Get the view contained in the dock widget
715716 shared_ptr<views::ViewBase> view;
716717
717 for (auto entry : view_docks_)
718 for (auto& entry : view_docks_)
718719 if (entry.first == dock)
719720 view = entry.second;
720721
758759 remove_session(session);
759760 }
760761
761 void MainWindow::on_view_coloured_bg_shortcut()
762 void MainWindow::on_view_colored_bg_shortcut()
762763 {
763764 GlobalSettings settings;
764765
765 bool state = settings.value(GlobalSettings::Key_View_ColouredBG).toBool();
766 settings.setValue(GlobalSettings::Key_View_ColouredBG, !state);
766 bool state = settings.value(GlobalSettings::Key_View_ColoredBG).toBool();
767 settings.setValue(GlobalSettings::Key_View_ColoredBG, !state);
767768 }
768769
769770 void MainWindow::on_view_sticky_scrolling_shortcut()
790791 settings.setValue(GlobalSettings::Key_View_ShowAnalogMinorGrid, !state);
791792 }
792793
793 void MainWindow::on_settingViewColouredBg_changed(const QVariant new_value)
794 void MainWindow::on_settingViewColoredBg_changed(const QVariant new_value)
794795 {
795796 bool state = new_value.toBool();
796797
797 for (auto entry : view_docks_) {
798 for (auto& entry : view_docks_) {
798799 shared_ptr<views::ViewBase> viewbase = entry.second;
799800
800801 // Only trace views have this setting
801802 views::trace::View* view =
802803 qobject_cast<views::trace::View*>(viewbase.get());
803804 if (view)
804 view->enable_coloured_bg(state);
805 view->enable_colored_bg(state);
805806 }
806807 }
807808
809810 {
810811 bool state = new_value.toBool();
811812
812 for (auto entry : view_docks_) {
813 for (auto& entry : view_docks_) {
813814 shared_ptr<views::ViewBase> viewbase = entry.second;
814815
815816 // Only trace views have this setting
824825 {
825826 bool state = new_value.toBool();
826827
827 for (auto entry : view_docks_) {
828 for (auto& entry : view_docks_) {
828829 shared_ptr<views::ViewBase> viewbase = entry.second;
829830
830831 // Only trace views have this setting
2929 #include <QTabWidget>
3030 #include <QToolButton>
3131
32 #include "globalsettings.hpp"
3233 #include "session.hpp"
3334 #include "views/viewbase.hpp"
3435
6061 #endif
6162 }
6263
63 class MainWindow : public QMainWindow
64 class MainWindow : public QMainWindow, public GlobalSettingsInterface
6465 {
6566 Q_OBJECT
6667
7273 QWidget *parent = nullptr);
7374
7475 ~MainWindow();
76
77 static void show_session_error(const QString text, const QString info_text);
7578
7679 shared_ptr<views::ViewBase> get_active_view() const;
7780
9194 void save_sessions();
9295 void restore_sessions();
9396
97 void on_setting_changed(const QString &key, const QVariant &value);
98
9499 private:
95100 void setup_ui();
96101
105110
106111 virtual bool restoreState(const QByteArray &state, int version = 0);
107112
108 void session_error(const QString text, const QString info_text);
109
110113 private Q_SLOTS:
111 void show_session_error(const QString text, const QString info_text);
112
113114 void on_add_view(const QString &title, views::ViewType type,
114115 Session *session);
115116
129130 void on_tab_changed(int index);
130131 void on_tab_close_requested(int index);
131132
132 void on_view_coloured_bg_shortcut();
133 void on_view_colored_bg_shortcut();
133134 void on_view_sticky_scrolling_shortcut();
134135 void on_view_show_sampling_points_shortcut();
135136 void on_view_show_analog_minor_grid_shortcut();
136137
137 void on_settingViewColouredBg_changed(const QVariant new_value);
138 void on_settingViewColoredBg_changed(const QVariant new_value);
138139 void on_settingViewShowSamplingPoints_changed(const QVariant new_value);
139140 void on_settingViewShowAnalogMinorGrid_changed(const QVariant new_value);
140141
162163 QShortcut *view_sticky_scrolling_shortcut_;
163164 QShortcut *view_show_sampling_points_shortcut_;
164165 QShortcut *view_show_analog_minor_grid_shortcut_;
165 QShortcut *view_coloured_bg_shortcut_;
166 QShortcut *view_colored_bg_shortcut_;
166167 QShortcut *run_stop_shortcut_;
167168 QShortcut *close_application_shortcut_;
168169 QShortcut *close_current_tab_shortcut_;
1818
1919 #include <map>
2020
21 #include <QApplication>
2122 #include <QCheckBox>
23 #include <QFontMetrics>
2224 #include <QFormLayout>
2325 #include <QGridLayout>
26 #include <QHBoxLayout>
2427 #include <QLabel>
2528
2629 #include "channels.hpp"
2730
31 #include <pv/session.hpp>
2832 #include <pv/binding/device.hpp>
33 #include <pv/data/logic.hpp>
34 #include <pv/data/logicsegment.hpp>
2935 #include <pv/data/signalbase.hpp>
3036 #include <pv/devices/device.hpp>
31 #include <pv/session.hpp>
32 #include <pv/views/trace/signal.hpp>
33
34 #include <libsigrokcxx/libsigrokcxx.hpp>
35
36 using namespace Qt;
37
37
38 using std::make_shared;
3839 using std::map;
40 using std::out_of_range;
3941 using std::shared_ptr;
40 using std::make_shared;
4142 using std::unordered_set;
4243 using std::vector;
4344
4445 using pv::data::SignalBase;
46 using pv::data::Logic;
47 using pv::data::LogicSegment;
4548
4649 using sigrok::Channel;
4750 using sigrok::ChannelGroup;
5457 Popup(parent),
5558 session_(session),
5659 updating_channels_(false),
57 enable_all_channels_(tr("Enable All"), this),
58 disable_all_channels_(tr("Disable All"), this),
60 enable_all_channels_(tr("All"), this),
61 disable_all_channels_(tr("All"), this),
62 enable_all_logic_channels_(tr("Logic"), this),
63 disable_all_logic_channels_(tr("Logic"), this),
64 enable_all_analog_channels_(tr("Analog"), this),
65 disable_all_analog_channels_(tr("Analog"), this),
66 enable_all_named_channels_(tr("Named"), this),
67 disable_all_unnamed_channels_(tr("Unnamed"), this),
68 enable_all_changing_channels_(tr("Changing"), this),
69 disable_all_non_changing_channels_(tr("Non-changing"), this),
5970 check_box_mapper_(this)
6071 {
6172 // Create the layout
6879 map<shared_ptr<Channel>, shared_ptr<SignalBase> > signal_map;
6980
7081 unordered_set< shared_ptr<SignalBase> > sigs;
71 for (const shared_ptr<data::SignalBase> b : session_.signalbases())
82 for (const shared_ptr<data::SignalBase>& b : session_.signalbases())
7283 sigs.insert(b);
7384
7485 for (const shared_ptr<SignalBase> &sig : sigs)
7586 signal_map[sig->channel()] = sig;
7687
7788 // Populate channel groups
78 for (auto entry : device->channel_groups()) {
79 shared_ptr<ChannelGroup> group = entry.second;
80 // Make a set of signals, and removed this signals from the
81 // signal map.
89 for (auto& entry : device->channel_groups()) {
90 const shared_ptr<ChannelGroup> group = entry.second;
91 // Make a set of signals and remove these signals from the signal map
8292 vector< shared_ptr<SignalBase> > group_sigs;
83 for (auto channel : group->channels()) {
93 for (auto& channel : group->channels()) {
8494 const auto iter = signal_map.find(channel);
8595
8696 if (iter == signal_map.end())
94104 }
95105
96106 // Make a vector of the remaining channels
97 vector< shared_ptr<SignalBase> > global_sigs;
98 for (auto channel : device->channels()) {
107 vector< shared_ptr<SignalBase> > global_analog_sigs, global_logic_sigs;
108 for (auto& channel : device->channels()) {
99109 const map<shared_ptr<Channel>, shared_ptr<SignalBase> >::
100110 const_iterator iter = signal_map.find(channel);
101 if (iter != signal_map.end())
102 global_sigs.push_back((*iter).second);
103 }
104
105 // Create a group
106 populate_group(nullptr, global_sigs);
111
112 if (iter != signal_map.end()) {
113 const shared_ptr<SignalBase> signal = (*iter).second;
114 if (signal->type() == SignalBase::AnalogChannel)
115 global_analog_sigs.push_back(signal);
116 else
117 global_logic_sigs.push_back(signal);
118 }
119 }
120
121 // Create the groups for the ungrouped channels
122 populate_group(nullptr, global_logic_sigs);
123 populate_group(nullptr, global_analog_sigs);
107124
108125 // Create the enable/disable all buttons
109 connect(&enable_all_channels_, SIGNAL(clicked()),
110 this, SLOT(enable_all_channels()));
111 connect(&disable_all_channels_, SIGNAL(clicked()),
112 this, SLOT(disable_all_channels()));
113
114 enable_all_channels_.setFlat(true);
115 disable_all_channels_.setFlat(true);
116
117 buttons_bar_.addWidget(&enable_all_channels_);
118 buttons_bar_.addWidget(&disable_all_channels_);
119 buttons_bar_.addStretch(1);
120
121 layout_.addRow(&buttons_bar_);
126 connect(&enable_all_channels_, SIGNAL(clicked()), this, SLOT(enable_all_channels()));
127 connect(&disable_all_channels_, SIGNAL(clicked()), this, SLOT(disable_all_channels()));
128 connect(&enable_all_logic_channels_, SIGNAL(clicked()), this, SLOT(enable_all_logic_channels()));
129 connect(&disable_all_logic_channels_, SIGNAL(clicked()), this, SLOT(disable_all_logic_channels()));
130 connect(&enable_all_analog_channels_, SIGNAL(clicked()), this, SLOT(enable_all_analog_channels()));
131 connect(&disable_all_analog_channels_, SIGNAL(clicked()), this, SLOT(disable_all_analog_channels()));
132 connect(&enable_all_named_channels_, SIGNAL(clicked()), this, SLOT(enable_all_named_channels()));
133 connect(&disable_all_unnamed_channels_, SIGNAL(clicked()), this, SLOT(disable_all_unnamed_channels()));
134 connect(&enable_all_changing_channels_, SIGNAL(clicked()),
135 this, SLOT(enable_all_changing_channels()));
136 connect(&disable_all_non_changing_channels_, SIGNAL(clicked()),
137 this, SLOT(disable_all_non_changing_channels()));
138
139 QLabel *label1 = new QLabel(tr("Disable: "));
140 filter_buttons_bar_.addWidget(label1, 0, 0);
141 filter_buttons_bar_.addWidget(&disable_all_channels_, 0, 1);
142 filter_buttons_bar_.addWidget(&disable_all_logic_channels_, 0, 2);
143 filter_buttons_bar_.addWidget(&disable_all_analog_channels_, 0, 3);
144 filter_buttons_bar_.addWidget(&disable_all_unnamed_channels_, 0, 4);
145 filter_buttons_bar_.addWidget(&disable_all_non_changing_channels_, 0, 5);
146
147 QLabel *label2 = new QLabel(tr("Enable: "));
148 filter_buttons_bar_.addWidget(label2, 1, 0);
149 filter_buttons_bar_.addWidget(&enable_all_channels_, 1, 1);
150 filter_buttons_bar_.addWidget(&enable_all_logic_channels_, 1, 2);
151 filter_buttons_bar_.addWidget(&enable_all_analog_channels_, 1, 3);
152 filter_buttons_bar_.addWidget(&enable_all_named_channels_, 1, 4);
153 filter_buttons_bar_.addWidget(&enable_all_changing_channels_, 1, 5);
154
155 layout_.addItem(new QSpacerItem(0, 15, QSizePolicy::Expanding, QSizePolicy::Expanding));
156 layout_.addRow(&filter_buttons_bar_);
122157
123158 // Connect the check-box signal mapper
124159 connect(&check_box_mapper_, SIGNAL(mapped(QWidget*)),
129164 {
130165 updating_channels_ = true;
131166
132 for (map<QCheckBox*, shared_ptr<SignalBase> >::const_iterator i =
133 check_box_signal_map_.begin();
134 i != check_box_signal_map_.end(); i++) {
135 const shared_ptr<SignalBase> sig = (*i).second;
167 for (auto& entry : check_box_signal_map_) {
168 QCheckBox *cb = entry.first;
169 const shared_ptr<SignalBase> sig = entry.second;
136170 assert(sig);
137171
138172 sig->set_enabled(set);
139 (*i).first->setChecked(set);
173 cb->setChecked(set);
174 }
175
176 updating_channels_ = false;
177 }
178
179 void Channels::enable_channels_conditionally(
180 function<bool (const shared_ptr<data::SignalBase>)> cond_func)
181 {
182 updating_channels_ = true;
183
184 for (auto& entry : check_box_signal_map_) {
185 QCheckBox *cb = entry.first;
186 const shared_ptr<SignalBase> sig = entry.second;
187 assert(sig);
188
189 if (cond_func(sig)) {
190 sig->set_enabled(true);
191 cb->setChecked(true);
192 }
193 }
194
195 updating_channels_ = false;
196 }
197
198 void Channels::disable_channels_conditionally(
199 function<bool (const shared_ptr<data::SignalBase>)> cond_func)
200 {
201 updating_channels_ = true;
202
203 for (auto& entry : check_box_signal_map_) {
204 QCheckBox *cb = entry.first;
205 const shared_ptr<SignalBase> sig = entry.second;
206 assert(sig);
207
208 if (cond_func(sig)) {
209 sig->set_enabled(false);
210 cb->setChecked(false);
211 }
140212 }
141213
142214 updating_channels_ = false;
155227 binding = make_shared<Device>(group);
156228
157229 // Create a title if the group is going to have any content
158 if ((!sigs.empty() || (binding && !binding->properties().empty())) &&
159 group)
160 layout_.addRow(new QLabel(
161 QString("<h3>%1</h3>").arg(group->name().c_str())));
230 if ((!sigs.empty() || (binding && !binding->properties().empty())) && group)
231 {
232 QLabel *label = new QLabel(
233 QString("<h3>%1</h3>").arg(group->name().c_str()));
234 layout_.addRow(label);
235 group_label_map_[group] = label;
236 }
162237
163238 // Create the channel group grid
164239 QGridLayout *const channel_grid = create_channel_group_grid(sigs);
180255 for (const shared_ptr<SignalBase>& sig : sigs) {
181256 assert(sig);
182257
183 QCheckBox *const checkbox = new QCheckBox(sig->name());
258 QCheckBox *const checkbox = new QCheckBox(sig->display_name());
184259 check_box_mapper_.setMapping(checkbox, checkbox);
185260 connect(checkbox, SIGNAL(toggled(bool)),
186261 &check_box_mapper_, SLOT(map()));
200275 {
201276 pv::widgets::Popup::showEvent(event);
202277
278 const shared_ptr<sigrok::Device> device = session_.device()->device();
279 assert(device);
280
281 // Update group labels
282 for (auto& entry : device->channel_groups()) {
283 const shared_ptr<ChannelGroup> group = entry.second;
284
285 try {
286 QLabel* label = group_label_map_.at(group);
287 label->setText(QString("<h3>%1</h3>").arg(group->name().c_str()));
288 } catch (out_of_range&) {
289 // Do nothing
290 }
291 }
292
203293 updating_channels_ = true;
204294
205 for (map<QCheckBox*, shared_ptr<SignalBase> >::const_iterator i =
206 check_box_signal_map_.begin();
207 i != check_box_signal_map_.end(); i++) {
208 const shared_ptr<SignalBase> sig = (*i).second;
295 for (auto& entry : check_box_signal_map_) {
296 QCheckBox *cb = entry.first;
297 const shared_ptr<SignalBase> sig = entry.second;
209298 assert(sig);
210299
211 (*i).first->setChecked(sig->enabled());
300 // Update the check box
301 cb->setChecked(sig->enabled());
302 cb->setText(sig->display_name());
212303 }
213304
214305 updating_channels_ = false;
243334 set_all_channels(false);
244335 }
245336
337 void Channels::enable_all_logic_channels()
338 {
339 enable_channels_conditionally([](const shared_ptr<SignalBase> signal)
340 { return signal->type() == SignalBase::LogicChannel; });
341 }
342
343 void Channels::disable_all_logic_channels()
344 {
345 disable_channels_conditionally([](const shared_ptr<SignalBase> signal)
346 { return signal->type() == SignalBase::LogicChannel; });
347 }
348
349 void Channels::enable_all_analog_channels()
350 {
351 enable_channels_conditionally([](const shared_ptr<SignalBase> signal)
352 { return signal->type() == SignalBase::AnalogChannel; });
353 }
354
355 void Channels::disable_all_analog_channels()
356 {
357 disable_channels_conditionally([](const shared_ptr<SignalBase> signal)
358 { return signal->type() == SignalBase::AnalogChannel; });
359 }
360
361 void Channels::enable_all_named_channels()
362 {
363 enable_channels_conditionally([](const shared_ptr<SignalBase> signal)
364 { return signal->name() != signal->internal_name(); });
365 }
366
367 void Channels::disable_all_unnamed_channels()
368 {
369 disable_channels_conditionally([](const shared_ptr<SignalBase> signal)
370 { return signal->name() == signal->internal_name(); });
371 }
372
373 void Channels::enable_all_changing_channels()
374 {
375 enable_channels_conditionally([](const shared_ptr<SignalBase> signal)
376 {
377 // Never enable channels without sample data
378 if (!signal->has_samples())
379 return false;
380
381 // Non-logic channels are considered to always have a signal
382 if (signal->type() != SignalBase::LogicChannel)
383 return true;
384
385 const shared_ptr<Logic> logic = signal->logic_data();
386 assert(logic);
387
388 // If any of the segments has edges, enable this channel
389 for (shared_ptr<LogicSegment> segment : logic->logic_segments()) {
390 vector<LogicSegment::EdgePair> edges;
391
392 segment->get_subsampled_edges(edges,
393 0, segment->get_sample_count() - 1,
394 LogicSegment::MipMapScaleFactor,
395 signal->index());
396
397 if (edges.size() > 2)
398 return true;
399 }
400
401 // No edges detected in any of the segments
402 return false;
403 });
404 }
405
406 void Channels::disable_all_non_changing_channels()
407 {
408 disable_channels_conditionally([](const shared_ptr<SignalBase> signal)
409 {
410 // Always disable channels without sample data
411 if (!signal->has_samples())
412 return true;
413
414 // Non-logic channels are considered to always have a signal
415 if (signal->type() != SignalBase::LogicChannel)
416 return false;
417
418 const shared_ptr<Logic> logic = signal->logic_data();
419 assert(logic);
420
421 // If any of the segments has edges, leave this channel enabled
422 for (shared_ptr<LogicSegment> segment : logic->logic_segments()) {
423 vector<LogicSegment::EdgePair> edges;
424
425 segment->get_subsampled_edges(edges,
426 0, segment->get_sample_count() - 1,
427 LogicSegment::MipMapScaleFactor,
428 signal->index());
429
430 if (edges.size() > 2)
431 return false;
432 }
433
434 // No edges detected in any of the segments
435 return true;
436 });
437 }
438
246439 } // namespace popups
247440 } // namespace pv
1919 #ifndef PULSEVIEW_PV_POPUPS_CHANNELS_HPP
2020 #define PULSEVIEW_PV_POPUPS_CHANNELS_HPP
2121
22 #include <functional>
2223 #include <map>
2324 #include <memory>
2425 #include <vector>
2526
27 #include <QCheckBox>
2628 #include <QFormLayout>
27 #include <QHBoxLayout>
29 #include <QGridLayout>
30 #include <QLabel>
2831 #include <QPushButton>
2932 #include <QSignalMapper>
3033
3134 #include <pv/widgets/popup.hpp>
3235
36 using std::function;
3337 using std::map;
3438 using std::shared_ptr;
3539 using std::vector;
36
37 class QCheckBox;
38 class QGridLayout;
3940
4041 namespace sigrok {
4142 class ChannelGroup;
6970 private:
7071 void set_all_channels(bool set);
7172
73 void enable_channels_conditionally(
74 function<bool (const shared_ptr<data::SignalBase>)> cond_func);
75 void disable_channels_conditionally(
76 function<bool (const shared_ptr<data::SignalBase>)> cond_func);
77
7278 void populate_group(shared_ptr<sigrok::ChannelGroup> group,
7379 const vector< shared_ptr<pv::data::SignalBase> > sigs);
7480
7581 QGridLayout* create_channel_group_grid(
7682 const vector< shared_ptr<pv::data::SignalBase> > sigs);
7783
78 private:
7984 void showEvent(QShowEvent *event);
8085
8186 private Q_SLOTS:
8388
8489 void enable_all_channels();
8590 void disable_all_channels();
91 void enable_all_logic_channels();
92 void disable_all_logic_channels();
93 void enable_all_analog_channels();
94 void disable_all_analog_channels();
95 void enable_all_named_channels();
96 void disable_all_unnamed_channels();
97 void enable_all_changing_channels();
98 void disable_all_non_changing_channels();
8699
87100 private:
88101 pv::Session &session_;
94107 vector< shared_ptr<pv::binding::Device> > group_bindings_;
95108 map< QCheckBox*, shared_ptr<pv::data::SignalBase> >
96109 check_box_signal_map_;
110 map< shared_ptr<sigrok::ChannelGroup>, QLabel*> group_label_map_;
97111
98 QHBoxLayout buttons_bar_;
99 QPushButton enable_all_channels_;
100 QPushButton disable_all_channels_;
112 QGridLayout filter_buttons_bar_;
113 QPushButton enable_all_channels_, disable_all_channels_;
114 QPushButton enable_all_logic_channels_, disable_all_logic_channels_;
115 QPushButton enable_all_analog_channels_, disable_all_analog_channels_;
116 QPushButton enable_all_named_channels_, disable_all_unnamed_channels_;
117 QPushButton enable_all_changing_channels_, disable_all_non_changing_channels_;
101118
102119 QSignalMapper check_box_mapper_;
103120 };
4848 return binding_;
4949 }
5050
51 void DeviceOptions::show()
52 {
53 // Update device config widgets with the current values supplied by the
54 // driver before actually showing the popup dialog
55 binding_.update_property_widgets();
56
57 Popup::show();
58 }
59
5160 } // namespace popups
5261 } // namespace pv
4343
4444 pv::binding::Device& binding();
4545
46 virtual void show();
47
4648 private:
4749 shared_ptr<sigrok::Device> device_;
4850
1919 #include <cassert>
2020
2121 #include <QCheckBox>
22 #include <QDebug>
23
24 #include <libsigrokcxx/libsigrokcxx.hpp>
2225
2326 #include "bool.hpp"
2427
3942 if (!getter_)
4043 return nullptr;
4144
42 Glib::VariantBase variant = getter_();
43 if (!variant.gobj())
45 try {
46 Glib::VariantBase variant = getter_();
47 if (!variant.gobj())
48 return nullptr;
49 } catch (const sigrok::Error &e) {
50 qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what());
4451 return nullptr;
45
46 bool value = Glib::VariantBase::cast_dynamic<Glib::Variant<bool>>(
47 variant).get();
52 }
4853
4954 check_box_ = new QCheckBox(name(), parent);
5055 check_box_->setToolTip(desc());
51 check_box_->setCheckState(value ? Qt::Checked : Qt::Unchecked);
56
57 update_widget();
5258
5359 if (auto_commit)
5460 connect(check_box_, SIGNAL(stateChanged(int)),
6268 return true;
6369 }
6470
71 void Bool::update_widget()
72 {
73 if (!check_box_)
74 return;
75
76 Glib::VariantBase variant;
77
78 try {
79 variant = getter_();
80 } catch (const sigrok::Error &e) {
81 qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what());
82 return;
83 }
84
85 assert(variant.gobj());
86 bool value = Glib::VariantBase::cast_dynamic<Glib::Variant<bool>>(
87 variant).get();
88
89 check_box_->setCheckState(value ? Qt::Checked : Qt::Unchecked);
90 }
91
6592 void Bool::commit()
6693 {
6794 assert(setter_);
6996 if (!check_box_)
7097 return;
7198
72 setter_(Glib::Variant<bool>::create(
73 check_box_->checkState() == Qt::Checked));
99 setter_(Glib::Variant<bool>::create(check_box_->checkState() == Qt::Checked));
74100 }
75101
76102 void Bool::on_state_changed(int)
3737
3838 QWidget* get_widget(QWidget *parent, bool auto_commit);
3939 bool labeled_widget() const;
40 void update_widget();
4041
4142 void commit();
4243
1818
1919 #include <cassert>
2020
21 #include <QDebug>
2122 #include <QDoubleSpinBox>
23
24 #include <libsigrokcxx/libsigrokcxx.hpp>
2225
2326 #include "double.hpp"
2427
5356 if (!getter_)
5457 return nullptr;
5558
56 Glib::VariantBase variant = getter_();
57 if (!variant.gobj())
59 try {
60 Glib::VariantBase variant = getter_();
61 if (!variant.gobj())
62 return nullptr;
63 } catch (const sigrok::Error &e) {
64 qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what());
5865 return nullptr;
59
60 double value = Glib::VariantBase::cast_dynamic<Glib::Variant<double>>(
61 variant).get();
66 }
6267
6368 spin_box_ = new QDoubleSpinBox(parent);
6469 spin_box_->setDecimals(decimals_);
6873 if (step_)
6974 spin_box_->setSingleStep(*step_);
7075
71 spin_box_->setValue(value);
76 update_widget();
7277
7378 if (auto_commit)
7479 connect(spin_box_, SIGNAL(valueChanged(double)),
7580 this, SLOT(on_value_changed(double)));
7681
7782 return spin_box_;
83 }
84
85 void Double::update_widget()
86 {
87 if (!spin_box_)
88 return;
89
90 Glib::VariantBase variant;
91
92 try {
93 variant = getter_();
94 } catch (const sigrok::Error &e) {
95 qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what());
96 return;
97 }
98
99 assert(variant.gobj());
100
101 double value = Glib::VariantBase::cast_dynamic<Glib::Variant<double>>(
102 variant).get();
103 spin_box_->setValue(value);
78104 }
79105
80106 void Double::commit()
4646 virtual ~Double() = default;
4747
4848 QWidget* get_widget(QWidget *parent, bool auto_commit);
49 void update_widget();
4950
5051 void commit();
5152
1717 */
1818
1919 #include <cassert>
20 #include <cfloat>
21 #include <cmath>
22 #include <limits>
23 #include <vector>
2024
2125 #include <QComboBox>
26 #include <QDebug>
27 #include <QHBoxLayout>
28 #include <QLabel>
29 #include <QSlider>
30
31 #include <libsigrokcxx/libsigrokcxx.hpp>
2232
2333 #include "enum.hpp"
2434
35 using std::abs;
36 // Note that "using std::isnan;" is _not_ put here since that would break
37 // compilation on some platforms. Use "std::isnan()" instead in checks below.
38 using std::numeric_limits;
2539 using std::pair;
2640 using std::vector;
2741
3347 Getter getter, Setter setter) :
3448 Property(name, desc, getter, setter),
3549 values_(values),
36 selector_(nullptr)
37 {
50 is_range_(false),
51 selector_(nullptr),
52 slider_layout_widget_(nullptr),
53 slider_(nullptr),
54 slider_label_(nullptr)
55 {
56 // Try to determine whether the values make up a range, created by e.g.
57 // std_gvar_min_max_step_thresholds()
58
59 vector<double> deltas;
60 double prev_value = 0;
61
62 for (const pair<Glib::VariantBase, QString> &v : values_) {
63 gdouble value;
64 if (v.first.is_of_type(Glib::VariantType("d"))) {
65 g_variant_get((GVariant*)(v.first.gobj()), "d", &value);
66 } else if (v.first.is_of_type(Glib::VariantType("(dd)"))) {
67 gdouble dummy;
68 g_variant_get((GVariant*)(v.first.gobj()), "(dd)", &value, &dummy);
69 } else
70 break; // Type not d or (dd), so not a range that we can handle
71 deltas.push_back(value - prev_value);
72 prev_value = value;
73 }
74
75 if (deltas.size() > 0) {
76 bool constant_delta = true;
77 double prev_delta = numeric_limits<double>::quiet_NaN();
78
79 bool skip_first = true;
80 for (double delta : deltas) {
81 // First value is incorrect, it's the delta to 0 since no
82 // previous value existed yet
83 if (skip_first) {
84 skip_first = false;
85 continue;
86 }
87 if (std::isnan(prev_delta))
88 prev_delta = delta;
89
90 // 2*DBL_EPSILON doesn't work here, so use a workaround
91 if (abs(delta - prev_delta) > (delta/10))
92 constant_delta = false;
93
94 prev_delta = delta;
95 }
96
97 if (constant_delta)
98 is_range_ = true;
99 }
38100 }
39101
40102 QWidget* Enum::get_widget(QWidget *parent, bool auto_commit)
41103 {
42 if (selector_)
43 return selector_;
44
45104 if (!getter_)
46105 return nullptr;
47106
48 Glib::VariantBase variant = getter_();
107 Glib::VariantBase variant;
108
109 try {
110 variant = getter_();
111 } catch (const sigrok::Error &e) {
112 qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what());
113 return nullptr;
114 }
115
49116 if (!variant.gobj())
50117 return nullptr;
51118
52 selector_ = new QComboBox(parent);
53 for (unsigned int i = 0; i < values_.size(); i++) {
54 const pair<Glib::VariantBase, QString> &v = values_[i];
55 selector_->addItem(v.second, qVariantFromValue(v.first));
56 if (v.first.equal(variant))
57 selector_->setCurrentIndex(i);
58 }
59
60 if (auto_commit)
61 connect(selector_, SIGNAL(currentIndexChanged(int)),
62 this, SLOT(on_current_item_changed(int)));
63
64 return selector_;
119 if (is_range_) {
120 // Use slider
121 if (slider_layout_widget_)
122 return slider_layout_widget_;
123
124 slider_ = new QSlider();
125 // Sliders can't handle float values, so we just use it to specify
126 // the number of steps that we're away from the range's beginning
127 slider_->setOrientation(Qt::Horizontal);
128 slider_->setMinimum(0);
129 slider_->setMaximum(values_.size() - 1);
130 slider_->setSingleStep(1);
131
132 slider_label_ = new QLabel();
133
134 slider_layout_widget_ = new QWidget(parent);
135 QHBoxLayout *layout = new QHBoxLayout(slider_layout_widget_);
136 layout->addWidget(slider_);
137 layout->addWidget(slider_label_);
138
139 update_widget();
140
141 if (auto_commit)
142 connect(slider_, SIGNAL(valueChanged(int)),
143 this, SLOT(on_value_changed(int)));
144
145 return slider_layout_widget_;
146
147 } else {
148 // Use combo box
149 if (selector_)
150 return selector_;
151
152 selector_ = new QComboBox(parent);
153 for (unsigned int i = 0; i < values_.size(); i++) {
154 const pair<Glib::VariantBase, QString> &v = values_[i];
155 selector_->addItem(v.second, qVariantFromValue(v.first));
156 }
157
158 update_widget();
159
160 if (auto_commit)
161 connect(selector_, SIGNAL(currentIndexChanged(int)),
162 this, SLOT(on_current_index_changed(int)));
163
164 return selector_;
165 }
166 }
167
168 void Enum::update_widget()
169 {
170 Glib::VariantBase variant;
171
172 try {
173 variant = getter_();
174 } catch (const sigrok::Error &e) {
175 qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what());
176 return;
177 }
178
179 assert(variant.gobj());
180
181 if (is_range_) {
182
183 // Use slider
184 if (!slider_layout_widget_)
185 return;
186
187 for (unsigned int i = 0; i < values_.size(); i++) {
188 const pair<Glib::VariantBase, QString> &v = values_[i];
189
190 // g_variant_equal() doesn't handle floating point properly
191 if (v.first.is_of_type(Glib::VariantType("d"))) {
192 gdouble a, b;
193 g_variant_get(variant.gobj(), "d", &a);
194 g_variant_get((GVariant*)(v.first.gobj()), "d", &b);
195
196 if (abs(a - b) <= 2 * DBL_EPSILON) {
197 slider_->setValue(i);
198 slider_label_->setText(v.second);
199 }
200 } else {
201 // Check for "(dd)" type and handle it if it's found
202 if (v.first.is_of_type(Glib::VariantType("(dd)"))) {
203 gdouble a1, a2, b1, b2;
204 g_variant_get(variant.gobj(), "(dd)", &a1, &a2);
205 g_variant_get((GVariant*)(v.first.gobj()), "(dd)", &b1, &b2);
206
207 if ((abs(a1 - b1) <= 2 * DBL_EPSILON) && \
208 (abs(a2 - b2) <= 2 * DBL_EPSILON)) {
209 slider_->setValue(i);
210 slider_label_->setText(v.second);
211 }
212
213 } else {
214 qWarning() << "Enum property" << name() << "encountered unsupported type";
215 return;
216 }
217 }
218 }
219
220 } else {
221 // Use combo box
222 if (!selector_)
223 return;
224
225 for (unsigned int i = 0; i < values_.size(); i++) {
226 const pair<Glib::VariantBase, QString> &v = values_[i];
227
228 // g_variant_equal() doesn't handle floating point properly
229 if (v.first.is_of_type(Glib::VariantType("d"))) {
230 gdouble a, b;
231 g_variant_get(variant.gobj(), "d", &a);
232 g_variant_get((GVariant*)(v.first.gobj()), "d", &b);
233 if (abs(a - b) <= 2 * DBL_EPSILON)
234 selector_->setCurrentIndex(i);
235 } else {
236 // Check for "(dd)" type and handle it if it's found
237 if (v.first.is_of_type(Glib::VariantType("(dd)"))) {
238 gdouble a1, a2, b1, b2;
239 g_variant_get(variant.gobj(), "(dd)", &a1, &a2);
240 g_variant_get((GVariant*)(v.first.gobj()), "(dd)", &b1, &b2);
241 if ((abs(a1 - b1) <= 2 * DBL_EPSILON) && \
242 (abs(a2 - b2) <= 2 * DBL_EPSILON))
243 selector_->setCurrentIndex(i);
244
245 } else
246 // Handle all other types
247 if (v.first.equal(variant))
248 selector_->setCurrentIndex(i);
249 }
250 }
251 }
65252 }
66253
67254 void Enum::commit()
68255 {
69256 assert(setter_);
70257
71 if (!selector_)
72 return;
73
74 const int index = selector_->currentIndex();
75 if (index < 0)
76 return;
77
78 setter_(selector_->itemData(index).value<Glib::VariantBase>());
79 }
80
81 void Enum::on_current_item_changed(int)
258 if (is_range_) {
259 // Use slider
260 if (!slider_layout_widget_)
261 return;
262
263 setter_(values_.at(slider_->value()).first);
264
265 update_widget();
266 } else {
267 // Use combo box
268 if (!selector_)
269 return;
270
271 const int index = selector_->currentIndex();
272 if (index < 0)
273 return;
274
275 setter_(selector_->itemData(index).value<Glib::VariantBase>());
276
277 // The combo box needs no update, it already shows the current value
278 // by definition: the user picked it
279 }
280 }
281
282 void Enum::on_current_index_changed(int)
283 {
284 commit();
285 }
286
287 void Enum::on_value_changed(int)
82288 {
83289 commit();
84290 }
3232 Q_DECLARE_METATYPE(Glib::VariantBase);
3333
3434 class QComboBox;
35 class QLabel;
36 class QSlider;
3537
3638 namespace pv {
3739 namespace prop {
4850 virtual ~Enum() = default;
4951
5052 QWidget* get_widget(QWidget *parent, bool auto_commit);
53 void update_widget();
5154
5255 void commit();
5356
5457 private Q_SLOTS:
55 void on_current_item_changed(int);
58 void on_current_index_changed(int);
59 void on_value_changed(int);
5660
5761 private:
5862 const vector< pair<Glib::VariantBase, QString> > values_;
63 bool is_range_;
5964
6065 QComboBox *selector_;
66
67 QWidget *slider_layout_widget_;
68 QSlider *slider_;
69 QLabel *slider_label_;
6170 };
6271
6372 } // namespace prop
1919 #include <cassert>
2020 #include <cstdint>
2121
22 #include <QDebug>
2223 #include <QSpinBox>
24
25 #include <libsigrokcxx/libsigrokcxx.hpp>
2326
2427 #include "int.hpp"
2528
4649
4750 QWidget* Int::get_widget(QWidget *parent, bool auto_commit)
4851 {
49 int64_t int_val = 0, range_min = 0;
52 int64_t range_min = 0;
5053 uint64_t range_max = 0;
5154
5255 if (spin_box_)
5558 if (!getter_)
5659 return nullptr;
5760
58 value_ = getter_();
61 try {
62 value_ = getter_();
63 } catch (const sigrok::Error &e) {
64 qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what());
65 return nullptr;
66 }
5967
6068 GVariant *value = value_.gobj();
6169 if (!value)
6876 assert(type);
6977
7078 if (g_variant_type_equal(type, G_VARIANT_TYPE_BYTE)) {
71 int_val = g_variant_get_byte(value);
7279 range_min = 0, range_max = UINT8_MAX;
7380 } else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT16)) {
74 int_val = g_variant_get_int16(value);
7581 range_min = INT16_MIN, range_max = INT16_MAX;
7682 } else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT16)) {
77 int_val = g_variant_get_uint16(value);
7883 range_min = 0, range_max = UINT16_MAX;
7984 } else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT32)) {
80 int_val = g_variant_get_int32(value);
8185 range_min = INT32_MIN, range_max = INT32_MAX;
8286 } else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT32)) {
83 int_val = g_variant_get_uint32(value);
8487 range_min = 0, range_max = UINT32_MAX;
8588 } else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT64)) {
86 int_val = g_variant_get_int64(value);
8789 range_min = INT64_MIN, range_max = INT64_MAX;
8890 } else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT64)) {
89 int_val = g_variant_get_uint64(value);
9091 range_min = 0, range_max = UINT64_MAX;
9192 } else {
9293 // Unexpected value type.
106107 else
107108 spin_box_->setRange((int)range_min, (int)range_max);
108109
109 spin_box_->setValue((int)int_val);
110 update_widget();
110111
111112 if (auto_commit)
112113 connect(spin_box_, SIGNAL(valueChanged(int)),
113114 this, SLOT(on_value_changed(int)));
114115
115116 return spin_box_;
117 }
118
119 void Int::update_widget()
120 {
121 if (!spin_box_)
122 return;
123
124 try {
125 value_ = getter_();
126 } catch (const sigrok::Error &e) {
127 qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what());
128 return;
129 }
130
131 GVariant *value = value_.gobj();
132 assert(value);
133
134 const GVariantType *const type = g_variant_get_type(value);
135 assert(type);
136
137 int64_t int_val = 0;
138
139 if (g_variant_type_equal(type, G_VARIANT_TYPE_BYTE)) {
140 int_val = g_variant_get_byte(value);
141 } else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT16)) {
142 int_val = g_variant_get_int16(value);
143 } else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT16)) {
144 int_val = g_variant_get_uint16(value);
145 } else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT32)) {
146 int_val = g_variant_get_int32(value);
147 } else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT32)) {
148 int_val = g_variant_get_uint32(value);
149 } else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT64)) {
150 int_val = g_variant_get_int64(value);
151 } else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT64)) {
152 int_val = g_variant_get_uint64(value);
153 } else {
154 // Unexpected value type.
155 assert(false);
156 }
157
158 spin_box_->setValue((int)int_val);
116159 }
117160
118161 void Int::commit()
4444 virtual ~Int() = default;
4545
4646 QWidget* get_widget(QWidget *parent, bool auto_commit);
47 void update_widget();
4748
4849 void commit();
4950
5252 const QString& name() const;
5353 const QString& desc() const;
5454
55 virtual QWidget* get_widget(QWidget *parent,
56 bool auto_commit = false) = 0;
55 virtual QWidget* get_widget(QWidget *parent, bool auto_commit = false) = 0;
5756 virtual bool labeled_widget() const;
57 virtual void update_widget() = 0;
5858
5959 virtual void commit() = 0;
6060
6262 const Getter getter_;
6363 const Setter setter_;
6464
65 private:
65 protected:
6666 QString name_;
6767 QString desc_;
6868 };
1818
1919 #include <cassert>
2020
21 #include <QDebug>
2122 #include <QLineEdit>
2223 #include <QSpinBox>
24
25 #include <libsigrokcxx/libsigrokcxx.hpp>
2326
2427 #include "string.hpp"
2528
4750 if (!getter_)
4851 return nullptr;
4952
50 Glib::VariantBase variant = getter_();
51 if (!variant.gobj())
53 try {
54 Glib::VariantBase variant = getter_();
55 if (!variant.gobj())
56 return nullptr;
57 } catch (const sigrok::Error &e) {
58 qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what());
5259 return nullptr;
53
54 string value = Glib::VariantBase::cast_dynamic<Glib::Variant<ustring>>(
55 variant).get();
60 }
5661
5762 line_edit_ = new QLineEdit(parent);
58 line_edit_->setText(QString::fromStdString(value));
63
64 update_widget();
5965
6066 if (auto_commit)
6167 connect(line_edit_, SIGNAL(textEdited(const QString&)),
6268 this, SLOT(on_text_edited(const QString&)));
6369
6470 return line_edit_;
71 }
72
73 void String::update_widget()
74 {
75 if (!line_edit_)
76 return;
77
78 Glib::VariantBase variant;
79
80 try {
81 variant = getter_();
82 } catch (const sigrok::Error &e) {
83 qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what());
84 return;
85 }
86
87 assert(variant.gobj());
88
89 string value = Glib::VariantBase::cast_dynamic<Glib::Variant<ustring>>(
90 variant).get();
91
92 line_edit_->setText(QString::fromStdString(value));
6593 }
6694
6795 void String::commit()
3434 String(QString name, QString desc, Getter getter, Setter setter);
3535
3636 QWidget* get_widget(QWidget *parent, bool auto_commit);
37 void update_widget();
3738
3839 void commit();
3940
1616 * along with this program; if not, see <http://www.gnu.org/licenses/>.
1717 */
1818
19 #include <QDebug>
1920 #include <QFileInfo>
2021
2122 #include <cassert>
2627 #include <sys/stat.h>
2728
2829 #include "devicemanager.hpp"
30 #include "mainwindow.hpp"
2931 #include "session.hpp"
3032
3133 #include "data/analog.hpp"
3234 #include "data/analogsegment.hpp"
3335 #include "data/decode/decoder.hpp"
34 #include "data/decoderstack.hpp"
3536 #include "data/logic.hpp"
3637 #include "data/logicsegment.hpp"
3738 #include "data/signalbase.hpp"
5253
5354 #ifdef ENABLE_DECODE
5455 #include <libsigrokdecode/libsigrokdecode.h>
56 #include "data/decodesignal.hpp"
5557 #endif
5658
5759 using std::bad_alloc;
8991 using Glib::VariantBase;
9092
9193 namespace pv {
94
95 shared_ptr<sigrok::Context> Session::sr_context;
96
9297 Session::Session(DeviceManager &device_manager, QString name) :
9398 device_manager_(device_manager),
9499 default_name_(name),
171176 {
172177 map<string, string> dev_info;
173178 list<string> key_list;
174 int stacks = 0, views = 0;
179 int decode_signals = 0, views = 0;
175180
176181 if (device_) {
177182 shared_ptr<devices::HardwareDevice> hw_device =
189194
190195 dev_info = device_manager_.get_device_info(device_);
191196
192 for (string key : key_list) {
197 for (string& key : key_list) {
193198 if (dev_info.count(key))
194199 settings.setValue(QString::fromUtf8(key.c_str()),
195200 QString::fromUtf8(dev_info.at(key).c_str()));
201206 }
202207
203208 shared_ptr<devices::SessionFile> sessionfile_device =
204 dynamic_pointer_cast< devices::SessionFile >(device_);
209 dynamic_pointer_cast<devices::SessionFile>(device_);
205210
206211 if (sessionfile_device) {
207212 settings.setValue("device_type", "sessionfile");
211216 settings.endGroup();
212217 }
213218
219 shared_ptr<devices::InputFile> inputfile_device =
220 dynamic_pointer_cast<devices::InputFile>(device_);
221
222 if (inputfile_device) {
223 settings.setValue("device_type", "inputfile");
224 settings.beginGroup("device");
225 inputfile_device->save_meta_to_settings(settings);
226 settings.endGroup();
227 }
228
214229 // Save channels and decoders
215 for (shared_ptr<data::SignalBase> base : signalbases_) {
230 for (const shared_ptr<data::SignalBase>& base : signalbases_) {
216231 #ifdef ENABLE_DECODE
217232 if (base->is_decode_signal()) {
218 shared_ptr<pv::data::DecoderStack> decoder_stack =
219 base->decoder_stack();
220 shared_ptr<data::decode::Decoder> top_decoder =
221 decoder_stack->stack().front();
222
223 settings.beginGroup("decoder_stack" + QString::number(stacks++));
224 settings.setValue("id", top_decoder->decoder()->id);
225 settings.setValue("name", top_decoder->decoder()->name);
233 settings.beginGroup("decode_signal" + QString::number(decode_signals++));
234 base->save_settings(settings);
226235 settings.endGroup();
227236 } else
228237 #endif
233242 }
234243 }
235244
236 settings.setValue("decoder_stacks", stacks);
245 settings.setValue("decode_signals", decode_signals);
237246
238247 // Save view states and their signal settings
239248 // Note: main_view must be saved as view0
241250 main_view_->save_settings(settings);
242251 settings.endGroup();
243252
244 for (shared_ptr<views::ViewBase> view : views_) {
253 for (const shared_ptr<views::ViewBase>& view : views_) {
245254 if (view != main_view_) {
246255 settings.beginGroup("view" + QString::number(views++));
247256 view->save_settings(settings);
290299 settings.endGroup();
291300 }
292301
293 if (device_type == "sessionfile") {
294 settings.beginGroup("device");
295 QString filename = settings.value("filename").toString();
296 settings.endGroup();
297
298 if (QFileInfo(filename).isReadable()) {
299 device = make_shared<devices::SessionFile>(device_manager_.context(),
300 filename.toStdString());
302 if ((device_type == "sessionfile") || (device_type == "inputfile")) {
303 if (device_type == "sessionfile") {
304 settings.beginGroup("device");
305 QString filename = settings.value("filename").toString();
306 settings.endGroup();
307
308 if (QFileInfo(filename).isReadable()) {
309 device = make_shared<devices::SessionFile>(device_manager_.context(),
310 filename.toStdString());
311 }
312 }
313
314 if (device_type == "inputfile") {
315 settings.beginGroup("device");
316 device = make_shared<devices::InputFile>(device_manager_.context(),
317 settings);
318 settings.endGroup();
319 }
320
321 if (device) {
301322 set_device(device);
302323
303 // TODO Perform error handling
304 start_capture([](QString infoMessage) { (void)infoMessage; });
305
306 set_name(QFileInfo(filename).fileName());
324 start_capture([](QString infoMessage) {
325 // TODO Emulate noquote()
326 qDebug() << "Session error:" << infoMessage; });
327
328 set_name(QString::fromStdString(
329 dynamic_pointer_cast<devices::File>(device)->display_name(device_manager_)));
307330 }
308331 }
309332
317340
318341 // Restore decoders
319342 #ifdef ENABLE_DECODE
320 int stacks = settings.value("decoder_stacks").toInt();
321
322 for (int i = 0; i < stacks; i++) {
323 settings.beginGroup("decoder_stack" + QString::number(i++));
324
325 QString id = settings.value("id").toString();
326 add_decoder(srd_decoder_get_by_id(id.toStdString().c_str()));
327
343 int decode_signals = settings.value("decode_signals").toInt();
344
345 for (int i = 0; i < decode_signals; i++) {
346 settings.beginGroup("decode_signal" + QString::number(i));
347 shared_ptr<data::DecodeSignal> signal = add_decode_signal();
348 signal->restore_settings(settings);
328349 settings.endGroup();
329350 }
330351 #endif
355376 else
356377 set_default_device();
357378 } catch (const QString &e) {
358 main_bar_->session_error(tr("Failed to Select Device"),
359 tr("Failed to Select Device"));
379 MainWindow::show_session_error(tr("Failed to select device"), e);
360380 }
361381 }
362382
376396 name_ = default_name_;
377397 name_changed();
378398
379 // Remove all stored data
399 // Remove all stored data and reset all views
380400 for (shared_ptr<views::ViewBase> view : views_) {
381401 view->clear_signals();
382402 #ifdef ENABLE_DECODE
383403 view->clear_decode_signals();
384404 #endif
385 }
386 for (const shared_ptr<data::SignalData> d : all_signal_data_)
405 view->reset_view_state();
406 }
407 for (const shared_ptr<data::SignalData>& d : all_signal_data_)
387408 d->clear();
388409 all_signal_data_.clear();
389410 signalbases_.clear();
390411 cur_logic_segment_.reset();
391412
392 for (auto entry : cur_analog_segments_) {
413 for (auto& entry : cur_analog_segments_) {
393414 shared_ptr<sigrok::Channel>(entry.first).reset();
394415 shared_ptr<data::AnalogSegment>(entry.second).reset();
395416 }
404425 device_->open();
405426 } catch (const QString &e) {
406427 device_.reset();
428 MainWindow::show_session_error(tr("Failed to open device"), e);
407429 }
408430
409431 if (device_) {
429451 // Try and find the demo device and select that by default
430452 const auto iter = find_if(devices.begin(), devices.end(),
431453 [] (const shared_ptr<devices::HardwareDevice> &d) {
432 return d->hardware_device()->driver()->name() == "demo"; });
454 return d->hardware_device()->driver()->name() == "demo"; });
433455 set_device((iter == devices.end()) ? devices.front() : *iter);
434456 }
435457
458 /**
459 * Convert generic options to data types that are specific to InputFormat.
460 *
461 * @param[in] user_spec Vector of tokenized words, string format.
462 * @param[in] fmt_opts Input format's options, result of InputFormat::options().
463 *
464 * @return Map of options suitable for InputFormat::create_input().
465 */
466 map<string, Glib::VariantBase>
467 Session::input_format_options(vector<string> user_spec,
468 map<string, shared_ptr<Option>> fmt_opts)
469 {
470 map<string, Glib::VariantBase> result;
471
472 for (auto& entry : user_spec) {
473 /*
474 * Split key=value specs. Accept entries without separator
475 * (for simplified boolean specifications).
476 */
477 string key, val;
478 size_t pos = entry.find("=");
479 if (pos == std::string::npos) {
480 key = entry;
481 val = "";
482 } else {
483 key = entry.substr(0, pos);
484 val = entry.substr(pos + 1);
485 }
486
487 /*
488 * Skip user specifications that are not a member of the
489 * format's set of supported options. Have the text input
490 * spec converted to the required input format specific
491 * data type.
492 */
493 auto found = fmt_opts.find(key);
494 if (found == fmt_opts.end())
495 continue;
496 shared_ptr<Option> opt = found->second;
497 result[key] = opt->parse_string(val);
498 }
499
500 return result;
501 }
502
436503 void Session::load_init_file(const string &file_name, const string &format)
437504 {
438505 shared_ptr<InputFormat> input_format;
506 map<string, Glib::VariantBase> input_opts;
439507
440508 if (!format.empty()) {
441509 const map<string, shared_ptr<InputFormat> > formats =
442510 device_manager_.context()->input_formats();
511 auto user_opts = pv::util::split_string(format, ":");
512 string user_name = user_opts.front();
513 user_opts.erase(user_opts.begin());
443514 const auto iter = find_if(formats.begin(), formats.end(),
444515 [&](const pair<string, shared_ptr<InputFormat> > f) {
445 return f.first == format; });
516 return f.first == user_name; });
446517 if (iter == formats.end()) {
447 main_bar_->session_error(tr("Error"),
518 MainWindow::show_session_error(tr("Error"),
448519 tr("Unexpected input format: %s").arg(QString::fromStdString(format)));
449520 return;
450521 }
451
452522 input_format = (*iter).second;
453 }
454
455 load_file(QString::fromStdString(file_name), input_format);
523 input_opts = input_format_options(user_opts,
524 input_format->options());
525 }
526
527 load_file(QString::fromStdString(file_name), input_format, input_opts);
456528 }
457529
458530 void Session::load_file(QString file_name,
462534 const QString errorMessage(
463535 QString("Failed to load file %1").arg(file_name));
464536
537 // In the absence of a caller's format spec, try to auto detect.
538 // Assume "sigrok session file" upon lookup miss.
539 if (!format)
540 format = device_manager_.context()->input_format_match(file_name.toStdString());
465541 try {
466542 if (format)
467543 set_device(shared_ptr<devices::Device>(
474550 new devices::SessionFile(
475551 device_manager_.context(),
476552 file_name.toStdString())));
477 } catch (Error e) {
478 main_bar_->session_error(tr("Failed to load ") + file_name, e.what());
553 } catch (Error& e) {
554 MainWindow::show_session_error(tr("Failed to load ") + file_name, e.what());
479555 set_default_device();
480556 main_bar_->update_device_list();
481557 return;
484560 main_bar_->update_device_list();
485561
486562 start_capture([&, errorMessage](QString infoMessage) {
487 main_bar_->session_error(errorMessage, infoMessage); });
563 MainWindow::show_session_error(errorMessage, infoMessage); });
488564
489565 set_name(QFileInfo(file_name).fileName());
490566 }
517593 }
518594
519595 // Clear signal data
520 for (const shared_ptr<data::SignalData> d : all_signal_data_)
596 for (const shared_ptr<data::SignalData>& d : all_signal_data_)
521597 d->clear();
598
599 trigger_list_.clear();
522600
523601 // Revert name back to default name (e.g. "Session 1") for real devices
524602 // as the (possibly saved) data is gone. File devices keep their name.
553631
554632 views_.push_back(view);
555633
634 // Add all device signals
556635 update_signals();
636
637 // Add all other signals
638 unordered_set< shared_ptr<data::SignalBase> > view_signalbases =
639 view->signalbases();
640
641 views::trace::View *trace_view =
642 qobject_cast<views::trace::View*>(view.get());
643
644 if (trace_view) {
645 for (const shared_ptr<data::SignalBase>& signalbase : signalbases_) {
646 const int sb_exists = count_if(
647 view_signalbases.cbegin(), view_signalbases.cend(),
648 [&](const shared_ptr<data::SignalBase> &sb) {
649 return sb == signalbase;
650 });
651 // Add the signal to the view as it doesn't have it yet
652 if (!sb_exists)
653 switch (signalbase->type()) {
654 case data::SignalBase::AnalogChannel:
655 case data::SignalBase::LogicChannel:
656 case data::SignalBase::DecodeChannel:
657 #ifdef ENABLE_DECODE
658 trace_view->add_decode_signal(
659 dynamic_pointer_cast<data::DecodeSignal>(signalbase));
660 #endif
661 break;
662 case data::SignalBase::MathChannel:
663 // TBD
664 break;
665 }
666 }
667 }
668
669 signals_changed();
557670 }
558671
559672 void Session::deregister_view(shared_ptr<views::ViewBase> view)
570683
571684 bool Session::has_view(shared_ptr<views::ViewBase> view)
572685 {
573 for (shared_ptr<views::ViewBase> v : views_)
686 for (shared_ptr<views::ViewBase>& v : views_)
574687 if (v == view)
575688 return true;
576689
581694 {
582695 double samplerate = 0.0;
583696
584 for (const shared_ptr<pv::data::SignalData> d : all_signal_data_) {
697 for (const shared_ptr<pv::data::SignalData>& d : all_signal_data_) {
585698 assert(d);
586699 const vector< shared_ptr<pv::data::Segment> > segments =
587700 d->segments();
588 for (const shared_ptr<pv::data::Segment> &s : segments)
701 for (const shared_ptr<pv::data::Segment>& s : segments)
589702 samplerate = max(samplerate, s->samplerate());
590703 }
591704 // If there is no sample rate given we use samples as unit
595708 return samplerate;
596709 }
597710
711 uint32_t Session::get_segment_count() const
712 {
713 uint32_t value = 0;
714
715 // Find the highest number of segments
716 for (const shared_ptr<data::SignalData>& data : all_signal_data_)
717 if (data->get_segment_count() > value)
718 value = data->get_segment_count();
719
720 return value;
721 }
722
723 vector<util::Timestamp> Session::get_triggers(uint32_t segment_id) const
724 {
725 vector<util::Timestamp> result;
726
727 for (const pair<uint32_t, util::Timestamp>& entry : trigger_list_)
728 if (entry.first == segment_id)
729 result.push_back(entry.second);
730
731 return result;
732 }
733
598734 const unordered_set< shared_ptr<data::SignalBase> > Session::signalbases() const
599735 {
600736 return signalbases_;
601737 }
602738
739 bool Session::all_segments_complete(uint32_t segment_id) const
740 {
741 bool all_complete = true;
742
743 for (const shared_ptr<data::SignalBase>& base : signalbases_)
744 if (!base->segment_is_complete(segment_id))
745 all_complete = false;
746
747 return all_complete;
748 }
749
603750 #ifdef ENABLE_DECODE
604 bool Session::add_decoder(srd_decoder *const dec)
605 {
606 if (!dec)
607 return false;
608
609 map<const srd_channel*, shared_ptr<data::SignalBase> > channels;
610 shared_ptr<data::DecoderStack> decoder_stack;
751 shared_ptr<data::DecodeSignal> Session::add_decode_signal()
752 {
753 shared_ptr<data::DecodeSignal> signal;
611754
612755 try {
613 // Create the decoder
614 decoder_stack = make_shared<data::DecoderStack>(*this, dec);
615
616 // Make a list of all the channels
617 vector<const srd_channel*> all_channels;
618 for (const GSList *i = dec->channels; i; i = i->next)
619 all_channels.push_back((const srd_channel*)i->data);
620 for (const GSList *i = dec->opt_channels; i; i = i->next)
621 all_channels.push_back((const srd_channel*)i->data);
622
623 // Auto select the initial channels
624 for (const srd_channel *pdch : all_channels)
625 for (shared_ptr<data::SignalBase> b : signalbases_) {
626 if (b->logic_data()) {
627 if (QString::fromUtf8(pdch->name).toLower().
628 contains(b->name().toLower()))
629 channels[pdch] = b;
630 }
631 }
632
633 assert(decoder_stack);
634 assert(!decoder_stack->stack().empty());
635 assert(decoder_stack->stack().front());
636 decoder_stack->stack().front()->set_channels(channels);
637
638756 // Create the decode signal
639 shared_ptr<data::SignalBase> signalbase =
640 make_shared<data::SignalBase>(nullptr, data::SignalBase::DecodeChannel);
641
642 signalbase->set_decoder_stack(decoder_stack);
643 signalbases_.insert(signalbase);
644
645 for (shared_ptr<views::ViewBase> view : views_)
646 view->add_decode_signal(signalbase);
647 } catch (runtime_error e) {
648 return false;
757 signal = make_shared<data::DecodeSignal>(*this);
758
759 signalbases_.insert(signal);
760
761 // Add the decode signal to all views
762 for (shared_ptr<views::ViewBase>& view : views_)
763 view->add_decode_signal(signal);
764 } catch (runtime_error& e) {
765 remove_decode_signal(signal);
766 return nullptr;
649767 }
650768
651769 signals_changed();
652770
653 // Do an initial decode
654 decoder_stack->begin_decode();
655
656 return true;
657 }
658
659 void Session::remove_decode_signal(shared_ptr<data::SignalBase> signalbase)
660 {
661 signalbases_.erase(signalbase);
662
663 for (shared_ptr<views::ViewBase> view : views_)
664 view->remove_decode_signal(signalbase);
771 return signal;
772 }
773
774 void Session::remove_decode_signal(shared_ptr<data::DecodeSignal> signal)
775 {
776 signalbases_.erase(signal);
777
778 for (shared_ptr<views::ViewBase>& view : views_)
779 view->remove_decode_signal(signal);
665780
666781 signals_changed();
667782 }
686801 if (!device_) {
687802 signalbases_.clear();
688803 logic_data_.reset();
689 for (shared_ptr<views::ViewBase> view : views_) {
804 for (shared_ptr<views::ViewBase>& view : views_) {
690805 view->clear_signals();
691806 #ifdef ENABLE_DECODE
692807 view->clear_decode_signals();
701816 if (!sr_dev) {
702817 signalbases_.clear();
703818 logic_data_.reset();
704 for (shared_ptr<views::ViewBase> view : views_) {
819 for (shared_ptr<views::ViewBase>& view : views_) {
705820 view->clear_signals();
706821 #ifdef ENABLE_DECODE
707822 view->clear_decode_signals();
732847 }
733848
734849 // Make the signals list
735 for (shared_ptr<views::ViewBase> viewbase : views_) {
850 for (shared_ptr<views::ViewBase>& viewbase : views_) {
736851 views::trace::View *trace_view =
737852 qobject_cast<views::trace::View*>(viewbase.get());
738853
758873 } else {
759874 // Find the signalbase for this channel if possible
760875 signalbase.reset();
761 for (const shared_ptr<data::SignalBase> b : signalbases_)
876 for (const shared_ptr<data::SignalBase>& b : signalbases_)
762877 if (b->channel() == channel)
763878 signalbase = b;
764879
834949 if (!device_)
835950 return;
836951
837 cur_samplerate_ = device_->read_config<uint64_t>(ConfigKey::SAMPLERATE);
952 try {
953 cur_samplerate_ = device_->read_config<uint64_t>(ConfigKey::SAMPLERATE);
954 } catch (Error& e) {
955 cur_samplerate_ = 0;
956 }
838957
839958 out_of_memory_ = false;
959
960 {
961 lock_guard<recursive_mutex> lock(data_mutex_);
962 cur_logic_segment_.reset();
963 cur_analog_segments_.clear();
964 }
965 highest_segment_id_ = -1;
966 frame_began_ = false;
840967
841968 try {
842969 device_->start();
843 } catch (Error e) {
970 } catch (Error& e) {
844971 error_handler(e.what());
845972 return;
846973 }
850977
851978 try {
852979 device_->run();
853 } catch (Error e) {
980 } catch (Error& e) {
854981 error_handler(e.what());
855982 set_capture_state(Stopped);
856983 return;
859986 set_capture_state(Stopped);
860987
861988 // Confirm that SR_DF_END was received
862 if (cur_logic_segment_) {
863 qDebug("SR_DF_END was not received.");
864 assert(false);
865 }
989 if (cur_logic_segment_)
990 qDebug() << "WARNING: SR_DF_END was not received.";
866991
867992 // Optimize memory usage
868993 free_unused_memory();
8801005
8811006 void Session::free_unused_memory()
8821007 {
883 for (shared_ptr<data::SignalData> data : all_signal_data_) {
1008 for (const shared_ptr<data::SignalData>& data : all_signal_data_) {
8841009 const vector< shared_ptr<data::Segment> > segments = data->segments();
8851010
886 for (shared_ptr<data::Segment> segment : segments) {
1011 for (const shared_ptr<data::Segment>& segment : segments)
8871012 segment->free_unused_memory();
888 }
889 }
1013 }
1014 }
1015
1016 void Session::signal_new_segment()
1017 {
1018 int new_segment_id = 0;
1019
1020 if ((cur_logic_segment_ != nullptr) || !cur_analog_segments_.empty()) {
1021
1022 // Determine new frame/segment number, assuming that all
1023 // signals have the same number of frames/segments
1024 if (cur_logic_segment_) {
1025 new_segment_id = logic_data_->get_segment_count() - 1;
1026 } else {
1027 shared_ptr<sigrok::Channel> any_channel =
1028 (*cur_analog_segments_.begin()).first;
1029
1030 shared_ptr<data::SignalBase> base = signalbase_from_channel(any_channel);
1031 assert(base);
1032
1033 shared_ptr<data::Analog> data(base->analog_data());
1034 assert(data);
1035
1036 new_segment_id = data->get_segment_count() - 1;
1037 }
1038 }
1039
1040 if (new_segment_id > highest_segment_id_) {
1041 highest_segment_id_ = new_segment_id;
1042 new_segment(highest_segment_id_);
1043 }
1044 }
1045
1046 void Session::signal_segment_completed()
1047 {
1048 int segment_id = 0;
1049
1050 for (const shared_ptr<data::SignalBase>& signalbase : signalbases_) {
1051 // We only care about analog and logic channels, not derived ones
1052 if (signalbase->type() == data::SignalBase::AnalogChannel) {
1053 segment_id = signalbase->analog_data()->get_segment_count() - 1;
1054 break;
1055 }
1056
1057 if (signalbase->type() == data::SignalBase::LogicChannel) {
1058 segment_id = signalbase->logic_data()->get_segment_count() - 1;
1059 break;
1060 }
1061 }
1062
1063 if (segment_id >= 0)
1064 segment_completed(segment_id);
8901065 }
8911066
8921067 void Session::feed_in_header()
8931068 {
894 cur_samplerate_ = device_->read_config<uint64_t>(ConfigKey::SAMPLERATE);
1069 // Nothing to do here for now
8951070 }
8961071
8971072 void Session::feed_in_meta(shared_ptr<Meta> meta)
8981073 {
899 for (auto entry : meta->config()) {
1074 for (auto& entry : meta->config()) {
9001075 switch (entry.first->id()) {
9011076 case SR_CONF_SAMPLERATE:
902 // We can't rely on the header to always contain the sample rate,
903 // so in case it's supplied via a meta packet, we use it.
904 if (!cur_samplerate_)
905 cur_samplerate_ = g_variant_get_uint64(entry.second.gobj());
906
907 /// @todo handle samplerate changes
1077 cur_samplerate_ = g_variant_get_uint64(entry.second.gobj());
9081078 break;
9091079 default:
910 // Unknown metadata is not an error.
1080 qDebug() << "Received meta data key" << entry.first->id() << ", ignoring.";
9111081 break;
9121082 }
9131083 }
9211091 uint64_t sample_count = 0;
9221092
9231093 {
924 for (const shared_ptr<pv::data::SignalData> d : all_signal_data_) {
1094 for (const shared_ptr<pv::data::SignalData>& d : all_signal_data_) {
9251095 assert(d);
9261096 uint64_t temp_count = 0;
9271097
9351105 }
9361106 }
9371107
938 trigger_event(sample_count / get_samplerate());
1108 uint32_t segment_id = 0; // Default segment when no frames are used
1109
1110 // If a frame began, we'd ideally be able to use the highest segment ID for
1111 // the trigger. However, as new segments are only created when logic or
1112 // analog data comes in, this doesn't work if the trigger appears right
1113 // after the beginning of the frame, before any sample data.
1114 // For this reason, we use highest segment ID + 1 if no sample data came in
1115 // yet and the highest segment ID otherwise.
1116 if (frame_began_) {
1117 segment_id = highest_segment_id_;
1118 if (!cur_logic_segment_ && (cur_analog_segments_.size() == 0))
1119 segment_id++;
1120 }
1121
1122 // TODO Create timestamp from segment start time + segment's current sample count
1123 util::Timestamp timestamp = sample_count / get_samplerate();
1124 trigger_list_.emplace_back(segment_id, timestamp);
1125 trigger_event(segment_id, timestamp);
9391126 }
9401127
9411128 void Session::feed_in_frame_begin()
9421129 {
943 if (cur_logic_segment_ || !cur_analog_segments_.empty())
944 frame_began();
1130 frame_began_ = true;
1131 }
1132
1133 void Session::feed_in_frame_end()
1134 {
1135 if (!frame_began_)
1136 return;
1137
1138 {
1139 lock_guard<recursive_mutex> lock(data_mutex_);
1140
1141 if (cur_logic_segment_)
1142 cur_logic_segment_->set_complete();
1143
1144 for (auto& entry : cur_analog_segments_) {
1145 shared_ptr<data::AnalogSegment> segment = entry.second;
1146 segment->set_complete();
1147 }
1148
1149 cur_logic_segment_.reset();
1150 cur_analog_segments_.clear();
1151 }
1152
1153 frame_began_ = false;
1154
1155 signal_segment_completed();
9451156 }
9461157
9471158 void Session::feed_in_logic(shared_ptr<Logic> logic)
9481159 {
1160 if (logic->data_length() == 0) {
1161 qDebug() << "WARNING: Received logic packet with 0 samples.";
1162 return;
1163 }
1164
1165 if (!cur_samplerate_)
1166 try {
1167 cur_samplerate_ = device_->read_config<uint64_t>(ConfigKey::SAMPLERATE);
1168 } catch (Error& e) {
1169 // Do nothing
1170 }
1171
9491172 lock_guard<recursive_mutex> lock(data_mutex_);
9501173
9511174 if (!logic_data_) {
9611184
9621185 // Create a new data segment
9631186 cur_logic_segment_ = make_shared<data::LogicSegment>(
964 *logic_data_, logic->unit_size(), cur_samplerate_);
1187 *logic_data_, logic_data_->get_segment_count(),
1188 logic->unit_size(), cur_samplerate_);
9651189 logic_data_->push_segment(cur_logic_segment_);
9661190
967 // @todo Putting this here means that only listeners querying
968 // for logic will be notified. Currently the only user of
969 // frame_began is DecoderStack, but in future we need to signal
970 // this after both analog and logic sweeps have begun.
971 frame_began();
1191 signal_new_segment();
9721192 }
9731193
9741194 cur_logic_segment_->append_payload(logic);
9781198
9791199 void Session::feed_in_analog(shared_ptr<Analog> analog)
9801200 {
1201 if (analog->num_samples() == 0) {
1202 qDebug() << "WARNING: Received analog packet with 0 samples.";
1203 return;
1204 }
1205
1206 if (!cur_samplerate_)
1207 try {
1208 cur_samplerate_ = device_->read_config<uint64_t>(ConfigKey::SAMPLERATE);
1209 } catch (Error& e) {
1210 // Do nothing
1211 }
1212
9811213 lock_guard<recursive_mutex> lock(data_mutex_);
9821214
9831215 const vector<shared_ptr<Channel>> channels = analog->channels();
984 const unsigned int channel_count = channels.size();
985 const size_t sample_count = analog->num_samples() / channel_count;
9861216 bool sweep_beginning = false;
9871217
988 unique_ptr<float> data(new float[analog->num_samples()]);
1218 unique_ptr<float[]> data(new float[analog->num_samples() * channels.size()]);
9891219 analog->get_data_as_float(data.get());
9901220
9911221 if (signalbases_.empty())
9921222 update_signals();
9931223
9941224 float *channel_data = data.get();
995 for (auto channel : channels) {
1225 for (auto& channel : channels) {
9961226 shared_ptr<data::AnalogSegment> segment;
9971227
9981228 // Try to get the segment of the channel
10151245
10161246 // Create a segment, keep it in the maps of channels
10171247 segment = make_shared<data::AnalogSegment>(
1018 *data, cur_samplerate_);
1248 *data, data->get_segment_count(), cur_samplerate_);
10191249 cur_analog_segments_[channel] = segment;
10201250
10211251 // Push the segment into the analog data.
10221252 data->push_segment(segment);
1253
1254 signal_new_segment();
10231255 }
10241256
10251257 assert(segment);
10261258
10271259 // Append the samples in the segment
1028 segment->append_interleaved_samples(channel_data++, sample_count,
1029 channel_count);
1260 segment->append_interleaved_samples(channel_data++, analog->num_samples(),
1261 channels.size());
10301262 }
10311263
10321264 if (sweep_beginning) {
10401272 void Session::data_feed_in(shared_ptr<sigrok::Device> device,
10411273 shared_ptr<Packet> packet)
10421274 {
1043 static bool frame_began = false;
1044
10451275 (void)device;
10461276
10471277 assert(device);
10611291 feed_in_trigger();
10621292 break;
10631293
1064 case SR_DF_FRAME_BEGIN:
1065 feed_in_frame_begin();
1066 frame_began = true;
1067 break;
1068
10691294 case SR_DF_LOGIC:
10701295 try {
10711296 feed_in_logic(dynamic_pointer_cast<Logic>(packet->payload()));
1072 } catch (bad_alloc) {
1297 } catch (bad_alloc&) {
10731298 out_of_memory_ = true;
10741299 device_->stop();
10751300 }
10781303 case SR_DF_ANALOG:
10791304 try {
10801305 feed_in_analog(dynamic_pointer_cast<Analog>(packet->payload()));
1081 } catch (bad_alloc) {
1306 } catch (bad_alloc&) {
10821307 out_of_memory_ = true;
10831308 device_->stop();
10841309 }
10851310 break;
10861311
1312 case SR_DF_FRAME_BEGIN:
1313 feed_in_frame_begin();
1314 break;
1315
10871316 case SR_DF_FRAME_END:
1317 feed_in_frame_end();
1318 break;
1319
10881320 case SR_DF_END:
1089 {
1321 // Strictly speaking, this is performed when a frame end marker was
1322 // received, so there's no point doing this again. However, not all
1323 // devices use frames, and for those devices, we need to do it here.
10901324 {
10911325 lock_guard<recursive_mutex> lock(data_mutex_);
1326
1327 if (cur_logic_segment_)
1328 cur_logic_segment_->set_complete();
1329
1330 for (auto& entry : cur_analog_segments_) {
1331 shared_ptr<data::AnalogSegment> segment = entry.second;
1332 segment->set_complete();
1333 }
1334
10921335 cur_logic_segment_.reset();
10931336 cur_analog_segments_.clear();
10941337 }
1095 if (frame_began) {
1096 frame_began = false;
1097 frame_ended();
1098 }
10991338 break;
1100 }
1339
11011340 default:
11021341 break;
11031342 }
1919 #ifndef PULSEVIEW_PV_SESSION_HPP
2020 #define PULSEVIEW_PV_SESSION_HPP
2121
22 #include <functional>
2223 #include <map>
2324 #include <memory>
2425 #include <mutex>
5455 class InputFormat;
5556 class Logic;
5657 class Meta;
58 class Option;
5759 class OutputFormat;
5860 class Packet;
5961 class Session;
6062 } // namespace sigrok
6163
64 using sigrok::Option;
65
6266 namespace pv {
6367
6468 class DeviceManager;
6670 namespace data {
6771 class Analog;
6872 class AnalogSegment;
73 class DecodeSignal;
6974 class Logic;
7075 class LogicSegment;
7176 class SignalBase;
95100 Running
96101 };
97102
103 static shared_ptr<sigrok::Context> sr_context;
104
98105 public:
99106 Session(DeviceManager &device_manager, QString name);
100107
156163
157164 double get_samplerate() const;
158165
166 uint32_t get_segment_count() const;
167
168 vector<util::Timestamp> get_triggers(uint32_t segment_id) const;
169
159170 void register_view(shared_ptr<views::ViewBase> view);
160171
161172 void deregister_view(shared_ptr<views::ViewBase> view);
164175
165176 const unordered_set< shared_ptr<data::SignalBase> > signalbases() const;
166177
178 bool all_segments_complete(uint32_t segment_id) const;
179
167180 #ifdef ENABLE_DECODE
168 bool add_decoder(srd_decoder *const dec);
169
170 void remove_decode_signal(shared_ptr<data::SignalBase> signalbase);
181 shared_ptr<data::DecodeSignal> add_decode_signal();
182
183 void remove_decode_signal(shared_ptr<data::DecodeSignal> signal);
171184 #endif
172185
173186 private:
178191 shared_ptr<data::SignalBase> signalbase_from_channel(
179192 shared_ptr<sigrok::Channel> channel) const;
180193
181 private:
194 static map<string, Glib::VariantBase> input_format_options(
195 vector<string> user_spec,
196 map<string, shared_ptr<Option>> fmt_opts);
197
182198 void sample_thread_proc(function<void (const QString)> error_handler);
183199
184200 void free_unused_memory();
185201
202 void signal_new_segment();
203 void signal_segment_completed();
204
186205 void feed_in_header();
187206
188207 void feed_in_meta(shared_ptr<sigrok::Meta> meta);
190209 void feed_in_trigger();
191210
192211 void feed_in_frame_begin();
212 void feed_in_frame_end();
193213
194214 void feed_in_logic(shared_ptr<sigrok::Logic> logic);
195215
197217
198218 void data_feed_in(shared_ptr<sigrok::Device> device,
199219 shared_ptr<sigrok::Packet> packet);
220
221 Q_SIGNALS:
222 void capture_state_changed(int state);
223 void device_changed();
224
225 void signals_changed();
226
227 void name_changed();
228
229 void trigger_event(int segment_id, util::Timestamp location);
230
231 void new_segment(int new_segment_id);
232 void segment_completed(int segment_id);
233
234 void data_received();
235
236 void add_view(const QString &title, views::ViewType type,
237 Session *session);
238
239 public Q_SLOTS:
240 void on_data_saved();
200241
201242 private:
202243 DeviceManager &device_manager_;
213254
214255 unordered_set< shared_ptr<data::SignalBase> > signalbases_;
215256 unordered_set< shared_ptr<data::SignalData> > all_signal_data_;
257
258 /// trigger_list_ contains pairs of <segment_id, timestamp> values.
259 vector< std::pair<uint32_t, util::Timestamp> > trigger_list_;
216260
217261 mutable recursive_mutex data_mutex_;
218262 shared_ptr<data::Logic> logic_data_;
220264 shared_ptr<data::LogicSegment> cur_logic_segment_;
221265 map< shared_ptr<sigrok::Channel>, shared_ptr<data::AnalogSegment> >
222266 cur_analog_segments_;
267 int32_t highest_segment_id_;
223268
224269 std::thread sampling_thread_;
225270
226271 bool out_of_memory_;
227272 bool data_saved_;
228
229 Q_SIGNALS:
230 void capture_state_changed(int state);
231 void device_changed();
232
233 void signals_changed();
234
235 void name_changed();
236
237 void trigger_event(util::Timestamp location);
238
239 void frame_began();
240
241 void data_received();
242
243 void frame_ended();
244
245 void add_view(const QString &title, views::ViewType type,
246 Session *session);
247
248 public Q_SLOTS:
249 void on_data_saved();
273 bool frame_began_;
250274 };
251275
252276 } // namespace pv
9696 vector< shared_ptr<data::SignalBase> > achannel_list;
9797 vector< shared_ptr<data::AnalogSegment> > asegment_list;
9898
99 for (shared_ptr<data::SignalBase> signal : sigs) {
99 for (const shared_ptr<data::SignalBase>& signal : sigs) {
100100 if (!signal->enabled())
101101 continue;
102102
144144 uint64_t end_sample;
145145
146146 if (sample_range_.first == sample_range_.second) {
147 // No sample range specified, save everything we have
147148 start_sample_ = 0;
148149 sample_count_ = any_segment->get_sample_count();
149150 } else {
158159 }
159160 }
160161
162 // Make sure the sample range is valid
163 if (start_sample_ > any_segment->get_sample_count()) {
164 error_ = tr("Can't save range without sample data.");
165 return false;
166 }
167
161168 // Begin storing
162169 try {
163170 const auto context = session_.device_manager().context();
174181 {{ConfigKey::SAMPLERATE, Glib::Variant<guint64>::create(
175182 any_segment->samplerate())}});
176183 output_->receive(meta);
177 } catch (Error error) {
184 } catch (Error& error) {
178185 error_ = tr("Error while saving: ") + error.what();
179186 return false;
180187 }
239246 shared_ptr<sigrok::Channel> achannel = (achannel_list.at(i))->channel();
240247 shared_ptr<data::AnalogSegment> asegment = asegment_list.at(i);
241248
242 const float *adata =
243 asegment->get_samples(start_sample_, start_sample_ + packet_len);
249 float *adata = new float[packet_len];
250 asegment->get_samples(start_sample_, start_sample_ + packet_len, adata);
244251
245252 auto analog = context->create_analog_packet(
246253 vector<shared_ptr<sigrok::Channel> >{achannel},
256263 }
257264
258265 if (lsegment) {
259 const uint8_t* ldata =
260 lsegment->get_samples(start_sample_, start_sample_ + packet_len);
261
262 const size_t length = packet_len * lunit_size;
263 auto logic = context->create_logic_packet((void*)ldata, length, lunit_size);
266 const size_t data_size = packet_len * lunit_size;
267 uint8_t* ldata = new uint8_t[data_size];
268 lsegment->get_samples(start_sample_, start_sample_ + packet_len, ldata);
269
270 auto logic = context->create_logic_packet((void*)ldata, data_size, lunit_size);
264271 const string ldata_str = output_->receive(logic);
265272
266273 if (output_stream_.is_open())
268275
269276 delete[] ldata;
270277 }
271 } catch (Error error) {
278 } catch (Error& error) {
272279 error_ = tr("Error while saving: ") + error.what();
273280 break;
274281 }
3434
3535 #include <boost/algorithm/string/join.hpp>
3636
37 #include <pv/data/decodesignal.hpp>
3738 #include <pv/devicemanager.hpp>
3839 #include <pv/devices/hardwaredevice.hpp>
3940 #include <pv/devices/inputfile.hpp>
8586 const char *MainBar::SettingOpenDirectory = "MainWindow/OpenDirectory";
8687 const char *MainBar::SettingSaveDirectory = "MainWindow/SaveDirectory";
8788
88 MainBar::MainBar(Session &session, QWidget *parent,
89 pv::views::trace::View *view) :
89 MainBar::MainBar(Session &session, QWidget *parent, pv::views::trace::View *view) :
9090 StandardBar(session, parent, view, false),
9191 action_new_view_(new QAction(this)),
9292 action_open_(new QAction(this)),
9595 action_connect_(new QAction(this)),
9696 open_button_(new QToolButton()),
9797 save_button_(new QToolButton()),
98 device_selector_(parent, session.device_manager(),
99 action_connect_),
98 device_selector_(parent, session.device_manager(), action_connect_),
10099 configure_button_(this),
101100 configure_button_action_(nullptr),
102101 channels_button_(this),
146145 widgets::ExportMenu *menu_file_export = new widgets::ExportMenu(this,
147146 session.device_manager().context());
148147 menu_file_export->setTitle(tr("&Export"));
149 connect(menu_file_export,
150 SIGNAL(format_selected(shared_ptr<sigrok::OutputFormat>)),
148 connect(menu_file_export, SIGNAL(format_selected(shared_ptr<sigrok::OutputFormat>)),
151149 this, SLOT(export_file(shared_ptr<sigrok::OutputFormat>)));
152150
153151 widgets::ImportMenu *menu_file_import = new widgets::ImportMenu(this,
154152 session.device_manager().context());
155153 menu_file_import->setTitle(tr("&Import"));
156 connect(menu_file_import,
157 SIGNAL(format_selected(shared_ptr<sigrok::InputFormat>)),
154 connect(menu_file_import, SIGNAL(format_selected(shared_ptr<sigrok::InputFormat>)),
158155 this, SLOT(import_file(shared_ptr<sigrok::InputFormat>)));
159156
160157 action_connect_->setText(tr("&Connect to Device..."));
164161 // Open button
165162 widgets::ImportMenu *import_menu = new widgets::ImportMenu(this,
166163 session.device_manager().context(), action_open_);
167 connect(import_menu,
168 SIGNAL(format_selected(shared_ptr<sigrok::InputFormat>)),
169 this,
170 SLOT(import_file(shared_ptr<sigrok::InputFormat>)));
164 connect(import_menu, SIGNAL(format_selected(shared_ptr<sigrok::InputFormat>)),
165 this, SLOT(import_file(shared_ptr<sigrok::InputFormat>)));
171166
172167 open_button_->setMenu(import_menu);
173168 open_button_->setDefaultAction(action_open_);
174169 open_button_->setPopupMode(QToolButton::MenuButtonPopup);
175170
176171 // Save button
177 vector<QAction *> open_actions;
172 vector<QAction*> open_actions;
178173 open_actions.push_back(action_save_as_);
179174 open_actions.push_back(action_save_selection_as_);
180175
181176 widgets::ExportMenu *export_menu = new widgets::ExportMenu(this,
182 session.device_manager().context(),
183 open_actions);
184 connect(export_menu,
185 SIGNAL(format_selected(shared_ptr<sigrok::OutputFormat>)),
186 this,
187 SLOT(export_file(shared_ptr<sigrok::OutputFormat>)));
177 session.device_manager().context(), open_actions);
178 connect(export_menu, SIGNAL(format_selected(shared_ptr<sigrok::OutputFormat>)),
179 this, SLOT(export_file(shared_ptr<sigrok::OutputFormat>)));
188180
189181 save_button_->setMenu(export_menu);
190182 save_button_->setDefaultAction(action_save_as_);
300292 return;
301293 }
302294
303 const shared_ptr<devices::Device> device =
304 device_selector_.selected_device();
295 const shared_ptr<devices::Device> device = device_selector_.selected_device();
305296 if (!device)
306297 return;
307298
310301
311302 const shared_ptr<sigrok::Device> sr_dev = device->device();
312303
304 sample_rate_.allow_user_entered_values(false);
305 if (sr_dev->config_check(ConfigKey::EXTERNAL_CLOCK, Capability::GET)) {
306 try {
307 auto gvar = sr_dev->config_get(ConfigKey::EXTERNAL_CLOCK);
308 if (gvar.gobj()) {
309 bool value = Glib::VariantBase::cast_dynamic<Glib::Variant<bool>>(
310 gvar).get();
311 sample_rate_.allow_user_entered_values(value);
312 }
313 } catch (Error& error) {
314 // Do nothing
315 }
316 }
317
318
313319 if (sr_dev->config_check(ConfigKey::SAMPLERATE, Capability::LIST)) {
314 gvar_dict = sr_dev->config_list(ConfigKey::SAMPLERATE);
320 try {
321 gvar_dict = sr_dev->config_list(ConfigKey::SAMPLERATE);
322 } catch (Error& error) {
323 qDebug() << tr("Failed to get sample rate list:") << error.what();
324 }
315325 } else {
316326 sample_rate_.show_none();
317327 updating_sample_rate_ = false;
321331 if ((gvar_list = g_variant_lookup_value(gvar_dict.gobj(),
322332 "samplerate-steps", G_VARIANT_TYPE("at")))) {
323333 elements = (const uint64_t *)g_variant_get_fixed_array(
324 gvar_list, &num_elements, sizeof(uint64_t));
334 gvar_list, &num_elements, sizeof(uint64_t));
325335
326336 const uint64_t min = elements[0];
327337 const uint64_t max = elements[1];
346356 } else if ((gvar_list = g_variant_lookup_value(gvar_dict.gobj(),
347357 "samplerates", G_VARIANT_TYPE("at")))) {
348358 elements = (const uint64_t *)g_variant_get_fixed_array(
349 gvar_list, &num_elements, sizeof(uint64_t));
359 gvar_list, &num_elements, sizeof(uint64_t));
350360 sample_rate_.show_list(elements, num_elements);
351361 g_variant_unref(gvar_list);
352362 }
360370 if (updating_sample_rate_)
361371 return;
362372
363 const shared_ptr<devices::Device> device =
364 device_selector_.selected_device();
373 const shared_ptr<devices::Device> device = device_selector_.selected_device();
365374 if (!device)
366375 return;
367376
373382 updating_sample_rate_ = true;
374383 sample_rate_.set_value(samplerate);
375384 updating_sample_rate_ = false;
376 } catch (Error error) {
377 qDebug() << "WARNING: Failed to get value of sample rate";
378 return;
385 } catch (Error& error) {
386 qDebug() << tr("Failed to get sample rate:") << error.what();
379387 }
380388 }
381389
384392 if (updating_sample_count_)
385393 return;
386394
387 const shared_ptr<devices::Device> device =
388 device_selector_.selected_device();
395 const shared_ptr<devices::Device> device = device_selector_.selected_device();
389396 if (!device)
390397 return;
391398
411418 }
412419
413420 if (sr_dev->config_check(ConfigKey::LIMIT_SAMPLES, Capability::LIST)) {
414 auto gvar = sr_dev->config_list(ConfigKey::LIMIT_SAMPLES);
415 if (gvar.gobj())
416 g_variant_get(gvar.gobj(), "(tt)",
417 &min_sample_count, &max_sample_count);
421 try {
422 auto gvar = sr_dev->config_list(ConfigKey::LIMIT_SAMPLES);
423 if (gvar.gobj())
424 g_variant_get(gvar.gobj(), "(tt)",
425 &min_sample_count, &max_sample_count);
426 } catch (Error& error) {
427 qDebug() << tr("Failed to get sample limit list:") << error.what();
428 }
418429 }
419430
420431 min_sample_count = min(max(min_sample_count, MinSampleCount),
446457 {
447458 using namespace pv::popups;
448459
449 const shared_ptr<devices::Device> device =
450 device_selector_.selected_device();
460 const shared_ptr<devices::Device> device = device_selector_.selected_device();
451461
452462 // Hide the widgets if no device is selected
453463 channels_button_action_->setVisible(!!device);
464474
465475 // Update the configure popup
466476 DeviceOptions *const opts = new DeviceOptions(sr_dev, this);
467 configure_button_action_->setVisible(
468 !opts->binding().properties().empty());
477 configure_button_action_->setVisible(!opts->binding().properties().empty());
469478 configure_button_.set_popup(opts);
470479
471480 // Update the channels popup
477486
478487 if (sr_dev->config_check(ConfigKey::LIMIT_SAMPLES, Capability::SET))
479488 sample_count_supported_ = true;
480
481 if (sr_dev->config_check(ConfigKey::LIMIT_FRAMES, Capability::SET)) {
482 sr_dev->config_set(ConfigKey::LIMIT_FRAMES,
483 Glib::Variant<guint64>::create(1));
484 on_config_changed();
485 }
486489
487490 // Add notification of reconfigure events
488491 disconnect(this, SLOT(on_config_changed()));
498501 {
499502 uint64_t sample_rate = 0;
500503
501 const shared_ptr<devices::Device> device =
502 device_selector_.selected_device();
504 const shared_ptr<devices::Device> device = device_selector_.selected_device();
503505 if (!device)
504506 return;
505507
506508 const shared_ptr<sigrok::Device> sr_dev = device->device();
507509
508510 sample_rate = sample_rate_.value();
509 if (sample_rate == 0)
510 return;
511511
512512 try {
513513 sr_dev->config_set(ConfigKey::SAMPLERATE,
514514 Glib::Variant<guint64>::create(sample_rate));
515515 update_sample_rate_selector();
516 } catch (Error error) {
517 qDebug() << "Failed to configure samplerate.";
516 } catch (Error& error) {
517 qDebug() << tr("Failed to configure samplerate:") << error.what();
518518 return;
519519 }
520520
528528 {
529529 uint64_t sample_count = 0;
530530
531 const shared_ptr<devices::Device> device =
532 device_selector_.selected_device();
531 const shared_ptr<devices::Device> device = device_selector_.selected_device();
533532 if (!device)
534533 return;
535534
541540 sr_dev->config_set(ConfigKey::LIMIT_SAMPLES,
542541 Glib::Variant<guint64>::create(sample_count));
543542 update_sample_count_selector();
544 } catch (Error error) {
545 qDebug() << "Failed to configure sample count.";
543 } catch (Error& error) {
544 qDebug() << tr("Failed to configure sample count:") << error.what();
546545 return;
547546 }
548547 }
551550 // configurations, so let's check what sample rate the driver
552551 // lets us use now.
553552 update_sample_rate_selector();
554 }
555
556 void MainBar::session_error(const QString text, const QString info_text)
557 {
558 QMetaObject::invokeMethod(this, "show_session_error",
559 Qt::QueuedConnection, Q_ARG(QString, text),
560 Q_ARG(QString, info_text));
561553 }
562554
563555 void MainBar::show_session_error(const QString text, const QString info_text)
574566 {
575567 #ifdef ENABLE_DECODE
576568 assert(decoder);
577 session_.add_decoder(decoder);
569 shared_ptr<data::DecodeSignal> signal = session_.add_decode_signal();
570 if (signal)
571 signal->stack_decoder(decoder);
578572 #else
579573 (void)decoder;
580574 #endif
599593
600594 if (!trace_view->cursors()->enabled()) {
601595 show_session_error(tr("Missing Cursors"), tr("You need to set the " \
602 "cursors before you can save the data enclosed by them " \
603 "to a session file (e.g. using the Show Cursors button)."));
596 "cursors before you can save the data enclosed by them " \
597 "to a session file (e.g. using the Show Cursors button)."));
604598 return;
605599 }
606600
610604 const pv::util::Timestamp& end_time = trace_view->cursors()->second()->time();
611605
612606 const uint64_t start_sample = (uint64_t)max(
613 (double)0, start_time.convert_to<double>() * samplerate);
607 0.0, start_time.convert_to<double>() * samplerate);
614608 const uint64_t end_sample = (uint64_t)max(
615 (double)0, end_time.convert_to<double>() * samplerate);
609 0.0, end_time.convert_to<double>() * samplerate);
610
611 if ((start_sample == 0) && (end_sample == 0)) {
612 // Both cursors are negative and were clamped to 0
613 show_session_error(tr("Invalid Range"), tr("The cursors don't " \
614 "define a valid range of samples."));
615 return;
616 }
616617
617618 sample_range = make_pair(start_sample, end_sample);
618619 } else {
670671
671672 // Construct the filter
672673 const vector<string> exts = format->extensions();
673 const QString filter = exts.empty() ? "" :
674 tr("%1 files (*.%2)").arg(
675 QString::fromStdString(format->description()),
676 QString::fromStdString(join(exts, ", *.")));
674 const QString filter_exts = exts.empty() ? "" : QString::fromStdString("%1 (%2)").arg(
675 tr("%1 files").arg(QString::fromStdString(format->description())),
676 QString::fromStdString("*.%1").arg(QString::fromStdString(join(exts, " *."))));
677 const QString filter_all = QString::fromStdString("%1 (%2)").arg(
678 tr("All Files"), QString::fromStdString("*"));
679 const QString filter = QString::fromStdString("%1%2%3").arg(
680 exts.empty() ? "" : filter_exts,
681 exts.empty() ? "" : ";;",
682 filter_all);
677683
678684 // Show the file dialog
679685 const QString file_name = QFileDialog::getOpenFileName(
680 this, tr("Import File"), dir, tr(
681 "%1 files (*);;All Files (*)").arg(
682 QString::fromStdString(format->description())));
686 this, tr("Import File"), dir, filter);
683687
684688 if (file_name.isEmpty())
685689 return;
705709 void MainBar::on_device_selected()
706710 {
707711 shared_ptr<devices::Device> device = device_selector_.selected_device();
708 if (!device) {
712
713 if (device)
714 session_.select_device(device);
715 else
709716 reset_device_selector();
710 return;
711 }
712
713 session_.select_device(device);
714717 }
715718
716719 void MainBar::on_device_changed()
738741
739742 void MainBar::on_config_changed()
740743 {
744 // We want to also call update_sample_rate_selector() here in case
745 // the user changed the SR_CONF_EXTERNAL_CLOCK option. However,
746 // commit_sample_rate() does this already, so we don't call it here
747
741748 commit_sample_count();
742749 commit_sample_rate();
743750 }
815822 bool MainBar::eventFilter(QObject *watched, QEvent *event)
816823 {
817824 if (sample_count_supported_ && (watched == &sample_count_ ||
818 watched == &sample_rate_) &&
819 (event->type() == QEvent::ToolTip)) {
825 watched == &sample_rate_) &&
826 (event->type() == QEvent::ToolTip)) {
827
820828 auto sec = pv::util::Timestamp(sample_count_.value()) / sample_rate_.value();
821829 QHelpEvent *help_event = static_cast<QHelpEvent*>(event);
822830
9999 QAction* action_save_selection_as() const;
100100 QAction* action_connect() const;
101101
102 void session_error(const QString text, const QString info_text);
103
104102 private:
105103 void run_stop();
106104
143143 return s;
144144 }
145145
146 QString format_value_si(double v, SIPrefix prefix, unsigned precision,
147 QString unit, bool sign)
148 {
149 if (prefix == SIPrefix::unspecified) {
150 // No prefix given, calculate it
151
152 if (v == 0) {
153 prefix = SIPrefix::none;
154 } else {
155 int exp = exponent(SIPrefix::yotta);
156 prefix = SIPrefix::yocto;
157 while ((fabs(v) * pow(Timestamp(10), exp)) > 999 &&
158 prefix < SIPrefix::yotta) {
159 prefix = successor(prefix);
160 exp -= 3;
161 }
162 }
163 }
164
165 assert(prefix >= SIPrefix::yocto);
166 assert(prefix <= SIPrefix::yotta);
167
168 const double multiplier = pow(10.0, -exponent(prefix));
169
170 QString s;
171 QTextStream ts(&s);
172 if (sign && (v != 0))
173 ts << forcesign;
174 ts.setRealNumberNotation(QTextStream::FixedNotation);
175 ts.setRealNumberPrecision(precision);
176 ts << (v * multiplier) << ' ' << prefix << unit;
177
178 return s;
179 }
180
146181 QString format_time_si_adjusted(const Timestamp& t, SIPrefix prefix,
147182 unsigned precision, QString unit, bool sign)
148183 {
223258 return s;
224259 }
225260
261 /**
262 * Split a string into tokens at occurences of the separator.
263 *
264 * @param[in] text The input string to split.
265 * @param[in] separator The delimiter between tokens.
266 *
267 * @return A vector of broken down tokens.
268 */
269 vector<string> split_string(string text, string separator)
270 {
271 vector<string> result;
272 size_t pos;
273
274 while ((pos = text.find(separator)) != std::string::npos) {
275 result.push_back(text.substr(0, pos));
276 text = text.substr(pos + separator.length());
277 }
278 result.push_back(text);
279
280 return result;
281 }
282
226283 } // namespace util
227284 } // namespace pv
2020 #define PULSEVIEW_UTIL_HPP
2121
2222 #include <cmath>
23 #include <string>
24 #include <vector>
2325
2426 #ifndef Q_MOC_RUN
2527 #include <boost/multiprecision/cpp_dec_float.hpp>
2729
2830 #include <QMetaType>
2931 #include <QString>
32
33 using std::string;
34 using std::vector;
3035
3136 namespace pv {
3237 namespace util {
7883 QString unit = "s", bool sign = true);
7984
8085 /**
86 * Formats a given value into a representation using SI units.
87 *
88 * If 'prefix' is left 'unspecified', the function chooses a prefix so that
89 * the value in front of the decimal point is between 1 and 999.
90 *
91 * @param value The value to format.
92 * @param prefix The SI prefix to use.
93 * @param precision The number of digits after the decimal separator.
94 * @param unit The unit of quantity.
95 * @param sign Whether or not to add a sign also for positive numbers.
96 *
97 * @return The formatted value.
98 */
99 QString format_value_si(double v,
100 SIPrefix prefix = SIPrefix::unspecified, unsigned precision = 0,
101 QString unit = "", bool sign = true);
102
103 /**
81104 * Wrapper around 'format_time_si()' that interprets the given 'precision'
82105 * value as the number of decimal places if the timestamp would be formatted
83106 * without a SI prefix (using 'SIPrefix::none') and adjusts the precision to
111134 QString format_time_minutes(const Timestamp& t, signed precision = 0,
112135 bool sign = true);
113136
137 vector<string> split_string(string text, string separator);
138
114139 } // namespace util
115140 } // namespace pv
116141
2727 #include <QApplication>
2828 #include <QCheckBox>
2929 #include <QComboBox>
30 #include <QDebug>
3031 #include <QFormLayout>
3132 #include <QGridLayout>
3233 #include <QLabel>
3637 #include "logicsignal.hpp"
3738 #include "view.hpp"
3839
40 #include "pv/util.hpp"
3941 #include "pv/data/analog.hpp"
4042 #include "pv/data/analogsegment.hpp"
4143 #include "pv/data/logic.hpp"
4850 using std::deque;
4951 using std::div;
5052 using std::div_t;
53 // Note that "using std::isnan;" is _not_ put here since that would break
54 // compilation on some platforms. Use "std::isnan()" instead in checks below.
5155 using std::max;
5256 using std::make_pair;
5357 using std::min;
5458 using std::numeric_limits;
59 using std::out_of_range;
5560 using std::pair;
5661 using std::shared_ptr;
5762 using std::vector;
5863
64 using pv::data::LogicSegment;
65 using pv::data::SignalBase;
66 using pv::util::SIPrefix;
67
5968 namespace pv {
6069 namespace views {
6170 namespace trace {
6271
63 const QColor AnalogSignal::SignalColours[4] = {
72 const QColor AnalogSignal::SignalColors[4] = {
6473 QColor(0xC4, 0xA0, 0x00), // Yellow
6574 QColor(0x87, 0x20, 0x7A), // Magenta
6675 QColor(0x20, 0x4A, 0x87), // Blue
6776 QColor(0x4E, 0x9A, 0x06) // Green
6877 };
6978
79 const QPen AnalogSignal::AxisPen(QColor(0, 0, 0, 30 * 256 / 100), 2);
7080 const QColor AnalogSignal::GridMajorColor = QColor(0, 0, 0, 40 * 256 / 100);
7181 const QColor AnalogSignal::GridMinorColor = QColor(0, 0, 0, 20 * 256 / 100);
7282
73 const QColor AnalogSignal::SamplingPointColour(0x77, 0x77, 0x77);
83 const QColor AnalogSignal::SamplingPointColor(0x77, 0x77, 0x77);
84 const QColor AnalogSignal::SamplingPointColorLo = QColor(200, 0, 0, 80 * 256 / 100);
85 const QColor AnalogSignal::SamplingPointColorNe = QColor(0, 0, 0, 80 * 256 / 100);
86 const QColor AnalogSignal::SamplingPointColorHi = QColor(0, 200, 0, 80 * 256 / 100);
87
88 const QColor AnalogSignal::ThresholdColor = QColor(0, 0, 0, 30 * 256 / 100);
89 const QColor AnalogSignal::ThresholdColorLo = QColor(255, 0, 0, 8 * 256 / 100);
90 const QColor AnalogSignal::ThresholdColorNe = QColor(0, 0, 0, 10 * 256 / 100);
91 const QColor AnalogSignal::ThresholdColorHi = QColor(0, 255, 0, 8 * 256 / 100);
7492
7593 const int64_t AnalogSignal::TracePaintBlockSize = 1024 * 1024; // 4 MiB (due to float)
7694 const float AnalogSignal::EnvelopeThreshold = 64.0f;
87105 shared_ptr<data::SignalBase> base) :
88106 Signal(session, base),
89107 scale_index_(4), // 20 per div
90 scale_index_drag_offset_(0),
91 div_height_(3 * QFontMetrics(QApplication::font()).height()),
92108 pos_vdivs_(1),
93109 neg_vdivs_(1),
94110 resolution_(0),
95 conversion_type_(data::SignalBase::NoConversion),
96111 display_type_(DisplayBoth),
97 autoranging_(true)
98 {
112 autoranging_(true),
113 value_at_hover_pos_(std::numeric_limits<float>::quiet_NaN())
114 {
115 axis_pen_ = AxisPen;
116
99117 pv::data::Analog* analog_data =
100118 dynamic_cast<pv::data::Analog*>(data().get());
101119
102 connect(analog_data, SIGNAL(samples_added(QObject*, uint64_t, uint64_t)),
103 this, SLOT(on_samples_added()));
104
105 base_->set_colour(SignalColours[base_->index() % countof(SignalColours)]);
120 connect(analog_data, SIGNAL(min_max_changed(float, float)),
121 this, SLOT(on_min_max_changed(float, float)));
122
123 GlobalSettings settings;
124 show_sampling_points_ =
125 settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool();
126 fill_high_areas_ =
127 settings.value(GlobalSettings::Key_View_FillSignalHighAreas).toBool();
128 high_fill_color_ = QColor::fromRgba(settings.value(
129 GlobalSettings::Key_View_FillSignalHighAreaColor).value<uint32_t>());
130 show_analog_minor_grid_ =
131 settings.value(GlobalSettings::Key_View_ShowAnalogMinorGrid).toBool();
132 conversion_threshold_disp_mode_ =
133 settings.value(GlobalSettings::Key_View_ConversionThresholdDispMode).toInt();
134 div_height_ = settings.value(GlobalSettings::Key_View_DefaultDivHeight).toInt();
135
136 base_->set_color(SignalColors[base_->index() % countof(SignalColors)]);
106137 update_scale();
107138 }
108139
116147 settings.setValue("pos_vdivs", pos_vdivs_);
117148 settings.setValue("neg_vdivs", neg_vdivs_);
118149 settings.setValue("scale_index", scale_index_);
119 settings.setValue("conversion_type", conversion_type_);
120150 settings.setValue("display_type", display_type_);
121151 settings.setValue("autoranging", autoranging_);
152 settings.setValue("div_height", div_height_);
122153 }
123154
124155 void AnalogSignal::restore_settings(QSettings &settings)
134165 update_scale();
135166 }
136167
137 if (settings.contains("conversion_type")) {
138 conversion_type_ = (data::SignalBase::ConversionType)(settings.value("conversion_type").toInt());
139 update_conversion_type();
140 }
141
142168 if (settings.contains("display_type"))
143169 display_type_ = (DisplayType)(settings.value("display_type").toInt());
144170
145171 if (settings.contains("autoranging"))
146172 autoranging_ = settings.value("autoranging").toBool();
173
174 if (settings.contains("div_height")) {
175 const int old_height = div_height_;
176 div_height_ = settings.value("div_height").toInt();
177
178 if ((div_height_ != old_height) && owner_) {
179 // Call order is important, otherwise the lazy event handler won't work
180 owner_->extents_changed(false, true);
181 owner_->row_item_appearance_changed(false, true);
182 }
183 }
147184 }
148185
149186 pair<int, int> AnalogSignal::v_extents() const
153190 return make_pair(-ph, nh);
154191 }
155192
156 int AnalogSignal::scale_handle_offset() const
157 {
158 const int h = (pos_vdivs_ + neg_vdivs_) * div_height_;
159
160 return ((scale_index_drag_offset_ - scale_index_) * h / 4) - h / 2;
161 }
162
163 void AnalogSignal::scale_handle_dragged(int offset)
164 {
165 const int h = (pos_vdivs_ + neg_vdivs_) * div_height_;
166
167 scale_index_ = scale_index_drag_offset_ - (offset + h / 2) / (h / 4);
168
169 update_scale();
170 }
171
172 void AnalogSignal::scale_handle_drag_release()
173 {
174 scale_index_drag_offset_ = scale_index_;
175 update_scale();
176 }
177
178193 void AnalogSignal::paint_back(QPainter &p, ViewItemPaintParams &pp)
179194 {
180 if (base_->enabled()) {
181 Trace::paint_back(p, pp);
195 if (!base_->enabled())
196 return;
197
198 bool paint_thr_bg =
199 conversion_threshold_disp_mode_ == GlobalSettings::ConvThrDispMode_Background;
200
201 const vector<double> thresholds = base_->get_conversion_thresholds();
202
203 // Only display thresholds if we have some and we show analog samples
204 if ((thresholds.size() > 0) && paint_thr_bg &&
205 ((display_type_ == DisplayAnalog) || (display_type_ == DisplayBoth))) {
206
207 const int visual_y = get_visual_y();
208 const pair<int, int> extents = v_extents();
209 const int top = visual_y + extents.first;
210 const int btm = visual_y + extents.second;
211
212 // Draw high/neutral/low areas
213 if (thresholds.size() == 2) {
214 int thr_lo = visual_y - thresholds[0] * scale_;
215 int thr_hi = visual_y - thresholds[1] * scale_;
216 thr_lo = min(max(thr_lo, top), btm);
217 thr_hi = min(max(thr_hi, top), btm);
218
219 p.fillRect(QRectF(pp.left(), top, pp.width(), thr_hi - top),
220 QBrush(ThresholdColorHi));
221 p.fillRect(QRectF(pp.left(), thr_hi, pp.width(), thr_lo - thr_hi),
222 QBrush(ThresholdColorNe));
223 p.fillRect(QRectF(pp.left(), thr_lo, pp.width(), btm - thr_lo),
224 QBrush(ThresholdColorLo));
225 } else {
226 int thr = visual_y - thresholds[0] * scale_;
227 thr = min(max(thr, top), btm);
228
229 p.fillRect(QRectF(pp.left(), top, pp.width(), thr - top),
230 QBrush(ThresholdColorHi));
231 p.fillRect(QRectF(pp.left(), thr, pp.width(), btm - thr),
232 QBrush(ThresholdColorLo));
233 }
234
235 paint_axis(p, pp, get_visual_y());
236 } else {
237 Signal::paint_back(p, pp);
182238 paint_axis(p, pp, get_visual_y());
183239 }
184240 }
196252 if ((display_type_ == DisplayAnalog) || (display_type_ == DisplayBoth)) {
197253 paint_grid(p, y, pp.left(), pp.right());
198254
199 const deque< shared_ptr<pv::data::AnalogSegment> > &segments =
200 base_->analog_data()->analog_segments();
201 if (segments.empty())
255 shared_ptr<pv::data::AnalogSegment> segment = get_analog_segment_to_paint();
256 if (!segment || (segment->get_sample_count() == 0))
202257 return;
203
204 const shared_ptr<pv::data::AnalogSegment> &segment =
205 segments.front();
206258
207259 const double pixels_offset = pp.pixels_offset();
208260 const double samplerate = max(1.0, segment->samplerate());
209261 const pv::util::Timestamp& start_time = segment->start_time();
210 const int64_t last_sample = segment->get_sample_count() - 1;
262 const int64_t last_sample = (int64_t)segment->get_sample_count() - 1;
211263 const double samples_per_pixel = samplerate * pp.scale();
212264 const pv::util::Timestamp start = samplerate * (pp.offset() - start_time);
213265 const pv::util::Timestamp end = start + samples_per_pixel * pp.width();
218270 (int64_t)0), last_sample);
219271
220272 if (samples_per_pixel < EnvelopeThreshold)
221 paint_trace(p, segment, y, pp.left(),
222 start_sample, end_sample,
273 paint_trace(p, segment, y, pp.left(), start_sample, end_sample,
223274 pixels_offset, samples_per_pixel);
224275 else
225 paint_envelope(p, segment, y, pp.left(),
226 start_sample, end_sample,
276 paint_envelope(p, segment, y, pp.left(), start_sample, end_sample,
227277 pixels_offset, samples_per_pixel);
228278 }
229279
230 if ((display_type_ == DisplayConverted) || (display_type_ == DisplayBoth)) {
231 if (((conversion_type_ == data::SignalBase::A2LConversionByTreshold) ||
232 (conversion_type_ == data::SignalBase::A2LConversionBySchmittTrigger))) {
233
234 paint_logic_mid(p, pp);
235 }
236 }
280 if ((display_type_ == DisplayConverted) || (display_type_ == DisplayBoth))
281 paint_logic_mid(p, pp);
237282 }
238283
239284 void AnalogSignal::paint_fore(QPainter &p, ViewItemPaintParams &pp)
244289 if ((display_type_ == DisplayAnalog) || (display_type_ == DisplayBoth)) {
245290 const int y = get_visual_y();
246291
247 // Show the info section on the right side of the trace
248 const QString infotext = QString("%1 V/div").arg(resolution_);
249
250 p.setPen(base_->colour());
292 QString infotext;
293
294 // Show the info section on the right side of the trace, including
295 // the value at the hover point when the hover marker is enabled
296 // and we have corresponding data available
297 if (show_hover_marker_ && !std::isnan(value_at_hover_pos_)) {
298 infotext = QString("[%1] %2 V/div")
299 .arg(format_value_si(value_at_hover_pos_, SIPrefix::unspecified, 0, "V", false))
300 .arg(resolution_);
301 } else
302 infotext = QString("%1 V/div").arg(resolution_);
303
304 p.setPen(base_->color());
251305 p.setFont(QApplication::font());
252306
253307 const QRectF bounding_rect = QRectF(pp.left(),
257311
258312 p.drawText(bounding_rect, Qt::AlignRight | Qt::AlignBottom, infotext);
259313 }
314
315 if (show_hover_marker_)
316 paint_hover_marker(p);
260317 }
261318
262319 void AnalogSignal::paint_grid(QPainter &p, int y, int left, int right)
263320 {
264321 p.setRenderHint(QPainter::Antialiasing, false);
265
266 GlobalSettings settings;
267 const bool show_analog_minor_grid =
268 settings.value(GlobalSettings::Key_View_ShowAnalogMinorGrid).toBool();
269322
270323 if (pos_vdivs_ > 0) {
271324 p.setPen(QPen(GridMajorColor, 1, Qt::DashLine));
275328 }
276329 }
277330
278 if ((pos_vdivs_ > 0) && show_analog_minor_grid) {
331 if ((pos_vdivs_ > 0) && show_analog_minor_grid_) {
279332 p.setPen(QPen(GridMinorColor, 1, Qt::DashLine));
280333 for (int i = 0; i < pos_vdivs_; i++) {
281334 const float dy = i * div_height_;
296349 }
297350 }
298351
299 if ((pos_vdivs_ > 0) && show_analog_minor_grid) {
352 if ((pos_vdivs_ > 0) && show_analog_minor_grid_) {
300353 p.setPen(QPen(GridMinorColor, 1, Qt::DashLine));
301354 for (int i = 0; i < neg_vdivs_; i++) {
302355 const float dy = i * div_height_;
320373 if (end <= start)
321374 return;
322375
376 bool paint_thr_dots =
377 (base_->get_conversion_type() != data::SignalBase::NoConversion) &&
378 (conversion_threshold_disp_mode_ == GlobalSettings::ConvThrDispMode_Dots);
379
380 vector<double> thresholds;
381 if (paint_thr_dots)
382 thresholds = base_->get_conversion_thresholds();
383
323384 // Calculate and paint the sampling points if enabled and useful
324385 GlobalSettings settings;
325386 const bool show_sampling_points =
326 settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool() &&
327 (samples_per_pixel < 0.25);
328
329 p.setPen(base_->colour());
330
331 const int64_t points_count = end - start;
387 (show_sampling_points_ || paint_thr_dots) && (samples_per_pixel < 0.25);
388
389 p.setPen(base_->color());
390
391 const int64_t points_count = end - start + 1;
332392
333393 QPointF *points = new QPointF[points_count];
334394 QPointF *point = points;
335395
336 QRectF *sampling_points = nullptr;
337 if (show_sampling_points)
338 sampling_points = new QRectF[points_count];
339 QRectF *sampling_point = sampling_points;
396 vector<QRectF> sampling_points[3];
340397
341398 int64_t sample_count = min(points_count, TracePaintBlockSize);
342399 int64_t block_sample = 0;
343 const float *sample_block = segment->get_samples(start, start + sample_count);
400 float *sample_block = new float[TracePaintBlockSize];
401 segment->get_samples(start, start + sample_count, sample_block);
402
403 if (show_hover_marker_)
404 reset_pixel_values();
344405
345406 const int w = 2;
346 for (int64_t sample = start; sample != end; sample++, block_sample++) {
347
407 for (int64_t sample = start; sample <= end; sample++, block_sample++) {
408
409 // Fetch next block of samples if we finished the current one
348410 if (block_sample == TracePaintBlockSize) {
349411 block_sample = 0;
350 delete[] sample_block;
351412 sample_count = min(points_count - sample, TracePaintBlockSize);
352 sample_block = segment->get_samples(sample, sample + sample_count);
353 }
354
355 const float x = (sample / samples_per_pixel -
356 pixels_offset) + left;
413 segment->get_samples(sample, sample + sample_count, sample_block);
414 }
415
416 const float abs_x = sample / samples_per_pixel - pixels_offset;
417 const float x = left + abs_x;
357418
358419 *point++ = QPointF(x, y - sample_block[block_sample] * scale_);
359420
360 if (show_sampling_points)
361 *sampling_point++ =
362 QRectF(x - (w / 2), y - sample_block[block_sample] * scale_ - (w / 2), w, w);
421 // Generate the pixel<->value lookup table for the mouse hover
422 if (show_hover_marker_)
423 process_next_sample_value(abs_x, sample_block[block_sample]);
424
425 // Create the sampling points if needed
426 if (show_sampling_points) {
427 int idx = 0; // Neutral
428
429 if (paint_thr_dots) {
430 if (thresholds.size() == 1)
431 idx = (sample_block[block_sample] >= thresholds[0]) ? 2 : 1;
432 else if (thresholds.size() == 2) {
433 if (sample_block[block_sample] > thresholds[1])
434 idx = 2; // High
435 else if (sample_block[block_sample] < thresholds[0])
436 idx = 1; // Low
437 }
438 }
439
440 sampling_points[idx].emplace_back(x - (w / 2), y - sample_block[block_sample] * scale_ - (w / 2), w, w);
441 }
363442 }
364443 delete[] sample_block;
365444
366445 p.drawPolyline(points, points_count);
367446
368447 if (show_sampling_points) {
369 p.setPen(SamplingPointColour);
370 p.drawRects(sampling_points, points_count);
371 delete[] sampling_points;
448 if (paint_thr_dots) {
449 p.setPen(SamplingPointColorNe);
450 p.drawRects(sampling_points[0].data(), sampling_points[0].size());
451 p.setPen(SamplingPointColorLo);
452 p.drawRects(sampling_points[1].data(), sampling_points[1].size());
453 p.setPen(SamplingPointColorHi);
454 p.drawRects(sampling_points[2].data(), sampling_points[2].size());
455 } else {
456 p.setPen(SamplingPointColor);
457 p.drawRects(sampling_points[0].data(), sampling_points[0].size());
458 }
372459 }
373460
374461 delete[] points;
381468 {
382469 using pv::data::AnalogSegment;
383470
471 // Note: Envelope painting currently doesn't generate a pixel<->value lookup table
472 if (show_hover_marker_)
473 reset_pixel_values();
474
384475 AnalogSegment::EnvelopeSection e;
385476 segment->get_envelope_section(e, start, end, samples_per_pixel);
386477
388479 return;
389480
390481 p.setPen(QPen(Qt::NoPen));
391 p.setBrush(base_->colour());
482 p.setBrush(base_->color());
392483
393484 QRectF *const rects = new QRectF[e.length];
394485 QRectF *rect = rects;
396487 for (uint64_t sample = 0; sample < e.length - 1; sample++) {
397488 const float x = ((e.scale * sample + e.start) /
398489 samples_per_pixel - pixels_offset) + left;
399 const AnalogSegment::EnvelopeSample *const s =
400 e.samples + sample;
490
491 const AnalogSegment::EnvelopeSample *const s = e.samples + sample;
401492
402493 // We overlap this sample with the next so that vertical
403494 // gaps do not appear during steep rising or falling edges
439530 const int nh = min(neg_vdivs_, 1) * div_height_;
440531 const float high_offset = y - ph + signal_margin + 0.5f;
441532 const float low_offset = y + nh - signal_margin - 0.5f;
442
443 const deque< shared_ptr<pv::data::LogicSegment> > &segments =
444 base_->logic_data()->logic_segments();
445
446 if (segments.empty())
533 const float signal_height = low_offset - high_offset;
534
535 shared_ptr<pv::data::LogicSegment> segment = get_logic_segment_to_paint();
536 if (!segment || (segment->get_sample_count() == 0))
447537 return;
448
449 const shared_ptr<pv::data::LogicSegment> &segment =
450 segments.front();
451538
452539 double samplerate = segment->samplerate();
453540
457544
458545 const double pixels_offset = pp.pixels_offset();
459546 const pv::util::Timestamp& start_time = segment->start_time();
460 const int64_t last_sample = segment->get_sample_count() - 1;
547 const int64_t last_sample = (int64_t)segment->get_sample_count() - 1;
461548 const double samples_per_pixel = samplerate * pp.scale();
462549 const double pixels_per_sample = 1 / samples_per_pixel;
463550 const pv::util::Timestamp start = samplerate * (pp.offset() - start_time);
472559 samples_per_pixel / LogicSignal::Oversampling, 0);
473560 assert(edges.size() >= 2);
474561
562 const float first_sample_x =
563 pp.left() + (edges.front().first / samples_per_pixel - pixels_offset);
564 const float last_sample_x =
565 pp.left() + (edges.back().first / samples_per_pixel - pixels_offset);
566
475567 // Check whether we need to paint the sampling points
476 GlobalSettings settings;
477 const bool show_sampling_points =
478 settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool() &&
479 (samples_per_pixel < 0.25);
480
568 const bool show_sampling_points = show_sampling_points_ && (samples_per_pixel < 0.25);
481569 vector<QRectF> sampling_points;
482 float sampling_point_x = 0.0f;
570 float sampling_point_x = first_sample_x;
483571 int64_t sampling_point_sample = start_sample;
484572 const int w = 2;
485573
486 if (show_sampling_points) {
574 if (show_sampling_points)
487575 sampling_points.reserve(end_sample - start_sample + 1);
488 sampling_point_x = (edges.cbegin()->first / samples_per_pixel - pixels_offset) + pp.left();
489 }
576
577 vector<QRectF> high_rects;
578 float rising_edge_x;
579 bool rising_edge_seen = false;
490580
491581 // Paint the edges
492582 const unsigned int edge_count = edges.size() - 2;
493583 QLineF *const edge_lines = new QLineF[edge_count];
494584 line = edge_lines;
495585
586 if (edges.front().second) {
587 // Beginning of trace is high
588 rising_edge_x = first_sample_x;
589 rising_edge_seen = true;
590 }
591
496592 for (auto i = edges.cbegin() + 1; i != edges.cend() - 1; i++) {
497 const float x = ((*i).first / samples_per_pixel -
498 pixels_offset) + pp.left();
593 // Note: multiple edges occupying a single pixel are represented by an edge
594 // with undefined logic level. This means that only the first falling edge
595 // after a rising edge corresponds to said rising edge - and vice versa. If
596 // more edges with the same logic level follow, they denote multiple edges.
597
598 const float x = pp.left() + ((*i).first / samples_per_pixel - pixels_offset);
499599 *line++ = QLineF(x, high_offset, x, low_offset);
600
601 if (fill_high_areas_) {
602 // Any edge terminates a high area
603 if (rising_edge_seen) {
604 const int width = x - rising_edge_x;
605 if (width > 0)
606 high_rects.emplace_back(rising_edge_x, high_offset,
607 width, signal_height);
608 rising_edge_seen = false;
609 }
610
611 // Only rising edges start high areas
612 if ((*i).second) {
613 rising_edge_x = x;
614 rising_edge_seen = true;
615 }
616 }
500617
501618 if (show_sampling_points)
502619 while (sampling_point_sample < (*i).first) {
519636 sampling_point_x += pixels_per_sample;
520637 };
521638
522 p.setPen(LogicSignal::EdgeColour);
639 if (fill_high_areas_) {
640 // Add last high rectangle if the signal is still high at the end of the trace
641 if (rising_edge_seen && (edges.cend() - 1)->second)
642 high_rects.emplace_back(rising_edge_x, high_offset,
643 last_sample_x - rising_edge_x, signal_height);
644
645 p.setPen(high_fill_color_);
646 p.setBrush(high_fill_color_);
647 p.drawRects((const QRectF*)(high_rects.data()), high_rects.size());
648 }
649
650 p.setPen(LogicSignal::EdgeColor);
523651 p.drawLines(edge_lines, edge_count);
524652 delete[] edge_lines;
525653
527655 const unsigned int max_cap_line_count = edges.size();
528656 QLineF *const cap_lines = new QLineF[max_cap_line_count];
529657
530 p.setPen(LogicSignal::HighColour);
658 p.setPen(LogicSignal::HighColor);
531659 paint_logic_caps(p, cap_lines, edges, true, samples_per_pixel,
532660 pixels_offset, pp.left(), high_offset);
533 p.setPen(LogicSignal::LowColour);
661 p.setPen(LogicSignal::LowColor);
534662 paint_logic_caps(p, cap_lines, edges, false, samples_per_pixel,
535663 pixels_offset, pp.left(), low_offset);
536664
538666
539667 // Paint the sampling points
540668 if (show_sampling_points) {
541 p.setPen(SamplingPointColour);
669 p.setPen(SamplingPointColor);
542670 p.drawRects(sampling_points.data(), sampling_points.size());
543671 }
544672 }
562690 p.drawLines(lines, line - lines);
563691 }
564692
693 shared_ptr<pv::data::AnalogSegment> AnalogSignal::get_analog_segment_to_paint() const
694 {
695 shared_ptr<pv::data::AnalogSegment> segment;
696
697 const deque< shared_ptr<pv::data::AnalogSegment> > &segments =
698 base_->analog_data()->analog_segments();
699
700 if (!segments.empty()) {
701 if (segment_display_mode_ == ShowLastSegmentOnly)
702 segment = segments.back();
703
704 if ((segment_display_mode_ == ShowSingleSegmentOnly) ||
705 (segment_display_mode_ == ShowLastCompleteSegmentOnly)) {
706 try {
707 segment = segments.at(current_segment_);
708 } catch (out_of_range&) {
709 qDebug() << "Current analog segment out of range for signal" << base_->name() << ":" << current_segment_;
710 }
711 }
712 }
713
714 return segment;
715 }
716
717 shared_ptr<pv::data::LogicSegment> AnalogSignal::get_logic_segment_to_paint() const
718 {
719 shared_ptr<pv::data::LogicSegment> segment;
720
721 const deque< shared_ptr<pv::data::LogicSegment> > &segments =
722 base_->logic_data()->logic_segments();
723
724 if (!segments.empty()) {
725 if (segment_display_mode_ == ShowLastSegmentOnly)
726 segment = segments.back();
727
728 if ((segment_display_mode_ == ShowSingleSegmentOnly) ||
729 (segment_display_mode_ == ShowLastCompleteSegmentOnly)) {
730 try {
731 segment = segments.at(current_segment_);
732 } catch (out_of_range&) {
733 qDebug() << "Current logic segment out of range for signal" << base_->name() << ":" << current_segment_;
734 }
735 }
736 }
737
738 return segment;
739 }
740
565741 float AnalogSignal::get_resolution(int scale_index)
566742 {
567743 const float seq[] = {1.0f, 2.0f, 5.0f};
579755 scale_ = div_height_ / resolution_;
580756 }
581757
582 void AnalogSignal::update_conversion_type()
583 {
584 base_->set_conversion_type(conversion_type_);
585
586 if (owner_)
587 owner_->row_item_appearance_changed(false, true);
758 void AnalogSignal::update_conversion_widgets()
759 {
760 SignalBase::ConversionType conv_type = base_->get_conversion_type();
761
762 // Enable or disable widgets depending on conversion state
763 conv_threshold_cb_->setEnabled(conv_type != SignalBase::NoConversion);
764 display_type_cb_->setEnabled(conv_type != SignalBase::NoConversion);
765
766 conv_threshold_cb_->clear();
767
768 vector < pair<QString, int> > presets = base_->get_conversion_presets();
769
770 // Prevent the combo box from firing the "edit text changed" signal
771 // as that would involuntarily select the first entry
772 conv_threshold_cb_->blockSignals(true);
773
774 // Set available options depending on chosen conversion
775 for (pair<QString, int>& preset : presets)
776 conv_threshold_cb_->addItem(preset.first, preset.second);
777
778 map < QString, QVariant > options = base_->get_conversion_options();
779
780 if (conv_type == SignalBase::A2LConversionByThreshold) {
781 const vector<double> thresholds = base_->get_conversion_thresholds(
782 SignalBase::A2LConversionByThreshold, true);
783 conv_threshold_cb_->addItem(
784 QString("%1V").arg(QString::number(thresholds[0], 'f', 1)), -1);
785 }
786
787 if (conv_type == SignalBase::A2LConversionBySchmittTrigger) {
788 const vector<double> thresholds = base_->get_conversion_thresholds(
789 SignalBase::A2LConversionBySchmittTrigger, true);
790 conv_threshold_cb_->addItem(QString("%1V/%2V").arg(
791 QString::number(thresholds[0], 'f', 1),
792 QString::number(thresholds[1], 'f', 1)), -1);
793 }
794
795 int preset_id = base_->get_current_conversion_preset();
796 conv_threshold_cb_->setCurrentIndex(
797 conv_threshold_cb_->findData(preset_id));
798
799 conv_threshold_cb_->blockSignals(false);
800 }
801
802 vector<data::LogicSegment::EdgePair> AnalogSignal::get_nearest_level_changes(uint64_t sample_pos)
803 {
804 assert(base_);
805 assert(owner_);
806
807 // Return if there's no logic data or we're showing only the analog trace
808 if (!base_->logic_data() || (display_type_ == DisplayAnalog))
809 return vector<data::LogicSegment::EdgePair>();
810
811 if (sample_pos == 0)
812 return vector<LogicSegment::EdgePair>();
813
814 shared_ptr<LogicSegment> segment = get_logic_segment_to_paint();
815 if (!segment || (segment->get_sample_count() == 0))
816 return vector<LogicSegment::EdgePair>();
817
818 const View *view = owner_->view();
819 assert(view);
820 const double samples_per_pixel = base_->get_samplerate() * view->scale();
821
822 vector<LogicSegment::EdgePair> edges;
823
824 segment->get_surrounding_edges(edges, sample_pos,
825 samples_per_pixel / LogicSignal::Oversampling, 0);
826
827 if (edges.empty())
828 return vector<LogicSegment::EdgePair>();
829
830 return edges;
588831 }
589832
590833 void AnalogSignal::perform_autoranging(bool keep_divs, bool force_update)
598841 static double prev_min = 0, prev_max = 0;
599842 double min = 0, max = 0;
600843
601 for (shared_ptr<pv::data::AnalogSegment> segment : segments) {
844 for (const shared_ptr<pv::data::AnalogSegment>& segment : segments) {
602845 pair<double, double> mm = segment->get_min_max();
603846 min = std::min(min, mm.first);
604847 max = std::max(max, mm.second);
657900 update_scale();
658901 }
659902
903 void AnalogSignal::reset_pixel_values()
904 {
905 value_at_pixel_pos_.clear();
906 current_pixel_pos_ = -1;
907 prev_value_at_pixel_ = std::numeric_limits<float>::quiet_NaN();
908 }
909
910 void AnalogSignal::process_next_sample_value(float x, float value)
911 {
912 // Note: NAN is used to indicate the non-existance of a value at this pixel
913
914 if (std::isnan(prev_value_at_pixel_)) {
915 if (x < 0) {
916 min_value_at_pixel_ = value;
917 max_value_at_pixel_ = value;
918 prev_value_at_pixel_ = value;
919 current_pixel_pos_ = x;
920 } else
921 prev_value_at_pixel_ = std::numeric_limits<float>::quiet_NaN();
922 }
923
924 const int pixel_pos = (int)(x + 0.5);
925
926 if (pixel_pos > current_pixel_pos_) {
927 if (pixel_pos - current_pixel_pos_ == 1) {
928 if (std::isnan(prev_value_at_pixel_)) {
929 value_at_pixel_pos_.push_back(prev_value_at_pixel_);
930 } else {
931 // Average the min/max range to create one value for the previous pixel
932 const float avg = (min_value_at_pixel_ + max_value_at_pixel_) / 2;
933 value_at_pixel_pos_.push_back(avg);
934 }
935 } else {
936 // Interpolate values to create values for the intermediate pixels
937 const float start_value = prev_value_at_pixel_;
938 const float end_value = value;
939 const int steps = fabs(pixel_pos - current_pixel_pos_);
940 const double gradient = (end_value - start_value) / steps;
941 for (int i = 0; i < steps; i++) {
942 if (current_pixel_pos_ + i < 0)
943 continue;
944 value_at_pixel_pos_.push_back(start_value + i * gradient);
945 }
946 }
947
948 min_value_at_pixel_ = value;
949 max_value_at_pixel_ = value;
950 prev_value_at_pixel_ = value;
951 current_pixel_pos_ = pixel_pos;
952 } else {
953 // Another sample for the same pixel
954 if (value < min_value_at_pixel_)
955 min_value_at_pixel_ = value;
956 if (value > max_value_at_pixel_)
957 max_value_at_pixel_ = value;
958 }
959 }
960
660961 void AnalogSignal::populate_popup_form(QWidget *parent, QFormLayout *form)
661962 {
662963 // Add the standard options
664965
665966 QFormLayout *const layout = new QFormLayout;
666967
667 // Add the number of vdivs
968 // Add div-related settings
668969 pvdiv_sb_ = new QSpinBox(parent);
669970 pvdiv_sb_->setRange(0, MaximumVDivs);
670971 pvdiv_sb_->setValue(pos_vdivs_);
679980 this, SLOT(on_neg_vdivs_changed(int)));
680981 layout->addRow(tr("Number of neg vertical divs"), nvdiv_sb_);
681982
983 div_height_sb_ = new QSpinBox(parent);
984 div_height_sb_->setRange(20, 1000);
985 div_height_sb_->setSingleStep(5);
986 div_height_sb_->setSuffix(tr(" pixels"));
987 div_height_sb_->setValue(div_height_);
988 connect(div_height_sb_, SIGNAL(valueChanged(int)),
989 this, SLOT(on_div_height_changed(int)));
990 layout->addRow(tr("Div height"), div_height_sb_);
991
682992 // Add the vertical resolution
683993 resolution_cb_ = new QComboBox(parent);
684994
7121022 // Add the conversion type dropdown
7131023 conversion_cb_ = new QComboBox();
7141024
715 conversion_cb_->addItem("none", data::SignalBase::NoConversion);
716 conversion_cb_->addItem("to logic via threshold", data::SignalBase::A2LConversionByTreshold);
717 conversion_cb_->addItem("to logic via schmitt-trigger", data::SignalBase::A2LConversionBySchmittTrigger);
718
719 cur_idx = conversion_cb_->findData(QVariant(conversion_type_));
1025 conversion_cb_->addItem(tr("none"),
1026 SignalBase::NoConversion);
1027 conversion_cb_->addItem(tr("to logic via threshold"),
1028 SignalBase::A2LConversionByThreshold);
1029 conversion_cb_->addItem(tr("to logic via schmitt-trigger"),
1030 SignalBase::A2LConversionBySchmittTrigger);
1031
1032 cur_idx = conversion_cb_->findData(QVariant(base_->get_conversion_type()));
7201033 conversion_cb_->setCurrentIndex(cur_idx);
7211034
722 // layout->addRow(tr("Conversion"), conversion_cb_);
1035 layout->addRow(tr("Conversion"), conversion_cb_);
7231036
7241037 connect(conversion_cb_, SIGNAL(currentIndexChanged(int)),
7251038 this, SLOT(on_conversion_changed(int)));
7261039
1040 // Add the conversion threshold settings
1041 conv_threshold_cb_ = new QComboBox();
1042 conv_threshold_cb_->setEditable(true);
1043
1044 layout->addRow(tr("Conversion threshold(s)"), conv_threshold_cb_);
1045
1046 connect(conv_threshold_cb_, SIGNAL(currentIndexChanged(int)),
1047 this, SLOT(on_conv_threshold_changed(int)));
1048 connect(conv_threshold_cb_, SIGNAL(editTextChanged(const QString&)),
1049 this, SLOT(on_conv_threshold_changed())); // index will be -1
1050
7271051 // Add the display type dropdown
7281052 display_type_cb_ = new QComboBox();
7291053
730 display_type_cb_->addItem(tr("Analog"), DisplayAnalog);
731 display_type_cb_->addItem(tr("Converted"), DisplayConverted);
732 display_type_cb_->addItem(tr("Both"), DisplayBoth);
1054 display_type_cb_->addItem(tr("analog"), DisplayAnalog);
1055 display_type_cb_->addItem(tr("converted"), DisplayConverted);
1056 display_type_cb_->addItem(tr("analog+converted"), DisplayBoth);
7331057
7341058 cur_idx = display_type_cb_->findData(QVariant(display_type_));
7351059 display_type_cb_->setCurrentIndex(cur_idx);
7361060
737 // layout->addRow(tr("Traces to show:"), display_type_cb_);
1061 layout->addRow(tr("Show traces for"), display_type_cb_);
7381062
7391063 connect(display_type_cb_, SIGNAL(currentIndexChanged(int)),
7401064 this, SLOT(on_display_type_changed(int)));
7411065
1066 // Update the conversion widget contents and states
1067 update_conversion_widgets();
1068
7421069 form->addRow(layout);
7431070 }
7441071
745 void AnalogSignal::on_samples_added()
746 {
747 perform_autoranging(false, false);
1072 void AnalogSignal::hover_point_changed(const QPoint &hp)
1073 {
1074 Signal::hover_point_changed(hp);
1075
1076 // Note: Even though the view area begins at 0, we exclude 0 because
1077 // that's also the value given when the cursor is over the header to the
1078 // left of the trace paint area
1079 if (hp.x() <= 0) {
1080 value_at_hover_pos_ = std::numeric_limits<float>::quiet_NaN();
1081 } else {
1082 try {
1083 value_at_hover_pos_ = value_at_pixel_pos_.at(hp.x());
1084 } catch (out_of_range&) {
1085 value_at_hover_pos_ = std::numeric_limits<float>::quiet_NaN();
1086 }
1087 }
1088 }
1089
1090 void AnalogSignal::on_setting_changed(const QString &key, const QVariant &value)
1091 {
1092 Signal::on_setting_changed(key, value);
1093
1094 if (key == GlobalSettings::Key_View_ShowSamplingPoints)
1095 show_sampling_points_ = value.toBool();
1096
1097 if (key == GlobalSettings::Key_View_FillSignalHighAreas)
1098 fill_high_areas_ = value.toBool();
1099
1100 if (key == GlobalSettings::Key_View_FillSignalHighAreaColor)
1101 high_fill_color_ = QColor::fromRgba(value.value<uint32_t>());
1102
1103 if (key == GlobalSettings::Key_View_ShowAnalogMinorGrid)
1104 show_analog_minor_grid_ = value.toBool();
1105
1106 if (key == GlobalSettings::Key_View_ConversionThresholdDispMode) {
1107 conversion_threshold_disp_mode_ = value.toInt();
1108
1109 if (owner_)
1110 owner_->row_item_appearance_changed(false, true);
1111 }
1112 }
1113
1114 void AnalogSignal::on_min_max_changed(float min, float max)
1115 {
1116 (void)min;
1117 (void)max;
1118
1119 if (autoranging_)
1120 perform_autoranging(false, false);
7481121 }
7491122
7501123 void AnalogSignal::on_pos_vdivs_changed(int vdivs)
8091182 }
8101183 }
8111184
812 void AnalogSignal::on_resolution_changed(int index)
813 {
814 scale_index_ = resolution_cb_->itemData(index).toInt();
1185 void AnalogSignal::on_div_height_changed(int height)
1186 {
1187 div_height_ = height;
8151188 update_scale();
816
817 if (owner_)
818 owner_->row_item_appearance_changed(false, true);
819 }
820
821 void AnalogSignal::on_autoranging_changed(int state)
822 {
823 autoranging_ = (state == Qt::Checked);
824
825 if (autoranging_)
826 perform_autoranging(false, true);
8271189
8281190 if (owner_) {
8291191 // Call order is important, otherwise the lazy event handler won't work
8321194 }
8331195 }
8341196
1197 void AnalogSignal::on_resolution_changed(int index)
1198 {
1199 scale_index_ = resolution_cb_->itemData(index).toInt();
1200 update_scale();
1201
1202 if (owner_)
1203 owner_->row_item_appearance_changed(false, true);
1204 }
1205
1206 void AnalogSignal::on_autoranging_changed(int state)
1207 {
1208 autoranging_ = (state == Qt::Checked);
1209
1210 if (autoranging_)
1211 perform_autoranging(false, true);
1212
1213 if (owner_) {
1214 // Call order is important, otherwise the lazy event handler won't work
1215 owner_->extents_changed(false, true);
1216 owner_->row_item_appearance_changed(false, true);
1217 }
1218 }
1219
8351220 void AnalogSignal::on_conversion_changed(int index)
8361221 {
837 data::SignalBase::ConversionType old_conv_type = conversion_type_;
838
839 conversion_type_ = (data::SignalBase::ConversionType)(conversion_cb_->itemData(index).toInt());
840
841 if (conversion_type_ != old_conv_type) {
842 base_->set_conversion_type(conversion_type_);
843 update_conversion_type();
844 }
1222 SignalBase::ConversionType old_conv_type = base_->get_conversion_type();
1223
1224 SignalBase::ConversionType conv_type =
1225 (SignalBase::ConversionType)(conversion_cb_->itemData(index).toInt());
1226
1227 if (conv_type != old_conv_type) {
1228 base_->set_conversion_type(conv_type);
1229 update_conversion_widgets();
1230
1231 if (owner_)
1232 owner_->row_item_appearance_changed(false, true);
1233 }
1234 }
1235
1236 void AnalogSignal::on_conv_threshold_changed(int index)
1237 {
1238 SignalBase::ConversionType conv_type = base_->get_conversion_type();
1239
1240 // Note: index is set to -1 if the text in the combo box matches none of
1241 // the entries in the combo box
1242
1243 if ((index == -1) && (conv_threshold_cb_->currentText().length() == 0))
1244 return;
1245
1246 // The combo box entry with the custom value has user_data set to -1
1247 const int user_data = conv_threshold_cb_->findText(
1248 conv_threshold_cb_->currentText());
1249
1250 const bool use_custom_thr = (index == -1) || (user_data == -1);
1251
1252 if (conv_type == SignalBase::A2LConversionByThreshold && use_custom_thr) {
1253 // Not one of the preset values, try to parse the combo box text
1254 // Note: Regex loosely based on
1255 // https://txt2re.com/index-c++.php3?s=0.1V&1&-13
1256 QString re1 = "([+-]?\\d*[\\.,]?\\d*)"; // Float value
1257 QString re2 = "([a-zA-Z]*)"; // SI unit
1258 QRegExp regex(re1 + re2);
1259
1260 const QString text = conv_threshold_cb_->currentText();
1261 if (!regex.exactMatch(text))
1262 return; // String doesn't match the regex
1263
1264 QStringList tokens = regex.capturedTexts();
1265
1266 // For now, we simply assume that the unit is volt without modifiers
1267 const double thr = tokens.at(1).toDouble();
1268
1269 // Only restart the conversion if the threshold was updated.
1270 // We're starting a delayed conversion because the user may still be
1271 // typing and the UI would lag if we kept on restarting it immediately
1272 if (base_->set_conversion_option("threshold_value", thr))
1273 base_->start_conversion(true);
1274 }
1275
1276 if (conv_type == SignalBase::A2LConversionBySchmittTrigger && use_custom_thr) {
1277 // Not one of the preset values, try to parse the combo box text
1278 // Note: Regex loosely based on
1279 // https://txt2re.com/index-c++.php3?s=0.1V/0.2V&2&14&-22&3&15
1280 QString re1 = "([+-]?\\d*[\\.,]?\\d*)"; // Float value
1281 QString re2 = "([a-zA-Z]*)"; // SI unit
1282 QString re3 = "\\/"; // Forward slash, not captured
1283 QString re4 = "([+-]?\\d*[\\.,]?\\d*)"; // Float value
1284 QString re5 = "([a-zA-Z]*)"; // SI unit
1285 QRegExp regex(re1 + re2 + re3 + re4 + re5);
1286
1287 const QString text = conv_threshold_cb_->currentText();
1288 if (!regex.exactMatch(text))
1289 return; // String doesn't match the regex
1290
1291 QStringList tokens = regex.capturedTexts();
1292
1293 // For now, we simply assume that the unit is volt without modifiers
1294 const double low_thr = tokens.at(1).toDouble();
1295 const double high_thr = tokens.at(3).toDouble();
1296
1297 // Only restart the conversion if one of the options was updated.
1298 // We're starting a delayed conversion because the user may still be
1299 // typing and the UI would lag if we kept on restarting it immediately
1300 bool o1 = base_->set_conversion_option("threshold_value_low", low_thr);
1301 bool o2 = base_->set_conversion_option("threshold_value_high", high_thr);
1302 if (o1 || o2)
1303 base_->start_conversion(true); // Start delayed conversion
1304 }
1305
1306 base_->set_conversion_preset((SignalBase::ConversionPreset)index);
1307
1308 // Immediately start the conversion if we're not using custom values
1309 // (i.e. we're using one of the presets)
1310 if (!use_custom_thr)
1311 base_->start_conversion();
1312 }
1313
1314 void AnalogSignal::on_delayed_conversion_starter()
1315 {
1316 base_->start_conversion();
8451317 }
8461318
8471319 void AnalogSignal::on_display_type_changed(int index)
1919 #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_ANALOGSIGNAL_HPP
2020 #define PULSEVIEW_PV_VIEWS_TRACEVIEW_ANALOGSIGNAL_HPP
2121
22 #include "signal.hpp"
23
2422 #include <memory>
2523
24 #include <QColor>
2625 #include <QComboBox>
2726 #include <QSpinBox>
27
28 #include <pv/views/trace/signal.hpp>
2829
2930 using std::pair;
3031 using std::shared_ptr;
4546 Q_OBJECT
4647
4748 private:
48 static const QColor SignalColours[4];
49 static const QPen AxisPen;
50 static const QColor SignalColors[4];
4951 static const QColor GridMajorColor, GridMinorColor;
50 static const QColor SamplingPointColour;
52 static const QColor SamplingPointColor;
53 static const QColor SamplingPointColorLo;
54 static const QColor SamplingPointColorNe;
55 static const QColor SamplingPointColorHi;
56 static const QColor ThresholdColor;
57 static const QColor ThresholdColorLo;
58 static const QColor ThresholdColorNe;
59 static const QColor ThresholdColorHi;
5160
5261 static const int64_t TracePaintBlockSize;
5362 static const float EnvelopeThreshold;
6574 public:
6675 AnalogSignal(pv::Session &session, shared_ptr<data::SignalBase> base);
6776
68 virtual ~AnalogSignal() = default;
69
7077 shared_ptr<pv::data::SignalData> data() const;
7178
7279 virtual void save_settings(QSettings &settings) const;
7885 * @return A pair containing the minimum and maximum y-values.
7986 */
8087 pair<int, int> v_extents() const;
81
82 /**
83 * Returns the offset to show the drag handle.
84 */
85 int scale_handle_offset() const;
86
87 /**
88 * Handles the scale handle being dragged to an offset.
89 * @param offset the offset the scale handle was dragged to.
90 */
91 void scale_handle_dragged(int offset);
92
93 /**
94 * @copydoc pv::view::Signal::signal_scale_handle_drag_release()
95 */
96 void scale_handle_drag_release();
9788
9889 /**
9990 * Paints the background layer of the signal with a QPainter
136127 bool level, double samples_per_pixel, double pixels_offset,
137128 float x_offset, float y_offset);
138129
130 shared_ptr<pv::data::AnalogSegment> get_analog_segment_to_paint() const;
131 shared_ptr<pv::data::LogicSegment> get_logic_segment_to_paint() const;
132
139133 /**
140134 * Computes the scale factor from the scale index and vdiv settings.
141135 */
143137
144138 void update_scale();
145139
146 void update_conversion_type();
140 void update_conversion_widgets();
141
142 /**
143 * Determines the closest level change (i.e. edge) to a given sample, which
144 * is useful for e.g. the "snap to edge" functionality.
145 *
146 * @param sample_pos Sample to use
147 * @return The changes left and right of the given position
148 */
149 virtual vector<data::LogicSegment::EdgePair> get_nearest_level_changes(uint64_t sample_pos);
147150
148151 void perform_autoranging(bool keep_divs, bool force_update);
152
153 void reset_pixel_values();
154 void process_next_sample_value(float x, float value);
149155
150156 protected:
151157 void populate_popup_form(QWidget *parent, QFormLayout *form);
152158
159 virtual void hover_point_changed(const QPoint &hp);
160
153161 private Q_SLOTS:
154 void on_samples_added();
162 virtual void on_setting_changed(const QString &key, const QVariant &value);
163
164 void on_min_max_changed(float min, float max);
155165
156166 void on_pos_vdivs_changed(int vdivs);
157167 void on_neg_vdivs_changed(int vdivs);
168 void on_div_height_changed(int height);
158169
159170 void on_resolution_changed(int index);
160171
161172 void on_autoranging_changed(int state);
162173
163174 void on_conversion_changed(int index);
175 void on_conv_threshold_changed(int index=-1);
176 void on_delayed_conversion_starter();
164177
165178 void on_display_type_changed(int index);
166179
167180 private:
168 QComboBox *resolution_cb_, *conversion_cb_, *display_type_cb_;
169 QSpinBox *pvdiv_sb_, *nvdiv_sb_;
181 QComboBox *resolution_cb_, *conversion_cb_, *conv_threshold_cb_,
182 *display_type_cb_;
183 QSpinBox *pvdiv_sb_, *nvdiv_sb_, *div_height_sb_;
170184
171185 float scale_;
172186 int scale_index_;
173 int scale_index_drag_offset_;
174187
175188 int div_height_;
176189 int pos_vdivs_, neg_vdivs_; // divs per positive/negative side
177 float resolution_; // e.g. 10 for 10 V/div
178
179 data::SignalBase::ConversionType conversion_type_;
190 float resolution_; // e.g. 10 for 10 V/div
191
192 bool show_analog_minor_grid_;
193 QColor high_fill_color_;
194 bool show_sampling_points_, fill_high_areas_;
195
180196 DisplayType display_type_;
181197 bool autoranging_;
198 int conversion_threshold_disp_mode_;
199
200 vector<float> value_at_pixel_pos_;
201 float value_at_hover_pos_;
202 float prev_value_at_pixel_; // Only used during lookup table update
203 float min_value_at_pixel_, max_value_at_pixel_; // Only used during lookup table update
204 int current_pixel_pos_; // Only used during lookup table update
182205 };
183206
184207 } // namespace trace
3333 #include <cstdio>
3434 #include <limits>
3535
36 using std::abs; // Force usage of std::abs() instead of C's abs().
36 using std::abs; // NOLINT. Force usage of std::abs() instead of C's abs().
3737 using std::shared_ptr;
3838
3939 namespace pv {
4040 namespace views {
4141 namespace trace {
4242
43 const QColor Cursor::FillColour(52, 101, 164);
43 const QColor Cursor::FillColor(52, 101, 164);
4444
4545 Cursor::Cursor(View &view, double time) :
46 TimeMarker(view, FillColour, time)
46 TimeMarker(view, FillColor, time)
4747 {
4848 }
4949
3838 Q_OBJECT
3939
4040 public:
41 static const QColor FillColour;
41 static const QColor FillColor;
4242
4343 public:
4444 /**
1616 * along with this program; if not, see <http://www.gnu.org/licenses/>.
1717 */
1818
19 #include <algorithm>
20 #include <cassert>
21
22 #include <QColor>
23 #include <QToolTip>
24
1925 #include "cursorpair.hpp"
2026
27 #include "pv/globalsettings.hpp"
2128 #include "pv/util.hpp"
2229 #include "ruler.hpp"
2330 #include "view.hpp"
24
25 #include <algorithm>
26 #include <cassert>
2731
2832 using std::max;
2933 using std::make_pair;
3640 namespace trace {
3741
3842 const int CursorPair::DeltaPadding = 8;
39 const QColor CursorPair::ViewportFillColour(220, 231, 243);
4043
4144 CursorPair::CursorPair(View &view) :
4245 TimeItem(view),
4346 first_(new Cursor(view, 0.0)),
4447 second_(new Cursor(view, 1.0))
4548 {
49 GlobalSettings::add_change_handler(this);
50
51 GlobalSettings settings;
52 fill_color_ = QColor::fromRgba(settings.value(
53 GlobalSettings::Key_View_CursorFillColor).value<uint32_t>());
54
55 connect(&view_, SIGNAL(hover_point_changed(const QWidget*, QPoint)),
56 this, SLOT(on_hover_point_changed(const QWidget*, QPoint)));
57 }
58
59 CursorPair::~CursorPair()
60 {
61 GlobalSettings::remove_change_handler(this);
4662 }
4763
4864 bool CursorPair::enabled() const
7288 return (first_->get_x() + second_->get_x()) / 2.0f;
7389 }
7490
75 QPoint CursorPair::point(const QRect &rect) const
76 {
77 return first_->point(rect);
91 QPoint CursorPair::drag_point(const QRect &rect) const
92 {
93 return first_->drag_point(rect);
7894 }
7995
8096 pv::widgets::Popup* CursorPair::create_popup(QWidget *parent)
109125 if (!enabled())
110126 return;
111127
112 const QColor text_colour =
113 ViewItem::select_text_colour(Cursor::FillColour);
114
115 p.setPen(text_colour);
116 compute_text_size(p);
128 const QColor text_color = ViewItem::select_text_color(Cursor::FillColor);
129 p.setPen(text_color);
130
131 QString text = format_string();
132 text_size_ = p.boundingRect(QRectF(), 0, text).size();
133
117134 QRectF delta_rect(label_rect(rect));
118
119135 const int radius = delta_rect.height() / 2;
120 const QRectF text_rect(delta_rect.intersected(
121 rect).adjusted(radius, 0, -radius, 0));
122 if (text_rect.width() >= text_size_.width()) {
123 const int highlight_radius = delta_rect.height() / 2 - 2;
124
125 if (selected()) {
126 p.setBrush(Qt::transparent);
127 p.setPen(highlight_pen());
128 p.drawRoundedRect(delta_rect, radius, radius);
129 }
130
131 p.setBrush(hover ? Cursor::FillColour.lighter() :
132 Cursor::FillColour);
133 p.setPen(Cursor::FillColour.darker());
136 QRectF text_rect(delta_rect.intersected(rect).adjusted(radius, 0, -radius, 0));
137
138 if (text_rect.width() < text_size_.width()) {
139 text = "...";
140 text_size_ = p.boundingRect(QRectF(), 0, text).size();
141 label_incomplete_ = true;
142 } else
143 label_incomplete_ = false;
144
145 if (selected()) {
146 p.setBrush(Qt::transparent);
147 p.setPen(highlight_pen());
134148 p.drawRoundedRect(delta_rect, radius, radius);
135
136 delta_rect.adjust(1, 1, -1, -1);
137 p.setPen(Cursor::FillColour.lighter());
138 p.drawRoundedRect(delta_rect, highlight_radius, highlight_radius);
139
140 p.setPen(text_colour);
141 p.drawText(text_rect, Qt::AlignCenter | Qt::AlignVCenter,
142 format_string());
143149 }
150
151 p.setBrush(hover ? Cursor::FillColor.lighter() : Cursor::FillColor);
152 p.setPen(Cursor::FillColor.darker());
153 p.drawRoundedRect(delta_rect, radius, radius);
154
155 delta_rect.adjust(1, 1, -1, -1);
156 p.setPen(Cursor::FillColor.lighter());
157 const int highlight_radius = delta_rect.height() / 2 - 2;
158 p.drawRoundedRect(delta_rect, highlight_radius, highlight_radius);
159 label_area_ = delta_rect;
160
161 p.setPen(text_color);
162 p.drawText(text_rect, Qt::AlignCenter | Qt::AlignVCenter, text);
144163 }
145164
146165 void CursorPair::paint_back(QPainter &p, ViewItemPaintParams &pp)
149168 return;
150169
151170 p.setPen(Qt::NoPen);
152 p.setBrush(QBrush(ViewportFillColour));
171 p.setBrush(fill_color_);
153172
154173 const pair<float, float> offsets(get_cursor_offsets());
155 const int l = (int)max(min(
156 offsets.first, offsets.second), 0.0f);
157 const int r = (int)min(max(
158 offsets.first, offsets.second), (float)pp.width());
174 const int l = (int)max(min(offsets.first, offsets.second), 0.0f);
175 const int r = (int)min(max(offsets.first, offsets.second), (float)pp.width());
159176
160177 p.drawRect(l, pp.top(), r - l, pp.height());
161178 }
166183 const pv::util::Timestamp diff = abs(second_->time() - first_->time());
167184
168185 const QString s1 = Ruler::format_time_with_distance(
169 diff, diff, prefix, view_.time_unit(), view_.tick_precision(), false);
186 diff, diff, prefix, view_.time_unit(), 12, false); /* Always use 12 precision digits */
170187 const QString s2 = util::format_time_si(
171188 1 / diff, pv::util::SIPrefix::unspecified, 4, "Hz", false);
172189
173 return QString("%1 / %2").arg(s1).arg(s2);
174 }
175
176 void CursorPair::compute_text_size(QPainter &p)
190 return QString("%1 / %2").arg(s1, s2);
191 }
192
193 pair<float, float> CursorPair::get_cursor_offsets() const
177194 {
178195 assert(first_);
179196 assert(second_);
180197
181 text_size_ = p.boundingRect(QRectF(), 0, format_string()).size();
182 }
183
184 pair<float, float> CursorPair::get_cursor_offsets() const
185 {
186 assert(first_);
187 assert(second_);
188
189198 return pair<float, float>(first_->get_x(), second_->get_x());
199 }
200
201 void CursorPair::on_setting_changed(const QString &key, const QVariant &value)
202 {
203 if (key == GlobalSettings::Key_View_CursorFillColor)
204 fill_color_ = QColor::fromRgba(value.value<uint32_t>());
205 }
206
207 void CursorPair::on_hover_point_changed(const QWidget* widget, const QPoint& hp)
208 {
209 if (widget != view_.ruler())
210 return;
211
212 if (!label_incomplete_)
213 return;
214
215 if (label_area_.contains(hp))
216 QToolTip::showText(view_.mapToGlobal(hp), format_string());
217 else
218 QToolTip::hideText(); // TODO Will break other tooltips when there can be others
190219 }
191220
192221 } // namespace trace
2020 #define PULSEVIEW_PV_VIEWS_TRACEVIEW_CURSORPAIR_HPP
2121
2222 #include "cursor.hpp"
23 #include "pv/globalsettings.hpp"
2324
2425 #include <memory>
2526
27 #include <QColor>
2628 #include <QPainter>
29 #include <QRect>
2730
2831 using std::pair;
2932 using std::shared_ptr;
3437 namespace views {
3538 namespace trace {
3639
37 class CursorPair : public TimeItem
40 class View;
41
42 class CursorPair : public TimeItem, public GlobalSettingsInterface
3843 {
44 Q_OBJECT
45
3946 private:
4047 static const int DeltaPadding;
41 static const QColor ViewportFillColour;
4248
4349 public:
4450 /**
4753 */
4854 CursorPair(View &view);
4955
50 public:
56 ~CursorPair();
57
5158 /**
5259 * Returns true if the item is visible and enabled.
5360 */
7077
7178 float get_x() const override;
7279
73 QPoint point(const QRect &rect) const override;
80 QPoint drag_point(const QRect &rect) const override;
7481
7582 pv::widgets::Popup* create_popup(QWidget *parent) override;
7683
77 public:
7884 QRectF label_rect(const QRectF &rect) const override;
7985
8086 /**
97103 */
98104 QString format_string();
99105
100 void compute_text_size(QPainter &p);
106 pair<float, float> get_cursor_offsets() const;
101107
102 pair<float, float> get_cursor_offsets() const;
108 virtual void on_setting_changed(const QString &key, const QVariant &value) override;
109
110 public Q_SLOTS:
111 void on_hover_point_changed(const QWidget* widget, const QPoint &hp);
103112
104113 private:
105114 shared_ptr<Cursor> first_, second_;
115 QColor fill_color_;
106116
107117 QSizeF text_size_;
118 QRectF label_area_;
119 bool label_incomplete_;
108120 };
109121
110122 } // namespace trace
2020 #include <libsigrokdecode/libsigrokdecode.h>
2121 }
2222
23 #include <limits>
2324 #include <mutex>
25 #include <tuple>
2426
2527 #include <extdef.h>
26
27 #include <tuple>
2828
2929 #include <boost/functional/hash.hpp>
3030
3131 #include <QAction>
3232 #include <QApplication>
3333 #include <QComboBox>
34 #include <QFileDialog>
3435 #include <QFormLayout>
3536 #include <QLabel>
3637 #include <QMenu>
38 #include <QMessageBox>
3739 #include <QPushButton>
40 #include <QTextStream>
3841 #include <QToolTip>
3942
4043 #include "decodetrace.hpp"
4245 #include "viewport.hpp"
4346
4447 #include <pv/globalsettings.hpp>
48 #include <pv/session.hpp>
49 #include <pv/strnatcmp.hpp>
50 #include <pv/data/decodesignal.hpp>
4551 #include <pv/data/decode/annotation.hpp>
4652 #include <pv/data/decode/decoder.hpp>
47 #include <pv/data/decoderstack.hpp>
4853 #include <pv/data/logic.hpp>
4954 #include <pv/data/logicsegment.hpp>
50 #include <pv/session.hpp>
51 #include <pv/strnatcmp.hpp>
5255 #include <pv/widgets/decodergroupbox.hpp>
5356 #include <pv/widgets/decodermenu.hpp>
5457
55 using std::all_of;
56 using std::list;
58 using std::abs;
5759 using std::make_pair;
5860 using std::max;
59 using std::make_pair;
60 using std::map;
6161 using std::min;
62 using std::numeric_limits;
6263 using std::out_of_range;
6364 using std::pair;
6465 using std::shared_ptr;
65 using std::make_shared;
6666 using std::tie;
67 using std::unordered_set;
6867 using std::vector;
68
69 using pv::data::decode::Annotation;
70 using pv::data::decode::Row;
71 using pv::data::DecodeChannel;
72 using pv::data::DecodeSignal;
6973
7074 namespace pv {
7175 namespace views {
7276 namespace trace {
7377
74 const QColor DecodeTrace::DecodeColours[4] = {
75 QColor(0xEF, 0x29, 0x29), // Red
76 QColor(0xFC, 0xE9, 0x4F), // Yellow
77 QColor(0x8A, 0xE2, 0x34), // Green
78 QColor(0x72, 0x9F, 0xCF) // Blue
79 };
80
81 const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
82 const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
78
79 #define DECODETRACE_COLOR_SATURATION (180) /* 0-255 */
80 #define DECODETRACE_COLOR_VALUE (170) /* 0-255 */
81
82 const QColor DecodeTrace::ErrorBgColor = QColor(0xEF, 0x29, 0x29);
83 const QColor DecodeTrace::NoDecodeColor = QColor(0x88, 0x8A, 0x85);
8384
8485 const int DecodeTrace::ArrowSize = 4;
8586 const double DecodeTrace::EndCapWidth = 5;
8687 const int DecodeTrace::RowTitleMargin = 10;
8788 const int DecodeTrace::DrawPadding = 100;
8889
89 const QColor DecodeTrace::Colours[16] = {
90 QColor(0xEF, 0x29, 0x29),
91 QColor(0xF6, 0x6A, 0x32),
92 QColor(0xFC, 0xAE, 0x3E),
93 QColor(0xFB, 0xCA, 0x47),
94 QColor(0xFC, 0xE9, 0x4F),
95 QColor(0xCD, 0xF0, 0x40),
96 QColor(0x8A, 0xE2, 0x34),
97 QColor(0x4E, 0xDC, 0x44),
98 QColor(0x55, 0xD7, 0x95),
99 QColor(0x64, 0xD1, 0xD2),
100 QColor(0x72, 0x9F, 0xCF),
101 QColor(0xD4, 0x76, 0xC4),
102 QColor(0x9D, 0x79, 0xB9),
103 QColor(0xAD, 0x7F, 0xA8),
104 QColor(0xC2, 0x62, 0x9B),
105 QColor(0xD7, 0x47, 0x6F)
106 };
107
108 const QColor DecodeTrace::OutlineColours[16] = {
109 QColor(0x77, 0x14, 0x14),
110 QColor(0x7B, 0x35, 0x19),
111 QColor(0x7E, 0x57, 0x1F),
112 QColor(0x7D, 0x65, 0x23),
113 QColor(0x7E, 0x74, 0x27),
114 QColor(0x66, 0x78, 0x20),
115 QColor(0x45, 0x71, 0x1A),
116 QColor(0x27, 0x6E, 0x22),
117 QColor(0x2A, 0x6B, 0x4A),
118 QColor(0x32, 0x68, 0x69),
119 QColor(0x39, 0x4F, 0x67),
120 QColor(0x6A, 0x3B, 0x62),
121 QColor(0x4E, 0x3C, 0x5C),
122 QColor(0x56, 0x3F, 0x54),
123 QColor(0x61, 0x31, 0x4D),
124 QColor(0x6B, 0x23, 0x37)
125 };
90 const int DecodeTrace::MaxTraceUpdateRate = 1; // No more than 1 Hz
12691
12792 DecodeTrace::DecodeTrace(pv::Session &session,
12893 shared_ptr<data::SignalBase> signalbase, int index) :
13398 delete_mapper_(this),
13499 show_hide_mapper_(this)
135100 {
136 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
101 decode_signal_ = dynamic_pointer_cast<data::DecodeSignal>(base_);
137102
138103 // Determine shortest string we want to see displayed in full
139104 QFontMetrics m(QApplication::font());
140105 min_useful_label_width_ = m.width("XX"); // e.g. two hex characters
141106
142 base_->set_name(QString::fromUtf8(decoder_stack->stack().front()->decoder()->name));
143 base_->set_colour(DecodeColours[index % countof(DecodeColours)]);
144
145 connect(decoder_stack.get(), SIGNAL(new_decode_data()),
146 this, SLOT(on_new_decode_data()));
107 // For the base color, we want to start at a very different color for
108 // every decoder stack, so multiply the index with a number that is
109 // rather close to 180 degrees of the color circle but not a dividend of 360
110 // Note: The offset equals the color of the first annotation
111 QColor color;
112 const int h = (120 + 160 * index) % 360;
113 const int s = DECODETRACE_COLOR_SATURATION;
114 const int v = DECODETRACE_COLOR_VALUE;
115 color.setHsv(h, s, v);
116 base_->set_color(color);
117
118 connect(decode_signal_.get(), SIGNAL(new_annotations()),
119 this, SLOT(on_new_annotations()));
120 connect(decode_signal_.get(), SIGNAL(decode_reset()),
121 this, SLOT(on_decode_reset()));
122 connect(decode_signal_.get(), SIGNAL(decode_finished()),
123 this, SLOT(on_decode_finished()));
124 connect(decode_signal_.get(), SIGNAL(channels_updated()),
125 this, SLOT(on_channels_updated()));
126
147127 connect(&delete_mapper_, SIGNAL(mapped(int)),
148128 this, SLOT(on_delete_decoder(int)));
149129 connect(&show_hide_mapper_, SIGNAL(mapped(int)),
150130 this, SLOT(on_show_hide_decoder(int)));
131
132 connect(&delayed_trace_updater_, SIGNAL(timeout()),
133 this, SLOT(on_delayed_trace_update()));
134 delayed_trace_updater_.setSingleShot(true);
135 delayed_trace_updater_.setInterval(1000 / MaxTraceUpdateRate);
151136 }
152137
153138 bool DecodeTrace::enabled() const
178163
179164 void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp)
180165 {
181 using namespace pv::data::decode;
182
183 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
184
185166 const int text_height = ViewItemPaintParams::text_height();
186167 row_height_ = (text_height * 6) / 4;
187168 const int annotation_height = (text_height * 5) / 4;
188169
189 assert(decoder_stack);
190 const QString err = decoder_stack->error_message();
191 if (!err.isEmpty()) {
192 draw_unresolved_period(
193 p, annotation_height, pp.left(), pp.right());
194 draw_error(p, err, pp);
195 return;
196 }
197
198170 // Set default pen to allow for text width calculation
199171 p.setPen(Qt::black);
200172
201173 // Iterate through the rows
202174 int y = get_visual_y();
203 pair<uint64_t, uint64_t> sample_range = get_sample_range(
204 pp.left(), pp.right());
205
206 const vector<Row> rows(decoder_stack->get_visible_rows());
175 pair<uint64_t, uint64_t> sample_range = get_view_sample_range(pp.left(), pp.right());
176
177 // Just because the view says we see a certain sample range it
178 // doesn't mean we have this many decoded samples, too, so crop
179 // the range to what has been decoded already
180 sample_range.second = min((int64_t)sample_range.second,
181 decode_signal_->get_decoded_sample_count(current_segment_, false));
182
183 const vector<Row> rows = decode_signal_->visible_rows();
207184
208185 visible_rows_.clear();
209186 for (const Row& row : rows) {
211188 int row_title_width;
212189 try {
213190 row_title_width = row_title_widths_.at(row);
214 } catch (out_of_range) {
191 } catch (out_of_range&) {
215192 const int w = p.boundingRect(QRectF(), 0, row.title()).width() +
216193 RowTitleMargin;
217194 row_title_widths_[row] = w;
218195 row_title_width = w;
219196 }
220197
221 // Determine the row's color
222 size_t base_colour = 0x13579BDF;
223 boost::hash_combine(base_colour, this);
224 boost::hash_combine(base_colour, row.decoder());
225 boost::hash_combine(base_colour, row.row());
226 base_colour >>= 16;
227
228198 vector<Annotation> annotations;
229 decoder_stack->get_annotation_subset(annotations, row,
230 sample_range.first, sample_range.second);
199 decode_signal_->get_annotation_subset(annotations, row,
200 current_segment_, sample_range.first, sample_range.second);
231201 if (!annotations.empty()) {
232202 draw_annotations(annotations, p, annotation_height, pp, y,
233 base_colour, row_title_width);
234
203 get_row_color(row.index()), row_title_width);
235204 y += row_height_;
236
237205 visible_rows_.push_back(row);
238206 }
239207 }
240208
241 // Draw the hatching
242209 draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
243210
244 if ((int)visible_rows_.size() > max_visible_rows_)
211 if ((int)visible_rows_.size() > max_visible_rows_) {
212 max_visible_rows_ = (int)visible_rows_.size();
213
214 // Call order is important, otherwise the lazy event handler won't work
245215 owner_->extents_changed(false, true);
246
247 // Update the maximum row count if needed
248 max_visible_rows_ = max(max_visible_rows_, (int)visible_rows_.size());
216 owner_->row_item_appearance_changed(false, true);
217 }
218
219 const QString err = decode_signal_->error_message();
220 if (!err.isEmpty())
221 draw_error(p, err, pp);
249222 }
250223
251224 void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp)
252225 {
253 using namespace pv::data::decode;
254
255226 assert(row_height_);
256227
257228 for (size_t i = 0; i < visible_rows_.size(); i++) {
286257 p.setPen(QApplication::palette().color(QPalette::WindowText));
287258 p.drawText(r, f, h);
288259 }
260
261 if (show_hover_marker_)
262 paint_hover_marker(p);
289263 }
290264
291265 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
292266 {
293267 using pv::data::decode::Decoder;
294268
295 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
296
297269 assert(form);
298 assert(parent);
299 assert(decoder_stack);
300270
301271 // Add the standard options
302272 Trace::populate_popup_form(parent, form);
303273
304274 // Add the decoder options
305275 bindings_.clear();
306 channel_selectors_.clear();
276 channel_id_map_.clear();
277 init_state_map_.clear();
307278 decoder_forms_.clear();
308279
309 const list< shared_ptr<Decoder> >& stack = decoder_stack->stack();
280 const vector< shared_ptr<Decoder> > &stack = decode_signal_->decoder_stack();
310281
311282 if (stack.empty()) {
312283 QLabel *const l = new QLabel(
340311 form->addRow(stack_button_box);
341312 }
342313
343 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
344 {
345 QMenu *const menu = Trace::create_context_menu(parent);
314 QMenu* DecodeTrace::create_header_context_menu(QWidget *parent)
315 {
316 QMenu *const menu = Trace::create_header_context_menu(parent);
346317
347318 menu->addSeparator();
348319
354325 return menu;
355326 }
356327
328 QMenu* DecodeTrace::create_view_context_menu(QWidget *parent, QPoint &click_pos)
329 {
330 // Get entries from default menu before adding our own
331 QMenu *const menu = new QMenu(parent);
332
333 QMenu* default_menu = Trace::create_view_context_menu(parent, click_pos);
334 if (default_menu) {
335 for (QAction *action : default_menu->actions()) { // clazy:exclude=range-loop
336 menu->addAction(action);
337 if (action->parent() == default_menu)
338 action->setParent(menu);
339 }
340 delete default_menu;
341
342 // Add separator if needed
343 if (menu->actions().length() > 0)
344 menu->addSeparator();
345 }
346
347 try {
348 selected_row_ = &visible_rows_[get_row_at_point(click_pos)];
349 } catch (out_of_range&) {
350 selected_row_ = nullptr;
351 }
352
353 // Default sample range is "from here"
354 const pair<uint64_t, uint64_t> sample_range =
355 get_view_sample_range(click_pos.x(), click_pos.x() + 1);
356 selected_sample_range_ = make_pair(sample_range.first, numeric_limits<uint64_t>::max());
357
358 if (decode_signal_->is_paused()) {
359 QAction *const resume =
360 new QAction(tr("Resume decoding"), this);
361 resume->setIcon(QIcon::fromTheme("media-playback-start",
362 QIcon(":/icons/media-playback-start.png")));
363 connect(resume, SIGNAL(triggered()), this, SLOT(on_pause_decode()));
364 menu->addAction(resume);
365 } else {
366 QAction *const pause =
367 new QAction(tr("Pause decoding"), this);
368 pause->setIcon(QIcon::fromTheme("media-playback-pause",
369 QIcon(":/icons/media-playback-pause.png")));
370 connect(pause, SIGNAL(triggered()), this, SLOT(on_pause_decode()));
371 menu->addAction(pause);
372 }
373
374 menu->addSeparator();
375
376 QAction *const export_all_rows =
377 new QAction(tr("Export all annotations"), this);
378 export_all_rows->setIcon(QIcon::fromTheme("document-save-as",
379 QIcon(":/icons/document-save-as.png")));
380 connect(export_all_rows, SIGNAL(triggered()), this, SLOT(on_export_all_rows()));
381 menu->addAction(export_all_rows);
382
383 QAction *const export_row =
384 new QAction(tr("Export all annotations for this row"), this);
385 export_row->setIcon(QIcon::fromTheme("document-save-as",
386 QIcon(":/icons/document-save-as.png")));
387 connect(export_row, SIGNAL(triggered()), this, SLOT(on_export_row()));
388 menu->addAction(export_row);
389
390 menu->addSeparator();
391
392 QAction *const export_all_rows_from_here =
393 new QAction(tr("Export all annotations, starting here"), this);
394 export_all_rows_from_here->setIcon(QIcon::fromTheme("document-save-as",
395 QIcon(":/icons/document-save-as.png")));
396 connect(export_all_rows_from_here, SIGNAL(triggered()), this, SLOT(on_export_all_rows_from_here()));
397 menu->addAction(export_all_rows_from_here);
398
399 QAction *const export_row_from_here =
400 new QAction(tr("Export annotations for this row, starting here"), this);
401 export_row_from_here->setIcon(QIcon::fromTheme("document-save-as",
402 QIcon(":/icons/document-save-as.png")));
403 connect(export_row_from_here, SIGNAL(triggered()), this, SLOT(on_export_row_from_here()));
404 menu->addAction(export_row_from_here);
405
406 menu->addSeparator();
407
408 QAction *const export_all_rows_with_cursor =
409 new QAction(tr("Export all annotations within cursor range"), this);
410 export_all_rows_with_cursor->setIcon(QIcon::fromTheme("document-save-as",
411 QIcon(":/icons/document-save-as.png")));
412 connect(export_all_rows_with_cursor, SIGNAL(triggered()), this, SLOT(on_export_all_rows_with_cursor()));
413 menu->addAction(export_all_rows_with_cursor);
414
415 QAction *const export_row_with_cursor =
416 new QAction(tr("Export annotations for this row within cursor range"), this);
417 export_row_with_cursor->setIcon(QIcon::fromTheme("document-save-as",
418 QIcon(":/icons/document-save-as.png")));
419 connect(export_row_with_cursor, SIGNAL(triggered()), this, SLOT(on_export_row_with_cursor()));
420 menu->addAction(export_row_with_cursor);
421
422 const View *view = owner_->view();
423 assert(view);
424
425 if (!view->cursors()->enabled()) {
426 export_all_rows_with_cursor->setEnabled(false);
427 export_row_with_cursor->setEnabled(false);
428 }
429
430 return menu;
431 }
432
357433 void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
358434 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
359 size_t base_colour, int row_title_width)
435 QColor row_color, int row_title_width)
360436 {
361437 using namespace pv::data::decode;
362438
363 vector<Annotation> a_block;
364 int p_end = INT_MIN;
439 Annotation::Class block_class = 0;
440 bool block_class_uniform = true;
441 qreal block_start = 0;
442 int block_ann_count = 0;
443
444 const Annotation *prev_ann;
445 qreal prev_end = INT_MIN;
446
447 qreal a_end;
365448
366449 double samples_per_pixel, pixels_offset;
367450 tie(pixels_offset, samples_per_pixel) =
376459 // Gather all annotations that form a visual "block" and draw them as such
377460 for (const Annotation &a : annotations) {
378461
379 const int a_start = a.start_sample() / samples_per_pixel - pixels_offset;
380 const int a_end = a.end_sample() / samples_per_pixel - pixels_offset;
381 const int a_width = a_end - a_start;
382
383 const int delta = a_end - p_end;
462 const qreal abs_a_start = a.start_sample() / samples_per_pixel;
463 const qreal abs_a_end = a.end_sample() / samples_per_pixel;
464
465 const qreal a_start = abs_a_start - pixels_offset;
466 a_end = abs_a_end - pixels_offset;
467
468 const qreal a_width = a_end - a_start;
469 const qreal delta = a_end - prev_end;
384470
385471 bool a_is_separate = false;
386472
387473 // Annotation wider than the threshold for a useful label width?
388474 if (a_width >= min_useful_label_width_) {
389475 for (const QString &ann_text : a.annotations()) {
390 const int w = p.boundingRect(QRectF(), 0, ann_text).width();
476 const qreal w = p.boundingRect(QRectF(), 0, ann_text).width();
391477 // Annotation wide enough to fit a label? Don't put it in a block then
392478 if (w <= a_width) {
393479 a_is_separate = true;
399485 // Were the previous and this annotation more than a pixel apart?
400486 if ((abs(delta) > 1) || a_is_separate) {
401487 // Block was broken, draw annotations that form the current block
402 if (a_block.size() == 1) {
403 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
488 if (block_ann_count == 1)
489 draw_annotation(*prev_ann, p, h, pp, y, row_color,
404490 row_title_width);
405 }
406 else
407 draw_annotation_block(a_block, p, h, y, base_colour);
408
409 a_block.clear();
491 else if (block_ann_count > 0)
492 draw_annotation_block(block_start, prev_end, block_class,
493 block_class_uniform, p, h, y, row_color);
494
495 block_ann_count = 0;
410496 }
411497
412498 if (a_is_separate) {
413 draw_annotation(a, p, h, pp, y, base_colour, row_title_width);
499 draw_annotation(a, p, h, pp, y, row_color, row_title_width);
414500 // Next annotation must start a new block. delta will be > 1
415 // because we set p_end to INT_MIN but that's okay since
416 // a_block will be empty, so nothing will be drawn
417 p_end = INT_MIN;
501 // because we set prev_end to INT_MIN but that's okay since
502 // block_ann_count will be 0 and nothing will be drawn
503 prev_end = INT_MIN;
504 block_ann_count = 0;
418505 } else {
419 a_block.push_back(a);
420 p_end = a_end;
506 prev_end = a_end;
507 prev_ann = &a;
508
509 if (block_ann_count == 0) {
510 block_start = a_start;
511 block_class = a.ann_class();
512 block_class_uniform = true;
513 } else
514 if (a.ann_class() != block_class)
515 block_class_uniform = false;
516
517 block_ann_count++;
421518 }
422519 }
423520
424 if (a_block.size() == 1)
425 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
426 row_title_width);
427 else
428 draw_annotation_block(a_block, p, h, y, base_colour);
521 if (block_ann_count == 1)
522 draw_annotation(*prev_ann, p, h, pp, y, row_color, row_title_width);
523 else if (block_ann_count > 0)
524 draw_annotation_block(block_start, prev_end, block_class,
525 block_class_uniform, p, h, y, row_color);
429526 }
430527
431528 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
432529 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
433 size_t base_colour, int row_title_width) const
530 QColor row_color, int row_title_width) const
434531 {
435532 double samples_per_pixel, pixels_offset;
436533 tie(pixels_offset, samples_per_pixel) =
440537 pixels_offset;
441538 const double end = a.end_sample() / samples_per_pixel - pixels_offset;
442539
443 const size_t colour = (base_colour + a.format()) % countof(Colours);
444 p.setPen(OutlineColours[colour]);
445 p.setBrush(Colours[colour]);
540 QColor color = get_annotation_color(row_color, a.ann_class());
541 p.setPen(color.darker());
542 p.setBrush(color);
446543
447544 if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
448545 return;
453550 draw_range(a, p, h, start, end, y, pp, row_title_width);
454551 }
455552
456 void DecodeTrace::draw_annotation_block(
457 vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
458 int y, size_t base_colour) const
459 {
460 using namespace pv::data::decode;
461
462 if (annotations.empty())
463 return;
464
465 double samples_per_pixel, pixels_offset;
466 tie(pixels_offset, samples_per_pixel) =
467 get_pixels_offset_samples_per_pixel();
468
469 const double start = annotations.front().start_sample() /
470 samples_per_pixel - pixels_offset;
471 const double end = annotations.back().end_sample() /
472 samples_per_pixel - pixels_offset;
473
553 void DecodeTrace::draw_annotation_block(qreal start, qreal end,
554 Annotation::Class ann_class, bool use_ann_format, QPainter &p, int h,
555 int y, QColor row_color) const
556 {
474557 const double top = y + .5 - h / 2;
475558 const double bottom = y + .5 + h / 2;
476
477 const size_t colour = (base_colour + annotations.front().format()) %
478 countof(Colours);
479
480 // Check if all annotations are of the same type (i.e. we can use one color)
481 // or if we should use a neutral color (i.e. gray)
482 const int format = annotations.front().format();
483 const bool single_format = all_of(
484 annotations.begin(), annotations.end(),
485 [&](const Annotation &a) { return a.format() == format; });
486559
487560 const QRectF rect(start, top, end - start, bottom - top);
488561 const int r = h / 4;
491564 p.setBrush(Qt::white);
492565 p.drawRoundedRect(rect, r, r);
493566
494 p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
495 p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
496 Qt::Dense4Pattern));
567 // If all annotations in this block are of the same type, we can use the
568 // one format that all of these annotations have. Otherwise, we should use
569 // a neutral color (i.e. gray)
570 if (use_ann_format) {
571 const QColor color = get_annotation_color(row_color, ann_class);
572 p.setPen(color.darker());
573 p.setBrush(QBrush(color, Qt::Dense4Pattern));
574 } else {
575 p.setPen(Qt::gray);
576 p.setBrush(QBrush(Qt::gray, Qt::Dense4Pattern));
577 }
578
497579 p.drawRoundedRect(rect, r, r);
498580 }
499581
500582 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
501 int h, double x, int y) const
583 int h, qreal x, int y) const
502584 {
503585 const QString text = a.annotations().empty() ?
504586 QString() : a.annotations().back();
505 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
587 const qreal w = min((qreal)p.boundingRect(QRectF(), 0, text).width(),
506588 0.0) + h;
507589 const QRectF rect(x - w / 2, y - h / 2, w, h);
508590
513595 }
514596
515597 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
516 int h, double start, double end, int y, const ViewItemPaintParams &pp,
598 int h, qreal start, qreal end, int y, const ViewItemPaintParams &pp,
517599 int row_title_width) const
518600 {
519 const double top = y + .5 - h / 2;
520 const double bottom = y + .5 + h / 2;
601 const qreal top = y + .5 - h / 2;
602 const qreal bottom = y + .5 + h / 2;
521603 const vector<QString> annotations = a.annotations();
522604
523605 // If the two ends are within 1 pixel, draw a vertical line
526608 return;
527609 }
528610
529 const double cap_width = min((end - start) / 4, EndCapWidth);
611 const qreal cap_width = min((end - start) / 4, EndCapWidth);
530612
531613 QPointF pts[] = {
532614 QPointF(start, y + .5f),
578660 {
579661 const int y = get_visual_y();
580662
581 p.setPen(ErrorBgColour.darker());
582 p.setBrush(ErrorBgColour);
583
584 const QRectF bounding_rect =
585 QRectF(pp.width(), INT_MIN / 2 + y, pp.width(), INT_MAX);
586 const QRectF text_rect = p.boundingRect(bounding_rect,
587 Qt::AlignCenter, message);
588 const float r = text_rect.height() / 4;
589
590 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
591 Qt::AbsoluteSize);
663 double samples_per_pixel, pixels_offset;
664 tie(pixels_offset, samples_per_pixel) = get_pixels_offset_samples_per_pixel();
665
666 p.setPen(ErrorBgColor.darker());
667 p.setBrush(ErrorBgColor);
668
669 const QRectF bounding_rect = QRectF(pp.left(), INT_MIN / 2 + y, pp.right(), INT_MAX);
670
671 const QRectF text_rect = p.boundingRect(bounding_rect, Qt::AlignCenter, message);
672 const qreal r = text_rect.height() / 4;
673
674 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r, Qt::AbsoluteSize);
592675
593676 p.setPen(Qt::black);
594677 p.drawText(text_rect, message);
595678 }
596679
597 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
598 int right) const
680 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left, int right) const
599681 {
600682 using namespace pv::data;
601683 using pv::data::decode::Decoder;
602684
603685 double samples_per_pixel, pixels_offset;
604686
605 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
606
607 assert(decoder_stack);
608
609 shared_ptr<Logic> data;
610 shared_ptr<data::SignalBase> signalbase;
611
612 const list< shared_ptr<Decoder> > &stack = decoder_stack->stack();
613
614 // We get the logic data of the first channel in the list.
615 // This works because we are currently assuming all
616 // LogicSignals have the same data/segment
617 for (const shared_ptr<Decoder> &dec : stack)
618 if (dec && !dec->channels().empty() &&
619 ((signalbase = (*dec->channels().begin()).second)) &&
620 ((data = signalbase->logic_data())))
621 break;
622
623 if (!data || data->logic_segments().empty())
624 return;
625
626 const shared_ptr<LogicSegment> segment = data->logic_segments().front();
627 assert(segment);
628 const int64_t sample_count = (int64_t)segment->get_sample_count();
687 const int64_t sample_count = decode_signal_->get_working_sample_count(current_segment_);
629688 if (sample_count == 0)
630689 return;
631690
632 const int64_t samples_decoded = decoder_stack->samples_decoded();
691 const int64_t samples_decoded = decode_signal_->get_decoded_sample_count(current_segment_, true);
633692 if (sample_count == samples_decoded)
634693 return;
635694
636695 const int y = get_visual_y();
637696
638 tie(pixels_offset, samples_per_pixel) =
639 get_pixels_offset_samples_per_pixel();
697 tie(pixels_offset, samples_per_pixel) = get_pixels_offset_samples_per_pixel();
640698
641699 const double start = max(samples_decoded /
642700 samples_per_pixel - pixels_offset, left - 1.0);
643701 const double end = min(sample_count / samples_per_pixel -
644702 pixels_offset, right + 1.0);
645 const QRectF no_decode_rect(start, y - (h / 2) + 0.5, end - start, h);
703 const QRectF no_decode_rect(start, y - (h / 2) - 0.5, end - start, h);
646704
647705 p.setPen(QPen(Qt::NoPen));
648706 p.setBrush(Qt::white);
649707 p.drawRect(no_decode_rect);
650708
651 p.setPen(NoDecodeColour);
652 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
709 p.setPen(NoDecodeColor);
710 p.setBrush(QBrush(NoDecodeColor, Qt::Dense6Pattern));
653711 p.drawRect(no_decode_rect);
654712 }
655713
656714 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
657715 {
658 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
659
660716 assert(owner_);
661 assert(decoder_stack);
662717
663718 const View *view = owner_->view();
664719 assert(view);
667722 assert(scale > 0);
668723
669724 const double pixels_offset =
670 ((view->offset() - decoder_stack->start_time()) / scale).convert_to<double>();
671
672 double samplerate = decoder_stack->samplerate();
725 ((view->offset() - decode_signal_->start_time()) / scale).convert_to<double>();
726
727 double samplerate = decode_signal_->samplerate();
673728
674729 // Show sample rate as 1Hz when it is unknown
675730 if (samplerate == 0.0)
678733 return make_pair(pixels_offset, samplerate * scale);
679734 }
680735
681 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
736 pair<uint64_t, uint64_t> DecodeTrace::get_view_sample_range(
682737 int x_start, int x_end) const
683738 {
684739 double samples_per_pixel, pixels_offset;
693748 return make_pair(start, end);
694749 }
695750
751 QColor DecodeTrace::get_row_color(int row_index) const
752 {
753 // For each row color, use the base color hue and add an offset that's
754 // not a dividend of 360
755
756 QColor color;
757 const int h = (base_->color().toHsv().hue() + 20 * row_index) % 360;
758 const int s = DECODETRACE_COLOR_SATURATION;
759 const int v = DECODETRACE_COLOR_VALUE;
760 color.setHsl(h, s, v);
761
762 return color;
763 }
764
765 QColor DecodeTrace::get_annotation_color(QColor row_color, int annotation_index) const
766 {
767 // For each row color, use the base color hue and add an offset that's
768 // not a dividend of 360 and not a multiple of the row offset
769
770 QColor color(row_color);
771 const int h = (color.toHsv().hue() + 55 * annotation_index) % 360;
772 const int s = DECODETRACE_COLOR_SATURATION;
773 const int v = DECODETRACE_COLOR_VALUE;
774 color.setHsl(h, s, v);
775
776 return color;
777 }
778
696779 int DecodeTrace::get_row_at_point(const QPoint &point)
697780 {
698781 if (!row_height_)
720803 return QString();
721804
722805 const pair<uint64_t, uint64_t> sample_range =
723 get_sample_range(point.x(), point.x() + 1);
806 get_view_sample_range(point.x(), point.x() + 1);
724807 const int row = get_row_at_point(point);
725808 if (row < 0)
726809 return QString();
727810
728 vector<pv::data::decode::Annotation> annotations;
729
730 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
731
732 assert(decoder_stack);
733 decoder_stack->get_annotation_subset(annotations, visible_rows_[row],
734 sample_range.first, sample_range.second);
811 vector<Annotation> annotations;
812
813 decode_signal_->get_annotation_subset(annotations, visible_rows_[row],
814 current_segment_, sample_range.first, sample_range.second);
735815
736816 return (annotations.empty()) ?
737817 QString() : annotations[0].annotations().front();
738818 }
739819
740 void DecodeTrace::hover_point_changed()
741 {
820 void DecodeTrace::hover_point_changed(const QPoint &hp)
821 {
822 Trace::hover_point_changed(hp);
823
742824 assert(owner_);
743825
744826 const View *const view = owner_->view();
745827 assert(view);
746828
747 QPoint hp = view->hover_point();
829 if (hp.x() == 0) {
830 QToolTip::hideText();
831 return;
832 }
833
748834 QString ann = get_annotation_at_point(hp);
749835
750836 assert(view);
767853 // If it did, the tool tip would constantly hide and re-appear.
768854 // We also push it up by one row so that it appears above the
769855 // decode trace, not below.
770 hp.setX(hp.x() - (text_size.width() / 2) - padding);
771
772 hp.setY(get_visual_y() - (row_height_ / 2) +
856 QPoint p = hp;
857 p.setX(hp.x() - (text_size.width() / 2) - padding);
858
859 p.setY(get_visual_y() - (row_height_ / 2) +
773860 (hover_row * row_height_) -
774861 row_height_ - text_size.height() - padding);
775862
776 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
863 QToolTip::showText(view->viewport()->mapToGlobal(p), ann);
777864 }
778865
779866 void DecodeTrace::create_decoder_form(int index,
780867 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
781868 QFormLayout *form)
782869 {
783 const GSList *l;
784870 GlobalSettings settings;
785871
786872 assert(dec);
809895 QFormLayout *const decoder_form = new QFormLayout;
810896 group->add_layout(decoder_form);
811897
812 // Add the mandatory channels
813 for (l = decoder->channels; l; l = l->next) {
814 const struct srd_channel *const pdch =
815 (struct srd_channel *)l->data;
816
817 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
818 QComboBox *const combo_initial_pin = create_channel_selector_initial_pin(parent, dec, pdch);
898 const vector<DecodeChannel> channels = decode_signal_->get_channels();
899
900 // Add the channels
901 for (const DecodeChannel& ch : channels) {
902 // Ignore channels not part of the decoder we create the form for
903 if (ch.decoder_ != dec)
904 continue;
905
906 QComboBox *const combo = create_channel_selector(parent, &ch);
907 QComboBox *const combo_init_state = create_channel_selector_init_state(parent, &ch);
908
909 channel_id_map_[combo] = ch.id;
910 init_state_map_[combo_init_state] = ch.id;
819911
820912 connect(combo, SIGNAL(currentIndexChanged(int)),
821913 this, SLOT(on_channel_selected(int)));
822 connect(combo_initial_pin, SIGNAL(currentIndexChanged(int)),
823 this, SLOT(on_initial_pin_selected(int)));
914 connect(combo_init_state, SIGNAL(currentIndexChanged(int)),
915 this, SLOT(on_init_state_changed(int)));
824916
825917 QHBoxLayout *const hlayout = new QHBoxLayout;
826918 hlayout->addWidget(combo);
827 hlayout->addWidget(combo_initial_pin);
919 hlayout->addWidget(combo_init_state);
828920
829921 if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool())
830 combo_initial_pin->hide();
831
832 decoder_form->addRow(tr("<b>%1</b> (%2) *")
833 .arg(QString::fromUtf8(pdch->name),
834 QString::fromUtf8(pdch->desc)), hlayout);
835
836 const ChannelSelector s = {combo, combo_initial_pin, dec, pdch};
837 channel_selectors_.push_back(s);
838 }
839
840 // Add the optional channels
841 for (l = decoder->opt_channels; l; l = l->next) {
842 const struct srd_channel *const pdch =
843 (struct srd_channel *)l->data;
844
845 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
846 QComboBox *const combo_initial_pin = create_channel_selector_initial_pin(parent, dec, pdch);
847
848 connect(combo, SIGNAL(currentIndexChanged(int)),
849 this, SLOT(on_channel_selected(int)));
850 connect(combo_initial_pin, SIGNAL(currentIndexChanged(int)),
851 this, SLOT(on_initial_pin_selected(int)));
852
853 QHBoxLayout *const hlayout = new QHBoxLayout;
854 hlayout->addWidget(combo);
855 hlayout->addWidget(combo_initial_pin);
856
857 if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool())
858 combo_initial_pin->hide();
859
860 decoder_form->addRow(tr("<b>%1</b> (%2)")
861 .arg(QString::fromUtf8(pdch->name),
862 QString::fromUtf8(pdch->desc)), hlayout);
863
864 const ChannelSelector s = {combo, combo_initial_pin, dec, pdch};
865 channel_selectors_.push_back(s);
866 }
867
868 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
922 combo_init_state->hide();
923
924 const QString required_flag = ch.is_optional ? QString() : QString("*");
925 decoder_form->addRow(tr("<b>%1</b> (%2) %3")
926 .arg(ch.name, ch.desc, required_flag), hlayout);
927 }
869928
870929 // Add the options
871930 shared_ptr<binding::Decoder> binding(
872 new binding::Decoder(decoder_stack, dec));
931 new binding::Decoder(decode_signal_, dec));
873932 binding->add_properties_to_form(decoder_form, true);
874933
875934 bindings_.push_back(binding);
878937 decoder_forms_.push_back(group);
879938 }
880939
881 QComboBox* DecodeTrace::create_channel_selector(
882 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
883 const srd_channel *const pdch)
884 {
885 assert(dec);
886
940 QComboBox* DecodeTrace::create_channel_selector(QWidget *parent, const DecodeChannel *ch)
941 {
887942 const auto sigs(session_.signalbases());
888943
944 // Sort signals in natural order
889945 vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
890946 sort(sig_list.begin(), sig_list.end(),
891947 [](const shared_ptr<data::SignalBase> &a,
893949 return strnatcasecmp(a->name().toStdString(),
894950 b->name().toStdString()) < 0; });
895951
896 const auto channel_iter = dec->channels().find(pdch);
897
898952 QComboBox *selector = new QComboBox(parent);
899953
900954 selector->addItem("-", qVariantFromValue((void*)nullptr));
901955
902 if (channel_iter == dec->channels().end())
956 if (!ch->assigned_signal)
903957 selector->setCurrentIndex(0);
904958
905959 for (const shared_ptr<data::SignalBase> &b : sig_list) {
908962 selector->addItem(b->name(),
909963 qVariantFromValue((void*)b.get()));
910964
911 if (channel_iter != dec->channels().end() &&
912 (*channel_iter).second == b)
913 selector->setCurrentIndex(
914 selector->count() - 1);
965 if (ch->assigned_signal == b.get())
966 selector->setCurrentIndex(selector->count() - 1);
915967 }
916968 }
917969
918970 return selector;
919971 }
920972
921 QComboBox* DecodeTrace::create_channel_selector_initial_pin(QWidget *parent,
922 const shared_ptr<data::decode::Decoder> &dec, const srd_channel *const pdch)
973 QComboBox* DecodeTrace::create_channel_selector_init_state(QWidget *parent,
974 const DecodeChannel *ch)
923975 {
924976 QComboBox *selector = new QComboBox(parent);
925977
927979 selector->addItem("1", qVariantFromValue((int)SRD_INITIAL_PIN_HIGH));
928980 selector->addItem("X", qVariantFromValue((int)SRD_INITIAL_PIN_SAME_AS_SAMPLE0));
929981
930 // Default to index 2 (SRD_INITIAL_PIN_SAME_AS_SAMPLE0).
931 const int idx = (!dec->initial_pins()) ? 2 : dec->initial_pins()->data[pdch->order];
932 selector->setCurrentIndex(idx);
982 selector->setCurrentIndex(ch->initial_pin_state);
933983
934984 selector->setToolTip("Initial (assumed) pin value before the first sample");
935985
936986 return selector;
937987 }
938988
939 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
940 {
941 assert(dec);
942
943 map<const srd_channel*, shared_ptr<data::SignalBase> > channel_map;
944
945 const unordered_set< shared_ptr<data::SignalBase> >
946 sigs(session_.signalbases());
947
948 GArray *const initial_pins = g_array_sized_new(FALSE, TRUE,
949 sizeof(uint8_t), channel_selectors_.size());
950 g_array_set_size(initial_pins, channel_selectors_.size());
951
952 for (const ChannelSelector &s : channel_selectors_) {
953 if (s.decoder_ != dec)
954 break;
955
956 const data::SignalBase *const selection =
957 (data::SignalBase*)s.combo_->itemData(
958 s.combo_->currentIndex()).value<void*>();
959
960 for (shared_ptr<data::SignalBase> sig : sigs)
961 if (sig.get() == selection) {
962 channel_map[s.pdch_] = sig;
963 break;
964 }
965
966 int selection_initial_pin = s.combo_initial_pin_->itemData(
967 s.combo_initial_pin_->currentIndex()).value<int>();
968
969 initial_pins->data[s.pdch_->order] = selection_initial_pin;
970 }
971
972 dec->set_channels(channel_map);
973 dec->set_initial_pins(initial_pins);
974 }
975
976 void DecodeTrace::commit_channels()
977 {
978 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
979
980 assert(decoder_stack);
981 for (shared_ptr<data::decode::Decoder> dec : decoder_stack->stack())
982 commit_decoder_channels(dec);
983
984 decoder_stack->begin_decode();
985 }
986
987 void DecodeTrace::on_new_decode_data()
989 void DecodeTrace::export_annotations(vector<Annotation> *annotations) const
990 {
991 using namespace pv::data::decode;
992
993 GlobalSettings settings;
994 const QString dir = settings.value("MainWindow/SaveDirectory").toString();
995
996 const QString file_name = QFileDialog::getSaveFileName(
997 owner_->view(), tr("Export annotations"), dir, tr("Text Files (*.txt);;All Files (*)"));
998
999 if (file_name.isEmpty())
1000 return;
1001
1002 QString format = settings.value(GlobalSettings::Key_Dec_ExportFormat).toString();
1003 const QString quote = format.contains("%q") ? "\"" : "";
1004 format = format.remove("%q");
1005
1006 QFile file(file_name);
1007 if (file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
1008 QTextStream out_stream(&file);
1009
1010 for (Annotation &ann : *annotations) {
1011 const QString sample_range = QString("%1-%2") \
1012 .arg(QString::number(ann.start_sample()), QString::number(ann.end_sample()));
1013
1014 const QString class_name = quote + ann.row()->class_name() + quote;
1015
1016 QString all_ann_text;
1017 for (const QString &s : ann.annotations())
1018 all_ann_text = all_ann_text + quote + s + quote + ",";
1019 all_ann_text.chop(1);
1020
1021 const QString first_ann_text = quote + ann.annotations().front() + quote;
1022
1023 QString out_text = format;
1024 out_text = out_text.replace("%s", sample_range);
1025 out_text = out_text.replace("%d",
1026 quote + QString::fromUtf8(ann.row()->decoder()->name) + quote);
1027 out_text = out_text.replace("%c", class_name);
1028 out_text = out_text.replace("%1", first_ann_text);
1029 out_text = out_text.replace("%a", all_ann_text);
1030 out_stream << out_text << '\n';
1031 }
1032
1033 if (out_stream.status() == QTextStream::Ok)
1034 return;
1035 }
1036
1037 QMessageBox msg(owner_->view());
1038 msg.setText(tr("Error"));
1039 msg.setInformativeText(tr("File %1 could not be written to.").arg(file_name));
1040 msg.setStandardButtons(QMessageBox::Ok);
1041 msg.setIcon(QMessageBox::Warning);
1042 msg.exec();
1043 }
1044
1045 void DecodeTrace::on_new_annotations()
1046 {
1047 if (!delayed_trace_updater_.isActive())
1048 delayed_trace_updater_.start();
1049 }
1050
1051 void DecodeTrace::on_delayed_trace_update()
9881052 {
9891053 if (owner_)
9901054 owner_->row_item_appearance_changed(false, true);
9911055 }
9921056
1057 void DecodeTrace::on_decode_reset()
1058 {
1059 visible_rows_.clear();
1060 max_visible_rows_ = 0;
1061
1062 if (owner_)
1063 owner_->row_item_appearance_changed(false, true);
1064 }
1065
1066 void DecodeTrace::on_decode_finished()
1067 {
1068 if (owner_)
1069 owner_->row_item_appearance_changed(false, true);
1070 }
1071
1072 void DecodeTrace::on_pause_decode()
1073 {
1074 if (decode_signal_->is_paused())
1075 decode_signal_->resume_decode();
1076 else
1077 decode_signal_->pause_decode();
1078 }
1079
9931080 void DecodeTrace::delete_pressed()
9941081 {
9951082 on_delete();
9971084
9981085 void DecodeTrace::on_delete()
9991086 {
1000 session_.remove_decode_signal(base_);
1087 session_.remove_decode_signal(decode_signal_);
10011088 }
10021089
10031090 void DecodeTrace::on_channel_selected(int)
10041091 {
1005 commit_channels();
1006 }
1007
1008 void DecodeTrace::on_initial_pin_selected(int)
1009 {
1010 commit_channels();
1092 QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
1093
1094 // Determine signal that was selected
1095 const data::SignalBase *signal =
1096 (data::SignalBase*)cb->itemData(cb->currentIndex()).value<void*>();
1097
1098 // Determine decode channel ID this combo box is the channel selector for
1099 const uint16_t id = channel_id_map_.at(cb);
1100
1101 decode_signal_->assign_signal(id, signal);
1102 }
1103
1104 void DecodeTrace::on_channels_updated()
1105 {
1106 if (owner_)
1107 owner_->row_item_appearance_changed(false, true);
1108 }
1109
1110 void DecodeTrace::on_init_state_changed(int)
1111 {
1112 QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
1113
1114 // Determine inital pin state that was selected
1115 int init_state = cb->itemData(cb->currentIndex()).value<int>();
1116
1117 // Determine decode channel ID this combo box is the channel selector for
1118 const uint16_t id = init_state_map_.at(cb);
1119
1120 decode_signal_->set_initial_pin_state(id, init_state);
10111121 }
10121122
10131123 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
10141124 {
1015 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
1016
1017 assert(decoder);
1018 assert(decoder_stack);
1019 decoder_stack->push(make_shared<data::decode::Decoder>(decoder));
1020 decoder_stack->begin_decode();
1125 decode_signal_->stack_decoder(decoder);
10211126
10221127 create_popup_form();
10231128 }
10241129
10251130 void DecodeTrace::on_delete_decoder(int index)
10261131 {
1027 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
1028
1029 decoder_stack->remove(index);
1132 decode_signal_->remove_decoder(index);
1133
1134 // Force re-calculation of the trace height, see paint_mid()
1135 max_visible_rows_ = 0;
1136 owner_->extents_changed(false, true);
10301137
10311138 // Update the popup
10321139 create_popup_form();
1033
1034 decoder_stack->begin_decode();
10351140 }
10361141
10371142 void DecodeTrace::on_show_hide_decoder(int index)
10381143 {
1039 using pv::data::decode::Decoder;
1040
1041 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
1042
1043 const list< shared_ptr<Decoder> > stack(decoder_stack->stack());
1044
1045 // Find the decoder in the stack
1046 auto iter = stack.cbegin();
1047 for (int i = 0; i < index; i++, iter++)
1048 assert(iter != stack.end());
1049
1050 shared_ptr<Decoder> dec = *iter;
1051 assert(dec);
1052
1053 const bool show = !dec->shown();
1054 dec->show(show);
1144 const bool state = decode_signal_->toggle_decoder_visibility(index);
10551145
10561146 assert(index < (int)decoder_forms_.size());
1057 decoder_forms_[index]->set_decoder_visible(show);
1147 decoder_forms_[index]->set_decoder_visible(state);
1148
1149 if (!state) {
1150 // Force re-calculation of the trace height, see paint_mid()
1151 max_visible_rows_ = 0;
1152 owner_->extents_changed(false, true);
1153 }
10581154
10591155 if (owner_)
10601156 owner_->row_item_appearance_changed(false, true);
1157 }
1158
1159 void DecodeTrace::on_export_row()
1160 {
1161 selected_sample_range_ = make_pair(0, numeric_limits<uint64_t>::max());
1162 on_export_row_from_here();
1163 }
1164
1165 void DecodeTrace::on_export_all_rows()
1166 {
1167 selected_sample_range_ = make_pair(0, numeric_limits<uint64_t>::max());
1168 on_export_all_rows_from_here();
1169 }
1170
1171 void DecodeTrace::on_export_row_with_cursor()
1172 {
1173 const View *view = owner_->view();
1174 assert(view);
1175
1176 if (!view->cursors()->enabled())
1177 return;
1178
1179 const double samplerate = session_.get_samplerate();
1180
1181 const pv::util::Timestamp& start_time = view->cursors()->first()->time();
1182 const pv::util::Timestamp& end_time = view->cursors()->second()->time();
1183
1184 const uint64_t start_sample = (uint64_t)max(
1185 0.0, start_time.convert_to<double>() * samplerate);
1186 const uint64_t end_sample = (uint64_t)max(
1187 0.0, end_time.convert_to<double>() * samplerate);
1188
1189 // Are both cursors negative and thus were clamped to 0?
1190 if ((start_sample == 0) && (end_sample == 0))
1191 return;
1192
1193 selected_sample_range_ = make_pair(start_sample, end_sample);
1194 on_export_row_from_here();
1195 }
1196
1197 void DecodeTrace::on_export_all_rows_with_cursor()
1198 {
1199 const View *view = owner_->view();
1200 assert(view);
1201
1202 if (!view->cursors()->enabled())
1203 return;
1204
1205 const double samplerate = session_.get_samplerate();
1206
1207 const pv::util::Timestamp& start_time = view->cursors()->first()->time();
1208 const pv::util::Timestamp& end_time = view->cursors()->second()->time();
1209
1210 const uint64_t start_sample = (uint64_t)max(
1211 0.0, start_time.convert_to<double>() * samplerate);
1212 const uint64_t end_sample = (uint64_t)max(
1213 0.0, end_time.convert_to<double>() * samplerate);
1214
1215 // Are both cursors negative and thus were clamped to 0?
1216 if ((start_sample == 0) && (end_sample == 0))
1217 return;
1218
1219 selected_sample_range_ = make_pair(start_sample, end_sample);
1220 on_export_all_rows_from_here();
1221 }
1222
1223 void DecodeTrace::on_export_row_from_here()
1224 {
1225 using namespace pv::data::decode;
1226
1227 if (!selected_row_)
1228 return;
1229
1230 vector<Annotation> *annotations = new vector<Annotation>();
1231
1232 decode_signal_->get_annotation_subset(*annotations, *selected_row_,
1233 current_segment_, selected_sample_range_.first, selected_sample_range_.second);
1234
1235 if (annotations->empty())
1236 return;
1237
1238 export_annotations(annotations);
1239 delete annotations;
1240 }
1241
1242 void DecodeTrace::on_export_all_rows_from_here()
1243 {
1244 using namespace pv::data::decode;
1245
1246 vector<Annotation> *annotations = new vector<Annotation>();
1247
1248 decode_signal_->get_annotation_subset(*annotations, current_segment_,
1249 selected_sample_range_.first, selected_sample_range_.second);
1250
1251 if (!annotations->empty())
1252 export_annotations(annotations);
1253
1254 delete annotations;
10611255 }
10621256
10631257 } // namespace trace
2626 #include <memory>
2727 #include <vector>
2828
29 #include <QColor>
30 #include <QComboBox>
2931 #include <QSignalMapper>
32 #include <QTimer>
3033
3134 #include <pv/binding/decoder.hpp>
35 #include <pv/data/decode/annotation.hpp>
3236 #include <pv/data/decode/row.hpp>
3337 #include <pv/data/signalbase.hpp>
3438
4145 struct srd_channel;
4246 struct srd_decoder;
4347
44 class QComboBox;
45
4648 namespace pv {
4749
4850 class Session;
4951
5052 namespace data {
51 class DecoderStack;
52 class SignalBase;
53 struct DecodeChannel;
54 class DecodeSignal;
5355
5456 namespace decode {
55 class Annotation;
5657 class Decoder;
57 class Row;
5858 }
59 }
59 } // namespace data
6060
6161 namespace widgets {
6262 class DecoderGroupBox;
7070 Q_OBJECT
7171
7272 private:
73 struct ChannelSelector
74 {
75 const QComboBox *combo_;
76 const QComboBox *combo_initial_pin_;
77 const shared_ptr<pv::data::decode::Decoder> decoder_;
78 const srd_channel *pdch_;
79 };
80
81 private:
82 static const QColor DecodeColours[4];
83 static const QColor ErrorBgColour;
84 static const QColor NoDecodeColour;
73 static const QColor ErrorBgColor;
74 static const QColor NoDecodeColor;
8575
8676 static const int ArrowSize;
8777 static const double EndCapWidth;
8878 static const int RowTitleMargin;
8979 static const int DrawPadding;
9080
91 static const QColor Colours[16];
92 static const QColor OutlineColours[16];
81 static const int MaxTraceUpdateRate;
9382
9483 public:
9584 DecodeTrace(pv::Session &session, shared_ptr<data::SignalBase> signalbase,
9786
9887 bool enabled() const;
9988
100 const shared_ptr<pv::data::DecoderStack>& decoder() const;
101
10289 shared_ptr<data::SignalBase> base() const;
10390
10491 /**
130117
131118 void populate_popup_form(QWidget *parent, QFormLayout *form);
132119
133 QMenu* create_context_menu(QWidget *parent);
120 QMenu* create_header_context_menu(QWidget *parent);
121
122 virtual QMenu* create_view_context_menu(QWidget *parent, QPoint &click_pos);
134123
135124 void delete_pressed();
136125
137126 private:
138127 void draw_annotations(vector<pv::data::decode::Annotation> annotations,
139128 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
140 size_t base_colour, int row_title_width);
129 QColor row_color, int row_title_width);
141130
142131 void draw_annotation(const pv::data::decode::Annotation &a, QPainter &p,
143132 int h, const ViewItemPaintParams &pp, int y,
144 size_t base_colour, int row_title_width) const;
145
146 void draw_annotation_block(vector<pv::data::decode::Annotation> annotations,
147 QPainter &p, int h, int y, size_t base_colour) const;
133 QColor row_color, int row_title_width) const;
134
135 void draw_annotation_block(qreal start, qreal end,
136 pv::data::decode::Annotation::Class ann_class, bool use_ann_format,
137 QPainter &p, int h, int y, QColor row_color) const;
148138
149139 void draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
150 int h, double x, int y) const;
140 int h, qreal x, int y) const;
151141
152142 void draw_range(const pv::data::decode::Annotation &a, QPainter &p,
153 int h, double start, double end, int y, const ViewItemPaintParams &pp,
143 int h, qreal start, qreal end, int y, const ViewItemPaintParams &pp,
154144 int row_title_width) const;
155145
156146 void draw_error(QPainter &p, const QString &message,
168158 * @return Returns a pair containing the start sample and the end
169159 * sample that correspond to the start and end coordinates.
170160 */
171 pair<uint64_t, uint64_t> get_sample_range(int x_start, int x_end) const;
161 pair<uint64_t, uint64_t> get_view_sample_range(int x_start, int x_end) const;
162
163 QColor get_row_color(int row_index) const;
164 QColor get_annotation_color(QColor row_color, int annotation_index) const;
172165
173166 int get_row_at_point(const QPoint &point);
174167
179172 QWidget *parent, QFormLayout *form);
180173
181174 QComboBox* create_channel_selector(QWidget *parent,
182 const shared_ptr<pv::data::decode::Decoder> &dec,
183 const srd_channel *const pdch);
184
185 QComboBox* create_channel_selector_initial_pin(QWidget *parent,
186 const shared_ptr<pv::data::decode::Decoder> &dec,
187 const srd_channel *const pdch);
188
189 void commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec);
190
191 void commit_channels();
175 const data::DecodeChannel *ch);
176 QComboBox* create_channel_selector_init_state(QWidget *parent,
177 const data::DecodeChannel *ch);
178
179 void export_annotations(vector<data::decode::Annotation> *annotations) const;
192180
193181 public:
194 void hover_point_changed();
182 virtual void hover_point_changed(const QPoint &hp);
195183
196184 private Q_SLOTS:
197 void on_new_decode_data();
185 void on_new_annotations();
186 void on_delayed_trace_update();
187 void on_decode_reset();
188 void on_decode_finished();
189 void on_pause_decode();
198190
199191 void on_delete();
200192
201193 void on_channel_selected(int);
202194
203 void on_initial_pin_selected(int);
195 void on_channels_updated();
196
197 void on_init_state_changed(int);
204198
205199 void on_stack_decoder(srd_decoder *decoder);
206200
207201 void on_delete_decoder(int index);
208202
209203 void on_show_hide_decoder(int index);
204
205 void on_export_row();
206 void on_export_all_rows();
207 void on_export_row_with_cursor();
208 void on_export_all_rows_with_cursor();
209 void on_export_row_from_here();
210 void on_export_all_rows_from_here();
210211
211212 private:
212213 pv::Session &session_;
214 shared_ptr<data::DecodeSignal> decode_signal_;
213215
214216 vector<data::decode::Row> visible_rows_;
215 uint64_t decode_start_, decode_end_;
216
217
218 map<QComboBox*, uint16_t> channel_id_map_; // channel selector -> decode channel ID
219 map<QComboBox*, uint16_t> init_state_map_; // init state selector -> decode channel ID
217220 list< shared_ptr<pv::binding::Decoder> > bindings_;
218221
219 list<ChannelSelector> channel_selectors_;
222 data::decode::Row *selected_row_;
223 pair<uint64_t, uint64_t> selected_sample_range_;
224
220225 vector<pv::widgets::DecoderGroupBox*> decoder_forms_;
221226
222227 map<data::decode::Row, int> row_title_widths_;
225230 int min_useful_label_width_;
226231
227232 QSignalMapper delete_mapper_, show_hide_mapper_;
233
234 QTimer delayed_trace_updater_;
228235 };
229236
230237 } // namespace trace
3535 namespace views {
3636 namespace trace {
3737
38 const QColor Flag::FillColour(0x73, 0xD2, 0x16);
38 const QColor Flag::FillColor(0x73, 0xD2, 0x16);
3939
4040 Flag::Flag(View &view, const pv::util::Timestamp& time, const QString &text) :
41 TimeMarker(view, FillColour, time),
41 TimeMarker(view, FillColor, time),
4242 text_(text)
4343 {
4444 }
4545
4646 Flag::Flag(const Flag &flag) :
47 TimeMarker(flag.view_, FillColour, flag.time_),
47 TimeMarker(flag.view_, FillColor, flag.time_),
4848 enable_shared_from_this<Flag>(flag)
4949 {
5050 }
6565
6666 Popup *const popup = TimeMarker::create_popup(parent);
6767 popup->set_position(parent->mapToGlobal(
68 point(parent->rect())), Popup::Bottom);
68 drag_point(parent->rect())), Popup::Bottom);
6969
7070 QFormLayout *const form = (QFormLayout*)popup->layout();
7171
8080 return popup;
8181 }
8282
83 QMenu* Flag::create_context_menu(QWidget *parent)
83 QMenu* Flag::create_header_context_menu(QWidget *parent)
8484 {
8585 QMenu *const menu = new QMenu(parent);
8686
3131 namespace views {
3232 namespace trace {
3333
34 /**
35 * The Flag class represents items on the @ref Ruler that mark important
36 * events on the timeline to the user. They are editable and thus non-static.
37 */
3438 class Flag : public TimeMarker, public enable_shared_from_this<Flag>
3539 {
3640 Q_OBJECT
3741
3842 public:
39 static const QColor FillColour;
43 static const QColor FillColor;
4044
4145 public:
4246 /**
6468
6569 pv::widgets::Popup* create_popup(QWidget *parent);
6670
67 QMenu* create_context_menu(QWidget *parent);
71 QMenu* create_header_context_menu(QWidget *parent);
6872
6973 void delete_pressed();
7074
102102
103103 stable_sort(items.begin(), items.end(),
104104 [](const shared_ptr<RowItem> &a, const shared_ptr<RowItem> &b) {
105 return a->point(QRect()).y() < b->point(QRect()).y(); });
105 return a->drag_point(QRect()).y() < b->drag_point(QRect()).y(); });
106106
107107 QPainter painter(this);
108108 painter.setRenderHint(QPainter::Antialiasing);
109109
110 for (const shared_ptr<RowItem> r : items) {
110 for (const shared_ptr<RowItem>& r : items) {
111111 assert(r);
112112
113113 const bool highlight = !item_dragging_ &&
124124 if (!r)
125125 return;
126126
127 QMenu *menu = r->create_context_menu(this);
127 QMenu *menu = r->create_header_context_menu(this);
128128 if (!menu)
129129 menu = new QMenu(this);
130130
141141 menu->addAction(group);
142142 }
143143
144 menu->exec(event->globalPos());
144 menu->popup(event->globalPos());
145145 }
146146
147147 void Header::keyPressEvent(QKeyEvent *event)
200200 restart = false;
201201 const vector< shared_ptr<TraceGroup> > groups(
202202 view_.list_by_type<TraceGroup>());
203 for (const shared_ptr<TraceGroup> tg : groups)
203 for (const shared_ptr<TraceGroup>& tg : groups)
204204 if (tg->selected()) {
205205 tg->ungroup();
206206 restart = true;
3636 class View;
3737 class ViewItem;
3838
39 /**
40 * The Header class provides an area for @ref Trace labels to be shown,
41 * trace-related settings to be edited, trace groups to be shown and similar.
42 * Essentially, it is the main management area of the @ref View itself and
43 * shown on the left-hand side of the trace area.
44 */
3945 class Header : public MarginWidget
4046 {
4147 Q_OBJECT
4545 using std::make_pair;
4646 using std::min;
4747 using std::none_of;
48 using std::out_of_range;
4849 using std::pair;
4950 using std::shared_ptr;
5051 using std::vector;
5556 using sigrok::TriggerMatch;
5657 using sigrok::TriggerMatchType;
5758
59 using pv::data::LogicSegment;
60
5861 namespace pv {
5962 namespace views {
6063 namespace trace {
6164
6265 const float LogicSignal::Oversampling = 2.0f;
6366
64 const QColor LogicSignal::EdgeColour(0x80, 0x80, 0x80);
65 const QColor LogicSignal::HighColour(0x00, 0xC0, 0x00);
66 const QColor LogicSignal::LowColour(0xC0, 0x00, 0x00);
67 const QColor LogicSignal::SamplingPointColour(0x77, 0x77, 0x77);
68
69 const QColor LogicSignal::SignalColours[10] = {
67 const QColor LogicSignal::EdgeColor(0x80, 0x80, 0x80);
68 const QColor LogicSignal::HighColor(0x00, 0xC0, 0x00);
69 const QColor LogicSignal::LowColor(0xC0, 0x00, 0x00);
70 const QColor LogicSignal::SamplingPointColor(0x77, 0x77, 0x77);
71
72 const QColor LogicSignal::SignalColors[10] = {
7073 QColor(0x16, 0x19, 0x1A), // Black
7174 QColor(0x8F, 0x52, 0x02), // Brown
7275 QColor(0xCC, 0x00, 0x00), // Red
7982 QColor(0xEE, 0xEE, 0xEC), // White
8083 };
8184
82 QColor LogicSignal::TriggerMarkerBackgroundColour = QColor(0xED, 0xD4, 0x00);
85 QColor LogicSignal::TriggerMarkerBackgroundColor = QColor(0xED, 0xD4, 0x00);
8386 const int LogicSignal::TriggerMarkerPadding = 2;
8487 const char* LogicSignal::TriggerMarkerIcons[8] = {
8588 nullptr,
100103 shared_ptr<devices::Device> device,
101104 shared_ptr<data::SignalBase> base) :
102105 Signal(session, base),
103 signal_height_(QFontMetrics(QApplication::font()).height() * 2),
104106 device_(device),
107 trigger_types_(get_trigger_types()),
105108 trigger_none_(nullptr),
106109 trigger_rising_(nullptr),
107110 trigger_high_(nullptr),
111114 {
112115 shared_ptr<Trigger> trigger;
113116
114 base_->set_colour(SignalColours[base->index() % countof(SignalColours)]);
117 base_->set_color(SignalColors[base->index() % countof(SignalColors)]);
118
119 GlobalSettings settings;
120 signal_height_ = settings.value(GlobalSettings::Key_View_DefaultLogicHeight).toInt();
121 show_sampling_points_ =
122 settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool();
123 fill_high_areas_ =
124 settings.value(GlobalSettings::Key_View_FillSignalHighAreas).toBool();
125 high_fill_color_ = QColor::fromRgba(settings.value(
126 GlobalSettings::Key_View_FillSignalHighAreaColor).value<uint32_t>());
115127
116128 /* Populate this channel's trigger setting with whatever we
117129 * find in the current session trigger, if anything. */
133145 return base_->logic_data();
134146 }
135147
148 void LogicSignal::save_settings(QSettings &settings) const
149 {
150 settings.setValue("trace_height", signal_height_);
151 }
152
153 void LogicSignal::restore_settings(QSettings &settings)
154 {
155 if (settings.contains("trace_height")) {
156 const int old_height = signal_height_;
157 signal_height_ = settings.value("trace_height").toInt();
158
159 if ((signal_height_ != old_height) && owner_) {
160 // Call order is important, otherwise the lazy event handler won't work
161 owner_->extents_changed(false, true);
162 owner_->row_item_appearance_changed(false, true);
163 }
164 }
165 }
166
136167 pair<int, int> LogicSignal::v_extents() const
137168 {
138169 const int signal_margin =
140171 return make_pair(-signal_height_ - signal_margin, signal_margin);
141172 }
142173
143 int LogicSignal::scale_handle_offset() const
144 {
145 return -signal_height_;
146 }
147
148 void LogicSignal::scale_handle_dragged(int offset)
149 {
150 const int font_height = QFontMetrics(QApplication::font()).height();
151 const int units = (-offset / font_height);
152 signal_height_ = ((units < 1) ? 1 : units) * font_height;
153 }
154
155174 void LogicSignal::paint_mid(QPainter &p, ViewItemPaintParams &pp)
156175 {
157176 QLineF *line;
166185 if (!base_->enabled())
167186 return;
168187
169 const float high_offset = y - signal_height_ + 0.5f;
170188 const float low_offset = y + 0.5f;
171
172 const deque< shared_ptr<pv::data::LogicSegment> > &segments =
173 base_->logic_data()->logic_segments();
174 if (segments.empty())
189 const float high_offset = low_offset - signal_height_;
190
191 shared_ptr<LogicSegment> segment = get_logic_segment_to_paint();
192 if (!segment || (segment->get_sample_count() == 0))
175193 return;
176
177 const shared_ptr<pv::data::LogicSegment> &segment = segments.front();
178194
179195 double samplerate = segment->samplerate();
180196
184200
185201 const double pixels_offset = pp.pixels_offset();
186202 const pv::util::Timestamp& start_time = segment->start_time();
187 const int64_t last_sample = segment->get_sample_count() - 1;
203 const int64_t last_sample = (int64_t)segment->get_sample_count() - 1;
188204 const double samples_per_pixel = samplerate * pp.scale();
189205 const double pixels_per_sample = 1 / samples_per_pixel;
190206 const pv::util::Timestamp start = samplerate * (pp.offset() - start_time);
199215 samples_per_pixel / Oversampling, base_->index());
200216 assert(edges.size() >= 2);
201217
218 const float first_sample_x =
219 pp.left() + (edges.front().first / samples_per_pixel - pixels_offset);
220 const float last_sample_x =
221 pp.left() + (edges.back().first / samples_per_pixel - pixels_offset);
222
202223 // Check whether we need to paint the sampling points
203 GlobalSettings settings;
204 const bool show_sampling_points =
205 settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool() &&
206 (samples_per_pixel < 0.25);
207
224 const bool show_sampling_points = show_sampling_points_ && (samples_per_pixel < 0.25);
208225 vector<QRectF> sampling_points;
209 float sampling_point_x = 0.0f;
226 float sampling_point_x = first_sample_x;
210227 int64_t sampling_point_sample = start_sample;
211228 const int w = 2;
212229
213 if (show_sampling_points) {
230 if (show_sampling_points)
214231 sampling_points.reserve(end_sample - start_sample + 1);
215 sampling_point_x = (edges.cbegin()->first / samples_per_pixel - pixels_offset) + pp.left();
216 }
232
233 vector<QRectF> high_rects;
234 float rising_edge_x;
235 bool rising_edge_seen = false;
217236
218237 // Paint the edges
219238 const unsigned int edge_count = edges.size() - 2;
220239 QLineF *const edge_lines = new QLineF[edge_count];
221240 line = edge_lines;
222241
242 if (edges.front().second) {
243 // Beginning of trace is high
244 rising_edge_x = first_sample_x;
245 rising_edge_seen = true;
246 }
247
223248 for (auto i = edges.cbegin() + 1; i != edges.cend() - 1; i++) {
224 const float x = ((*i).first / samples_per_pixel -
225 pixels_offset) + pp.left();
249 // Note: multiple edges occupying a single pixel are represented by an edge
250 // with undefined logic level. This means that only the first falling edge
251 // after a rising edge corresponds to said rising edge - and vice versa. If
252 // more edges with the same logic level follow, they denote multiple edges.
253
254 const float x = pp.left() + ((*i).first / samples_per_pixel - pixels_offset);
226255 *line++ = QLineF(x, high_offset, x, low_offset);
256
257 if (fill_high_areas_) {
258 // Any edge terminates a high area
259 if (rising_edge_seen) {
260 const int width = x - rising_edge_x;
261 if (width > 0)
262 high_rects.emplace_back(rising_edge_x, high_offset,
263 width, signal_height_);
264 rising_edge_seen = false;
265 }
266
267 // Only rising edges start high areas
268 if ((*i).second) {
269 rising_edge_x = x;
270 rising_edge_seen = true;
271 }
272 }
227273
228274 if (show_sampling_points)
229275 while (sampling_point_sample < (*i).first) {
246292 sampling_point_x += pixels_per_sample;
247293 };
248294
249 p.setPen(EdgeColour);
295 if (fill_high_areas_) {
296 // Add last high rectangle if the signal is still high at the end of the trace
297 if (rising_edge_seen && (edges.cend() - 1)->second)
298 high_rects.emplace_back(rising_edge_x, high_offset,
299 last_sample_x - rising_edge_x, signal_height_);
300
301 p.setPen(high_fill_color_);
302 p.setBrush(high_fill_color_);
303 p.drawRects((const QRectF*)(high_rects.data()), high_rects.size());
304 }
305
306 p.setPen(EdgeColor);
250307 p.drawLines(edge_lines, edge_count);
251308 delete[] edge_lines;
252309
254311 const unsigned int max_cap_line_count = edges.size();
255312 QLineF *const cap_lines = new QLineF[max_cap_line_count];
256313
257 p.setPen(HighColour);
314 p.setPen(HighColor);
258315 paint_caps(p, cap_lines, edges, true, samples_per_pixel,
259316 pixels_offset, pp.left(), high_offset);
260 p.setPen(LowColour);
317 p.setPen(LowColor);
261318 paint_caps(p, cap_lines, edges, false, samples_per_pixel,
262319 pixels_offset, pp.left(), low_offset);
263320
265322
266323 // Paint the sampling points
267324 if (show_sampling_points) {
268 p.setPen(SamplingPointColour);
325 p.setPen(SamplingPointColor);
269326 p.drawRects(sampling_points.data(), sampling_points.size());
270327 }
271328 }
272329
273330 void LogicSignal::paint_fore(QPainter &p, ViewItemPaintParams &pp)
274331 {
275 // Draw the trigger marker
276 if (!trigger_match_ || !base_->enabled())
277 return;
278
279 const int y = get_visual_y();
280 const vector<int32_t> trig_types = get_trigger_types();
281 for (int32_t type_id : trig_types) {
282 const TriggerMatchType *const type =
283 TriggerMatchType::get(type_id);
284 if (trigger_match_ != type || type_id < 0 ||
285 (size_t)type_id >= countof(TriggerMarkerIcons) ||
286 !TriggerMarkerIcons[type_id])
287 continue;
288
289 const QPixmap *const pixmap = get_pixmap(
290 TriggerMarkerIcons[type_id]);
291 if (!pixmap)
292 continue;
293
294 const float pad = TriggerMarkerPadding - 0.5f;
295 const QSize size = pixmap->size();
296 const QPoint point(
297 pp.right() - size.width() - pad * 2,
298 y - (signal_height_ + size.height()) / 2);
299
300 p.setPen(QPen(TriggerMarkerBackgroundColour.darker()));
301 p.setBrush(TriggerMarkerBackgroundColour);
302 p.drawRoundedRect(QRectF(point, size).adjusted(
303 -pad, -pad, pad, pad), pad, pad);
304 p.drawPixmap(point, *pixmap);
305
306 break;
307 }
332 if (base_->enabled()) {
333 if (trigger_match_) {
334 // Draw the trigger marker
335 const int y = get_visual_y();
336
337 for (int32_t type_id : trigger_types_) {
338 const TriggerMatchType *const type =
339 TriggerMatchType::get(type_id);
340 if (trigger_match_ != type || type_id < 0 ||
341 (size_t)type_id >= countof(TriggerMarkerIcons) ||
342 !TriggerMarkerIcons[type_id])
343 continue;
344
345 const QPixmap *const pixmap = get_pixmap(
346 TriggerMarkerIcons[type_id]);
347 if (!pixmap)
348 continue;
349
350 const float pad = TriggerMarkerPadding - 0.5f;
351 const QSize size = pixmap->size();
352 const QPoint point(
353 pp.right() - size.width() - pad * 2,
354 y - (signal_height_ + size.height()) / 2);
355
356 p.setPen(QPen(TriggerMarkerBackgroundColor.darker()));
357 p.setBrush(TriggerMarkerBackgroundColor);
358 p.drawRoundedRect(QRectF(point, size).adjusted(
359 -pad, -pad, pad, pad), pad, pad);
360 p.drawPixmap(point, *pixmap);
361
362 break;
363 }
364 }
365
366 if (show_hover_marker_)
367 paint_hover_marker(p);
368 }
369 }
370
371 vector<LogicSegment::EdgePair> LogicSignal::get_nearest_level_changes(uint64_t sample_pos)
372 {
373 assert(base_);
374 assert(owner_);
375
376 if (sample_pos == 0)
377 return vector<LogicSegment::EdgePair>();
378
379 shared_ptr<LogicSegment> segment = get_logic_segment_to_paint();
380 if (!segment || (segment->get_sample_count() == 0))
381 return vector<LogicSegment::EdgePair>();
382
383 const View *view = owner_->view();
384 assert(view);
385 const double samples_per_pixel = base_->get_samplerate() * view->scale();
386
387 vector<LogicSegment::EdgePair> edges;
388
389 segment->get_surrounding_edges(edges, sample_pos,
390 samples_per_pixel / Oversampling, base_->index());
391
392 if (edges.empty())
393 return vector<LogicSegment::EdgePair>();
394
395 return edges;
308396 }
309397
310398 void LogicSignal::paint_caps(QPainter &p, QLineF *const lines,
324412 }
325413
326414 p.drawLines(lines, line - lines);
415 }
416
417 shared_ptr<pv::data::LogicSegment> LogicSignal::get_logic_segment_to_paint() const
418 {
419 shared_ptr<pv::data::LogicSegment> segment;
420
421 const deque< shared_ptr<pv::data::LogicSegment> > &segments =
422 base_->logic_data()->logic_segments();
423
424 if (!segments.empty()) {
425 if (segment_display_mode_ == ShowLastSegmentOnly) {
426 segment = segments.back();
427 }
428
429 if ((segment_display_mode_ == ShowSingleSegmentOnly) ||
430 (segment_display_mode_ == ShowLastCompleteSegmentOnly)) {
431 try {
432 segment = segments.at(current_segment_);
433 } catch (out_of_range&) {
434 qDebug() << "Current logic segment out of range for signal" << base_->name() << ":" << current_segment_;
435 }
436 }
437 }
438
439 return segment;
327440 }
328441
329442 void LogicSignal::init_trigger_actions(QWidget *parent)
438551 {
439552 Signal::populate_popup_form(parent, form);
440553
554 signal_height_sb_ = new QSpinBox(parent);
555 signal_height_sb_->setRange(5, 1000);
556 signal_height_sb_->setSingleStep(5);
557 signal_height_sb_->setSuffix(tr(" pixels"));
558 signal_height_sb_->setValue(signal_height_);
559 connect(signal_height_sb_, SIGNAL(valueChanged(int)),
560 this, SLOT(on_signal_height_changed(int)));
561 form->addRow(tr("Trace height"), signal_height_sb_);
562
563 // Trigger settings
441564 const vector<int32_t> trig_types = get_trigger_types();
442565
443566 if (!trig_types.empty()) {
453576 trigger_bar_->addAction(action);
454577 action->setChecked(trigger_match_ == type);
455578 }
579
580 // Only allow triggers to be changed when we're stopped
581 if (session_.get_capture_state() != Session::Stopped)
582 for (QAction* action : trigger_bar_->findChildren<QAction*>()) // clazy:exclude=range-loop
583 action->setEnabled(false);
584
456585 form->addRow(tr("Trigger"), trigger_bar_);
457586 }
458587 }
517646 return pixmap_cache_.take(path);
518647 }
519648
649 void LogicSignal::on_setting_changed(const QString &key, const QVariant &value)
650 {
651 Signal::on_setting_changed(key, value);
652
653 if (key == GlobalSettings::Key_View_ShowSamplingPoints)
654 show_sampling_points_ = value.toBool();
655
656 if (key == GlobalSettings::Key_View_FillSignalHighAreas)
657 fill_high_areas_ = value.toBool();
658
659 if (key == GlobalSettings::Key_View_FillSignalHighAreaColor)
660 high_fill_color_ = QColor::fromRgba(value.value<uint32_t>());
661 }
662
520663 void LogicSignal::on_trigger()
521664 {
522665 QAction *action;
530673 modify_trigger();
531674 }
532675
676 void LogicSignal::on_signal_height_changed(int height)
677 {
678 signal_height_ = height;
679
680 if (owner_) {
681 // Call order is important, otherwise the lazy event handler won't work
682 owner_->extents_changed(false, true);
683 owner_->row_item_appearance_changed(false, true);
684 }
685 }
686
533687 } // namespace trace
534688 } // namespace views
535689 } // namespace pv
2020 #define PULSEVIEW_PV_VIEWS_TRACEVIEW_LOGICSIGNAL_HPP
2121
2222 #include <QCache>
23 #include <QColor>
24 #include <QDebug>
25 #include <QSpinBox>
2326
2427 #include "signal.hpp"
2528
5659 public:
5760 static const float Oversampling;
5861
59 static const QColor EdgeColour;
60 static const QColor HighColour;
61 static const QColor LowColour;
62 static const QColor SamplingPointColour;
62 static const QColor EdgeColor;
63 static const QColor HighColor;
64 static const QColor LowColor;
65 static const QColor SamplingPointColor;
6366
64 static const QColor SignalColours[10];
67 static const QColor SignalColors[10];
6568
66 static QColor TriggerMarkerBackgroundColour;
69 static QColor TriggerMarkerBackgroundColor;
6770 static const int TriggerMarkerPadding;
6871 static const char* TriggerMarkerIcons[8];
6972
7780
7881 shared_ptr<pv::data::Logic> logic_data() const;
7982
83 virtual void save_settings(QSettings &settings) const;
84 virtual void restore_settings(QSettings &settings);
85
8086 /**
8187 * Computes the vertical extents of the contents of this row item.
8288 * @return A pair containing the minimum and maximum y-values.
8389 */
8490 pair<int, int> v_extents() const;
85
86 /**
87 * Returns the offset to show the drag handle.
88 */
89 int scale_handle_offset() const;
90
91 /**
92 * Handles the scale handle being dragged to an offset.
93 * @param offset the offset the scale handle was dragged to.
94 */
95 void scale_handle_dragged(int offset);
9691
9792 /**
9893 * Paints the mid-layer of the signal with a QPainter
108103 */
109104 virtual void paint_fore(QPainter &p, ViewItemPaintParams &pp);
110105
106 /**
107 * Determines the closest level change (i.e. edge) to a given sample, which
108 * is useful for e.g. the "snap to edge" functionality.
109 *
110 * @param sample_pos Sample to use
111 * @return The changes left and right of the given position
112 */
113 virtual vector<data::LogicSegment::EdgePair> get_nearest_level_changes(uint64_t sample_pos);
114
111115 private:
112116 void paint_caps(QPainter &p, QLineF *const lines,
113117 vector< pair<int64_t, bool> > &edges,
114118 bool level, double samples_per_pixel, double pixels_offset,
115119 float x_offset, float y_offset);
120
121 shared_ptr<pv::data::LogicSegment> get_logic_segment_to_paint() const;
116122
117123 void init_trigger_actions(QWidget *parent);
118124
127133 static const QPixmap* get_pixmap(const char *path);
128134
129135 private Q_SLOTS:
136 void on_setting_changed(const QString &key, const QVariant &value);
137
130138 void on_trigger();
139
140 void on_signal_height_changed(int height);
131141
132142 private:
133143 int signal_height_;
144 QColor high_fill_color_;
145 bool show_sampling_points_, fill_high_areas_;
134146
135147 shared_ptr<pv::devices::Device> device_;
136148
149 QSpinBox *signal_height_sb_;
150
137151 const sigrok::TriggerMatchType *trigger_match_;
152 const vector<int32_t> trigger_types_;
138153 QToolBar *trigger_bar_;
139154 QAction *trigger_none_;
140155 QAction *trigger_rising_;
5252
5353 void MarginWidget::contextMenuEvent(QContextMenuEvent *event)
5454 {
55 event->setAccepted(false);
56
5557 const shared_ptr<ViewItem> r = get_mouse_over_item(mouse_point_);
5658 if (!r)
5759 return;
5860
59 QMenu *menu = r->create_context_menu(this);
60 if (menu)
61 menu->exec(event->globalPos());
61 QMenu *menu = r->create_header_context_menu(this);
62 if (menu) {
63 event->setAccepted(true);
64 menu->popup(event->globalPos());
65 }
6266 }
6367
6468 void MarginWidget::keyPressEvent(QKeyEvent *event)
2222 namespace views {
2323 namespace trace {
2424
25 void RowItem::hover_point_changed()
25 void RowItem::hover_point_changed(const QPoint &hp)
2626 {
27 (void)hp;
2728 }
2829
2930 } // namespace trace
3030 Q_OBJECT
3131
3232 public:
33 virtual void hover_point_changed();
33 virtual void hover_point_changed(const QPoint &hp);
3434 };
3535
3636 } // namespace trace
2020
2121 #include <QApplication>
2222 #include <QFontMetrics>
23 #include <QMenu>
2324 #include <QMouseEvent>
25
26 #include <pv/globalsettings.hpp>
2427
2528 #include "ruler.hpp"
2629 #include "view.hpp"
2831 using namespace Qt;
2932
3033 using std::function;
34 using std::max;
35 using std::min;
3136 using std::shared_ptr;
3237 using std::vector;
3338
3641 namespace trace {
3742
3843 const float Ruler::RulerHeight = 2.5f; // x Text Height
39 const int Ruler::MinorTickSubdivision = 4;
4044
4145 const float Ruler::HoverArrowSize = 0.5f; // x Text Height
4246
4549 {
4650 setMouseTracking(true);
4751
48 connect(&view_, SIGNAL(hover_point_changed()),
49 this, SLOT(hover_point_changed()));
52 connect(&view_, SIGNAL(hover_point_changed(const QWidget*, QPoint)),
53 this, SLOT(on_hover_point_changed(const QWidget*, QPoint)));
5054 connect(&view_, SIGNAL(offset_changed()),
5155 this, SLOT(invalidate_tick_position_cache()));
5256 connect(&view_, SIGNAL(scale_changed()),
109113 return pv::util::format_time_minutes(t, precision, sign);
110114 }
111115
116 pv::util::Timestamp Ruler::get_time_from_x_pos(uint32_t x) const
117 {
118 return view_.ruler_offset() + ((double)x + 0.5) * view_.scale();
119 }
120
121 void Ruler::contextMenuEvent(QContextMenuEvent *event)
122 {
123 MarginWidget::contextMenuEvent(event);
124
125 // Don't show a context menu if the MarginWidget found a widget that shows one
126 if (event->isAccepted())
127 return;
128
129 context_menu_x_pos_ = event->pos().x();
130
131 QMenu *const menu = new QMenu(this);
132
133 QAction *const create_marker = new QAction(tr("Create marker here"), this);
134 connect(create_marker, SIGNAL(triggered()), this, SLOT(on_createMarker()));
135 menu->addAction(create_marker);
136
137 QAction *const set_zero_position = new QAction(tr("Set as zero point"), this);
138 connect(set_zero_position, SIGNAL(triggered()), this, SLOT(on_setZeroPosition()));
139 menu->addAction(set_zero_position);
140
141 QAction *const toggle_hover_marker = new QAction(this);
142 connect(toggle_hover_marker, SIGNAL(triggered()), this, SLOT(on_toggleHoverMarker()));
143 menu->addAction(toggle_hover_marker);
144
145 GlobalSettings settings;
146 const bool hover_marker_shown =
147 settings.value(GlobalSettings::Key_View_ShowHoverMarker).toBool();
148 toggle_hover_marker->setText(hover_marker_shown ?
149 tr("Disable mouse hover marker") : tr("Enable mouse hover marker"));
150
151 event->setAccepted(true);
152 menu->popup(event->globalPos());
153 }
154
155 void Ruler::resizeEvent(QResizeEvent*)
156 {
157 // the tick calculation depends on the width of this widget
158 invalidate_tick_position_cache();
159 }
160
112161 vector< shared_ptr<ViewItem> > Ruler::items()
113162 {
114163 const vector< shared_ptr<TimeItem> > time_items(view_.time_items());
123172 if ((*i)->enabled() && (*i)->label_rect(rect()).contains(pt))
124173 return *i;
125174 return nullptr;
175 }
176
177 void Ruler::mouseDoubleClickEvent(QMouseEvent *event)
178 {
179 view_.add_flag(get_time_from_x_pos(event->x()));
126180 }
127181
128182 void Ruler::paintEvent(QPaintEvent*)
139193
140194 tick_position_cache_ = calculate_tick_positions(
141195 view_.tick_period(),
142 view_.offset(),
196 view_.ruler_offset(),
143197 view_.scale(),
144198 width(),
199 view_.minor_tick_count(),
145200 ffunc);
146201 }
147202
158213 p.setPen(palette().color(foregroundRole()));
159214
160215 for (const auto& tick: tick_position_cache_->major) {
161 p.drawText(tick.first, ValueMargin, 0, text_height,
216 const int leftedge = 0;
217 const int rightedge = width();
218 const int x_tick = tick.first;
219 if ((x_tick > leftedge) && (x_tick < rightedge)) {
220 const int x_left_bound = QFontMetrics(font()).width(tick.second) / 2;
221 const int x_right_bound = rightedge - x_left_bound;
222 const int x_legend = min(max(x_tick, x_left_bound), x_right_bound);
223 p.drawText(x_legend, ValueMargin, 0, text_height,
162224 AlignCenter | AlignTop | TextDontClip, tick.second);
163 p.drawLine(QPointF(tick.first, major_tick_y1),
225 p.drawLine(QPointF(x_tick, major_tick_y1),
164226 QPointF(tick.first, ruler_height));
227 }
165228 }
166229
167230 for (const auto& tick: tick_position_cache_->minor) {
186249 i->label_rect(r).contains(mouse_point_);
187250 i->paint_label(p, r, highlight);
188251 }
189 }
190
191 Ruler::TickPositions Ruler::calculate_tick_positions(
192 const pv::util::Timestamp& major_period,
193 const pv::util::Timestamp& offset,
194 const double scale,
195 const int width,
196 function<QString(const pv::util::Timestamp&)> format_function)
197 {
198 TickPositions tp;
199
200 const pv::util::Timestamp minor_period = major_period / MinorTickSubdivision;
201 const pv::util::Timestamp first_major_division = floor(offset / major_period);
202 const pv::util::Timestamp first_minor_division = ceil(offset / minor_period);
203 const pv::util::Timestamp t0 = first_major_division * major_period;
204
205 int division = (round(first_minor_division -
206 first_major_division * MinorTickSubdivision)).convert_to<int>() - 1;
207
208 double x;
209
210 do {
211 pv::util::Timestamp t = t0 + division * minor_period;
212 x = ((t - offset) / scale).convert_to<double>();
213
214 if (division % MinorTickSubdivision == 0) {
215 // Recalculate 't' without using 'minor_period' which is a fraction
216 t = t0 + division / MinorTickSubdivision * major_period;
217 tp.major.emplace_back(x, format_function(t));
218 } else {
219 tp.minor.emplace_back(x);
220 }
221
222 division++;
223 } while (x < width);
224
225 return tp;
226 }
227
228 void Ruler::mouseDoubleClickEvent(QMouseEvent *event)
229 {
230 view_.add_flag(view_.offset() + ((double)event->x() + 0.5) * view_.scale());
231252 }
232253
233254 void Ruler::draw_hover_mark(QPainter &p, int text_height)
255276 return QFontMetrics(font()).ascent();
256277 }
257278
258 void Ruler::hover_point_changed()
259 {
279 TickPositions Ruler::calculate_tick_positions(
280 const pv::util::Timestamp& major_period,
281 const pv::util::Timestamp& offset,
282 const double scale,
283 const int width,
284 const unsigned int minor_tick_count,
285 function<QString(const pv::util::Timestamp&)> format_function)
286 {
287 TickPositions tp;
288
289 const pv::util::Timestamp minor_period = major_period / minor_tick_count;
290 const pv::util::Timestamp first_major_division = floor(offset / major_period);
291 const pv::util::Timestamp first_minor_division = ceil(offset / minor_period);
292 const pv::util::Timestamp t0 = first_major_division * major_period;
293
294 int division = (round(first_minor_division -
295 first_major_division * minor_tick_count)).convert_to<int>() - 1;
296
297 double x;
298
299 do {
300 pv::util::Timestamp t = t0 + division * minor_period;
301 x = ((t - offset) / scale).convert_to<double>();
302
303 if (division % minor_tick_count == 0) {
304 // Recalculate 't' without using 'minor_period' which is a fraction
305 t = t0 + division / minor_tick_count * major_period;
306 tp.major.emplace_back(x, format_function(t));
307 } else {
308 tp.minor.emplace_back(x);
309 }
310
311 division++;
312 } while (x < width);
313
314 return tp;
315 }
316
317 void Ruler::on_hover_point_changed(const QWidget* widget, const QPoint &hp)
318 {
319 (void)widget;
320 (void)hp;
321
260322 update();
261323 }
262324
265327 tick_position_cache_ = boost::none;
266328 }
267329
268 void Ruler::resizeEvent(QResizeEvent*)
269 {
270 // the tick calculation depends on the width of this widget
271 invalidate_tick_position_cache();
330 void Ruler::on_createMarker()
331 {
332 view_.add_flag(get_time_from_x_pos(mouse_down_point_.x()));
333 }
334
335 void Ruler::on_setZeroPosition()
336 {
337 view_.set_zero_position(get_time_from_x_pos(mouse_down_point_.x()));
338 }
339
340 void Ruler::on_toggleHoverMarker()
341 {
342 GlobalSettings settings;
343 const bool state = settings.value(GlobalSettings::Key_View_ShowHoverMarker).toBool();
344 settings.setValue(GlobalSettings::Key_View_ShowHoverMarker, !state);
272345 }
273346
274347 } // namespace trace
4545 class TimeItem;
4646 class ViewItem;
4747
48 struct TickPositions
49 {
50 vector<pair<double, QString>> major;
51 vector<double> minor;
52 };
53
54 /**
55 * The Ruler class manages and displays the time scale above the trace canvas.
56 * It may also contain @ref TimeItem instances used to identify or highlight
57 * time-related information.
58 */
4859 class Ruler : public MarginWidget
4960 {
5061 Q_OBJECT
5768 /// Height of the ruler in multipes of the text height
5869 static const float RulerHeight;
5970
60 static const int MinorTickSubdivision;
61
6271 /// Height of the hover arrow in multiples of the text height
6372 static const float HoverArrowSize;
6473
6574 public:
6675 Ruler(View &parent);
6776
68 public:
6977 QSize sizeHint() const override;
7078
7179 /**
108116 unsigned precision = 0,
109117 bool sign = true);
110118
119 pv::util::Timestamp get_time_from_x_pos(uint32_t x) const;
120
121 protected:
122 virtual void contextMenuEvent(QContextMenuEvent *event) override;
123 void resizeEvent(QResizeEvent*) override;
124
111125 private:
112126 /**
113127 * Gets the time items.
122136 */
123137 shared_ptr<ViewItem> get_mouse_over_item(const QPoint &pt) override;
124138
139 void mouseDoubleClickEvent(QMouseEvent *event) override;
140
125141 void paintEvent(QPaintEvent *event) override;
126
127 void mouseDoubleClickEvent(QMouseEvent *event) override;
128142
129143 /**
130144 * Draw a hover arrow under the cursor position.
135149
136150 int calculate_text_height() const;
137151
138 struct TickPositions
139 {
140 vector<pair<double, QString>> major;
141 vector<double> minor;
142 };
143
144 /**
145 * Holds the tick positions so that they don't have to be recalculated on
146 * every redraw. Set by 'paintEvent()' when needed.
147 */
148 boost::optional<TickPositions> tick_position_cache_;
149
150152 /**
151153 * Calculates the major and minor tick positions.
152154 *
153155 * @param major_period The period between the major ticks.
154 * @param offset The time at the left border of the ruler.
156 * @param offset The virtual time at the left border of the ruler.
155157 * @param scale The scale in seconds per pixel.
156158 * @param width the Width of the ruler.
157159 * @param format_function A function used to format the major tick times.
164166 const pv::util::Timestamp& offset,
165167 const double scale,
166168 const int width,
169 const unsigned int minor_tick_count,
167170 function<QString(const pv::util::Timestamp&)> format_function);
168171
169 protected:
170 void resizeEvent(QResizeEvent*) override;
172 private Q_SLOTS:
173 void on_hover_point_changed(const QWidget* widget, const QPoint &hp);
171174
172 private Q_SLOTS:
173 void hover_point_changed();
175 void invalidate_tick_position_cache();
174176
175 // Resets the 'tick_position_cache_'.
176 void invalidate_tick_position_cache();
177 void on_createMarker();
178 void on_setZeroPosition();
179 void on_toggleHoverMarker();
180
181 private:
182 /**
183 * Holds the tick positions so that they don't have to be recalculated on
184 * every redraw. Set by 'paintEvent()' when needed.
185 */
186 boost::optional<TickPositions> tick_position_cache_;
187
188 uint32_t context_menu_x_pos_;
177189 };
178190
179191 } // namespace trace
3535 #include "view.hpp"
3636
3737 using std::shared_ptr;
38 using std::make_shared;
3938
4039 namespace pv {
4140 namespace views {
6261 shared_ptr<data::SignalBase> channel) :
6362 Trace(channel),
6463 session_(session),
65 scale_handle_(make_shared<SignalScaleHandle>(*this)),
66 items_({scale_handle_}),
6764 name_widget_(nullptr)
6865 {
6966 assert(base_);
7471
7572 void Signal::set_name(QString name)
7673 {
77 Trace::set_name(name);
74 base_->set_name(name);
7875
7976 if (name != name_widget_->currentText())
8077 name_widget_->setEditText(name);
9895 void Signal::restore_settings(QSettings &settings)
9996 {
10097 (void)settings;
101 }
102
103 const ViewItemOwner::item_list& Signal::child_items() const
104 {
105 return items_;
10698 }
10799
108100 void Signal::paint_back(QPainter &p, ViewItemPaintParams &pp)
134126
135127 form->addRow(tr("Name"), name_widget_);
136128
137 add_colour_option(parent, form);
129 add_color_option(parent, form);
138130 }
139131
140 QMenu* Signal::create_context_menu(QWidget *parent)
132 QMenu* Signal::create_header_context_menu(QWidget *parent)
141133 {
142 QMenu *const menu = Trace::create_context_menu(parent);
134 QMenu *const menu = Trace::create_header_context_menu(parent);
143135
144136 menu->addSeparator();
145137
2626
2727 #include <cstdint>
2828
29 #include "signalscalehandle.hpp"
29 #include <pv/data/logicsegment.hpp>
30
3031 #include "trace.hpp"
3132 #include "viewitemowner.hpp"
3233
4445 namespace views {
4546 namespace trace {
4647
48 /**
49 * The Signal class represents a series of numeric values that can be drawn.
50 * This is the main difference to the more generic @ref Trace class.
51 *
52 * It is generally accepted that Signal instances consider themselves to be
53 * individual channels on e.g. an oscilloscope, though it should be kept in
54 * mind that virtual signals (e.g. math) will also be served by the Signal
55 * class.
56 */
4757 class Signal : public Trace, public ViewItemOwner
4858 {
4959 Q_OBJECT
6070 virtual shared_ptr<pv::data::SignalData> data() const = 0;
6171
6272 /**
73 * Determines the closest level change (i.e. edge) to a given sample, which
74 * is useful for e.g. the "snap to edge" functionality.
75 *
76 * @param sample_pos Sample to use
77 * @return The changes left and right of the given position
78 */
79 virtual vector<data::LogicSegment::EdgePair> get_nearest_level_changes(uint64_t sample_pos) = 0;
80
81 /**
6382 * Returns true if the trace is visible and enabled.
6483 */
6584 bool enabled() const;
7089
7190 virtual void restore_settings(QSettings &settings);
7291
73 /**
74 * Returns a list of row items owned by this object.
75 */
76 const item_list& child_items() const;
77
7892 void paint_back(QPainter &p, ViewItemPaintParams &pp);
7993
8094 virtual void populate_popup_form(QWidget *parent, QFormLayout *form);
8195
82 QMenu* create_context_menu(QWidget *parent);
96 QMenu* create_header_context_menu(QWidget *parent);
8397
8498 void delete_pressed();
85
86 /**
87 * Returns the offset to show the drag handle.
88 */
89 virtual int scale_handle_offset() const = 0;
90
91 /**
92 * Handles the scale handle being dragged to an offset.
93 * @param offset the offset the scale handle was dragged to.
94 */
95 virtual void scale_handle_dragged(int offset) = 0;
96
97 /**
98 * Handles the scale handle being being released.
99 */
100 virtual void scale_handle_released() {};
10199
102100 protected Q_SLOTS:
103101 virtual void on_name_changed(const QString &text);
109107 protected:
110108 pv::Session &session_;
111109
112 const shared_ptr<SignalScaleHandle> scale_handle_;
113 const item_list items_;
114
115110 QComboBox *name_widget_;
116111 };
117112
+0
-108
pv/views/trace/signalscalehandle.cpp less more
0 /*
1 * This file is part of the PulseView project.
2 *
3 * Copyright (C) 2015 Joel Holdsworth <joel@airwebreathe.org.uk>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <algorithm>
20
21 #include <QRadialGradient>
22
23 #include "signal.hpp"
24 #include "signalscalehandle.hpp"
25 #include "tracetreeitemowner.hpp"
26
27 using std::max;
28 using std::min;
29
30 namespace pv {
31 namespace views {
32 namespace trace {
33
34 SignalScaleHandle::SignalScaleHandle(Signal &owner) :
35 owner_(owner)
36 {
37 }
38
39 bool SignalScaleHandle::enabled() const
40 {
41 return selected() || owner_.selected();
42 }
43
44 void SignalScaleHandle::select(bool select)
45 {
46 ViewItem::select(select);
47 owner_.owner()->row_item_appearance_changed(true, true);
48 }
49
50 void SignalScaleHandle::drag_release()
51 {
52 RowItem::drag_release();
53 owner_.scale_handle_released();
54 owner_.owner()->row_item_appearance_changed(true, true);
55 }
56
57 void SignalScaleHandle::drag_by(const QPoint &delta)
58 {
59 owner_.scale_handle_dragged(
60 drag_point_.y() + delta.y() - owner_.get_visual_y());
61 owner_.owner()->row_item_appearance_changed(true, true);
62 }
63
64 QPoint SignalScaleHandle::point(const QRect &rect) const
65 {
66 return owner_.point(rect) + QPoint(0, owner_.scale_handle_offset());
67 }
68
69 QRectF SignalScaleHandle::hit_box_rect(const ViewItemPaintParams &pp) const
70 {
71 const int text_height = ViewItemPaintParams::text_height();
72 const double x = -pp.pixels_offset() - text_height / 2;
73 const double min_x = pp.left() + text_height;
74 const double max_x = pp.right() - text_height * 2;
75 return QRectF(min(max(x, min_x), max_x),
76 owner_.get_visual_y() + owner_.scale_handle_offset() -
77 text_height / 2,
78 text_height, text_height);
79 }
80
81 void SignalScaleHandle::paint_fore(QPainter &p, ViewItemPaintParams &pp)
82 {
83 if (!enabled())
84 return;
85
86 const QRectF r(hit_box_rect(pp));
87 const QPointF c = (r.topLeft() + 2 * r.center()) / 3;
88 QRadialGradient gradient(c, r.width(), c);
89
90 if (selected()) {
91 gradient.setColorAt(0.0, QColor(255, 255, 255));
92 gradient.setColorAt(0.75, QColor(192, 192, 192));
93 gradient.setColorAt(1.0, QColor(128, 128, 128));
94 } else {
95 gradient.setColorAt(0.0, QColor(192, 192, 192));
96 gradient.setColorAt(0.75, QColor(128, 128, 128));
97 gradient.setColorAt(1.0, QColor(128, 128, 128));
98 }
99
100 p.setBrush(QBrush(gradient));
101 p.setPen(QColor(128, 128, 128));
102 p.drawEllipse(r);
103 }
104
105 } // namespace trace
106 } // namespace views
107 } // namespace pv
+0
-94
pv/views/trace/signalscalehandle.hpp less more
0 /*
1 * This file is part of the PulseView project.
2 *
3 * Copyright (C) 2015 Joel Holdsworth <joel@airwebreathe.org.uk>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_SIGNALSCALEHANDLE_HPP
20 #define PULSEVIEW_PV_VIEWS_TRACEVIEW_SIGNALSCALEHANDLE_HPP
21
22 #include "rowitem.hpp"
23
24 namespace pv {
25 namespace views {
26 namespace trace {
27
28 class Signal;
29
30 /**
31 * A row item owned by a @c Signal that implements the v-scale adjustment grab
32 * handle.
33 */
34 class SignalScaleHandle : public RowItem
35 {
36 Q_OBJECT
37 public:
38 /**
39 * Constructor
40 */
41 explicit SignalScaleHandle(Signal &owner);
42
43 public:
44 /**
45 * Returns true if the parent item is enabled.
46 */
47 bool enabled() const;
48
49 /**
50 * Selects or deselects the signal.
51 */
52 void select(bool select = true);
53
54 /**
55 * Sets this item into the un-dragged state.
56 */
57 void drag_release();
58
59 /**
60 * Drags the item to a delta relative to the drag point.
61 * @param delta the offset from the drag point.
62 */
63 void drag_by(const QPoint &delta);
64
65 /**
66 * Get the drag point.
67 * @param rect the rectangle of the widget area.
68 */
69 QPoint point(const QRect &rect) const;
70
71 /**
72 * Computes the outline rectangle of the viewport hit-box.
73 * @param rect the rectangle of the viewport area.
74 * @return Returns the rectangle of the hit-box.
75 */
76 QRectF hit_box_rect(const ViewItemPaintParams &pp) const;
77
78 /**
79 * Paints the foreground layer of the item with a QPainter
80 * @param p the QPainter to paint into.
81 * @param pp the painting parameters object to paint with.
82 */
83 void paint_fore(QPainter &p, ViewItemPaintParams &pp);
84
85 private:
86 Signal &owner_;
87 };
88
89 } // namespace trace
90 } // namespace views
91 } // namespace pv
92
93 #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_SIGNALSCALEHANDLE_HPP
4040 action_view_zoom_in_(new QAction(this)),
4141 action_view_zoom_out_(new QAction(this)),
4242 action_view_zoom_fit_(new QAction(this)),
43 action_view_zoom_one_to_one_(new QAction(this)),
44 action_view_show_cursors_(new QAction(this))
43 action_view_show_cursors_(new QAction(this)),
44 segment_display_mode_selector_(new QToolButton(this)),
45 action_sdm_last_(new QAction(this)),
46 action_sdm_last_complete_(new QAction(this)),
47 action_sdm_single_(new QAction(this)),
48 segment_selector_(new QSpinBox(this))
4549 {
4650 setObjectName(QString::fromUtf8("StandardBar"));
4751
6973 connect(action_view_zoom_fit_, SIGNAL(triggered(bool)),
7074 this, SLOT(on_actionViewZoomFit_triggered(bool)));
7175
72 action_view_zoom_one_to_one_->setText(tr("Zoom to O&ne-to-One"));
73 action_view_zoom_one_to_one_->setIcon(QIcon::fromTheme("zoom-original",
74 QIcon(":/icons/zoom-original.png")));
75 action_view_zoom_one_to_one_->setShortcut(QKeySequence(Qt::Key_O));
76 connect(action_view_zoom_one_to_one_, SIGNAL(triggered(bool)),
77 this, SLOT(on_actionViewZoomOneToOne_triggered()));
78
7976 action_view_show_cursors_->setCheckable(true);
8077 action_view_show_cursors_->setIcon(QIcon(":/icons/show-cursors.svg"));
8178 action_view_show_cursors_->setShortcut(QKeySequence(Qt::Key_C));
8380 this, SLOT(on_actionViewShowCursors_triggered()));
8481 action_view_show_cursors_->setText(tr("Show &Cursors"));
8582
83 action_sdm_last_->setIcon(QIcon(":/icons/view-displaymode-last_segment.svg"));
84 action_sdm_last_->setText(tr("Display last segment only"));
85 connect(action_sdm_last_, SIGNAL(triggered(bool)),
86 this, SLOT(on_actionSDMLast_triggered()));
87
88 action_sdm_last_complete_->setIcon(QIcon(":/icons/view-displaymode-last_complete_segment.svg"));
89 action_sdm_last_complete_->setText(tr("Display last complete segment only"));
90 connect(action_sdm_last_complete_, SIGNAL(triggered(bool)),
91 this, SLOT(on_actionSDMLastComplete_triggered()));
92
93 action_sdm_single_->setIcon(QIcon(":/icons/view-displaymode-single_segment.svg"));
94 action_sdm_single_->setText(tr("Display a single segment"));
95 connect(action_view_show_cursors_, SIGNAL(triggered(bool)),
96 this, SLOT(on_actionSDMSingle_triggered()));
97
98 segment_display_mode_selector_->addAction(action_sdm_last_);
99 segment_display_mode_selector_->addAction(action_sdm_last_complete_);
100 segment_display_mode_selector_->addAction(action_sdm_single_);
101 segment_display_mode_selector_->setPopupMode(QToolButton::InstantPopup);
102 segment_display_mode_selector_->hide();
103
104 segment_selector_->setMinimum(1);
105 segment_selector_->hide();
106
107 connect(&session_, SIGNAL(new_segment(int)),
108 this, SLOT(on_new_segment(int)));
109
110 connect(&session_, SIGNAL(segment_completed(int)),
111 view_, SLOT(on_segment_completed(int)));
112
113 connect(segment_selector_, SIGNAL(valueChanged(int)),
114 this, SLOT(on_segment_selected(int)));
115 connect(view_, SIGNAL(segment_changed(int)),
116 this, SLOT(on_segment_changed(int)));
117
118 connect(this, SIGNAL(segment_selected(int)),
119 view_, SLOT(on_segment_changed(int)));
120
121 connect(view_, SIGNAL(segment_display_mode_changed(int, bool)),
122 this, SLOT(on_segment_display_mode_changed(int, bool)));
123
86124 connect(view_, SIGNAL(always_zoom_to_fit_changed(bool)),
87125 this, SLOT(on_always_zoom_to_fit_changed(bool)));
126
127 connect(view_, SIGNAL(cursor_state_changed(bool)),
128 this, SLOT(on_cursor_state_changed(bool)));
88129
89130 if (add_default_widgets)
90131 add_toolbar_widgets();
101142 addAction(action_view_zoom_in_);
102143 addAction(action_view_zoom_out_);
103144 addAction(action_view_zoom_fit_);
104 addAction(action_view_zoom_one_to_one_);
105145 addSeparator();
106146 addAction(action_view_show_cursors_);
147 multi_segment_actions_.push_back(addSeparator());
148 multi_segment_actions_.push_back(addWidget(segment_display_mode_selector_));
149 multi_segment_actions_.push_back(addWidget(segment_selector_));
150 addSeparator();
151
152 // Hide the multi-segment UI until we know that there are multiple segments
153 show_multi_segment_ui(false);
154 }
155
156 void StandardBar::show_multi_segment_ui(const bool state)
157 {
158 for (QAction* action : multi_segment_actions_)
159 action->setVisible(state);
160
161 on_segment_display_mode_changed(view_->segment_display_mode(),
162 view_->segment_is_selectable());
107163 }
108164
109165 QAction* StandardBar::action_view_zoom_in() const
121177 return action_view_zoom_fit_;
122178 }
123179
124 QAction* StandardBar::action_view_zoom_one_to_one() const
125 {
126 return action_view_zoom_one_to_one_;
127 }
128
129180 QAction* StandardBar::action_view_show_cursors() const
130181 {
131182 return action_view_show_cursors_;
146197 view_->zoom_fit(checked);
147198 }
148199
149 void StandardBar::on_actionViewZoomOneToOne_triggered()
150 {
151 view_->zoom_one_to_one();
152 }
153
154200 void StandardBar::on_actionViewShowCursors_triggered()
155201 {
156 const bool show = !view_->cursors_shown();
202 const bool show = action_view_show_cursors_->isChecked();
203
157204 if (show)
158205 view_->centre_cursors();
159206
160207 view_->show_cursors(show);
161208 }
162209
210 void StandardBar::on_actionSDMLast_triggered()
211 {
212 view_->set_segment_display_mode(Trace::ShowLastSegmentOnly);
213 }
214
215 void StandardBar::on_actionSDMLastComplete_triggered()
216 {
217 view_->set_segment_display_mode(Trace::ShowLastCompleteSegmentOnly);
218 }
219
220 void StandardBar::on_actionSDMSingle_triggered()
221 {
222 view_->set_segment_display_mode(Trace::ShowSingleSegmentOnly);
223 }
224
163225 void StandardBar::on_always_zoom_to_fit_changed(bool state)
164226 {
165227 action_view_zoom_fit_->setChecked(state);
228 }
229
230 void StandardBar::on_new_segment(int new_segment_id)
231 {
232 if (new_segment_id > 0) {
233 show_multi_segment_ui(true);
234 segment_selector_->setMaximum(new_segment_id + 1);
235 } else
236 show_multi_segment_ui(false);
237 }
238
239 void StandardBar::on_segment_changed(int segment_id)
240 {
241 // We need to adjust the value by 1 because internally, segments
242 // start at 0 while they start with 1 for the spinbox
243 const uint32_t ui_segment_id = segment_id + 1;
244
245 // This is called when the current segment was changed
246 // by other parts of the UI, e.g. the view itself
247
248 // Make sure our value isn't limited by a too low maximum
249 // Note: this can happen if on_segment_changed() is called before
250 // on_new_segment()
251 if ((uint32_t)segment_selector_->maximum() < ui_segment_id)
252 segment_selector_->setMaximum(ui_segment_id);
253
254 segment_selector_->setValue(ui_segment_id);
255 }
256
257 void StandardBar::on_segment_selected(int ui_segment_id)
258 {
259 // We need to adjust the value by 1 because internally, segments
260 // start at 0 while they start with 1 for the spinbox
261 const uint32_t segment_id = ui_segment_id - 1;
262
263 // This is called when the user selected a segment using the spin box
264 // or when the value of the spinbox was assigned a new value. Since we
265 // only care about the former, we filter out the latter:
266 if (segment_id == view_->current_segment())
267 return;
268
269 // No matter which segment display mode we were in, we now show a single segment
270 if (view_->segment_display_mode() != Trace::ShowSingleSegmentOnly)
271 on_actionSDMSingle_triggered();
272
273 segment_selected(segment_id);
274 }
275
276 void StandardBar::on_segment_display_mode_changed(int mode, bool segment_selectable)
277 {
278 segment_selector_->setReadOnly(!segment_selectable);
279
280 switch ((Trace::SegmentDisplayMode)mode) {
281 case Trace::ShowLastSegmentOnly:
282 segment_display_mode_selector_->setDefaultAction(action_sdm_last_);
283 break;
284 case Trace::ShowLastCompleteSegmentOnly:
285 segment_display_mode_selector_->setDefaultAction(action_sdm_last_complete_);
286 break;
287 case Trace::ShowSingleSegmentOnly:
288 segment_display_mode_selector_->setDefaultAction(action_sdm_single_);
289 break;
290 default:
291 break;
292 }
293 }
294
295 void StandardBar::on_cursor_state_changed(bool show)
296 {
297 action_view_show_cursors_->setChecked(show);
166298 }
167299
168300 } // namespace trace
2323 #include <cstdint>
2424
2525 #include <QAction>
26 #include <QSpinBox>
2627 #include <QToolBar>
28 #include <QToolButton>
2729 #include <QWidget>
2830
2931 #include <pv/session.hpp>
32
33 #include "trace.hpp"
3034
3135 namespace pv {
3236
5458 QAction* action_view_zoom_in() const;
5559 QAction* action_view_zoom_out() const;
5660 QAction* action_view_zoom_fit() const;
57 QAction* action_view_zoom_one_to_one() const;
5861 QAction* action_view_show_cursors() const;
5962
6063 protected:
6164 virtual void add_toolbar_widgets();
65
66 virtual void show_multi_segment_ui(const bool state);
6267
6368 Session &session_;
6469 trace::View *view_;
6671 QAction *const action_view_zoom_in_;
6772 QAction *const action_view_zoom_out_;
6873 QAction *const action_view_zoom_fit_;
69 QAction *const action_view_zoom_one_to_one_;
7074 QAction *const action_view_show_cursors_;
75
76 QToolButton *segment_display_mode_selector_;
77 QAction *const action_sdm_last_;
78 QAction *const action_sdm_last_complete_;
79 QAction *const action_sdm_single_;
80
81 QSpinBox *segment_selector_;
82
83 Q_SIGNALS:
84 void segment_selected(int segment_id);
7185
7286 protected Q_SLOTS:
7387 void on_actionViewZoomIn_triggered();
7690
7791 void on_actionViewZoomFit_triggered(bool checked);
7892
79 void on_actionViewZoomOneToOne_triggered();
93 void on_actionViewShowCursors_triggered();
94 void on_cursor_state_changed(bool show);
8095
81 void on_actionViewShowCursors_triggered();
96 void on_actionSDMLast_triggered();
97 void on_actionSDMLastComplete_triggered();
98 void on_actionSDMSingle_triggered();
8299
83100 void on_always_zoom_to_fit_changed(bool state);
101
102 void on_new_segment(int new_segment_id);
103 void on_segment_changed(int segment_id);
104 void on_segment_selected(int ui_segment_id);
105 void on_segment_display_mode_changed(int mode, bool segment_selectable);
106
107 private:
108 vector<QAction*> multi_segment_actions_;
84109 };
85110
86111 } // namespace trace
1616 * along with this program; if not, see <http://www.gnu.org/licenses/>.
1717 */
1818
19 #include "signal.hpp"
1920 #include "timeitem.hpp"
2021 #include "view.hpp"
2122
2930
3031 void TimeItem::drag_by(const QPoint &delta)
3132 {
32 set_time(view_.offset() + (drag_point_.x() + delta.x() - 0.5) *
33 view_.scale());
33 int64_t sample_num = view_.get_nearest_level_change(drag_point_ + delta);
34
35 if (sample_num > -1)
36 set_time(sample_num / view_.get_signal_under_mouse_cursor()->base()->get_samplerate());
37 else
38 set_time(view_.offset() + (drag_point_.x() + delta.x() - 0.5) *
39 view_.scale());
3440 }
3541
3642 } // namespace trace
2727
2828 class View;
2929
30 /**
31 * The TimeItem class represents items on the @ref Ruler. It is generic in
32 * nature, not making assumptions about the kind of item shown.
33 */
3034 class TimeItem : public ViewItem
31
3235 {
3336 Q_OBJECT
3437
4343 const int TimeMarker::ArrowSize = 4;
4444
4545 TimeMarker::TimeMarker(
46 View &view, const QColor &colour, const pv::util::Timestamp& time) :
46 View &view, const QColor &color, const pv::util::Timestamp& time) :
4747 TimeItem(view),
48 colour_(colour),
48 color_(color),
4949 time_(time),
5050 value_action_(nullptr),
5151 value_widget_(nullptr),
7777 return roundf(((time_ - view_.offset()) / view_.scale()).convert_to<float>()) + 0.5f;
7878 }
7979
80 QPoint TimeMarker::point(const QRect &rect) const
81 {
82 return QPoint(get_x(), rect.bottom());
80 QPoint TimeMarker::drag_point(const QRect &rect) const
81 {
82 (void)rect;
83
84 return QPoint(get_x(), view_.mapFromGlobal(QCursor::pos()).y());
8385 }
8486
8587 QRectF TimeMarker::label_rect(const QRectF &rect) const
139141 }
140142
141143 p.setPen(Qt::transparent);
142 p.setBrush(hover ? colour_.lighter() : colour_);
144 p.setBrush(hover ? color_.lighter() : color_);
143145 p.drawPolygon(points, countof(points));
144146
145 p.setPen(colour_.lighter());
147 p.setPen(color_.lighter());
146148 p.setBrush(Qt::transparent);
147149 p.drawPolygon(highlight_points, countof(highlight_points));
148150
149 p.setPen(colour_.darker());
151 p.setPen(color_.darker());
150152 p.setBrush(Qt::transparent);
151153 p.drawPolygon(points, countof(points));
152154
153 p.setPen(select_text_colour(colour_));
155 p.setPen(select_text_color(color_));
154156 p.drawText(r, Qt::AlignCenter | Qt::AlignVCenter, get_text());
155157 }
156158
160162 return;
161163
162164 const float x = get_x();
163 p.setPen(colour_.darker());
165 p.setPen(color_.darker());
164166 p.drawLine(QPointF(x, pp.top()), QPointF(x, pp.bottom()));
165167 }
166168
170172
171173 Popup *const popup = new Popup(parent);
172174 popup->set_position(parent->mapToGlobal(
173 point(parent->rect())), Popup::Bottom);
175 drag_point(parent->rect())), Popup::Bottom);
174176
175177 QFormLayout *const form = new QFormLayout(popup);
176178 popup->setLayout(form);
4040
4141 class View;
4242
43 /**
44 * The TimeMarker class represents items on the @ref Ruler that highlight a
45 * single point in time to the user. Aside from this, it is generic in nature.
46 */
4347 class TimeMarker : public TimeItem
4448 {
4549 Q_OBJECT
5155 /**
5256 * Constructor.
5357 * @param view A reference to the view that owns this marker.
54 * @param colour A reference to the colour of this cursor.
58 * @param color A reference to the color of this cursor.
5559 * @param time The time to set the flag to.
5660 */
57 TimeMarker(View &view, const QColor &colour, const pv::util::Timestamp& time);
61 TimeMarker(View &view, const QColor &color, const pv::util::Timestamp& time);
5862
5963 public:
6064 /**
7377 * Gets the arrow-tip point of the time marker.
7478 * @param rect the rectangle of the ruler area.
7579 */
76 QPoint point(const QRect &rect) const override;
80 QPoint drag_point(const QRect &rect) const override;
7781
7882 /**
7983 * Computes the outline rectangle of a label.
115119 void on_value_changed(const pv::util::Timestamp& value);
116120
117121 protected:
118 const QColor &colour_;
122 const QColor &color_;
119123
120124 pv::util::Timestamp time_;
121125
2525 #include <QFormLayout>
2626 #include <QKeyEvent>
2727 #include <QLineEdit>
28
28 #include <QMenu>
29
30 #include "ruler.hpp"
2931 #include "trace.hpp"
3032 #include "tracepalette.hpp"
3133 #include "view.hpp"
3234
3335 #include "pv/globalsettings.hpp"
34 #include "pv/widgets/colourbutton.hpp"
36 #include "pv/widgets/colorbutton.hpp"
3537 #include "pv/widgets/popup.hpp"
3638
3739 using std::pair;
4446 const QPen Trace::AxisPen(QColor(0, 0, 0, 30 * 256 / 100));
4547 const int Trace::LabelHitPadding = 2;
4648
47 const QColor Trace::BrightGrayBGColour = QColor(0, 0, 0, 10 * 255 / 100);
48 const QColor Trace::DarkGrayBGColour = QColor(0, 0, 0, 15 * 255 / 100);
49 const QColor Trace::BrightGrayBGColor = QColor(0, 0, 0, 10 * 255 / 100);
50 const QColor Trace::DarkGrayBGColor = QColor(0, 0, 0, 15 * 255 / 100);
4951
5052 Trace::Trace(shared_ptr<data::SignalBase> channel) :
5153 base_(channel),
54 axis_pen_(AxisPen),
55 segment_display_mode_(ShowLastSegmentOnly), // Will be overwritten by View
56 current_segment_(0),
5257 popup_(nullptr),
5358 popup_form_(nullptr)
5459 {
5560 connect(channel.get(), SIGNAL(name_changed(const QString&)),
5661 this, SLOT(on_name_changed(const QString&)));
57 connect(channel.get(), SIGNAL(colour_changed(const QColor&)),
58 this, SLOT(on_colour_changed(const QColor&)));
62 connect(channel.get(), SIGNAL(color_changed(const QColor&)),
63 this, SLOT(on_color_changed(const QColor&)));
64
65 GlobalSettings::add_change_handler(this);
66
67 GlobalSettings settings;
68 show_hover_marker_ =
69 settings.value(GlobalSettings::Key_View_ShowHoverMarker).toBool();
70 }
71
72 Trace::~Trace()
73 {
74 GlobalSettings::remove_change_handler(this);
5975 }
6076
6177 shared_ptr<data::SignalBase> Trace::base() const
6379 return base_;
6480 }
6581
82 bool Trace::is_selectable(QPoint pos) const
83 {
84 // True if the header was clicked, false if the trace area was clicked
85 const View *view = owner_->view();
86 assert(view);
87
88 return (pos.x() <= view->header_width());
89 }
90
91 bool Trace::is_draggable(QPoint pos) const
92 {
93 // While the header label that belongs to this trace is draggable,
94 // the trace itself shall not be. Hence we return true if the header
95 // was clicked and false if the trace area was clicked
96 const View *view = owner_->view();
97 assert(view);
98
99 return (pos.x() <= view->header_width());
100 }
101
102 void Trace::set_segment_display_mode(SegmentDisplayMode mode)
103 {
104 segment_display_mode_ = mode;
105
106 if (owner_)
107 owner_->row_item_appearance_changed(true, true);
108 }
109
110 void Trace::on_setting_changed(const QString &key, const QVariant &value)
111 {
112 if (key == GlobalSettings::Key_View_ShowHoverMarker)
113 show_hover_marker_ = value.toBool();
114
115 // Force a repaint since many options alter the way traces look
116 if (owner_)
117 owner_->row_item_appearance_changed(false, true);
118 }
119
66120 void Trace::paint_label(QPainter &p, const QRect &rect, bool hover)
67121 {
68122 const int y = get_visual_y();
69123
70 p.setBrush(base_->colour());
124 p.setBrush(base_->color());
71125
72126 if (!enabled())
73127 return;
101155 }
102156
103157 p.setPen(Qt::transparent);
104 p.setBrush(hover ? base_->colour().lighter() : base_->colour());
158 p.setBrush(hover ? base_->color().lighter() : base_->color());
105159 p.drawPolygon(points, countof(points));
106160
107 p.setPen(base_->colour().lighter());
161 p.setPen(base_->color().lighter());
108162 p.setBrush(Qt::transparent);
109163 p.drawPolygon(highlight_points, countof(highlight_points));
110164
111 p.setPen(base_->colour().darker());
165 p.setPen(base_->color().darker());
112166 p.setBrush(Qt::transparent);
113167 p.drawPolygon(points, countof(points));
114168
115169 // Paint the text
116 p.setPen(select_text_colour(base_->colour()));
170 p.setPen(select_text_color(base_->color()));
117171 p.setFont(QApplication::font());
118172 p.drawText(QRectF(r.x(), r.y(),
119173 r.width() - label_arrow_length, r.height()),
120174 Qt::AlignCenter | Qt::AlignVCenter, base_->name());
121175 }
122176
123 QMenu* Trace::create_context_menu(QWidget *parent)
124 {
125 QMenu *const menu = ViewItem::create_context_menu(parent);
177 QMenu* Trace::create_header_context_menu(QWidget *parent)
178 {
179 QMenu *const menu = ViewItem::create_header_context_menu(parent);
180
181 return menu;
182 }
183
184 QMenu* Trace::create_view_context_menu(QWidget *parent, QPoint &click_pos)
185 {
186 context_menu_x_pos_ = click_pos.x();
187
188 // Get entries from default menu before adding our own
189 QMenu *const menu = new QMenu(parent);
190
191 QMenu* default_menu = TraceTreeItem::create_view_context_menu(parent, click_pos);
192 if (default_menu) {
193 for (QAction *action : default_menu->actions()) { // clazy:exclude=range-loop
194 menu->addAction(action);
195 if (action->parent() == default_menu)
196 action->setParent(menu);
197 }
198 delete default_menu;
199
200 // Add separator if needed
201 if (menu->actions().length() > 0)
202 menu->addSeparator();
203 }
204
205 QAction *const create_marker_here = new QAction(tr("Create marker here"), this);
206 connect(create_marker_here, SIGNAL(triggered()), this, SLOT(on_create_marker_here()));
207 menu->addAction(create_marker_here);
126208
127209 return menu;
128210 }
133215
134216 popup_ = new Popup(parent);
135217 popup_->set_position(parent->mapToGlobal(
136 point(parent->rect())), Popup::Right);
218 drag_point(parent->rect())), Popup::Right);
137219
138220 create_popup_form();
139221
158240 label_size.height());
159241 }
160242
243 QRectF Trace::hit_box_rect(const ViewItemPaintParams &pp) const
244 {
245 // This one is only for the trace itself, excluding the header area
246 const View *view = owner_->view();
247 assert(view);
248
249 pair<int, int> extents = v_extents();
250 const int top = pp.top() + get_visual_y() + extents.first;
251 const int height = extents.second - extents.first;
252
253 return QRectF(pp.left() + view->header_width(), top,
254 pp.width() - view->header_width(), height);
255 }
256
257 void Trace::set_current_segment(const int segment)
258 {
259 current_segment_ = segment;
260 }
261
262 int Trace::get_current_segment() const
263 {
264 return current_segment_;
265 }
266
267 void Trace::hover_point_changed(const QPoint &hp)
268 {
269 (void)hp;
270
271 if (owner_)
272 owner_->row_item_appearance_changed(false, true);
273 }
274
161275 void Trace::paint_back(QPainter &p, ViewItemPaintParams &pp)
162276 {
163277 const View *view = owner_->view();
164278 assert(view);
165279
166 if (view->coloured_bg())
167 p.setBrush(base_->bgcolour());
280 if (view->colored_bg())
281 p.setBrush(base_->bgcolor());
168282 else
169 p.setBrush(pp.next_bg_colour_state() ? BrightGrayBGColour : DarkGrayBGColour);
283 p.setBrush(pp.next_bg_color_state() ? BrightGrayBGColor : DarkGrayBGColor);
170284
171285 p.setPen(QPen(Qt::NoPen));
172286
179293 {
180294 p.setRenderHint(QPainter::Antialiasing, false);
181295
182 p.setPen(AxisPen);
296 p.setPen(axis_pen_);
183297 p.drawLine(QPointF(pp.left(), y), QPointF(pp.right(), y));
184298
185299 p.setRenderHint(QPainter::Antialiasing, true);
186300 }
187301
188 void Trace::add_colour_option(QWidget *parent, QFormLayout *form)
189 {
190 using pv::widgets::ColourButton;
191
192 ColourButton *const colour_button = new ColourButton(
302 void Trace::add_color_option(QWidget *parent, QFormLayout *form)
303 {
304 using pv::widgets::ColorButton;
305
306 ColorButton *const color_button = new ColorButton(
193307 TracePalette::Rows, TracePalette::Cols, parent);
194 colour_button->set_palette(TracePalette::Colours);
195 colour_button->set_colour(base_->colour());
196 connect(colour_button, SIGNAL(selected(const QColor&)),
197 this, SLOT(on_colouredit_changed(const QColor&)));
198
199 form->addRow(tr("Colour"), colour_button);
308 color_button->set_palette(TracePalette::Colors);
309 color_button->set_color(base_->color());
310 connect(color_button, SIGNAL(selected(const QColor&)),
311 this, SLOT(on_coloredit_changed(const QColor&)));
312
313 form->addRow(tr("Color"), color_button);
314 }
315
316 void Trace::paint_hover_marker(QPainter &p)
317 {
318 const View *view = owner_->view();
319 assert(view);
320
321 const int x = view->hover_point().x();
322
323 if (x == -1)
324 return;
325
326 p.setPen(QPen(QColor(Qt::lightGray)));
327
328 const pair<int, int> extents = v_extents();
329
330 p.setRenderHint(QPainter::Antialiasing, false);
331 p.drawLine(x, get_visual_y() + extents.first,
332 x, get_visual_y() + extents.second);
333 p.setRenderHint(QPainter::Antialiasing, true);
200334 }
201335
202336 void Trace::create_popup_form()
227361 this, SLOT(on_nameedit_changed(const QString&)));
228362 form->addRow(tr("Name"), name_edit);
229363
230 add_colour_option(parent, form);
231 }
232
233 void Trace::set_name(QString name)
234 {
235 base_->set_name(name);
236 }
237
238 void Trace::set_colour(QColor colour)
239 {
240 base_->set_colour(colour);
364 add_color_option(parent, form);
241365 }
242366
243367 void Trace::on_name_changed(const QString &text)
251375 }
252376 }
253377
254 void Trace::on_colour_changed(const QColor &colour)
255 {
256 /* This event handler is called by SignalBase when the colour was changed there */
257 (void)colour;
378 void Trace::on_color_changed(const QColor &color)
379 {
380 /* This event handler is called by SignalBase when the color was changed there */
381 (void)color;
258382
259383 if (owner_)
260384 owner_->row_item_appearance_changed(true, true);
269393 void Trace::on_nameedit_changed(const QString &name)
270394 {
271395 /* This event handler notifies SignalBase that the name changed */
272 set_name(name);
273 }
274
275 void Trace::on_colouredit_changed(const QColor &colour)
276 {
277 /* This event handler notifies SignalBase that the colour changed */
278 set_colour(colour);
396 base_->set_name(name);
397 }
398
399 void Trace::on_coloredit_changed(const QColor &color)
400 {
401 /* This event handler notifies SignalBase that the color changed */
402 base_->set_color(color);
403 }
404
405 void Trace::on_create_marker_here() const
406 {
407 View *view = owner_->view();
408 assert(view);
409
410 const Ruler *ruler = view->ruler();
411 QPoint p = ruler->mapFrom(view, QPoint(context_menu_x_pos_, 0));
412
413 view->add_flag(ruler->get_time_from_x_pos(p.x()));
279414 }
280415
281416 } // namespace trace
2929
3030 #include "tracetreeitem.hpp"
3131
32 #include <pv/globalsettings.hpp>
3233 #include "pv/data/signalbase.hpp"
3334
3435 using std::shared_ptr;
4849 namespace views {
4950 namespace trace {
5051
51 class Trace : public TraceTreeItem
52 /**
53 * The Trace class represents a @ref TraceTreeItem which occupies some vertical
54 * space on the canvas and spans across its entire width, essentially showing
55 * a time series of values, events, objects or similar. While easily confused
56 * with @ref Signal, the difference is that Trace may represent anything that
57 * can be drawn, not just numeric values. One example is a @ref DecodeTrace.
58 *
59 * For this reason, Trace is more generic and contains properties and helpers
60 * that benefit any kind of time series items.
61 */
62 class Trace : public TraceTreeItem, public GlobalSettingsInterface
5263 {
5364 Q_OBJECT
65
66 public:
67 /**
68 * Allowed values for the multi-segment display mode.
69 *
70 * Note: Consider these locations when updating the list:
71 * *
72 * @ref View::set_segment_display_mode
73 * @ref View::on_segment_changed
74 * @ref AnalogSignal::get_analog_segment_to_paint
75 * @ref AnalogSignal::get_logic_segment_to_paint
76 * @ref LogicSignal::get_logic_segment_to_paint
77 * @ref StandardBar
78 */
79 enum SegmentDisplayMode {
80 ShowLastSegmentOnly = 1,
81 ShowLastCompleteSegmentOnly,
82 ShowSingleSegmentOnly,
83 ShowAllSegments,
84 ShowAccumulatedIntensity
85 };
5486
5587 private:
5688 static const QPen AxisPen;
5789 static const int LabelHitPadding;
5890
59 static const QColor BrightGrayBGColour;
60 static const QColor DarkGrayBGColour;
91 static const QColor BrightGrayBGColor;
92 static const QColor DarkGrayBGColor;
6193
6294 protected:
6395 Trace(shared_ptr<data::SignalBase> channel);
96 ~Trace();
6497
6598 public:
6699 /**
69102 shared_ptr<data::SignalBase> base() const;
70103
71104 /**
72 * Sets the name of the signal.
73 */
74 virtual void set_name(QString name);
75
76 /**
77 * Set the colour of the signal.
78 */
79 virtual void set_colour(QColor colour);
105 * Returns true if the item may be selected.
106 */
107 virtual bool is_selectable(QPoint pos) const;
108
109 /**
110 * Returns true if the item may be dragged/moved.
111 */
112 virtual bool is_draggable(QPoint pos) const;
113
114 /**
115 * Configures the segment display mode to use.
116 */
117 virtual void set_segment_display_mode(SegmentDisplayMode mode);
118
119 virtual void on_setting_changed(const QString &key, const QVariant &value);
80120
81121 /**
82122 * Paints the signal label.
86126 */
87127 virtual void paint_label(QPainter &p, const QRect &rect, bool hover);
88128
89 virtual QMenu* create_context_menu(QWidget *parent);
129 virtual QMenu* create_header_context_menu(QWidget *parent);
130
131 virtual QMenu* create_view_context_menu(QWidget *parent, QPoint &click_pos);
90132
91133 pv::widgets::Popup* create_popup(QWidget *parent);
92134
96138 * @return Returns the rectangle of the signal label.
97139 */
98140 QRectF label_rect(const QRectF &rect) const;
141
142 /**
143 * Computes the outline rectangle of the viewport hit-box.
144 * @param rect the rectangle of the viewport area.
145 * @return Returns the rectangle of the hit-box.
146 * @remarks The default implementation returns an empty hit-box.
147 */
148 virtual QRectF hit_box_rect(const ViewItemPaintParams &pp) const;
149
150 void set_current_segment(const int segment);
151
152 int get_current_segment() const;
153
154 virtual void hover_point_changed(const QPoint &hp);
99155
100156 protected:
101157 /**
113169 */
114170 void paint_axis(QPainter &p, ViewItemPaintParams &pp, int y);
115171
116 void add_colour_option(QWidget *parent, QFormLayout *form);
172 /**
173 * Draw a hover marker under the cursor position.
174 * @param p The painter to draw into.
175 */
176 void paint_hover_marker(QPainter &p);
177
178 void add_color_option(QWidget *parent, QFormLayout *form);
117179
118180 void create_popup_form();
119181
122184 protected Q_SLOTS:
123185 virtual void on_name_changed(const QString &text);
124186
125 virtual void on_colour_changed(const QColor &colour);
187 virtual void on_color_changed(const QColor &color);
126188
127189 void on_popup_closed();
128190
129191 private Q_SLOTS:
130192 void on_nameedit_changed(const QString &name);
131193
132 void on_colouredit_changed(const QColor &colour);
194 void on_coloredit_changed(const QColor &color);
195
196 void on_create_marker_here() const;
133197
134198 protected:
135199 shared_ptr<data::SignalBase> base_;
200 QPen axis_pen_;
201
202 SegmentDisplayMode segment_display_mode_;
203 bool show_hover_marker_;
204
205 uint32_t context_menu_x_pos_;
206
207 /// The ID of the currently displayed segment
208 int current_segment_;
136209
137210 private:
138211 pv::widgets::Popup *popup_;
3838 const int TraceGroup::Padding = 8;
3939 const int TraceGroup::Width = 12;
4040 const int TraceGroup::LineThickness = 5;
41 const QColor TraceGroup::LineColour(QColor(0x55, 0x57, 0x53));
41 const QColor TraceGroup::LineColor(QColor(0x55, 0x57, 0x53));
4242
4343 TraceGroup::~TraceGroup()
4444 {
8484 void TraceGroup::paint_label(QPainter &p, const QRect &rect, bool hover)
8585 {
8686 const QRectF r = label_rect(rect).adjusted(
87 LineThickness / 2, LineThickness / 2,
88 -LineThickness / 2, -LineThickness / 2);
87 LineThickness / 2.0, LineThickness / 2.0,
88 -LineThickness / 2.0, -LineThickness / 2.0);
8989
9090 // Paint the label
9191 const QPointF points[] = {
103103 p.drawPolyline(points, countof(points));
104104 }
105105
106 p.setPen(QPen(QBrush(LineColour.darker()), LineThickness,
106 p.setPen(QPen(QBrush(LineColor.darker()), LineThickness,
107107 Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin));
108108 p.drawPolyline(points, countof(points));
109 p.setPen(QPen(QBrush(hover ? LineColour.lighter() : LineColour),
109 p.setPen(QPen(QBrush(hover ? LineColor.lighter() : LineColor),
110110 LineThickness - 2, Qt::SolidLine, Qt::SquareCap,
111111 Qt::RoundJoin));
112112 p.drawPolyline(points, countof(points));
115115 QRectF TraceGroup::label_rect(const QRectF &rect) const
116116 {
117117 QRectF child_rect;
118 for (const shared_ptr<ViewItem> r : child_items())
118 for (const shared_ptr<ViewItem>& r : child_items())
119119 if (r && r->enabled())
120120 child_rect = child_rect.united(r->label_rect(rect));
121121
132132 return false;
133133 }
134134
135 QMenu* TraceGroup::create_context_menu(QWidget *parent)
135 QMenu* TraceGroup::create_header_context_menu(QWidget *parent)
136136 {
137137 QMenu *const menu = new QMenu(parent);
138138
155155 return owner_ ? visual_v_offset() + owner_->owner_visual_v_offset() : 0;
156156 }
157157
158 void TraceGroup::restack_items()
159 {
160 vector<shared_ptr<TraceTreeItem>> items(trace_tree_child_items());
161
162 // Sort by the centre line of the extents
163 stable_sort(items.begin(), items.end(),
164 [](const shared_ptr<TraceTreeItem> &a, const shared_ptr<TraceTreeItem> &b) {
165 const auto aext = a->v_extents();
166 const auto bext = b->v_extents();
167 return a->layout_v_offset() +
168 (aext.first + aext.second) / 2 <
169 b->layout_v_offset() +
170 (bext.first + bext.second) / 2;
171 });
172
173 int total_offset = 0;
174 for (shared_ptr<TraceTreeItem> r : items) {
175 const pair<int, int> extents = r->v_extents();
176 if (extents.first == 0 && extents.second == 0)
177 continue;
178
179 // We position disabled traces, so that they are close to the
180 // animation target positon should they be re-enabled
181 if (r->enabled())
182 total_offset += -extents.first;
183
184 if (!r->dragging())
185 r->set_layout_v_offset(total_offset);
186
187 if (r->enabled())
188 total_offset += extents.second;
189 }
190 }
191
192158 unsigned int TraceGroup::depth() const
193159 {
194160 return owner_ ? owner_->depth() + 1 : 0;
199165 const vector<shared_ptr<TraceTreeItem>> items(trace_tree_child_items());
200166 clear_child_items();
201167
202 for (shared_ptr<TraceTreeItem> r : items)
168 for (const shared_ptr<TraceTreeItem>& r : items)
203169 owner_->add_child_item(r);
204170
205171 owner_->remove_child_item(shared_from_this());
3636 static const int Padding;
3737 static const int Width;
3838 static const int LineThickness;
39 static const QColor LineColour;
39 static const QColor LineColor;
4040
4141 public:
4242 /**
101101 */
102102 bool pt_in_label_rect(int left, int right, const QPoint &point);
103103
104 QMenu* create_context_menu(QWidget *parent);
104 QMenu* create_header_context_menu(QWidget *parent);
105105
106106 pv::widgets::Popup* create_popup(QWidget *parent);
107107
109109 * Returns the total vertical offset of this trace and all it's owners
110110 */
111111 int owner_visual_v_offset() const;
112
113 void restack_items();
114112
115113 /**
116114 * Returns the number of nested parents that this row item owner has.
2222 namespace views {
2323 namespace trace {
2424
25 const QColor TracePalette::Colours[Cols * Rows] = {
25 const QColor TracePalette::Colors[Cols * Rows] = {
2626
27 // Light Colours
27 // Light Colors
2828 QColor(0xFC, 0xE9, 0x4F), // Butter
2929 QColor(0xFC, 0xAF, 0x3E), // Orange
3030 QColor(0xE9, 0xB9, 0x6E), // Chocolate
3434 QColor(0xCF, 0x72, 0xC3), // Magenta
3535 QColor(0xEF, 0x29, 0x29), // Scarlet Red
3636
37 // Mid Colours
37 // Mid Colors
3838 QColor(0xED, 0xD4, 0x00), // Butter
3939 QColor(0xF5, 0x79, 0x00), // Orange
4040 QColor(0xC1, 0x7D, 0x11), // Chocolate
4444 QColor(0xA3, 0x34, 0x96), // Magenta
4545 QColor(0xCC, 0x00, 0x00), // Scarlet Red
4646
47 // Dark Colours
47 // Dark Colors
4848 QColor(0xC4, 0xA0, 0x00), // Butter
4949 QColor(0xCE, 0x5C, 0x00), // Orange
5050 QColor(0x8F, 0x59, 0x02), // Chocolate
3030 public:
3131 static const unsigned int Cols = 8;
3232 static const unsigned int Rows = 4;
33 static const QColor Colours[Cols * Rows];
33 static const QColor Colors[Cols * Rows];
3434 };
3535
3636 } // namespace trace
132132 owner_->owner_visual_v_offset());
133133 }
134134
135 QPoint TraceTreeItem::point(const QRect &rect) const
135 QPoint TraceTreeItem::drag_point(const QRect &rect) const
136136 {
137137 return QPoint(rect.right(), get_visual_y());
138138 }
110110 * Gets the arrow-tip point of the row item marker.
111111 * @param rect the rectangle of the header area.
112112 */
113 QPoint point(const QRect &rect) const;
113 QPoint drag_point(const QRect &rect) const;
114114
115115 /**
116116 * Computes the vertical extents of the contents of this row item.
8888 bool has_children = false;
8989
9090 pair<int, int> extents(INT_MAX, INT_MIN);
91 for (const shared_ptr<TraceTreeItem> t : trace_tree_child_items()) {
91 for (const shared_ptr<TraceTreeItem>& t : trace_tree_child_items()) {
9292 assert(t);
9393 if (!t->enabled())
9494 continue;
111111
112112 void TraceTreeItemOwner::restack_items()
113113 {
114 vector<shared_ptr<TraceTreeItem>> items(trace_tree_child_items());
115
116 // Sort by the centre line of the extents
117 stable_sort(items.begin(), items.end(),
118 [](const shared_ptr<TraceTreeItem> &a, const shared_ptr<TraceTreeItem> &b) {
119 const auto aext = a->v_extents();
120 const auto bext = b->v_extents();
121 return a->layout_v_offset() +
122 (aext.first + aext.second) / 2 <
123 b->layout_v_offset() +
124 (bext.first + bext.second) / 2;
125 });
126
127 int total_offset = 0;
128 for (shared_ptr<TraceTreeItem> r : items) {
129 const pair<int, int> extents = r->v_extents();
130 if (extents.first == 0 && extents.second == 0)
131 continue;
132
133 // We position disabled traces, so that they are close to the
134 // animation target positon should they be re-enabled
135 if (r->enabled())
136 total_offset += -extents.first;
137
138 if (!r->dragging())
139 r->set_layout_v_offset(total_offset);
140
141 if (r->enabled())
142 total_offset += extents.second;
143 }
114144 }
115145
116146 } // namespace trace
2323 namespace views {
2424 namespace trace {
2525
26 const QColor TriggerMarker::Colour(0x00, 0x00, 0xB0);
26 const QColor TriggerMarker::Color(0x00, 0x00, 0xB0);
2727
2828 TriggerMarker::TriggerMarker(View &view, const pv::util::Timestamp& time) :
2929 TimeItem(view),
4242 return true;
4343 }
4444
45 bool TriggerMarker::is_draggable() const
45 bool TriggerMarker::is_draggable(QPoint pos) const
4646 {
47 (void)pos;
4748 return false;
4849 }
4950
5960 return ((time_ - view_.offset()) / view_.scale()).convert_to<float>();
6061 }
6162
62 QPoint TriggerMarker::point(const QRect &rect) const
63 QPoint TriggerMarker::drag_point(const QRect &rect) const
6364 {
64 return QPoint(get_x(), rect.bottom());
65 (void)rect;
66
67 // The trigger marker cannot be moved, so there is no drag point
68 return QPoint(INT_MIN, INT_MIN);
6569 }
6670
6771 void TriggerMarker::paint_fore(QPainter &p, ViewItemPaintParams &pp)
6973 if (!enabled())
7074 return;
7175
72 QPen pen(Colour);
76 QPen pen(Color);
7377 pen.setStyle(Qt::DashLine);
7478
7579 const float x = get_x();
2121
2222 #include "timeitem.hpp"
2323
24 #include <QPoint>
25
2426 namespace pv {
2527 namespace views {
2628 namespace trace {
2729
30 /**
31 * The TriggerMarker class is used to show to the user at what point in time
32 * a trigger occured. It is not editable by the user.
33 */
2834 class TriggerMarker : public TimeItem
2935 {
3036 Q_OBJECT
3137
3238 public:
33 static const QColor Colour;
39 static const QColor Color;
3440
3541 public:
3642 /**
5359 /**
5460 Returns true if the item may be dragged/moved.
5561 */
56 bool is_draggable() const override;
62 bool is_draggable(QPoint pos) const override;
5763
5864 /**
5965 * Sets the time of the marker.
6672 * Gets the arrow-tip point of the time marker.
6773 * @param rect the rectangle of the ruler area.
6874 */
69 QPoint point(const QRect &rect) const override;
75 QPoint drag_point(const QRect &rect) const override;
7076
7177 /**
7278 * Paints the foreground layer of the item with a QPainter
3737 #include <QApplication>
3838 #include <QEvent>
3939 #include <QFontMetrics>
40 #include <QMenu>
4041 #include <QMouseEvent>
4142 #include <QScrollBar>
4243 #include <QVBoxLayout>
7273 using std::back_inserter;
7374 using std::copy_if;
7475 using std::count_if;
75 using std::dynamic_pointer_cast;
7676 using std::inserter;
7777 using std::max;
7878 using std::make_pair;
7979 using std::make_shared;
8080 using std::min;
81 using std::numeric_limits;
8182 using std::pair;
8283 using std::set;
8384 using std::set_difference;
124125
125126 View::View(Session &session, bool is_main_view, QWidget *parent) :
126127 ViewBase(session, is_main_view, parent),
128
129 // Note: Place defaults in View::reset_view_state(), not here
127130 splitter_(new QSplitter()),
128 scale_(1e-3),
129 offset_(0),
130 updating_scroll_(false),
131 settings_restored_(false),
132 sticky_scrolling_(false), // Default setting is set in MainWindow::setup_ui()
133 always_zoom_to_fit_(false),
134 tick_period_(0),
135 tick_prefix_(pv::util::SIPrefix::yocto),
136 tick_precision_(0),
137 time_unit_(util::TimeUnit::Time),
138 show_cursors_(false),
139 cursors_(new CursorPair(*this)),
140 next_flag_text_('A'),
141 trigger_markers_(),
142 hover_point_(-1, -1),
143 scroll_needs_defaults_(true),
144 saved_v_offset_(0)
131 header_was_shrunk_(false), // The splitter remains unchanged after a reset, so this goes here
132 sticky_scrolling_(false) // Default setting is set in MainWindow::setup_ui()
145133 {
146134 QVBoxLayout *root_layout = new QVBoxLayout(this);
147135 root_layout->setContentsMargins(0, 0, 0, 0);
189177
190178 // Set up settings and event handlers
191179 GlobalSettings settings;
192 coloured_bg_ = settings.value(GlobalSettings::Key_View_ColouredBG).toBool();
180 colored_bg_ = settings.value(GlobalSettings::Key_View_ColoredBG).toBool();
181 snap_distance_ = settings.value(GlobalSettings::Key_View_SnapDistance).toInt();
182
183 GlobalSettings::add_change_handler(this);
193184
194185 connect(scrollarea_->horizontalScrollBar(), SIGNAL(valueChanged(int)),
195186 this, SLOT(h_scroll_value_changed(int)));
209200 connect(splitter_, SIGNAL(splitterMoved(int, int)),
210201 this, SLOT(on_splitter_moved()));
211202
212 connect(this, SIGNAL(hover_point_changed()),
213 this, SLOT(on_hover_point_changed()));
214
215203 connect(&lazy_event_handler_, SIGNAL(timeout()),
216204 this, SLOT(process_sticky_events()));
217205 lazy_event_handler_.setSingleShot(true);
224212 ruler_->raise();
225213 header_->raise();
226214
215 reset_view_state();
216 }
217
218 View::~View()
219 {
220 GlobalSettings::remove_change_handler(this);
221 }
222
223 void View::reset_view_state()
224 {
225 ViewBase::reset_view_state();
226
227 segment_display_mode_ = Trace::ShowLastSegmentOnly;
228 segment_selectable_ = false;
229 scale_ = 1e-3;
230 offset_ = 0;
231 ruler_offset_ = 0;
232 updating_scroll_ = false;
233 settings_restored_ = false;
234 always_zoom_to_fit_ = false;
235 tick_period_ = 0;
236 tick_prefix_ = pv::util::SIPrefix::yocto;
237 tick_precision_ = 0;
238 time_unit_ = util::TimeUnit::Time;
239 show_cursors_ = false;
240 cursors_ = make_shared<CursorPair>(*this);
241 next_flag_text_ = 'A';
242 trigger_markers_.clear();
243 hover_widget_ = nullptr;
244 hover_point_ = QPoint(-1, -1);
245 scroll_needs_defaults_ = true;
246 saved_v_offset_ = 0;
247 scale_at_acq_start_ = 0;
248 offset_at_acq_start_ = 0;
249 suppress_zoom_to_fit_after_acq_ = false;
250
251 show_cursors_ = false;
252 cursor_state_changed(show_cursors_);
253 flags_.clear();
254
227255 // Update the zoom state
228256 calculate_tick_spacing();
257
258 // Make sure the standard bar's segment selector is in sync
259 set_segment_display_mode(segment_display_mode_);
229260 }
230261
231262 Session& View::session()
253284 {
254285 ViewBase::add_signalbase(signal->base());
255286 signals_.insert(signal);
287
288 signal->set_segment_display_mode(segment_display_mode_);
289 signal->set_current_segment(current_segment_);
290
291 connect(signal->base().get(), SIGNAL(name_changed(const QString&)),
292 this, SLOT(on_signal_name_changed()));
256293 }
257294
258295 #ifdef ENABLE_DECODE
261298 decode_traces_.clear();
262299 }
263300
264 void View::add_decode_signal(shared_ptr<data::SignalBase> signalbase)
301 void View::add_decode_signal(shared_ptr<data::DecodeSignal> signal)
265302 {
266303 shared_ptr<DecodeTrace> d(
267 new DecodeTrace(session_, signalbase, decode_traces_.size()));
304 new DecodeTrace(session_, signal, decode_traces_.size()));
268305 decode_traces_.push_back(d);
269 }
270
271 void View::remove_decode_signal(shared_ptr<data::SignalBase> signalbase)
306
307 d->set_segment_display_mode(segment_display_mode_);
308 d->set_current_segment(current_segment_);
309
310 connect(signal.get(), SIGNAL(name_changed(const QString&)),
311 this, SLOT(on_signal_name_changed()));
312 }
313
314 void View::remove_decode_signal(shared_ptr<data::DecodeSignal> signal)
272315 {
273316 for (auto i = decode_traces_.begin(); i != decode_traces_.end(); i++)
274 if ((*i)->base() == signalbase) {
317 if ((*i)->base() == signal) {
275318 decode_traces_.erase(i);
276319 signals_changed();
277320 return;
279322 }
280323 #endif
281324
325 shared_ptr<Signal> View::get_signal_under_mouse_cursor() const
326 {
327 return signal_under_mouse_cursor_;
328 }
329
282330 View* View::view()
283331 {
284332 return this;
297345 const Viewport* View::viewport() const
298346 {
299347 return viewport_;
348 }
349
350 const Ruler* View::ruler() const
351 {
352 return ruler_;
300353 }
301354
302355 void View::save_settings(QSettings &settings) const
306359 scrollarea_->verticalScrollBar()->sliderPosition());
307360
308361 settings.setValue("splitter_state", splitter_->saveState());
309
310 stringstream ss;
311 boost::archive::text_oarchive oa(ss);
312 oa << boost::serialization::make_nvp("offset", offset_);
313 settings.setValue("offset", QString::fromStdString(ss.str()));
314
315 for (shared_ptr<Signal> signal : signals_) {
362 settings.setValue("segment_display_mode", segment_display_mode_);
363
364 {
365 stringstream ss;
366 boost::archive::text_oarchive oa(ss);
367 oa << boost::serialization::make_nvp("ruler_shift", ruler_shift_);
368 settings.setValue("ruler_shift", QString::fromStdString(ss.str()));
369 }
370 {
371 stringstream ss;
372 boost::archive::text_oarchive oa(ss);
373 oa << boost::serialization::make_nvp("offset", offset_);
374 settings.setValue("offset", QString::fromStdString(ss.str()));
375 }
376
377 for (const shared_ptr<Signal>& signal : signals_) {
316378 settings.beginGroup(signal->base()->internal_name());
317379 signal->save_settings(settings);
318380 settings.endGroup();
326388
327389 if (settings.contains("scale"))
328390 set_scale(settings.value("scale").toDouble());
391
392 if (settings.contains("ruler_shift")) {
393 util::Timestamp shift;
394 stringstream ss;
395 ss << settings.value("ruler_shift").toString().toStdString();
396
397 try {
398 boost::archive::text_iarchive ia(ss);
399 ia >> boost::serialization::make_nvp("ruler_shift", shift);
400 ruler_shift_ = shift;
401 } catch (boost::archive::archive_exception&) {
402 qDebug() << "Could not restore the view ruler shift";
403 }
404 }
329405
330406 if (settings.contains("offset")) {
331407 util::Timestamp offset;
332408 stringstream ss;
333409 ss << settings.value("offset").toString().toStdString();
334410
335 boost::archive::text_iarchive ia(ss);
336 ia >> boost::serialization::make_nvp("offset", offset);
337
338 set_offset(offset);
411 try {
412 boost::archive::text_iarchive ia(ss);
413 ia >> boost::serialization::make_nvp("offset", offset);
414 // This also updates ruler_offset_
415 set_offset(offset);
416 } catch (boost::archive::archive_exception&) {
417 qDebug() << "Could not restore the view offset";
418 }
339419 }
340420
341421 if (settings.contains("splitter_state"))
342422 splitter_->restoreState(settings.value("splitter_state").toByteArray());
423
424 if (settings.contains("segment_display_mode"))
425 set_segment_display_mode(
426 (Trace::SegmentDisplayMode)(settings.value("segment_display_mode").toInt()));
343427
344428 for (shared_ptr<Signal> signal : signals_) {
345429 settings.beginGroup(signal->base()->internal_name());
355439 }
356440
357441 settings_restored_ = true;
442 suppress_zoom_to_fit_after_acq_ = true;
443
444 // Update the ruler so that it uses the new scale
445 calculate_tick_spacing();
358446 }
359447
360448 vector< shared_ptr<TimeItem> > View::time_items() const
361449 {
362450 const vector<shared_ptr<Flag>> f(flags());
363451 vector<shared_ptr<TimeItem>> items(f.begin(), f.end());
364 items.push_back(cursors_);
365 items.push_back(cursors_->first());
366 items.push_back(cursors_->second());
367
368 for (auto trigger_marker : trigger_markers_)
452
453 if (cursors_) {
454 items.push_back(cursors_);
455 items.push_back(cursors_->first());
456 items.push_back(cursors_->second());
457 }
458
459 for (auto& trigger_marker : trigger_markers_)
369460 items.push_back(trigger_marker);
370461
371462 return items;
384475 }
385476 }
386477
478 void View::set_offset(const pv::util::Timestamp& offset, bool force_update)
479 {
480 if ((offset_ != offset) || force_update) {
481 offset_ = offset;
482 ruler_offset_ = offset_ + ruler_shift_;
483 offset_changed();
484 }
485 }
486
387487 const Timestamp& View::offset() const
388488 {
389489 return offset_;
390490 }
391491
392 void View::set_offset(const pv::util::Timestamp& offset)
393 {
394 if (offset_ != offset) {
395 offset_ = offset;
396 offset_changed();
397 }
492 const Timestamp& View::ruler_offset() const
493 {
494 return ruler_offset_;
495 }
496
497 void View::set_zero_position(const pv::util::Timestamp& position)
498 {
499 // ruler shift is a negative offset and the new zero position is relative
500 // to the current offset. Hence, we adjust the ruler shift only by the
501 // difference.
502 ruler_shift_ = -(position + (-ruler_shift_));
503
504 // Force an immediate update of the offsets
505 set_offset(offset_, true);
506 ruler_->update();
507 }
508
509 void View::reset_zero_position()
510 {
511 ruler_shift_ = 0;
512
513 // Force an immediate update of the offsets
514 set_offset(offset_, true);
515 ruler_->update();
398516 }
399517
400518 int View::owner_visual_v_offset() const
414532 return 0;
415533 }
416534
535 uint32_t View::current_segment() const
536 {
537 return current_segment_;
538 }
539
417540 pv::util::SIPrefix View::tick_prefix() const
418541 {
419542 return tick_prefix_;
445568 return tick_period_;
446569 }
447570
571 unsigned int View::minor_tick_count() const
572 {
573 return minor_tick_count_;
574 }
575
448576 void View::set_tick_period(const pv::util::Timestamp& tick_period)
449577 {
450578 if (tick_period_ != tick_period) {
464592 time_unit_ = time_unit;
465593 time_unit_changed();
466594 }
595 }
596
597 void View::set_current_segment(uint32_t segment_id)
598 {
599 current_segment_ = segment_id;
600
601 for (const shared_ptr<Signal>& signal : signals_)
602 signal->set_current_segment(current_segment_);
603 #ifdef ENABLE_DECODE
604 for (shared_ptr<DecodeTrace>& dt : decode_traces_)
605 dt->set_current_segment(current_segment_);
606 #endif
607
608 vector<util::Timestamp> triggers = session_.get_triggers(current_segment_);
609
610 trigger_markers_.clear();
611 for (util::Timestamp timestamp : triggers)
612 trigger_markers_.push_back(make_shared<TriggerMarker>(*this, timestamp));
613
614 // When enabled, the first trigger for this segment is used as the zero position
615 GlobalSettings settings;
616 bool trigger_is_zero_time = settings.value(GlobalSettings::Key_View_TriggerIsZeroTime).toBool();
617
618 if (trigger_is_zero_time && (triggers.size() > 0))
619 set_zero_position(triggers.front());
620
621 viewport_->update();
622
623 segment_changed(segment_id);
624 }
625
626 bool View::segment_is_selectable() const
627 {
628 return segment_selectable_;
629 }
630
631 Trace::SegmentDisplayMode View::segment_display_mode() const
632 {
633 return segment_display_mode_;
634 }
635
636 void View::set_segment_display_mode(Trace::SegmentDisplayMode mode)
637 {
638 segment_display_mode_ = mode;
639
640 for (const shared_ptr<Signal>& signal : signals_)
641 signal->set_segment_display_mode(mode);
642
643 uint32_t last_segment = session_.get_segment_count() - 1;
644
645 switch (mode) {
646 case Trace::ShowLastSegmentOnly:
647 if (current_segment_ != last_segment)
648 set_current_segment(last_segment);
649 break;
650
651 case Trace::ShowLastCompleteSegmentOnly:
652 // Do nothing if we only have one segment so far
653 if (last_segment > 0) {
654 // If the last segment isn't complete, the previous one must be
655 uint32_t segment_id =
656 (session_.all_segments_complete(last_segment)) ?
657 last_segment : last_segment - 1;
658
659 if (current_segment_ != segment_id)
660 set_current_segment(segment_id);
661 }
662 break;
663
664 case Trace::ShowSingleSegmentOnly:
665 case Trace::ShowAllSegments:
666 case Trace::ShowAccumulatedIntensity:
667 default:
668 // Current segment remains as-is
669 break;
670 }
671
672 segment_selectable_ = true;
673
674 if ((mode == Trace::ShowAllSegments) || (mode == Trace::ShowAccumulatedIntensity))
675 segment_selectable_ = false;
676
677 viewport_->update();
678
679 segment_display_mode_changed((int)mode, segment_selectable_);
467680 }
468681
469682 void View::zoom(double steps)
501714 set_scale_offset(scale.convert_to<double>(), extents.first);
502715 }
503716
504 void View::zoom_one_to_one()
505 {
506 using pv::data::SignalData;
507
508 // Make a set of all the visible data objects
509 set< shared_ptr<SignalData> > visible_data = get_visible_data();
510 if (visible_data.empty())
511 return;
512
513 assert(viewport_);
514 const int w = viewport_->width();
515 if (w <= 0)
516 return;
517
518 set_zoom(1.0 / session_.get_samplerate(), w / 2);
519 }
520
521717 void View::set_scale_offset(double scale, const Timestamp& offset)
522718 {
523719 // Disable sticky scrolling / always zoom to fit when acquisition runs
550746 {
551747 // Make a set of all the visible data objects
552748 set< shared_ptr<SignalData> > visible_data;
553 for (const shared_ptr<Signal> sig : signals_)
749 for (const shared_ptr<Signal>& sig : signals_)
554750 if (sig->enabled())
555751 visible_data.insert(sig->data());
556752
561757 {
562758 boost::optional<Timestamp> left_time, right_time;
563759 const set< shared_ptr<SignalData> > visible_data = get_visible_data();
564 for (const shared_ptr<SignalData> d : visible_data) {
760 for (const shared_ptr<SignalData>& d : visible_data) {
565761 const vector< shared_ptr<Segment> > segments = d->segments();
566 for (const shared_ptr<Segment> &s : segments) {
762 for (const shared_ptr<Segment>& s : segments) {
567763 double samplerate = s->samplerate();
568764 samplerate = (samplerate <= 0.0) ? 1.0 : samplerate;
569765
598794 viewport_->update();
599795 }
600796
601 void View::enable_coloured_bg(bool state)
602 {
603 coloured_bg_ = state;
797 void View::enable_colored_bg(bool state)
798 {
799 colored_bg_ = state;
604800 viewport_->update();
605801 }
606802
607 bool View::coloured_bg() const
608 {
609 return coloured_bg_;
803 bool View::colored_bg() const
804 {
805 return colored_bg_;
610806 }
611807
612808 bool View::cursors_shown() const
617813 void View::show_cursors(bool show)
618814 {
619815 show_cursors_ = show;
816 cursor_state_changed(show);
620817 ruler_->update();
621818 viewport_->update();
622819 }
623820
624821 void View::centre_cursors()
625822 {
626 const double time_width = scale_ * viewport_->width();
627 cursors_->first()->set_time(offset_ + time_width * 0.4);
628 cursors_->second()->set_time(offset_ + time_width * 0.6);
629 ruler_->update();
630 viewport_->update();
823 if (cursors_) {
824 const double time_width = scale_ * viewport_->width();
825 cursors_->first()->set_time(offset_ + time_width * 0.4);
826 cursors_->second()->set_time(offset_ + time_width * 0.6);
827
828 ruler_->update();
829 viewport_->update();
830 }
631831 }
632832
633833 shared_ptr<CursorPair> View::cursors() const
666866 const QPoint& View::hover_point() const
667867 {
668868 return hover_point_;
869 }
870
871 const QWidget* View::hover_widget() const
872 {
873 return hover_widget_;
874 }
875
876 int64_t View::get_nearest_level_change(const QPoint &p)
877 {
878 // Is snapping disabled?
879 if (snap_distance_ == 0)
880 return -1;
881
882 struct entry_t {
883 entry_t(shared_ptr<Signal> s) :
884 signal(s), delta(numeric_limits<int64_t>::max()), sample(-1), is_dense(false) {}
885 shared_ptr<Signal> signal;
886 int64_t delta;
887 int64_t sample;
888 bool is_dense;
889 };
890
891 vector<entry_t> list;
892
893 // Create list of signals to consider
894 if (signal_under_mouse_cursor_)
895 list.emplace_back(signal_under_mouse_cursor_);
896 else
897 for (shared_ptr<Signal> s : signals_) {
898 if (!s->enabled())
899 continue;
900
901 list.emplace_back(s);
902 }
903
904 // Get data for listed signals
905 for (entry_t &e : list) {
906 // Calculate sample number from cursor position
907 const double samples_per_pixel = e.signal->base()->get_samplerate() * scale();
908 const int64_t x_offset = offset().convert_to<double>() / scale();
909 const int64_t sample_num = max(((x_offset + p.x()) * samples_per_pixel), 0.0);
910
911 vector<data::LogicSegment::EdgePair> edges =
912 e.signal->get_nearest_level_changes(sample_num);
913
914 if (edges.empty())
915 continue;
916
917 // Check first edge
918 const int64_t first_sample_delta = abs(sample_num - edges.front().first);
919 const int64_t first_delta = first_sample_delta / samples_per_pixel;
920 e.delta = first_delta;
921 e.sample = edges.front().first;
922
923 // Check second edge if available
924 if (edges.size() == 2) {
925 // Note: -1 because this is usually the right edge and sample points are left-aligned
926 const int64_t second_sample_delta = abs(sample_num - edges.back().first - 1);
927 const int64_t second_delta = second_sample_delta / samples_per_pixel;
928
929 // If both edges are too close, we mark this signal as being dense
930 if ((first_delta + second_delta) <= snap_distance_)
931 e.is_dense = true;
932
933 if (second_delta < first_delta) {
934 e.delta = second_delta;
935 e.sample = edges.back().first;
936 }
937 }
938 }
939
940 // Look for the best match: non-dense first, then dense
941 entry_t *match = nullptr;
942
943 for (entry_t &e : list) {
944 if (e.delta > snap_distance_ || e.is_dense)
945 continue;
946
947 if (match) {
948 if (e.delta < match->delta)
949 match = &e;
950 } else
951 match = &e;
952 }
953
954 if (!match) {
955 for (entry_t &e : list) {
956 if (!e.is_dense)
957 continue;
958
959 if (match) {
960 if (e.delta < match->delta)
961 match = &e;
962 } else
963 match = &e;
964 }
965 }
966
967 if (match) {
968 // Somewhat ugly hack to make TimeItem::drag_by() work
969 signal_under_mouse_cursor_ = match->signal;
970
971 return match->sample;
972 }
973
974 return -1;
669975 }
670976
671977 void View::restack_all_trace_tree_items()
690996 i->animate_to_layout_v_offset();
691997 }
692998
693 void View::trigger_event(util::Timestamp location)
694 {
999 int View::header_width() const
1000 {
1001 return header_->extended_size_hint().width();
1002 }
1003
1004 void View::on_setting_changed(const QString &key, const QVariant &value)
1005 {
1006 if (key == GlobalSettings::Key_View_TriggerIsZeroTime)
1007 on_settingViewTriggerIsZeroTime_changed(value);
1008
1009 if (key == GlobalSettings::Key_View_SnapDistance) {
1010 GlobalSettings settings;
1011 snap_distance_ = settings.value(GlobalSettings::Key_View_SnapDistance).toInt();
1012 }
1013 }
1014
1015 void View::trigger_event(int segment_id, util::Timestamp location)
1016 {
1017 // TODO This doesn't work if we're showing multiple segments at once
1018 if ((uint32_t)segment_id != current_segment_)
1019 return;
1020
1021 // Set zero location if the Key_View_TriggerIsZeroTime setting is set and
1022 // if this is the first trigger for this segment.
1023 GlobalSettings settings;
1024 bool trigger_is_zero_time = settings.value(GlobalSettings::Key_View_TriggerIsZeroTime).toBool();
1025
1026 size_t trigger_count = session_.get_triggers(current_segment_).size();
1027
1028 if (trigger_is_zero_time && trigger_count == 1)
1029 set_zero_position(location);
1030
6951031 trigger_markers_.push_back(make_shared<TriggerMarker>(*this, location));
6961032 }
6971033
7561092 (ScaleUnits[unit++] + tp_margin);
7571093 } while (tp_with_margin < min_period && unit < countof(ScaleUnits));
7581094
1095 minor_tick_count_ = (unit == 2) ? 4 : 5;
7591096 tick_period = order_decimal * ScaleUnits[unit - 1];
7601097 tick_prefix = static_cast<pv::util::SIPrefix>(
7611098 (order - pv::util::exponent(pv::util::SIPrefix::yocto)) / 3);
8481185 vscrollbar->setRange(extents.first - areaSize.height(),
8491186 extents.second);
8501187
851 if (scroll_needs_defaults_)
1188 if (scroll_needs_defaults_) {
8521189 set_scroll_default();
1190 scroll_needs_defaults_ = false;
1191 }
8531192 }
8541193
8551194 void View::reset_scroll()
8761215 set_v_offset(extents.first);
8771216 }
8781217
879 bool View::header_was_shrunk() const
880 {
881 const int header_pane_width = splitter_->sizes().front();
882 const int header_width = header_->extended_size_hint().width();
1218 void View::determine_if_header_was_shrunk()
1219 {
1220 const int header_pane_width =
1221 splitter_->sizes().front(); // clazy:exclude=detaching-temporary
8831222
8841223 // Allow for a slight margin of error so that we also accept
8851224 // slight differences when e.g. a label name change increased
8861225 // the overall width
887 return (header_pane_width < (header_width - 10));
888 }
889
890 void View::expand_header_to_fit()
891 {
1226 header_was_shrunk_ = (header_pane_width < (header_width() - 10));
1227 }
1228
1229 void View::resize_header_to_fit()
1230 {
1231 // Setting the maximum width of the header widget doesn't work as
1232 // expected because the splitter would allow the user to make the
1233 // pane wider than that, creating empty space as a result.
1234 // To make this work, we stricly enforce the maximum width by
1235 // expanding the header unless the user shrunk it on purpose.
1236 // As we're then setting the width of the header pane, we set the
1237 // splitter to the maximum allowed position.
1238
8921239 int splitter_area_width = 0;
893 for (int w : splitter_->sizes())
1240 for (int w : splitter_->sizes()) // clazy:exclude=range-loop
8941241 splitter_area_width += w;
8951242
8961243 // Make sure the header has enough horizontal space to show all labels fully
9161263 vector<TraceTreeItemOwner*> owner_list;
9171264
9181265 // Make a set and a list of all the owners
919 for (const auto &channel : group->channels()) {
920 for (auto entry : signal_map) {
1266 for (const auto& channel : group->channels()) {
1267 for (auto& entry : signal_map) {
9211268 if (entry.first->channel() == channel) {
9221269 TraceTreeItemOwner *const o = (entry.second)->owner();
9231270 owner_list.push_back(o);
9501297 {
9511298 vector< shared_ptr<Trace> > filtered_traces;
9521299
953 for (const auto &channel : channels) {
954 for (auto entry : signal_map) {
1300 for (const auto& channel : channels) {
1301 for (auto& entry : signal_map) {
9551302 if (entry.first->channel() == channel) {
9561303 shared_ptr<Trace> trace = entry.second;
9571304 const auto list_iter = add_list.find(trace);
9721319 // Check whether we know the sample rate and hence can use time as the unit
9731320 if (time_unit_ == util::TimeUnit::Samples) {
9741321 // Check all signals but...
975 for (const shared_ptr<Signal> signal : signals_) {
1322 for (const shared_ptr<Signal>& signal : signals_) {
9761323 const shared_ptr<SignalData> data = signal->data();
9771324
9781325 // ...only check first segment of each
9911338 const QEvent::Type type = event->type();
9921339 if (type == QEvent::MouseMove) {
9931340
1341 if (object)
1342 hover_widget_ = qobject_cast<QWidget*>(object);
1343
9941344 const QMouseEvent *const mouse_event = (QMouseEvent*)event;
9951345 if (object == viewport_)
9961346 hover_point_ = mouse_event->pos();
9971347 else if (object == ruler_)
998 hover_point_ = QPoint(mouse_event->x(), 0);
1348 hover_point_ = mouse_event->pos();
9991349 else if (object == header_)
10001350 hover_point_ = QPoint(0, mouse_event->y());
10011351 else
10021352 hover_point_ = QPoint(-1, -1);
10031353
1004 hover_point_changed();
1354 update_hover_point();
10051355
10061356 } else if (type == QEvent::Leave) {
10071357 hover_point_ = QPoint(-1, -1);
1008 hover_point_changed();
1358 update_hover_point();
10091359 } else if (type == QEvent::Show) {
10101360
10111361 // This is somewhat of a hack, unfortunately. We cannot use
10171367 // resized to their final sizes.
10181368 update_layout();
10191369
1020 if (!settings_restored_)
1021 expand_header_to_fit();
1370 if (settings_restored_)
1371 determine_if_header_was_shrunk();
1372 else
1373 resize_header_to_fit();
10221374
10231375 if (scroll_needs_defaults_) {
10241376 set_scroll_default();
10341386 return QObject::eventFilter(object, event);
10351387 }
10361388
1389 void View::contextMenuEvent(QContextMenuEvent *event)
1390 {
1391 QPoint pos = event->pos() - QPoint(0, ruler_->sizeHint().height());
1392
1393 const shared_ptr<ViewItem> r = viewport_->get_mouse_over_item(pos);
1394 if (!r)
1395 return;
1396
1397 QMenu *menu = r->create_view_context_menu(this, pos);
1398 if (menu)
1399 menu->popup(event->globalPos());
1400 }
1401
10371402 void View::resizeEvent(QResizeEvent* event)
10381403 {
10391404 // Only adjust the top margin if we shrunk vertically
10411406 adjust_top_margin();
10421407
10431408 update_layout();
1409 }
1410
1411 void View::update_hover_point()
1412 {
1413 // Determine signal that the mouse cursor is hovering over
1414 signal_under_mouse_cursor_.reset();
1415 if (hover_widget_ == this) {
1416 for (const shared_ptr<Signal>& s : signals_) {
1417 const pair<int, int> extents = s->v_extents();
1418 const int top = s->get_visual_y() + extents.first;
1419 const int btm = s->get_visual_y() + extents.second;
1420 if ((hover_point_.y() >= top) && (hover_point_.y() <= btm)
1421 && s->base()->enabled())
1422 signal_under_mouse_cursor_ = s;
1423 }
1424 }
1425
1426 // Update all trace tree items
1427 const vector<shared_ptr<TraceTreeItem>> trace_tree_items(
1428 list_by_type<TraceTreeItem>());
1429 for (const shared_ptr<TraceTreeItem>& r : trace_tree_items)
1430 r->hover_point_changed(hover_point_);
1431
1432 // Notify any other listeners
1433 hover_point_changed(hover_widget_, hover_point_);
10441434 }
10451435
10461436 void View::row_item_appearance_changed(bool label, bool content)
10701460 (horz ? TraceTreeItemHExtentsChanged : 0) |
10711461 (vert ? TraceTreeItemVExtentsChanged : 0);
10721462
1463 lazy_event_handler_.stop();
10731464 lazy_event_handler_.start();
10741465 }
10751466
1467 void View::on_signal_name_changed()
1468 {
1469 if (!header_was_shrunk_)
1470 resize_header_to_fit();
1471 }
1472
10761473 void View::on_splitter_moved()
10771474 {
1078 // Setting the maximum width of the header widget doesn't work as
1079 // expected because the splitter would allow the user to make the
1080 // pane wider than that, creating empty space as a result.
1081 // To make this work, we stricly enforce the maximum width by
1082 // expanding the header unless the user shrunk it on purpose.
1083 // As we're then setting the width of the header pane, we set the
1084 // splitter to the maximum allowed position.
1085 if (!header_was_shrunk())
1086 expand_header_to_fit();
1475 // The header can only shrink when the splitter is moved manually
1476 determine_if_header_was_shrunk();
1477
1478 if (!header_was_shrunk_)
1479 resize_header_to_fit();
10871480 }
10881481
10891482 void View::h_scroll_value_changed(int value)
11241517
11251518 vector< shared_ptr<Channel> > channels;
11261519 shared_ptr<sigrok::Device> sr_dev;
1520 bool signals_added_or_removed = false;
11271521
11281522 // Do we need to set the vertical scrollbar to its default position later?
11291523 // We do if there are no traces, i.e. the scroll bar has no range set
11671561 // Make a look-up table of sigrok Channels to pulseview Signals
11681562 unordered_map<shared_ptr<data::SignalBase>, shared_ptr<Signal> >
11691563 signal_map;
1170 for (const shared_ptr<Signal> &sig : signals_)
1564 for (const shared_ptr<Signal>& sig : signals_)
11711565 signal_map[sig->base()] = sig;
11721566
11731567 // Populate channel groups
11741568 if (sr_dev)
1175 for (auto entry : sr_dev->channel_groups()) {
1569 for (auto& entry : sr_dev->channel_groups()) {
11761570 const shared_ptr<sigrok::ChannelGroup> &group = entry.second;
11771571
11781572 if (group->channels().size() <= 1)
11981592 // Add the traces to the group
11991593 const pair<int, int> prev_v_extents = owner->v_extents();
12001594 int offset = prev_v_extents.second - prev_v_extents.first;
1201 for (shared_ptr<Trace> trace : new_traces_in_group) {
1595 for (const shared_ptr<Trace>& trace : new_traces_in_group) {
12021596 assert(trace);
12031597 owner->add_child_item(trace);
12041598
12331627 if (non_grouped_logic_signals.size() > 0) {
12341628 const shared_ptr<TraceGroup> non_grouped_trace_group(
12351629 make_shared<TraceGroup>());
1236 for (shared_ptr<Trace> trace : non_grouped_logic_signals)
1630 for (const shared_ptr<Trace>& trace : non_grouped_logic_signals)
12371631 non_grouped_trace_group->add_child_item(trace);
12381632
12391633 non_grouped_trace_group->restack_items();
12511645 add_traces.begin(), add_traces.end());
12521646
12531647 // Remove any removed traces
1254 for (shared_ptr<Trace> trace : remove_traces) {
1648 for (const shared_ptr<Trace>& trace : remove_traces) {
12551649 TraceTreeItemOwner *const owner = trace->owner();
12561650 assert(owner);
12571651 owner->remove_child_item(trace);
1652 signals_added_or_removed = true;
12581653 }
12591654
12601655 // Remove any empty trace groups
12791674
12801675 if (item->enabled())
12811676 offset += extents.second;
1282 }
1283
1284
1285 if (!new_top_level_items.empty())
1286 // Expand the header pane because the header should become fully
1287 // visible when new signals are added
1288 expand_header_to_fit();
1677 signals_added_or_removed = true;
1678 }
1679
1680
1681 if (signals_added_or_removed && !header_was_shrunk_)
1682 resize_header_to_fit();
12891683
12901684 update_layout();
12911685
12981692
12991693 void View::capture_state_updated(int state)
13001694 {
1695 GlobalSettings settings;
1696
13011697 if (state == Session::Running) {
13021698 set_time_unit(util::TimeUnit::Samples);
13031699
13041700 trigger_markers_.clear();
1701
1702 scale_at_acq_start_ = scale_;
1703 offset_at_acq_start_ = offset_;
13051704
13061705 // Activate "always zoom to fit" if the setting is enabled and we're
13071706 // the main view of this session (other trace views may be used for
13081707 // zooming and we don't want to mess them up)
1309 GlobalSettings settings;
1310 bool state = settings.value(GlobalSettings::Key_View_AlwaysZoomToFit).toBool();
1708 bool state = settings.value(GlobalSettings::Key_View_ZoomToFitDuringAcq).toBool();
13111709 if (is_main_view_ && state) {
13121710 always_zoom_to_fit_ = true;
13131711 always_zoom_to_fit_changed(always_zoom_to_fit_);
13151713
13161714 // Enable sticky scrolling if the setting is enabled
13171715 sticky_scrolling_ = settings.value(GlobalSettings::Key_View_StickyScrolling).toBool();
1716
1717 // Reset all traces to segment 0
1718 current_segment_ = 0;
1719 set_current_segment(current_segment_);
13181720 }
13191721
13201722 if (state == Session::Stopped) {
13291731 always_zoom_to_fit_ = false;
13301732 always_zoom_to_fit_changed(always_zoom_to_fit_);
13311733 }
1332 }
1734
1735 bool zoom_to_fit_after_acq =
1736 settings.value(GlobalSettings::Key_View_ZoomToFitAfterAcq).toBool();
1737
1738 // Only perform zoom-to-fit if the user hasn't altered the viewport and
1739 // we didn't restore settings in the meanwhile
1740 if (zoom_to_fit_after_acq &&
1741 !suppress_zoom_to_fit_after_acq_ &&
1742 (scale_ == scale_at_acq_start_) &&
1743 (offset_ == offset_at_acq_start_))
1744 zoom_fit(false); // We're stopped, so the GUI state doesn't matter
1745
1746 suppress_zoom_to_fit_after_acq_ = false;
1747 }
1748 }
1749
1750 void View::on_new_segment(int new_segment_id)
1751 {
1752 on_segment_changed(new_segment_id);
1753 }
1754
1755 void View::on_segment_completed(int segment_id)
1756 {
1757 on_segment_changed(segment_id);
1758 }
1759
1760 void View::on_segment_changed(int segment)
1761 {
1762 switch (segment_display_mode_) {
1763 case Trace::ShowLastSegmentOnly:
1764 case Trace::ShowSingleSegmentOnly:
1765 set_current_segment(segment);
1766 break;
1767
1768 case Trace::ShowLastCompleteSegmentOnly:
1769 // Only update if all segments are complete
1770 if (session_.all_segments_complete(segment))
1771 set_current_segment(segment);
1772 break;
1773
1774 case Trace::ShowAllSegments:
1775 case Trace::ShowAccumulatedIntensity:
1776 default:
1777 break;
1778 }
1779 }
1780
1781 void View::on_settingViewTriggerIsZeroTime_changed(const QVariant new_value)
1782 {
1783 if (new_value.toBool()) {
1784 // The first trigger for this segment is used as the zero position
1785 vector<util::Timestamp> triggers = session_.get_triggers(current_segment_);
1786 if (triggers.size() > 0)
1787 set_zero_position(triggers.front());
1788 } else
1789 reset_zero_position();
13331790 }
13341791
13351792 void View::perform_delayed_view_update()
13671824 sticky_events_ = 0;
13681825 }
13691826
1370 void View::on_hover_point_changed()
1371 {
1372 const vector<shared_ptr<TraceTreeItem>> trace_tree_items(
1373 list_by_type<TraceTreeItem>());
1374 for (shared_ptr<TraceTreeItem> r : trace_tree_items)
1375 r->hover_point_changed();
1376 }
1377
13781827 } // namespace trace
13791828 } // namespace views
13801829 } // namespace pv
3030 #include <QSizeF>
3131 #include <QSplitter>
3232
33 #include <pv/globalsettings.hpp>
34 #include <pv/util.hpp>
3335 #include <pv/data/signaldata.hpp>
34 #include <pv/util.hpp>
3536 #include <pv/views/viewbase.hpp>
3637
3738 #include "cursorpair.hpp"
3839 #include "flag.hpp"
40 #include "trace.hpp"
3941 #include "tracetreeitemowner.hpp"
4042
4143 using std::list;
6163
6264 namespace trace {
6365
64 class CursorHeader;
6566 class DecodeTrace;
6667 class Header;
6768 class Ruler;
6869 class Signal;
69 class Trace;
7070 class Viewport;
7171 class TriggerMarker;
7272
7979 bool viewportEvent(QEvent *event);
8080 };
8181
82 class View : public ViewBase, public TraceTreeItemOwner
82 class View : public ViewBase, public TraceTreeItemOwner, public GlobalSettingsInterface
8383 {
8484 Q_OBJECT
8585
100100 public:
101101 explicit View(Session &session, bool is_main_view=false, QWidget *parent = nullptr);
102102
103 ~View();
104
105 /**
106 * Resets the view to its default state after construction. It does however
107 * not reset the signal bases or any other connections with the session.
108 */
109 virtual void reset_view_state();
110
103111 Session& session();
104112 const Session& session() const;
105113
115123 #ifdef ENABLE_DECODE
116124 virtual void clear_decode_signals();
117125
118 virtual void add_decode_signal(shared_ptr<data::SignalBase> signalbase);
119
120 virtual void remove_decode_signal(shared_ptr<data::SignalBase> signalbase);
126 virtual void add_decode_signal(shared_ptr<data::DecodeSignal> signal);
127
128 virtual void remove_decode_signal(shared_ptr<data::DecodeSignal> signal);
121129 #endif
122130
131 shared_ptr<Signal> get_signal_under_mouse_cursor() const;
132
123133 /**
124134 * Returns the view of the owner.
125135 */
134144
135145 const Viewport* viewport() const;
136146
147 const Ruler* ruler() const;
148
137149 virtual void save_settings(QSettings &settings) const;
138150
139151 virtual void restore_settings(QSettings &settings);
149161 double scale() const;
150162
151163 /**
152 * Returns the time offset of the left edge of the view in
153 * seconds.
164 * Returns the internal view version of the time offset of the left edge
165 * of the view in seconds.
154166 */
155167 const pv::util::Timestamp& offset() const;
156168
157169 /**
170 * Returns the ruler version of the time offset of the left edge
171 * of the view in seconds.
172 */
173 const pv::util::Timestamp& ruler_offset() const;
174
175 void set_zero_position(const pv::util::Timestamp& position);
176
177 void reset_zero_position();
178
179 /**
158180 * Returns the vertical scroll offset.
159181 */
160182 int owner_visual_v_offset() const;
180202 const pv::util::Timestamp& tick_period() const;
181203
182204 /**
205 * Returns number of minor division ticks per time marking.
206 */
207 unsigned int minor_tick_count() const;
208
209 /**
183210 * Returns the unit of time currently used.
184211 */
185212 util::TimeUnit time_unit() const;
188215 * Returns the number of nested parents that this row item owner has.
189216 */
190217 unsigned int depth() const;
218
219 /**
220 * Returns the currently displayed segment, starting at 0.
221 */
222 uint32_t current_segment() const;
223
224 /**
225 * Returns whether the currently shown segment can be influenced
226 * (selected) or not.
227 */
228 bool segment_is_selectable() const;
229
230 Trace::SegmentDisplayMode segment_display_mode() const;
231 void set_segment_display_mode(Trace::SegmentDisplayMode mode);
191232
192233 void zoom(double steps);
193234 void zoom(double steps, int offset);
194235
195236 void zoom_fit(bool gui_state);
196
197 void zoom_one_to_one();
198237
199238 /**
200239 * Sets the scale and offset.
208247 pair<pv::util::Timestamp, pv::util::Timestamp> get_time_extents() const;
209248
210249 /**
211 * Enables or disables coloured trace backgrounds. If they're not
212 * coloured then they will use alternating colors.
213 */
214 void enable_coloured_bg(bool state);
215
216 /**
217 * Returns true if the trace background should be drawn with a coloured background.
218 */
219 bool coloured_bg() const;
250 * Enables or disables colored trace backgrounds. If they're not
251 * colored then they will use alternating colors.
252 */
253 void enable_colored_bg(bool state);
254
255 /**
256 * Returns true if the trace background should be drawn with a colored background.
257 */
258 bool colored_bg() const;
220259
221260 /**
222261 * Enable or disable showing sampling points.
264303 vector< shared_ptr<Flag> > flags() const;
265304
266305 const QPoint& hover_point() const;
306 const QWidget* hover_widget() const;
307
308 /**
309 * Determines the closest level change (i.e. edge) to a given point, which
310 * is useful for e.g. the "snap to edge" functionality.
311 *
312 * @param p The current position of the mouse cursor
313 * @return The sample number of the nearest level change or -1 if none
314 */
315 int64_t get_nearest_level_change(const QPoint &p);
267316
268317 void restack_all_trace_tree_items();
269318
319 int header_width() const;
320
321 void on_setting_changed(const QString &key, const QVariant &value);
322
270323 Q_SIGNALS:
271 void hover_point_changed();
324 void hover_point_changed(const QWidget* widget, const QPoint &hp);
272325
273326 void selection_changed();
274327
294347 /// Emitted when the time_unit changed.
295348 void time_unit_changed();
296349
350 /// Emitted when the currently selected segment changed
351 void segment_changed(int segment_id);
352
353 /// Emitted when the multi-segment display mode changed
354 /// @param mode is a value of Trace::SegmentDisplayMode
355 void segment_display_mode_changed(int mode, bool segment_selectable);
356
357 /// Emitted when the cursors are shown/hidden
358 void cursor_state_changed(bool show);
359
297360 public Q_SLOTS:
298 void trigger_event(util::Timestamp location);
361 void trigger_event(int segment_id, util::Timestamp location);
299362
300363 private:
301364 void get_scroll_layout(double &length, pv::util::Timestamp &offset) const;
322385
323386 void set_scroll_default();
324387
325 bool header_was_shrunk() const;
326
327 void expand_header_to_fit();
388 void determine_if_header_was_shrunk();
389
390 void resize_header_to_fit();
328391
329392 void update_layout();
330393
344407
345408 bool eventFilter(QObject *object, QEvent *event);
346409
410 virtual void contextMenuEvent(QContextMenuEvent *event);
411
347412 void resizeEvent(QResizeEvent *event);
413
414 void update_hover_point();
348415
349416 public:
350417 void row_item_appearance_changed(bool label, bool content);
354421
355422 private Q_SLOTS:
356423
424 void on_signal_name_changed();
357425 void on_splitter_moved();
358426
359427 void h_scroll_value_changed(int value);
362430 void signals_changed();
363431 void capture_state_updated(int state);
364432
433 void on_new_segment(int new_segment_id);
434 void on_segment_completed(int new_segment_id);
435 void on_segment_changed(int segment);
436
437 void on_settingViewTriggerIsZeroTime_changed(const QVariant new_value);
438
365439 virtual void perform_delayed_view_update();
366440
367441 void process_sticky_events();
368442
369 void on_hover_point_changed();
370
371 /**
372 * Sets the 'offset_' member and emits the 'offset_changed'
443 /**
444 * Sets the 'offset_' and ruler_offset_ members and emits the 'offset_changed'
373445 * signal if needed.
374446 */
375 void set_offset(const pv::util::Timestamp& offset);
447 void set_offset(const pv::util::Timestamp& offset, bool force_update = false);
376448
377449 /**
378450 * Sets the 'scale_' member and emits the 'scale_changed'
403475 * signal if needed.
404476 */
405477 void set_time_unit(pv::util::TimeUnit time_unit);
478
479 /**
480 * Sets the current segment with the first segment starting at 0.
481 */
482 void set_current_segment(uint32_t segment_id);
406483
407484 private:
408485 CustomScrollArea *scrollarea_;
417494 vector< shared_ptr<DecodeTrace> > decode_traces_;
418495 #endif
419496
497 Trace::SegmentDisplayMode segment_display_mode_;
498
499 /// Signals whether the user can change the currently shown segment.
500 bool segment_selectable_;
501
420502 /// The view time scale in seconds per pixel.
421503 double scale_;
422504
423 /// The view time offset in seconds.
505 /// The internal view version of the time offset in seconds.
424506 pv::util::Timestamp offset_;
507 /// The ruler version of the time offset in seconds.
508 pv::util::Timestamp ruler_offset_;
425509
426510 bool updating_scroll_;
427511 bool settings_restored_;
512 bool header_was_shrunk_;
428513
429514 bool sticky_scrolling_;
430 bool coloured_bg_;
515 bool colored_bg_;
431516 bool always_zoom_to_fit_;
432517
433518 pv::util::Timestamp tick_period_;
434519 pv::util::SIPrefix tick_prefix_;
520 unsigned int minor_tick_count_;
435521 unsigned int tick_precision_;
436522 util::TimeUnit time_unit_;
437523
443529
444530 vector< shared_ptr<TriggerMarker> > trigger_markers_;
445531
532 QWidget* hover_widget_;
446533 QPoint hover_point_;
534 shared_ptr<Signal> signal_under_mouse_cursor_;
535 uint16_t snap_distance_;
447536
448537 unsigned int sticky_events_;
449538 QTimer lazy_event_handler_;
453542
454543 // A nonzero value indicates the v offset to restore. See View::resizeEvent()
455544 int saved_v_offset_;
545
546 // These are used to determine whether the view was altered after acq started
547 double scale_at_acq_start_;
548 pv::util::Timestamp offset_at_acq_start_;
549
550 // Used to suppress performing a "zoom to fit" when the session stops. This
551 // is needed when the view's settings are restored before acquisition ends.
552 // In that case we want to keep the restored settings, not have a "zoom to fit"
553 // mess them up.
554 bool suppress_zoom_to_fit_after_acq_;
456555 };
457556
458557 } // namespace trace
3838 {
3939 }
4040
41 bool ViewItem::is_selectable(QPoint pos) const
42 {
43 (void)pos;
44 return true;
45 }
46
4147 bool ViewItem::selected() const
4248 {
4349 return selected_;
4854 selected_ = select;
4955 }
5056
51 bool ViewItem::is_draggable() const
57 bool ViewItem::is_draggable(QPoint pos) const
5258 {
59 (void)pos;
5360 return true;
5461 }
5562
6067
6168 void ViewItem::drag()
6269 {
63 if (is_draggable())
64 drag_point_ = point(QRect());
70 drag_point_ = drag_point(QRect());
6571 }
6672
6773 void ViewItem::drag_release()
8187 return QRectF();
8288 }
8389
84 QMenu* ViewItem::create_context_menu(QWidget *parent)
90 QMenu* ViewItem::create_header_context_menu(QWidget *parent)
8591 {
8692 context_parent_ = parent;
8793 return new QMenu(parent);
94 }
95
96 QMenu* ViewItem::create_view_context_menu(QWidget *parent, QPoint &click_pos)
97 {
98 (void)parent;
99 (void)click_pos;
100 return nullptr;
88101 }
89102
90103 widgets::Popup* ViewItem::create_popup(QWidget *parent)
129142 (void)pp;
130143 }
131144
132 QColor ViewItem::select_text_colour(QColor background)
145 QColor ViewItem::select_text_color(QColor background)
133146 {
134147 return (background.lightness() > 110) ? Qt::black : Qt::white;
135148 }
2222 #include <list>
2323
2424 #include <QPen>
25 #include <QPoint>
2526
2627 #include "viewitempaintparams.hpp"
2728
5152 public:
5253 ViewItem();
5354
54 public:
5555 /**
5656 * Returns true if the item is visible and enabled.
5757 */
5858 virtual bool enabled() const = 0;
59
60 /**
61 * Returns true if the item may be selected.
62 */
63 virtual bool is_selectable(QPoint pos) const;
5964
6065 /**
6166 * Returns true if the item has been selected by the user.
7075 /**
7176 * Returns true if the item may be dragged/moved.
7277 */
73 virtual bool is_draggable() const;
78 virtual bool is_draggable(QPoint pos) const;
7479
7580 /**
7681 * Returns true if the item is being dragged.
97102 * Get the drag point.
98103 * @param rect the rectangle of the widget area.
99104 */
100 virtual QPoint point(const QRect &rect) const = 0;
105 virtual QPoint drag_point(const QRect &rect) const = 0;
101106
102107 /**
103108 * Computes the outline rectangle of a label.
144149 */
145150 virtual void paint_fore(QPainter &p, ViewItemPaintParams &pp);
146151
147 public:
148152 /**
149 * Gets the text colour.
150 * @remarks This colour is computed by comparing the lightness
151 * of the trace colour against a threshold to determine whether
153 * Gets the text color.
154 * @remarks This color is computed by comparing the lightness
155 * of the trace color against a threshold to determine whether
152156 * white or black would be more visible.
153157 */
154 static QColor select_text_colour(QColor background);
158 static QColor select_text_color(QColor background);
155159
156160 public:
157 virtual QMenu* create_context_menu(QWidget *parent);
161 virtual QMenu* create_header_context_menu(QWidget *parent);
162
163 virtual QMenu* create_view_context_menu(QWidget *parent, QPoint &click_pos);
158164
159165 virtual pv::widgets::Popup* create_popup(QWidget *parent);
160166
2626 namespace views {
2727 namespace trace {
2828
29 const ViewItemOwner::item_list& ViewItemOwner::child_items() const
30 {
31 return items_;
32 }
33
2934 ViewItemOwner::iterator ViewItemOwner::begin()
3035 {
3136 return iterator(this, items_.begin());
4949 /**
5050 * Returns a list of row items owned by this object.
5151 */
52 virtual const item_list& child_items() const = 0;
52 virtual const item_list& child_items() const;
5353
5454 /**
5555 * Returns a depth-first iterator at the beginning of the child ViewItem
3232 rect_(rect),
3333 scale_(scale),
3434 offset_(offset),
35 bg_colour_state_(false)
35 bg_color_state_(false)
3636 {
3737 assert(scale > 0.0);
3838 }
7474 return (offset_ / scale_).convert_to<double>();
7575 }
7676
77 bool next_bg_colour_state() {
78 const bool state = bg_colour_state_;
79 bg_colour_state_ = !bg_colour_state_;
77 bool next_bg_color_state() {
78 const bool state = bg_color_state_;
79 bg_color_state_ = !bg_color_state_;
8080 return state;
8181 }
8282
8989 QRect rect_;
9090 double scale_;
9191 pv::util::Timestamp offset_;
92 bool bg_colour_state_;
92 bool bg_color_state_;
9393 };
9494
9595 } // namespace trace
3636 using std::back_inserter;
3737 using std::copy;
3838 using std::dynamic_pointer_cast;
39 using std::none_of; // Used in assert()s.
39 using std::none_of; // NOLINT. Used in assert()s.
4040 using std::shared_ptr;
4141 using std::stable_sort;
4242 using std::vector;
6363 return nullptr;
6464 }
6565
66 void Viewport::item_hover(const shared_ptr<ViewItem> &item)
67 {
68 if (item && item->is_draggable())
66 void Viewport::item_hover(const shared_ptr<ViewItem> &item, QPoint pos)
67 {
68 if (item && item->is_draggable(pos))
6969 setCursor(dynamic_pointer_cast<RowItem>(item) ?
7070 Qt::SizeVerCursor : Qt::SizeHorCursor);
7171 else
164164
165165 stable_sort(row_items.begin(), row_items.end(),
166166 [](const shared_ptr<RowItem> &a, const shared_ptr<RowItem> &b) {
167 return a->point(QRect()).y() < b->point(QRect()).y(); });
167 return a->drag_point(QRect()).y() < b->drag_point(QRect()).y(); });
168168
169169 const vector< shared_ptr<TimeItem> > time_items(view_.time_items());
170170 assert(none_of(time_items.begin(), time_items.end(),
176176 for (LayerPaintFunc *paint_func = layer_paint_funcs;
177177 *paint_func; paint_func++) {
178178 ViewItemPaintParams time_pp(rect(), view_.scale(), view_.offset());
179 for (const shared_ptr<TimeItem> t : time_items)
179 for (const shared_ptr<TimeItem>& t : time_items)
180180 (t.get()->*(*paint_func))(p, time_pp);
181181
182182 ViewItemPaintParams row_pp(rect(), view_.scale(), view_.offset());
183 for (const shared_ptr<RowItem> r : row_items)
183 for (const shared_ptr<RowItem>& r : row_items)
184184 (r.get()->*(*paint_func))(p, row_pp);
185185 }
186186
2121
2222 #include <boost/optional.hpp>
2323
24 #include <QPoint>
2425 #include <QTimer>
2526 #include <QTouchEvent>
2627
4748 public:
4849 explicit Viewport(View &parent);
4950
50 private:
51 /**
52 * Indicates when a view item is being hovered over.
53 * @param item The item that is being hovered over, or @c nullptr
54 * if no view item is being hovered over.
55 */
56 void item_hover(const shared_ptr<ViewItem> &item);
57
5851 /**
5952 * Gets the first view item which has a hit-box that contains @c pt .
6053 * @param pt the point to search with.
6255 * @c shared_ptr if no item was found.
6356 */
6457 shared_ptr<ViewItem> get_mouse_over_item(const QPoint &pt);
58
59 private:
60 /**
61 * Indicates when a view item is being hovered over.
62 * @param item The item that is being hovered over, or @c nullptr
63 * if no view item is being hovered over.
64 */
65 void item_hover(const shared_ptr<ViewItem> &item, QPoint pos);
6566
6667 /**
6768 * Sets this item into the dragged state.
4949 i->select(false);
5050 }
5151
52 void ViewWidget::item_hover(const shared_ptr<ViewItem> &item)
52 void ViewWidget::item_hover(const shared_ptr<ViewItem> &item, QPoint pos)
5353 {
5454 (void)item;
55 (void)pos;
5556 }
5657
5758 void ViewWidget::item_clicked(const shared_ptr<ViewItem> &item)
7576 if (any_row_items_selected && !any_time_items_selected) {
7677 // Check all the drag items share a common owner
7778 TraceTreeItemOwner *item_owner = nullptr;
78 for (shared_ptr<TraceTreeItem> r : trace_tree_items)
79 for (const shared_ptr<TraceTreeItem>& r : trace_tree_items)
7980 if (r->dragging()) {
8081 if (!item_owner)
8182 item_owner = r->owner();
105106 // Drag the row items
106107 const vector< shared_ptr<RowItem> > row_items(
107108 view_.list_by_type<RowItem>());
108 for (shared_ptr<RowItem> r : row_items)
109 for (const shared_ptr<RowItem>& r : row_items)
109110 if (r->dragging()) {
110111 r->drag_by(delta);
111112
119120 TraceTreeItemOwner *item_owner = nullptr;
120121 const vector< shared_ptr<TraceTreeItem> > trace_tree_items(
121122 view_.list_by_type<TraceTreeItem>());
122 for (shared_ptr<TraceTreeItem> i : trace_tree_items)
123 for (const shared_ptr<TraceTreeItem>& i : trace_tree_items)
123124 if (i->dragging())
124125 item_owner = i->owner();
125126
168169 clear_selection();
169170
170171 // Set the signal selection state if the item has been clicked
171 if (mouse_down_item_) {
172 if (mouse_down_item_ && mouse_down_item_->is_selectable(event->pos())) {
172173 if (ctrl_pressed)
173174 mouse_down_item_->select(!mouse_down_item_->selected());
174175 else
179180 bool item_dragged = false;
180181 const auto items = this->items();
181182 for (auto &i : items)
182 if (i->selected()) {
183 if (i->selected() && i->is_draggable(event->pos())) {
183184 item_dragged = true;
184185 i->drag();
185186 }
250251 {
251252 assert(event);
252253
253 /* Ignore right click events as they will open context menus when
254 if (event->button() & Qt::LeftButton) {
255 mouse_down_point_ = event->pos();
256 mouse_down_item_ = get_mouse_over_item(event->pos());
257 mouse_left_press_event(event);
258 }
259
260 /* Don't forward right click events as they will open context menus when
254261 * used on trace labels. Those menus prevent ViewWidget::mouseReleaseEvent()
255262 * to be triggered upon button release, making mouse_down_item_
256263 * hold the last reference to a view item that might have been deleted
257264 * from the context menu, preventing it from being freed as intended.
265 * TODO Remove this once context menus are handled separately
258266 */
259 if (event->button() & Qt::LeftButton) {
267 if (event->button() & Qt::RightButton)
260268 mouse_down_point_ = event->pos();
261 mouse_down_item_ = get_mouse_over_item(event->pos());
262 mouse_left_press_event(event);
263 }
264269 }
265270
266271 void ViewWidget::mouseReleaseEvent(QMouseEvent *event)
280285 mouse_point_ = event->pos();
281286
282287 if (!event->buttons())
283 item_hover(get_mouse_over_item(event->pos()));
288 item_hover(get_mouse_over_item(event->pos()), event->pos());
284289 else if (event->buttons() & Qt::LeftButton) {
285290 if (!item_dragging_) {
286291 if ((event->pos() - mouse_down_point_).manhattanLength() <
2121
2222 #include <memory>
2323
24 #include <QPoint>
2425 #include <QWidget>
2526
2627 using std::shared_ptr;
4849 * if no view item is being hovered over.
4950 * @remarks the default implementation does nothing.
5051 */
51 virtual void item_hover(const shared_ptr<ViewItem> &item);
52 virtual void item_hover(const shared_ptr<ViewItem> &item, QPoint pos);
5253
5354 /**
5455 * Indicates the event an a view item has been clicked.
2525
2626 #include "pv/session.hpp"
2727 #include "pv/util.hpp"
28 #include "pv/data/segment.hpp"
2829
2930 using std::shared_ptr;
3031
3435 const int ViewBase::MaxViewAutoUpdateRate = 25; // No more than 25 Hz
3536
3637 ViewBase::ViewBase(Session &session, bool is_main_view, QWidget *parent) :
38 // Note: Place defaults in ViewBase::reset_view_state(), not here
3739 session_(session),
3840 is_main_view_(is_main_view)
3941 {
4345 this, SLOT(signals_changed()));
4446 connect(&session_, SIGNAL(capture_state_changed(int)),
4547 this, SLOT(capture_state_updated(int)));
48 connect(&session_, SIGNAL(new_segment(int)),
49 this, SLOT(on_new_segment(int)));
4650
4751 connect(&delayed_view_updater_, SIGNAL(timeout()),
4852 this, SLOT(perform_delayed_view_update()));
4953 delayed_view_updater_.setSingleShot(true);
5054 delayed_view_updater_.setInterval(1000 / MaxViewAutoUpdateRate);
55 }
56
57 void ViewBase::reset_view_state()
58 {
59 ruler_shift_ = 0;
60 current_segment_ = 0;
5161 }
5262
5363 Session& ViewBase::session()
7181
7282 void ViewBase::clear_signalbases()
7383 {
74 for (shared_ptr<data::SignalBase> signalbase : signalbases_) {
84 for (const shared_ptr<data::SignalBase>& signalbase : signalbases_) {
7585 disconnect(signalbase.get(), SIGNAL(samples_cleared()),
7686 this, SLOT(on_data_updated()));
77 disconnect(signalbase.get(), SIGNAL(samples_added(QObject*, uint64_t, uint64_t)),
78 this, SLOT(on_data_updated()));
87 disconnect(signalbase.get(), SIGNAL(samples_added(uint64_t, uint64_t, uint64_t)),
88 this, SLOT(on_samples_added(uint64_t, uint64_t, uint64_t)));
7989 }
8090
8191 signalbases_.clear();
8797
8898 connect(signalbase.get(), SIGNAL(samples_cleared()),
8999 this, SLOT(on_data_updated()));
90 connect(signalbase.get(), SIGNAL(samples_added(QObject*, uint64_t, uint64_t)),
91 this, SLOT(on_data_updated()));
100 connect(signalbase.get(), SIGNAL(samples_added(uint64_t, uint64_t, uint64_t)),
101 this, SLOT(on_samples_added(uint64_t, uint64_t, uint64_t)));
92102 }
93103
94104 #ifdef ENABLE_DECODE
96106 {
97107 }
98108
99 void ViewBase::add_decode_signal(shared_ptr<data::SignalBase> signalbase)
109 void ViewBase::add_decode_signal(shared_ptr<data::DecodeSignal> signal)
100110 {
101 (void)signalbase;
111 (void)signal;
102112 }
103113
104 void ViewBase::remove_decode_signal(shared_ptr<data::SignalBase> signalbase)
114 void ViewBase::remove_decode_signal(shared_ptr<data::DecodeSignal> signal)
105115 {
106 (void)signalbase;
116 (void)signal;
107117 }
108118 #endif
109119
117127 (void)settings;
118128 }
119129
120 void ViewBase::trigger_event(util::Timestamp location)
130 void ViewBase::trigger_event(int segment_id, util::Timestamp location)
121131 {
132 (void)segment_id;
122133 (void)location;
123134 }
124135
125136 void ViewBase::signals_changed()
126137 {
138 }
139
140 void ViewBase::on_new_segment(int new_segment_id)
141 {
142 (void)new_segment_id;
143 }
144
145 void ViewBase::on_segment_completed(int new_segment_id)
146 {
147 (void)new_segment_id;
127148 }
128149
129150 void ViewBase::capture_state_updated(int state)
135156 {
136157 }
137158
159 void ViewBase::on_samples_added(uint64_t segment_id, uint64_t start_sample,
160 uint64_t end_sample)
161 {
162 (void)start_sample;
163 (void)end_sample;
164
165 if (segment_id != current_segment_)
166 return;
167
168 if (!delayed_view_updater_.isActive())
169 delayed_view_updater_.start();
170 }
171
138172 void ViewBase::on_data_updated()
139173 {
140174 if (!delayed_view_updater_.isActive())
3131 #include <pv/data/signalbase.hpp>
3232 #include <pv/util.hpp>
3333
34 #ifdef ENABLE_DECODE
35 #include <pv/data/decodesignal.hpp>
36 #endif
37
3438 using std::shared_ptr;
3539 using std::unordered_set;
3640
6064 public:
6165 explicit ViewBase(Session &session, bool is_main_view = false, QWidget *parent = nullptr);
6266
67 /**
68 * Resets the view to its default state after construction. It does however
69 * not reset the signal bases or any other connections with the session.
70 */
71 virtual void reset_view_state();
72
6373 Session& session();
6474 const Session& session() const;
6575
7787 #ifdef ENABLE_DECODE
7888 virtual void clear_decode_signals();
7989
80 virtual void add_decode_signal(shared_ptr<data::SignalBase> signalbase);
90 virtual void add_decode_signal(shared_ptr<data::DecodeSignal> signal);
8191
82 virtual void remove_decode_signal(shared_ptr<data::SignalBase> signalbase);
92 virtual void remove_decode_signal(shared_ptr<data::DecodeSignal> signal);
8393 #endif
8494
8595 virtual void save_settings(QSettings &settings) const;
8797 virtual void restore_settings(QSettings &settings);
8898
8999 public Q_SLOTS:
90 virtual void trigger_event(util::Timestamp location);
100 virtual void trigger_event(int segment_id, util::Timestamp location);
91101 virtual void signals_changed();
92102 virtual void capture_state_updated(int state);
103 virtual void on_new_segment(int new_segment_id);
104 virtual void on_segment_completed(int new_segment_id);
93105 virtual void perform_delayed_view_update();
94106
95107 private Q_SLOTS:
108 void on_samples_added(uint64_t segment_id, uint64_t start_sample,
109 uint64_t end_sample);
110
96111 void on_data_updated();
97112
98113 protected:
100115
101116 const bool is_main_view_;
102117
118 util::Timestamp ruler_shift_;
103119 util::TimeUnit time_unit_;
104120
105121 unordered_set< shared_ptr<data::SignalBase> > signalbases_;
122
123 /// The ID of the currently displayed segment
124 uint32_t current_segment_;
106125
107126 QTimer delayed_view_updater_;
108127 };
0 /*
1 * This file is part of the PulseView project.
2 *
3 * Copyright (C) 2013 Joel Holdsworth <joel@airwebreathe.org.uk>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "colorbutton.hpp"
20
21 #include <cassert>
22
23 #include <QApplication>
24 #include <QColorDialog>
25 #include <QPainter>
26
27 namespace pv {
28 namespace widgets {
29
30 const int ColorButton::SwatchMargin = 7;
31
32 ColorButton::ColorButton(QWidget *parent) :
33 QPushButton("", parent),
34 popup_(nullptr)
35 {
36 connect(this, SIGNAL(clicked(bool)), this, SLOT(on_clicked(bool)));
37 }
38
39 ColorButton::ColorButton(int rows, int cols, QWidget *parent) :
40 QPushButton("", parent),
41 popup_(new ColorPopup(rows, cols, this))
42 {
43 connect(this, SIGNAL(clicked(bool)), this, SLOT(on_clicked(bool)));
44 connect(popup_, SIGNAL(selected(int, int)),
45 this, SLOT(on_selected(int, int)));
46 }
47
48 ColorPopup* ColorButton::popup()
49 {
50 return popup_;
51 }
52
53 const QColor& ColorButton::color() const
54 {
55 return cur_color_;
56 }
57
58 void ColorButton::set_color(QColor color)
59 {
60 cur_color_ = color;
61
62 if (popup_) {
63 const unsigned int rows = popup_->well_array().numRows();
64 const unsigned int cols = popup_->well_array().numCols();
65
66 for (unsigned int r = 0; r < rows; r++)
67 for (unsigned int c = 0; c < cols; c++)
68 if (popup_->well_array().cellBrush(r, c).color() == color) {
69 popup_->well_array().setSelected(r, c);
70 popup_->well_array().setCurrent(r, c);
71 return;
72 }
73 }
74 }
75
76 void ColorButton::set_palette(const QColor *const palette)
77 {
78 assert(palette);
79 assert(popup_);
80
81 const unsigned int rows = popup_->well_array().numRows();
82 const unsigned int cols = popup_->well_array().numCols();
83
84 for (unsigned int r = 0; r < rows; r++)
85 for (unsigned int c = 0; c < cols; c++)
86 popup_->well_array().setCellBrush(r, c, QBrush(palette[r * cols + c]));
87 }
88
89 void ColorButton::on_clicked(bool)
90 {
91 if (popup_) {
92 popup_->set_position(mapToGlobal(rect().center()), Popup::Bottom);
93 popup_->show();
94 } else {
95 QColorDialog dlg(this);
96 dlg.setOption(QColorDialog::ShowAlphaChannel);
97 dlg.setOption(QColorDialog::DontUseNativeDialog);
98 connect(&dlg, SIGNAL(colorSelected(const QColor)),
99 this, SLOT(on_color_selected(const QColor)));
100 dlg.setCurrentColor(cur_color_);
101 dlg.exec();
102 }
103 }
104
105 void ColorButton::on_selected(int row, int col)
106 {
107 assert(popup_);
108
109 cur_color_ = popup_->well_array().cellBrush(row, col).color();
110 selected(cur_color_);
111 }
112
113 void ColorButton::on_color_selected(const QColor& color)
114 {
115 cur_color_ = color;
116 selected(cur_color_);
117 }
118
119 void ColorButton::paintEvent(QPaintEvent *event)
120 {
121 QPushButton::paintEvent(event);
122
123 QPainter p(this);
124
125 const QRect r = rect().adjusted(SwatchMargin, SwatchMargin,
126 -SwatchMargin, -SwatchMargin);
127 p.setPen(QApplication::palette().color(QPalette::Dark));
128 p.setBrush(QBrush(cur_color_));
129 p.drawRect(r);
130 }
131
132 } // namespace widgets
133 } // namespace pv
0 /*
1 * This file is part of the PulseView project.
2 *
3 * Copyright (C) 2013 Joel Holdsworth <joel@airwebreathe.org.uk>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #ifndef PULSEVIEW_PV_WIDGETS_COLORBUTTON_HPP
20 #define PULSEVIEW_PV_WIDGETS_COLORBUTTON_HPP
21
22 #include <QPushButton>
23
24 #include "colorpopup.hpp"
25
26 namespace pv {
27 namespace widgets {
28
29 class ColorButton : public QPushButton
30 {
31 Q_OBJECT;
32
33 private:
34 static const int SwatchMargin;
35
36 public:
37 /**
38 * Construct a ColorButton instance that uses a QColorDialog
39 */
40 ColorButton(QWidget *parent);
41
42 /**
43 * Construct a ColorButton instance that uses a ColorPopup
44 */
45 ColorButton(int rows, int cols, QWidget *parent);
46
47 ColorPopup* popup();
48
49 const QColor& color() const;
50
51 void set_color(QColor color);
52
53 void set_palette(const QColor *const palette);
54
55 private:
56 void paintEvent(QPaintEvent *event);
57
58 private Q_SLOTS:
59 void on_clicked(bool);
60 void on_selected(int row, int col);
61 void on_color_selected(const QColor& color);
62
63 Q_SIGNALS:
64 void selected(const QColor &color);
65
66 private:
67 ColorPopup* popup_;
68 QColor cur_color_;
69 };
70
71 } // namespace widgets
72 } // namespace pv
73
74 #endif // PULSEVIEW_PV_WIDGETS_COLORBUTTON_HPP
0 /*
1 * This file is part of the PulseView project.
2 *
3 * Copyright (C) 2013 Joel Holdsworth <joel@airwebreathe.org.uk>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "colorpopup.hpp"
20
21 namespace pv {
22 namespace widgets {
23
24 ColorPopup::ColorPopup(int rows, int cols, QWidget *parent) :
25 Popup(parent),
26 well_array_(rows, cols, this),
27 layout_(this)
28 {
29 layout_.addWidget(&well_array_);
30 setLayout(&layout_);
31
32 connect(&well_array_, SIGNAL(selected(int, int)),
33 this, SIGNAL(selected(int, int)));
34 connect(&well_array_, SIGNAL(selected(int, int)),
35 this, SLOT(color_selected(int, int)));
36 }
37
38 WellArray& ColorPopup::well_array()
39 {
40 return well_array_;
41 }
42
43 void ColorPopup::color_selected(int, int)
44 {
45 close();
46 }
47
48 } // namespace widgets
49 } // namespace pv
0 /*
1 * This file is part of the PulseView project.
2 *
3 * Copyright (C) 2013 Joel Holdsworth <joel@airwebreathe.org.uk>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #ifndef PULSEVIEW_PV_WIDGETS_COLORPOPUP_HPP
20 #define PULSEVIEW_PV_WIDGETS_COLORPOPUP_HPP
21
22 #include "popup.hpp"
23 #include "wellarray.hpp"
24
25 #include <QVBoxLayout>
26
27 namespace pv {
28 namespace widgets {
29
30 class ColorPopup : public Popup
31 {
32 Q_OBJECT
33
34 public:
35 ColorPopup(int rows, int cols, QWidget *parent);
36
37 WellArray& well_array();
38
39 Q_SIGNALS:
40 void selected(int row, int col);
41
42 private Q_SLOTS:
43 void color_selected(int, int);
44
45 private:
46 WellArray well_array_;
47 QVBoxLayout layout_;
48 };
49
50 } // namespace widgets
51 } // namespace pv
52
53 #endif // PULSEVIEW_PV_WIDGETS_COLORPOPUP_HPP
+0
-106
pv/widgets/colourbutton.cpp less more
0 /*
1 * This file is part of the PulseView project.
2 *
3 * Copyright (C) 2013 Joel Holdsworth <joel@airwebreathe.org.uk>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "colourbutton.hpp"
20
21 #include <cassert>
22
23 #include <QApplication>
24 #include <QPainter>
25
26 namespace pv {
27 namespace widgets {
28
29 const int ColourButton::SwatchMargin = 7;
30
31 ColourButton::ColourButton(int rows, int cols, QWidget *parent) :
32 QPushButton("", parent),
33 popup_(rows, cols, this)
34 {
35 connect(this, SIGNAL(clicked(bool)), this, SLOT(on_clicked(bool)));
36 connect(&popup_, SIGNAL(selected(int, int)),
37 this, SLOT(on_selected(int, int)));
38 }
39
40 ColourPopup& ColourButton::popup()
41 {
42 return popup_;
43 }
44
45 const QColor& ColourButton::colour() const
46 {
47 return cur_colour_;
48 }
49
50 void ColourButton::set_colour(QColor colour)
51 {
52 cur_colour_ = colour;
53
54 const unsigned int rows = popup_.well_array().numRows();
55 const unsigned int cols = popup_.well_array().numCols();
56
57 for (unsigned int r = 0; r < rows; r++)
58 for (unsigned int c = 0; c < cols; c++)
59 if (popup_.well_array().cellBrush(r, c).color() == colour) {
60 popup_.well_array().setSelected(r, c);
61 popup_.well_array().setCurrent(r, c);
62 return;
63 }
64 }
65
66 void ColourButton::set_palette(const QColor *const palette)
67 {
68 assert(palette);
69
70 const unsigned int rows = popup_.well_array().numRows();
71 const unsigned int cols = popup_.well_array().numCols();
72
73 for (unsigned int r = 0; r < rows; r++)
74 for (unsigned int c = 0; c < cols; c++)
75 popup_.well_array().setCellBrush(r, c,
76 QBrush(palette[r * cols + c]));
77 }
78
79 void ColourButton::on_clicked(bool)
80 {
81 popup_.set_position(mapToGlobal(rect().center()), Popup::Bottom);
82 popup_.show();
83 }
84
85 void ColourButton::on_selected(int row, int col)
86 {
87 cur_colour_ = popup_.well_array().cellBrush(row, col).color();
88 selected(cur_colour_);
89 }
90
91 void ColourButton::paintEvent(QPaintEvent *event)
92 {
93 QPushButton::paintEvent(event);
94
95 QPainter p(this);
96
97 const QRect r = rect().adjusted(SwatchMargin, SwatchMargin,
98 -SwatchMargin, -SwatchMargin);
99 p.setPen(QApplication::palette().color(QPalette::Dark));
100 p.setBrush(QBrush(cur_colour_));
101 p.drawRect(r);
102 }
103
104 } // namespace widgets
105 } // namespace pv
+0
-67
pv/widgets/colourbutton.hpp less more
0 /*
1 * This file is part of the PulseView project.
2 *
3 * Copyright (C) 2013 Joel Holdsworth <joel@airwebreathe.org.uk>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #ifndef PULSEVIEW_PV_WIDGETS_COLOURBUTTON_HPP
20 #define PULSEVIEW_PV_WIDGETS_COLOURBUTTON_HPP
21
22 #include <QPushButton>
23
24 #include "colourpopup.hpp"
25
26 namespace pv {
27 namespace widgets {
28
29 class ColourButton : public QPushButton
30 {
31 Q_OBJECT;
32
33 private:
34 static const int SwatchMargin;
35
36 public:
37 ColourButton(int rows, int cols, QWidget *parent);
38
39 ColourPopup& popup();
40
41 const QColor& colour() const;
42
43 void set_colour(QColor colour);
44
45 void set_palette(const QColor *const palette);
46
47 private:
48 void paintEvent(QPaintEvent *event);
49
50 private Q_SLOTS:
51 void on_clicked(bool);
52
53 void on_selected(int row, int col);
54
55 Q_SIGNALS:
56 void selected(const QColor &colour);
57
58 private:
59 ColourPopup popup_;
60 QColor cur_colour_;
61 };
62
63 } // namespace widgets
64 } // namespace pv
65
66 #endif // PULSEVIEW_PV_WIDGETS_COLOURBUTTON_HPP
+0
-50
pv/widgets/colourpopup.cpp less more
0 /*
1 * This file is part of the PulseView project.
2 *
3 * Copyright (C) 2013 Joel Holdsworth <joel@airwebreathe.org.uk>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "colourpopup.hpp"
20
21 namespace pv {
22 namespace widgets {
23
24 ColourPopup::ColourPopup(int rows, int cols, QWidget *parent) :
25 Popup(parent),
26 well_array_(rows, cols, this),
27 layout_(this)
28 {
29 layout_.addWidget(&well_array_);
30 setLayout(&layout_);
31
32 connect(&well_array_, SIGNAL(selected(int, int)),
33 this, SIGNAL(selected(int, int)));
34 connect(&well_array_, SIGNAL(selected(int, int)),
35 this, SLOT(colour_selected(int, int)));
36 }
37
38 WellArray& ColourPopup::well_array()
39 {
40 return well_array_;
41 }
42
43 void ColourPopup::colour_selected(int, int)
44 {
45 close();
46 }
47
48 } // namespace widgets
49 } // namespace pv
+0
-54
pv/widgets/colourpopup.hpp less more
0 /*
1 * This file is part of the PulseView project.
2 *
3 * Copyright (C) 2013 Joel Holdsworth <joel@airwebreathe.org.uk>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #ifndef PULSEVIEW_PV_WIDGETS_COLOURPOPUP_HPP
20 #define PULSEVIEW_PV_WIDGETS_COLOURPOPUP_HPP
21
22 #include "popup.hpp"
23 #include "wellarray.hpp"
24
25 #include <QVBoxLayout>
26
27 namespace pv {
28 namespace widgets {
29
30 class ColourPopup : public Popup
31 {
32 Q_OBJECT
33
34 public:
35 ColourPopup(int rows, int cols, QWidget *parent);
36
37 WellArray& well_array();
38
39 Q_SIGNALS:
40 void selected(int row, int col);
41
42 private Q_SLOTS:
43 void colour_selected(int, int);
44
45 private:
46 WellArray well_array_;
47 QVBoxLayout layout_;
48 };
49
50 } // namespace widgets
51 } // namespace pv
52
53 #endif // PULSEVIEW_PV_WIDGETS_COLOURPOPUP_HPP
2929 QMenu(parent),
3030 mapper_(this)
3131 {
32 GSList *l = g_slist_sort(g_slist_copy(
32 GSList *li = g_slist_sort(g_slist_copy(
3333 (GSList*)srd_decoder_list()), decoder_name_cmp);
34 for (; l; l = l->next) {
34 for (GSList *l = li; l; l = l->next) {
3535 const srd_decoder *const d = (srd_decoder*)l->data;
3636 assert(d);
3737
4545 &mapper_, SLOT(map()));
4646 }
4747 }
48 g_slist_free(l);
48 g_slist_free(li);
4949
5050 connect(&mapper_, SIGNAL(mapped(QObject*)),
5151 this, SLOT(on_action(QObject*)));
9090 menu_.setDefaultAction(connect_action_);
9191 menu_.addSeparator();
9292
93 for (weak_ptr<Device> dev_weak_ptr : devices_) {
93 for (weak_ptr<Device>& dev_weak_ptr : devices_) {
9494 shared_ptr<Device> dev(dev_weak_ptr.lock());
9595 if (!dev)
9696 continue;
116116 selected_device_.reset();
117117
118118 Device *const dev = (Device*)((QAction*)action)->data().value<void*>();
119 for (weak_ptr<Device> dev_weak_ptr : devices_) {
119 for (weak_ptr<Device>& dev_weak_ptr : devices_) {
120120 shared_ptr<Device> dev_ptr(dev_weak_ptr);
121121 if (dev_ptr.get() == dev) {
122122 selected_device_ = shared_ptr<Device>(dev_ptr);
5252
5353 bool eventFilter(QObject *obj, QEvent *event);
5454
55 void show();
55 virtual void show();
5656
5757 private:
5858 bool space_for_arrow() const;
4646
4747 connect(&list_, SIGNAL(currentIndexChanged(int)),
4848 this, SIGNAL(value_changed()));
49 connect(&list_, SIGNAL(editTextChanged(const QString&)),
50 this, SIGNAL(value_changed()));
51
4952 connect(&value_, SIGNAL(editingFinished()),
5053 this, SIGNAL(value_changed()));
5154
5558 layout_.addWidget(&value_);
5659
5760 show_none();
61 }
62
63 void SweepTimingWidget::allow_user_entered_values(bool value)
64 {
65 list_.setEditable(value);
5866 }
5967
6068 void SweepTimingWidget::show_none()
143151 return (uint64_t)value_.value();
144152 case List:
145153 {
154 if (list_.isEditable()) {
155 uint64_t value;
156 sr_parse_sizestring(list_.currentText().toUtf8().data(), &value);
157 return value;
158 }
159
146160 const int index = list_.currentIndex();
147 return (index >= 0) ? list_.itemData(
148 index).value<uint64_t>() : 0;
161 return (index >= 0) ? list_.itemData(index).value<uint64_t>() : 0;
149162 }
150163 default:
151164 // Unexpected value type
158171 {
159172 value_.setValue(value);
160173
161 int best_match = list_.count() - 1;
162 int64_t best_variance = INT64_MAX;
174 if (list_.isEditable()) {
175 char *const s = sr_si_string_u64(value, suffix_);
176 list_.lineEdit()->setText(QString::fromUtf8(s));
177 g_free(s);
178 } else {
179 int best_match = list_.count() - 1;
180 int64_t best_variance = INT64_MAX;
163181
164 for (int i = 0; i < list_.count(); i++) {
165 const int64_t this_variance = abs(
166 (int64_t)value - list_.itemData(i).value<int64_t>());
167 if (this_variance < best_variance) {
168 best_variance = this_variance;
169 best_match = i;
182 for (int i = 0; i < list_.count(); i++) {
183 const int64_t this_variance = abs(
184 (int64_t)value - list_.itemData(i).value<int64_t>());
185 if (this_variance < best_variance) {
186 best_variance = this_variance;
187 best_match = i;
188 }
170189 }
190
191 list_.setCurrentIndex(best_match);
171192 }
172
173 list_.setCurrentIndex(best_match);
174193 }
175194
176195 } // namespace widgets
4545 public:
4646 SweepTimingWidget(const char *suffix, QWidget *parent = nullptr);
4747
48 void allow_user_entered_values(bool value);
49
4850 void show_none();
4951 void show_min_max_step(uint64_t min, uint64_t max, uint64_t step);
5052 void show_list(const uint64_t *vals, size_t count);
5151 class WellArray : public QWidget
5252 {
5353 Q_OBJECT
54 Q_PROPERTY(int selectedColumn READ selectedColumn)
55 Q_PROPERTY(int selectedRow READ selectedRow)
54 Q_PROPERTY(int selectedColumn READ selectedColumn) // clazy-exclude:qproperty-without-notify
55 Q_PROPERTY(int selectedRow READ selectedRow) // clazy-exclude:qproperty-without-notify
5656
5757 public:
5858 WellArray(int rows, int cols, QWidget* parent = nullptr);
1616 * along with this program; if not, see <http://www.gnu.org/licenses/>.
1717 */
1818
19 #ifndef SIGNALHANDLER_HPP
20 #define SIGNALHANDLER_HPP
19 #ifndef PULSEVIEW_PV_SIGNALHANDLER_HPP
20 #define PULSEVIEW_PV_SIGNALHANDLER_HPP
2121
2222 #include <QObject>
2323
5050 static int sockets_[2];
5151 };
5252
53 #endif // SIGNALHANDLER_HPP
53 #endif // PULSEVIEW_PV_SIGNALHANDLER_HPP
1818 ##
1919
2020 set(pulseview_TEST_SOURCES
21 ${PROJECT_SOURCE_DIR}/pv/application.cpp
2122 ${PROJECT_SOURCE_DIR}/pv/devicemanager.cpp
2223 ${PROJECT_SOURCE_DIR}/pv/globalsettings.cpp
24 ${PROJECT_SOURCE_DIR}/pv/logging.cpp
25 ${PROJECT_SOURCE_DIR}/pv/mainwindow.cpp
2326 ${PROJECT_SOURCE_DIR}/pv/session.cpp
2427 ${PROJECT_SOURCE_DIR}/pv/storesession.cpp
2528 ${PROJECT_SOURCE_DIR}/pv/util.cpp
4043 ${PROJECT_SOURCE_DIR}/pv/devices/sessionfile.cpp
4144 ${PROJECT_SOURCE_DIR}/pv/dialogs/connect.cpp
4245 ${PROJECT_SOURCE_DIR}/pv/dialogs/inputoutputoptions.cpp
46 ${PROJECT_SOURCE_DIR}/pv/dialogs/settings.cpp
4347 ${PROJECT_SOURCE_DIR}/pv/dialogs/storeprogress.cpp
4448 ${PROJECT_SOURCE_DIR}/pv/prop/bool.cpp
4549 ${PROJECT_SOURCE_DIR}/pv/prop/double.cpp
6064 ${PROJECT_SOURCE_DIR}/pv/views/trace/rowitem.cpp
6165 ${PROJECT_SOURCE_DIR}/pv/views/trace/ruler.cpp
6266 ${PROJECT_SOURCE_DIR}/pv/views/trace/signal.cpp
63 ${PROJECT_SOURCE_DIR}/pv/views/trace/signalscalehandle.cpp
6467 ${PROJECT_SOURCE_DIR}/pv/views/trace/timeitem.cpp
6568 ${PROJECT_SOURCE_DIR}/pv/views/trace/timemarker.cpp
6669 ${PROJECT_SOURCE_DIR}/pv/views/trace/trace.cpp
7780 ${PROJECT_SOURCE_DIR}/pv/views/trace/viewwidget.cpp
7881 ${PROJECT_SOURCE_DIR}/pv/views/viewbase.cpp
7982 ${PROJECT_SOURCE_DIR}/pv/views/trace/standardbar.cpp
80 ${PROJECT_SOURCE_DIR}/pv/widgets/colourbutton.cpp
81 ${PROJECT_SOURCE_DIR}/pv/widgets/colourpopup.cpp
83 ${PROJECT_SOURCE_DIR}/pv/widgets/colorbutton.cpp
84 ${PROJECT_SOURCE_DIR}/pv/widgets/colorpopup.cpp
8285 ${PROJECT_SOURCE_DIR}/pv/widgets/devicetoolbutton.cpp
8386 ${PROJECT_SOURCE_DIR}/pv/widgets/exportmenu.cpp
8487 ${PROJECT_SOURCE_DIR}/pv/widgets/importmenu.cpp
97100
98101 # This list includes only QObject derived class headers.
99102 set(pulseview_TEST_HEADERS
103 ${PROJECT_SOURCE_DIR}/pv/application.hpp
104 ${PROJECT_SOURCE_DIR}/pv/devicemanager.hpp
100105 ${PROJECT_SOURCE_DIR}/pv/globalsettings.hpp
106 ${PROJECT_SOURCE_DIR}/pv/logging.hpp
107 ${PROJECT_SOURCE_DIR}/pv/mainwindow.hpp
101108 ${PROJECT_SOURCE_DIR}/pv/session.hpp
102109 ${PROJECT_SOURCE_DIR}/pv/storesession.hpp
103110 ${PROJECT_SOURCE_DIR}/pv/binding/device.hpp
109116 ${PROJECT_SOURCE_DIR}/pv/devices/device.hpp
110117 ${PROJECT_SOURCE_DIR}/pv/dialogs/connect.hpp
111118 ${PROJECT_SOURCE_DIR}/pv/dialogs/inputoutputoptions.hpp
119 ${PROJECT_SOURCE_DIR}/pv/dialogs/settings.hpp
112120 ${PROJECT_SOURCE_DIR}/pv/dialogs/storeprogress.hpp
113121 ${PROJECT_SOURCE_DIR}/pv/popups/channels.hpp
114122 ${PROJECT_SOURCE_DIR}/pv/popups/deviceoptions.hpp
128136 ${PROJECT_SOURCE_DIR}/pv/views/trace/rowitem.hpp
129137 ${PROJECT_SOURCE_DIR}/pv/views/trace/ruler.hpp
130138 ${PROJECT_SOURCE_DIR}/pv/views/trace/signal.hpp
131 ${PROJECT_SOURCE_DIR}/pv/views/trace/signalscalehandle.hpp
132139 ${PROJECT_SOURCE_DIR}/pv/views/trace/timeitem.hpp
133140 ${PROJECT_SOURCE_DIR}/pv/views/trace/timemarker.hpp
134141 ${PROJECT_SOURCE_DIR}/pv/views/trace/trace.hpp
141148 ${PROJECT_SOURCE_DIR}/pv/views/trace/viewwidget.hpp
142149 ${PROJECT_SOURCE_DIR}/pv/views/viewbase.hpp
143150 ${PROJECT_SOURCE_DIR}/pv/views/trace/standardbar.hpp
144 ${PROJECT_SOURCE_DIR}/pv/widgets/colourbutton.hpp
145 ${PROJECT_SOURCE_DIR}/pv/widgets/colourpopup.hpp
151 ${PROJECT_SOURCE_DIR}/pv/widgets/colorbutton.hpp
152 ${PROJECT_SOURCE_DIR}/pv/widgets/colorpopup.hpp
146153 ${PROJECT_SOURCE_DIR}/pv/widgets/devicetoolbutton.hpp
147154 ${PROJECT_SOURCE_DIR}/pv/widgets/exportmenu.hpp
148155 ${PROJECT_SOURCE_DIR}/pv/widgets/importmenu.hpp
156163 if(ENABLE_DECODE)
157164 list(APPEND pulseview_TEST_SOURCES
158165 ${PROJECT_SOURCE_DIR}/pv/binding/decoder.cpp
159 ${PROJECT_SOURCE_DIR}/pv/data/decoderstack.cpp
166 ${PROJECT_SOURCE_DIR}/pv/data/decodesignal.cpp
160167 ${PROJECT_SOURCE_DIR}/pv/data/decode/annotation.cpp
161168 ${PROJECT_SOURCE_DIR}/pv/data/decode/decoder.cpp
162169 ${PROJECT_SOURCE_DIR}/pv/data/decode/row.cpp
164171 ${PROJECT_SOURCE_DIR}/pv/views/trace/decodetrace.cpp
165172 ${PROJECT_SOURCE_DIR}/pv/widgets/decodergroupbox.cpp
166173 ${PROJECT_SOURCE_DIR}/pv/widgets/decodermenu.cpp
167 data/decoderstack.cpp
168174 )
169175
170176 list(APPEND pulseview_TEST_HEADERS
171 ${PROJECT_SOURCE_DIR}/pv/data/decoderstack.hpp
177 ${PROJECT_SOURCE_DIR}/pv/data/decodesignal.hpp
172178 ${PROJECT_SOURCE_DIR}/pv/views/trace/decodetrace.hpp
173179 ${PROJECT_SOURCE_DIR}/pv/widgets/decodergroupbox.hpp
174180 ${PROJECT_SOURCE_DIR}/pv/widgets/decodermenu.hpp
3333 /* --- For debugging only
3434 BOOST_AUTO_TEST_CASE(SmallSize8Single)
3535 {
36 Segment s(1, sizeof(uint8_t));
36 Segment s(0, 1, sizeof(uint8_t));
3737 uint32_t num_samples = 10;
3838
3939 //----- Chunk size << pv::data::Segment::MaxChunkSize @ 8bit, added in 1 call ----//
4646
4747 BOOST_CHECK(s.get_sample_count() == num_samples);
4848
49 for (uint32_t i = 0; i < num_samples; i++) {
50 uint8_t* sample_data = s.get_raw_samples(i, 1);
49 uint8_t *sample_data = new uint8_t[1];
50 for (uint32_t i = 0; i < num_samples; i++) {
51 s.get_raw_samples(i, 1, sample_data);
5152 BOOST_CHECK_EQUAL(*sample_data, i);
52 delete[] sample_data;
53 }
53 }
54 delete[] sample_data;
5455 } */
5556
5657 /* --- For debugging only
5758 BOOST_AUTO_TEST_CASE(MediumSize8Single)
5859 {
59 Segment s(1, sizeof(uint8_t));
60 Segment s(0, 1, sizeof(uint8_t));
6061 uint32_t num_samples = pv::data::Segment::MaxChunkSize;
6162
6263 //----- Chunk size == pv::data::Segment::MaxChunkSize @ 8bit, added in 1 call ----//
6970
7071 BOOST_CHECK(s.get_sample_count() == num_samples);
7172
72 for (uint32_t i = 0; i < num_samples; i++) {
73 uint8_t* sample_data = s.get_raw_samples(i, 1);
73 uint8_t *sample_data = new uint8_t[1];
74 for (uint32_t i = 0; i < num_samples; i++) {
75 s.get_raw_samples(i, 1, sample_data);
7476 BOOST_CHECK_EQUAL(*sample_data, i % 256);
75 delete[] sample_data;
76 }
77 }
78 delete[] sample_data;
7779 } */
7880
7981 /* --- For debugging only
8082 BOOST_AUTO_TEST_CASE(MaxSize8Single)
8183 {
82 Segment s(1, sizeof(uint8_t));
84 Segment s(0, 1, sizeof(uint8_t));
8385
8486 // We want to see proper behavior across chunk boundaries
8587 uint32_t num_samples = 2*pv::data::Segment::MaxChunkSize;
9496
9597 BOOST_CHECK(s.get_sample_count() == num_samples);
9698
97 for (uint32_t i = 0; i < num_samples; i++) {
98 uint8_t* sample_data = s.get_raw_samples(i, 1);
99 uint8_t *sample_data = new uint8_t[1];
100 for (uint32_t i = 0; i < num_samples; i++) {
101 s.get_raw_samples(i, 1, sample_data);
99102 BOOST_CHECK_EQUAL(*sample_data, i % 256);
100 delete[] sample_data;
101 }
103 }
104 delete[] sample_data;
102105 } */
103106
104107 /* --- For debugging only
105108 BOOST_AUTO_TEST_CASE(MediumSize24Single)
106109 {
107 Segment s(1, 3);
110 Segment s(0, 1, 3);
108111
109112 // Chunk size is num*unit_size, so with pv::data::Segment::MaxChunkSize/unit_size, we reach the maximum size
110113 uint32_t num_samples = pv::data::Segment::MaxChunkSize / 3;
119122
120123 BOOST_CHECK(s.get_sample_count() == num_samples);
121124
122 for (uint32_t i = 0; i < num_samples; i++) {
123 uint8_t* sample_data = s.get_raw_samples(i, 1);
125 uint8_t *sample_data = new uint8_t[3];
126 for (uint32_t i = 0; i < num_samples; i++) {
127 s.get_raw_samples(i, 1, sample_data);
124128 BOOST_CHECK_EQUAL(*((uint8_t*)sample_data), 3*i % 256);
125129 BOOST_CHECK_EQUAL(*((uint8_t*)(sample_data+1)), (3*i+1) % 256);
126130 BOOST_CHECK_EQUAL(*((uint8_t*)(sample_data+2)), (3*i+2) % 256);
127 delete[] sample_data;
128 }
131 }
132 delete[] sample_data;
129133 } */
130134
131135 /* --- For debugging only
132136 BOOST_AUTO_TEST_CASE(MediumSize32Single)
133137 {
134 Segment s(1, sizeof(uint32_t));
138 Segment s(0, 1, sizeof(uint32_t));
135139
136140 // Chunk size is num*unit_size, so with pv::data::Segment::MaxChunkSize/unit_size, we reach the maximum size
137141 uint32_t num_samples = pv::data::Segment::MaxChunkSize / sizeof(uint32_t);
146150
147151 BOOST_CHECK(s.get_sample_count() == num_samples);
148152
149 for (uint32_t i = 0; i < num_samples; i++) {
150 uint8_t* sample_data = s.get_raw_samples(i, 1);
151 BOOST_CHECK_EQUAL(*((uint32_t*)sample_data), i);
152 delete[] sample_data;
153 }
153 uint8_t *sample_data = new uint8_t[sizeof(uint32_t)];
154 for (uint32_t i = 0; i < num_samples; i++) {
155 s.get_raw_samples(i, 1, sample_data);
156 BOOST_CHECK_EQUAL(*((uint32_t*)sample_data), i);
157 }
158 delete[] sample_data;
154159 } */
155160
156161 /* --- For debugging only
157162 BOOST_AUTO_TEST_CASE(MaxSize32Single)
158163 {
159 Segment s(1, sizeof(uint32_t));
164 Segment s(0, 1, sizeof(uint32_t));
160165
161166 // Chunk size is num*unit_size, so with pv::data::Segment::MaxChunkSize/unit_size, we reach the maximum size
162167 // Also, we want to see proper behavior across chunk boundaries
172177
173178 BOOST_CHECK(s.get_sample_count() == num_samples);
174179
175 for (uint32_t i = 0; i < num_samples; i++) {
176 uint8_t* sample_data = s.get_raw_samples(i, 1);
177 BOOST_CHECK_EQUAL(*((uint32_t*)sample_data), i);
178 delete[] sample_data;
179 }
180 uint8_t *sample_data = new uint8_t[sizeof(uint32_t)];
181 for (uint32_t i = 0; i < num_samples; i++) {
182 s.get_raw_samples(i, 1, sample_data);
183 BOOST_CHECK_EQUAL(*((uint32_t*)sample_data), i);
184 }
185 delete[] sample_data;
180186 } */
181187
182188 /* --- For debugging only
183189 BOOST_AUTO_TEST_CASE(MediumSize32Multi)
184190 {
185 Segment s(1, sizeof(uint32_t));
191 Segment s(0, 1, sizeof(uint32_t));
186192
187193 // Chunk size is num*unit_size, so with pv::data::Segment::MaxChunkSize/unit_size, we reach the maximum size
188194 uint32_t num_samples = pv::data::Segment::MaxChunkSize / sizeof(uint32_t);
196202
197203 BOOST_CHECK(s.get_sample_count() == num_samples);
198204
199 for (uint32_t i = 0; i < num_samples; i++) {
200 uint8_t* sample_data = s.get_raw_samples(i, 1);
201 BOOST_CHECK_EQUAL(*((uint32_t*)sample_data), i);
202 delete[] sample_data;
203 }
205 uint8_t *sample_data = new uint8_t[sizeof(uint32_t)];
206 for (uint32_t i = 0; i < num_samples; i++) {
207 s.get_raw_samples(i, 1, sample_data);
208 BOOST_CHECK_EQUAL(*((uint32_t*)sample_data), i);
209 }
210 delete[] sample_data;
204211 } */
205212
206213 BOOST_AUTO_TEST_CASE(MaxSize32Multi)
207214 {
208 Segment s(1, sizeof(uint32_t));
215 Segment s(0, 1, sizeof(uint32_t));
209216
210217 // Chunk size is num*unit_size, so with pv::data::Segment::MaxChunkSize/unit_size, we reach the maximum size
211218 uint32_t num_samples = 2*(pv::data::Segment::MaxChunkSize / sizeof(uint32_t));
219226
220227 BOOST_CHECK(s.get_sample_count() == num_samples);
221228
222 for (uint32_t i = 0; i < num_samples; i++) {
223 uint8_t* sample_data = s.get_raw_samples(i, 1);
224 BOOST_CHECK_EQUAL(*((uint32_t*)sample_data), i);
225 delete[] sample_data;
226 }
227
228 uint8_t* sample_data = s.get_raw_samples(0, num_samples);
229 uint8_t *sample_data = new uint8_t[sizeof(uint32_t) * num_samples];
230 for (uint32_t i = 0; i < num_samples; i++) {
231 s.get_raw_samples(i, 1, sample_data);
232 BOOST_CHECK_EQUAL(*((uint32_t*)sample_data), i);
233 }
234
235 s.get_raw_samples(0, num_samples, sample_data);
229236 for (uint32_t i = 0; i < num_samples; i++) {
230237 BOOST_CHECK_EQUAL(*((uint32_t*)(sample_data + i * sizeof(uint32_t))), i);
231238 }
234241
235242 BOOST_AUTO_TEST_CASE(MaxSize32MultiAtOnce)
236243 {
237 Segment s(1, sizeof(uint32_t));
244 Segment s(0, 1, sizeof(uint32_t));
238245
239246 // Chunk size is num*unit_size, so with pv::data::Segment::MaxChunkSize/unit_size, we reach the maximum size
240247 uint32_t num_samples = 3*(pv::data::Segment::MaxChunkSize / sizeof(uint32_t));
249256
250257 BOOST_CHECK(s.get_sample_count() == num_samples);
251258
252 for (uint32_t i = 0; i < num_samples; i++) {
253 uint8_t* sample_data = s.get_raw_samples(i, 1);
254 BOOST_CHECK_EQUAL(*((uint32_t*)sample_data), i);
255 delete[] sample_data;
256 }
257
258 uint8_t* sample_data = s.get_raw_samples(0, num_samples);
259 uint8_t *sample_data = new uint8_t[sizeof(uint32_t) * num_samples];
260 for (uint32_t i = 0; i < num_samples; i++) {
261 s.get_raw_samples(i, 1, sample_data);
262 BOOST_CHECK_EQUAL(*((uint32_t*)sample_data), i);
263 }
264
265 s.get_raw_samples(0, num_samples, sample_data);
259266 for (uint32_t i = 0; i < num_samples; i++) {
260267 BOOST_CHECK_EQUAL(*((uint32_t*)(sample_data + i * sizeof(uint32_t))), i);
261268 }
264271
265272 BOOST_AUTO_TEST_CASE(MaxSize32MultiIterated)
266273 {
267 Segment s(1, sizeof(uint32_t));
274 Segment s(0, 1, sizeof(uint32_t));
268275
269276 // Chunk size is num*unit_size, so with pv::data::Segment::MaxChunkSize/unit_size, we reach the maximum size
270277 uint32_t num_samples = 2*(pv::data::Segment::MaxChunkSize / sizeof(uint32_t));
278285
279286 BOOST_CHECK(s.get_sample_count() == num_samples);
280287
281 pv::data::SegmentRawDataIterator* it = s.begin_raw_sample_iteration(0);
282
283 for (uint32_t i = 0; i < num_samples; i++) {
284 uint8_t* sample_data = it->value;
285 BOOST_CHECK_EQUAL(*((uint32_t*)sample_data), i);
286 s.continue_raw_sample_iteration(it, 1);
287 }
288
289 s.end_raw_sample_iteration(it);
288 pv::data::SegmentDataIterator* it = s.begin_sample_iteration(0);
289
290 for (uint32_t i = 0; i < num_samples; i++) {
291 BOOST_CHECK_EQUAL(*((uint32_t*)s.get_iterator_value(it)), i);
292 s.continue_sample_iteration(it, 1);
293 }
294
295 s.end_sample_iteration(it);
290296 }
291297
292298 BOOST_AUTO_TEST_SUITE_END()
4141 const pv::util::Timestamp offset("0");
4242 const double scale(0.001);
4343 const int width(500);
44 const unsigned int minor_tick_count(4);
4445
45 const Ruler::TickPositions ts = Ruler::calculate_tick_positions(
46 major_period, offset, scale, width, format);
46 const TickPositions ts = Ruler::calculate_tick_positions(
47 major_period, offset, scale, width, minor_tick_count, format);
4748
4849 BOOST_REQUIRE_EQUAL(ts.major.size(), 6);
4950
8788 const pv::util::Timestamp offset("-0.463");
8889 const double scale(0.001);
8990 const int width(500);
91 const unsigned int minor_tick_count(4);
9092
91 const Ruler::TickPositions ts = Ruler::calculate_tick_positions(
92 major_period, offset, scale, width, format);
93 const TickPositions ts = Ruler::calculate_tick_positions(
94 major_period, offset, scale, width, minor_tick_count, format);
9395
9496 BOOST_REQUIRE_EQUAL(ts.major.size(), 5);
9597
131133 const pv::util::Timestamp offset("8");
132134 const double scale(0.129746);
133135 const int width(580);
136 const unsigned int minor_tick_count(4);
134137
135 const Ruler::TickPositions ts = Ruler::calculate_tick_positions(
136 major_period, offset, scale, width, format);
138 const TickPositions ts = Ruler::calculate_tick_positions(
139 major_period, offset, scale, width, minor_tick_count, format);
137140
138141 const double mp = 5;
139142 const int off = 8;
0 QScrollArea {
1 background-color: transparent; /* Workaround for widget background on Windows, see https://stackoverflow.com/questions/25012224/qt-widget-background-differs-from-linux-to-windows/25088075 */
2 }
3 QToolTip{
4 color:#ffffff;
5 background-color:palette(base);
6 border:1px solid palette(highlight);
7 border-radius:4px;
8 }
9 QStatusBar{
10 background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
11 color:palette(mid);
12 }
13 QMenuBar{
14 background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
15 border-bottom:2px solid rgba(25,25,25,75);
16 }
17 QMenuBar::item{
18 spacing:2px;
19 padding:3px 4px;
20 background:transparent;
21 }
22 QMenuBar::item:selected{
23 background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(106,106,106,255),stop:1 rgba(106,106,106,75));
24 border-left:1px solid rgba(106,106,106,127);
25 border-right:1px solid rgba(106,106,106,127);
26 }
27 QMenuBar::item:pressed{
28 background-color:palette(highlight);
29 border-left:1px solid rgba(25,25,25,127);
30 border-right:1px solid rgba(25,25,25,127);
31 }
32 QMenu{
33 background-color:palette(window);
34 border:1px solid palette(shadow);
35 }
36 QMenu::item{
37 padding:3px 25px 3px 25px;
38 border:1px solid transparent;
39 }
40 QMenu::item:disabled{
41 background-color:rgba(35,35,35,127);
42 color:palette(disabled);
43 }
44 QMenu::item:selected{
45 border-color:rgba(147,191,236,127);
46 background:palette(highlight);
47 }
48 QMenu::icon:checked{
49 background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
50 border:1px solid palette(highlight);
51 border-radius:2px;
52 }
53 QMenu::separator{
54 height:1px;
55 background:palette(alternate-base);
56 margin-left:5px;
57 margin-right:5px;
58 }
59 QMenu::indicator{
60 width:18px;
61 height:18px;
62 }
63 QMenu::indicator:non-exclusive:checked{
64 image:url(:/themes/darkstyle/icon_checkbox_checked.png);
65 padding-left:2px;
66 }
67 QMenu::indicator:non-exclusive:unchecked{
68 image:url(:/themes/darkstyle/icon_checkbox_unchecked.png);
69 padding-left:2px;
70 }
71 QMenu::indicator:exclusive:checked{
72 image:url(:/themes/darkstyle/icon_radiobutton_checked.png);
73 padding-left:2px;
74 }
75 QMenu::indicator:exclusive:unchecked{
76 image:url(:/themes/darkstyle/icon_radiobutton_unchecked.png);
77 padding-left:2px;
78 }
79 QToolBar::top{
80 background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
81 border-bottom:3px solid qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
82 }
83 QToolBar::bottom{
84 background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
85 border-top:3px solid qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
86 }
87 QToolBar::left{
88 background-color:qlineargradient(x1:0,y1:0,x2:1,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
89 border-right:3px solid qlineargradient(x1:0,y1:0,x2:1,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
90 }
91 QToolBar::right{
92 background-color:qlineargradient(x1:1,y1:0,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
93 border-left:3px solid qlineargradient(x1:1,y1:0,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
94 }
95 QMainWindow::separator{
96 width:6px;
97 height:5px;
98 padding:2px;
99 }
100 QSplitter::handle:horizontal{
101 width:10px;
102 }
103 QSplitter::handle:vertical{
104 height:10px;
105 }
106 QMainWindow::separator:hover,QSplitter::handle:hover{
107 background:palette(highlight);
108 }
109 QDockWidget::title{
110 padding:4px;
111 background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
112 border:1px solid rgba(25,25,25,75);
113 border-bottom:2px solid rgba(25,25,25,75);
114 }
115 QDockWidget{
116 titlebar-close-icon:url(:/themes/darkstyle/icon_close.png);
117 titlebar-normal-icon:url(:/themes/darkstyle/icon_restore.png);
118 }
119 QDockWidget::close-button,QDockWidget::float-button{
120 subcontrol-position:top right;
121 subcontrol-origin:margin;
122 position:absolute;
123 top:3px;
124 bottom:0px;
125 width:20px;
126 height:20px;
127 }
128 QDockWidget::close-button{
129 right:3px;
130 }
131 QDockWidget::float-button{
132 right:25px;
133 }
134 QGroupBox{
135 background-color:rgba(66,66,66,50%);
136 margin-top:27px;
137 border:1px solid rgba(25,25,25,127);
138 border-radius:4px;
139 }
140 QGroupBox::title{
141 subcontrol-origin:margin;
142 subcontrol-position:left top;
143 padding:4px 6px;
144 margin-left:3px;
145 background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
146 border:1px solid rgba(25,25,25,75);
147 border-bottom:2px solid rgb(127,127,127);
148 border-top-left-radius:4px;
149 border-top-right-radius:4px;
150 }
151 QTabWidget::pane{
152 background-color:rgba(66,66,66,50%);
153 border-top:1px solid rgba(25,25,25,50%);
154 }
155 QTabWidget::tab-bar{
156 left:3px;
157 top:1px;
158 }
159 QTabBar{
160 background-color:transparent;
161 qproperty-drawBase:0;
162 border-bottom:1px solid rgba(25,25,25,50%);
163 }
164 QTabBar::tab{
165 padding:4px 6px;
166 background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
167 border:1px solid rgba(25,25,25,75);
168 border-top-left-radius:4px;
169 border-top-right-radius:4px;
170 }
171 QTabBar::tab:selected,QTabBar::tab:hover{
172 background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(53,53,53,127),stop:1 rgba(66,66,66,50%));
173 border-bottom-color:rgba(66,66,66,75%);
174 }
175 QTabBar::tab:selected{
176 border-bottom:2px solid palette(highlight);
177 }
178 QTabBar::tab::selected:disabled{
179 border-bottom:2px solid rgb(127,127,127);
180 }
181 QTabBar::tab:!selected{
182 margin-top:2px;
183 }
184 QCheckBox::indicator{
185 width:18px;
186 height:18px;
187 }
188 QCheckBox::indicator:checked,QTreeView::indicator:checked,QTableView::indicator:checked,QGroupBox::indicator:checked{
189 image:url(:/themes/darkstyle/icon_checkbox_checked.png);
190 }
191 QCheckBox::indicator:checked:pressed,QTreeView::indicator:checked:pressed,QTableView::indicator:checked:pressed,QGroupBox::indicator:checked:pressed{
192 image:url(:/themes/darkstyle/icon_checkbox_checked_pressed.png);
193 }
194 QCheckBox::indicator:checked:disabled,QTreeView::indicator:checked:disabled,QTableView::indicator:checked:disabled,QGroupBox::indicator:checked:disabled{
195 image:url(:/themes/darkstyle/icon_checkbox_checked_disabled.png);
196 }
197 QCheckBox::indicator:unchecked,QTreeView::indicator:unchecked,QTableView::indicator:unchecked,QGroupBox::indicator:unchecked{
198 image:url(:/themes/darkstyle/icon_checkbox_unchecked.png);
199 }
200 QCheckBox::indicator:unchecked:pressed,QTreeView::indicator:unchecked:pressed,QTableView::indicator:unchecked:pressed,QGroupBox::indicator:unchecked:pressed{
201 image:url(:/themes/darkstyle/icon_checkbox_unchecked_pressed.png);
202 }
203 QCheckBox::indicator:unchecked:disabled,QTreeView::indicator:unchecked:disabled,QTableView::indicator:unchecked:disabled,QGroupBox::indicator:unchecked:disabled{
204 image:url(:/themes/darkstyle/icon_checkbox_unchecked_disabled.png);
205 }
206 QCheckBox::indicator:indeterminate,QTreeView::indicator:indeterminate,QTableView::indicator:indeterminate,QGroupBox::indicator:indeterminate{
207 image:url(:/themes/darkstyle/icon_checkbox_indeterminate.png);
208 }
209 QCheckBox::indicator:indeterminate:pressed,QTreeView::indicator:indeterminate:pressed,QTableView::indicator:indeterminate:pressed,QGroupBox::indicator:indeterminate:pressed{
210 image:url(:/themes/darkstyle/icon_checkbox_indeterminate_pressed.png);
211 }
212 QCheckBox::indicator:indeterminate:disabled,QTreeView::indicator:indeterminate:disabled,QTableView::indicator:indeterminate:disabled,QGroupBox::indicator:indeterminate:disabled{
213 image:url(:/themes/darkstyle/icon_checkbox_indeterminate_disabled.png);
214 }
215 QRadioButton::indicator{
216 width:18px;
217 height:18px;
218 }
219 QRadioButton::indicator:checked{
220 image:url(:/themes/darkstyle/icon_radiobutton_checked.png);
221 }
222 QRadioButton::indicator:checked:pressed{
223 image:url(:/themes/darkstyle/icon_radiobutton_checked_pressed.png);
224 }
225 QRadioButton::indicator:checked:disabled{
226 image:url(:/themes/darkstyle/icon_radiobutton_checked_disabled.png);
227 }
228 QRadioButton::indicator:unchecked{
229 image:url(:/themes/darkstyle/icon_radiobutton_unchecked.png);
230 }
231 QRadioButton::indicator:unchecked:pressed{
232 image:url(:/themes/darkstyle/icon_radiobutton_unchecked_pressed.png);
233 }
234 QRadioButton::indicator:unchecked:disabled{
235 image:url(:/themes/darkstyle/icon_radiobutton_unchecked_disabled.png);
236 }
237 QTreeView, QTableView{
238 alternate-background-color:palette(window);
239 background:palette(base);
240 }
241 QTreeView QHeaderView::section, QTableView QHeaderView::section{
242 background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75));
243 border-style:none;
244 border-bottom:1px solid palette(dark);
245 padding-left:5px;
246 padding-right:5px;
247 }
248 QTreeView::item:selected:disabled, QTableView::item:selected:disabled{
249 background:rgb(80,80,80);
250 }
251 QTreeView::branch{
252 background-color:palette(base);
253 }
254 QTreeView::branch:has-siblings:!adjoins-item{
255 border-image:url(:/themes/darkstyle/icon_vline.png) 0;
256 }
257 QTreeView::branch:has-siblings:adjoins-item{
258 border-image:url(:/themes/darkstyle/icon_branch_more.png) 0;
259 }
260 QTreeView::branch:!has-children:!has-siblings:adjoins-item{
261 border-image:url(:/themes/darkstyle/icon_branch_end.png) 0;
262 }
263 QTreeView::branch:has-children:!has-siblings:closed,
264 QTreeView::branch:closed:has-children:has-siblings{
265 border-image:none;
266 image:url(:/themes/darkstyle/icon_branch_closed.png);
267 }
268 QTreeView::branch:open:has-children:!has-siblings,
269 QTreeView::branch:open:has-children:has-siblings{
270 border-image:none;
271 image:url(:/themes/darkstyle/icon_branch_open.png);
272 }
273 QScrollBar:vertical{
274 background:palette(base);
275 border-top-right-radius:2px;
276 border-bottom-right-radius:2px;
277 width:16px;
278 margin:0px;
279 }
280 QScrollBar::handle:vertical{
281 background-color:palette(alternate-base);
282 border-radius:2px;
283 min-height:20px;
284 margin:2px 4px 2px 4px;
285 }
286 QScrollBar::handle:vertical:hover{
287 background-color:palette(highlight);
288 }
289 QScrollBar::add-line:vertical{
290 background:none;
291 height:0px;
292 subcontrol-position:right;
293 subcontrol-origin:margin;
294 }
295 QScrollBar::sub-line:vertical{
296 background:none;
297 height:0px;
298 subcontrol-position:left;
299 subcontrol-origin:margin;
300 }
301 QScrollBar:horizontal{
302 background:palette(base);
303 height:16px;
304 margin:0px;
305 }
306 QScrollBar::handle:horizontal{
307 background-color:palette(alternate-base);
308 border-radius:2px;
309 min-width:20px;
310 margin:4px 2px 4px 2px;
311 }
312 QScrollBar::handle:horizontal:hover{
313 background-color:palette(highlight);
314 }
315 QScrollBar::add-line:horizontal{
316 background:none;
317 width:0px;
318 subcontrol-position:bottom;
319 subcontrol-origin:margin;
320 }
321 QScrollBar::sub-line:horizontal{
322 background:none;
323 width:0px;
324 subcontrol-position:top;
325 subcontrol-origin:margin;
326 }
327 QSlider::handle:horizontal{
328 border-radius:4px;
329 border:1px solid rgba(25,25,25,255);
330 background-color:palette(alternate-base);
331 min-height:20px;
332 margin:0 -4px;
333 }
334 QSlider::handle:horizontal:hover{
335 background:palette(highlight);
336 }
337 QSlider::add-page:horizontal{
338 background:palette(base);
339 }
340 QSlider::sub-page:horizontal{
341 background:palette(highlight);
342 }
343 QSlider::sub-page:horizontal:disabled{
344 background:rgb(80,80,80);
345 }
0 QScrollArea {
1 background-color: transparent; /* Workaround for widget background on Windows, see https://stackoverflow.com/questions/25012224/qt-widget-background-differs-from-linux-to-windows/25088075 */
2 }
3
4 QToolTip {
5 border: 1px solid #76797C;
6 background-color: #5A7566;
7 color: white;
8 padding: 0px; /*remove padding, for fix combobox tooltip.*/
9 opacity: 200;
10 }
11
12 QWidget {
13 color: #eff0f1;
14 background-color: #31363b;
15 selection-background-color: #3daee9;
16 selection-color: #eff0f1;
17 background-clip: border;
18 border-image: none;
19 border: 0px transparent black;
20 outline: 0;
21 }
22
23 QWidget:item:hover {
24 background-color: #18465d;
25 color: #eff0f1;
26 }
27
28 QWidget:item:selected {
29 background-color: #18465d;
30 }
31
32 QCheckBox {
33 spacing: 5px;
34 outline: none;
35 color: #eff0f1;
36 margin-bottom: 2px;
37 }
38
39 QCheckBox:disabled {
40 color: #76797C;
41 }
42
43 QCheckBox::indicator,
44 QGroupBox::indicator {
45 width: 18px;
46 height: 18px;
47 }
48
49 QGroupBox::indicator {
50 margin-left: 2px;
51 }
52
53 QCheckBox::indicator:unchecked,
54 QGroupBox::indicator:unchecked {
55 image: url(:/themes/qdarkstyle/rc/checkbox_unchecked.png);
56 }
57
58 QCheckBox::indicator:unchecked:hover,
59 QCheckBox::indicator:unchecked:focus,
60 QCheckBox::indicator:unchecked:pressed,
61 QGroupBox::indicator:unchecked:hover,
62 QGroupBox::indicator:unchecked:focus,
63 QGroupBox::indicator:unchecked:pressed {
64 border: none;
65 image: url(:/themes/qdarkstyle/rc/checkbox_unchecked_focus.png);
66 }
67
68 QCheckBox::indicator:checked,
69 QGroupBox::indicator:checked {
70 image: url(:/themes/qdarkstyle/rc/checkbox_checked.png);
71 }
72
73 QCheckBox::indicator:checked:hover,
74 QCheckBox::indicator:checked:focus,
75 QCheckBox::indicator:checked:pressed,
76 QGroupBox::indicator:checked:hover,
77 QGroupBox::indicator:checked:focus,
78 QGroupBox::indicator:checked:pressed {
79 border: none;
80 image: url(:/themes/qdarkstyle/rc/checkbox_checked_focus.png);
81 }
82
83 QCheckBox::indicator:indeterminate {
84 image: url(:/themes/qdarkstyle/rc/checkbox_indeterminate.png);
85 }
86
87 QCheckBox::indicator:indeterminate:focus,
88 QCheckBox::indicator:indeterminate:hover,
89 QCheckBox::indicator:indeterminate:pressed {
90 image: url(:/themes/qdarkstyle/rc/checkbox_indeterminate_focus.png);
91 }
92
93 QCheckBox::indicator:checked:disabled,
94 QGroupBox::indicator:checked:disabled {
95 image: url(:/themes/qdarkstyle/rc/checkbox_checked_disabled.png);
96 }
97
98 QCheckBox::indicator:unchecked:disabled,
99 QGroupBox::indicator:unchecked:disabled {
100 image: url(:/themes/qdarkstyle/rc/checkbox_unchecked_disabled.png);
101 }
102
103 QRadioButton {
104 spacing: 5px;
105 outline: none;
106 color: #eff0f1;
107 margin-bottom: 2px;
108 }
109
110 QRadioButton:disabled {
111 color: #76797C;
112 }
113
114 QRadioButton::indicator {
115 width: 21px;
116 height: 21px;
117 }
118
119 QRadioButton::indicator:unchecked {
120 image: url(:/themes/qdarkstyle/rc/radio_unchecked.png);
121 }
122
123 QRadioButton::indicator:unchecked:hover,
124 QRadioButton::indicator:unchecked:focus,
125 QRadioButton::indicator:unchecked:pressed {
126 border: none;
127 outline: none;
128 image: url(:/themes/qdarkstyle/rc/radio_unchecked_focus.png);
129 }
130
131 QRadioButton::indicator:checked {
132 border: none;
133 outline: none;
134 image: url(:/themes/qdarkstyle/rc/radio_checked.png);
135 }
136
137 QRadioButton::indicator:checked:hover,
138 QRadioButton::indicator:checked:focus,
139 QRadioButton::indicator:checked:pressed {
140 border: none;
141 outline: none;
142 image: url(:/themes/qdarkstyle/rc/radio_checked_focus.png);
143 }
144
145 QRadioButton::indicator:checked:disabled {
146 outline: none;
147 image: url(:/themes/qdarkstyle/rc/radio_checked_disabled.png);
148 }
149
150 QRadioButton::indicator:unchecked:disabled {
151 image: url(:/themes/qdarkstyle/rc/radio_unchecked_disabled.png);
152 }
153
154 QMenuBar {
155 background-color: #31363b;
156 color: #eff0f1;
157 }
158
159 QMenuBar::item {
160 background: transparent;
161 }
162
163 QMenuBar::item:selected {
164 background: transparent;
165 border: 1px solid #76797C;
166 }
167
168 QMenuBar::item:pressed {
169 border: 1px solid #76797C;
170 background-color: #3daee9;
171 color: #eff0f1;
172 margin-bottom: -1px;
173 padding-bottom: 1px;
174 }
175
176 QMenu {
177 border: 1px solid #76797C;
178 color: #eff0f1;
179 margin: 2px;
180 }
181
182 QMenu::icon {
183 margin: 5px;
184 }
185
186 QMenu::item {
187 padding: 5px 30px 5px 30px;
188 border: 1px solid transparent;
189 /* reserve space for selection border */
190 }
191
192 QMenu::item:selected {
193 color: #eff0f1;
194 }
195
196 QMenu::separator {
197 height: 2px;
198 background: lightblue;
199 margin-left: 10px;
200 margin-right: 5px;
201 }
202
203 QMenu::indicator {
204 width: 18px;
205 height: 18px;
206 }
207
208
209 /* non-exclusive indicator = check box style indicator
210 (see QActionGroup::setExclusive) */
211
212 QMenu::indicator:non-exclusive:unchecked {
213 image: url(:/themes/qdarkstyle/rc/checkbox_unchecked.png);
214 }
215
216 QMenu::indicator:non-exclusive:unchecked:selected {
217 image: url(:/themes/qdarkstyle/rc/checkbox_unchecked_disabled.png);
218 }
219
220 QMenu::indicator:non-exclusive:checked {
221 image: url(:/themes/qdarkstyle/rc/checkbox_checked.png);
222 }
223
224 QMenu::indicator:non-exclusive:checked:selected {
225 image: url(:/themes/qdarkstyle/rc/checkbox_checked_disabled.png);
226 }
227
228
229 /* exclusive indicator = radio button style indicator (see QActionGroup::setExclusive) */
230
231 QMenu::indicator:exclusive:unchecked {
232 image: url(:/themes/qdarkstyle/rc/radio_unchecked.png);
233 }
234
235 QMenu::indicator:exclusive:unchecked:selected {
236 image: url(:/themes/qdarkstyle/rc/radio_unchecked_disabled.png);
237 }
238
239 QMenu::indicator:exclusive:checked {
240 image: url(:/themes/qdarkstyle/rc/radio_checked.png);
241 }
242
243 QMenu::indicator:exclusive:checked:selected {
244 image: url(:/themes/qdarkstyle/rc/radio_checked_disabled.png);
245 }
246
247 QMenu::right-arrow {
248 margin: 5px;
249 image: url(:/themes/qdarkstyle/rc/right_arrow.png)
250 }
251
252 QWidget:disabled {
253 color: #454545;
254 background-color: #31363b;
255 }
256
257 QAbstractItemView {
258 alternate-background-color: #31363b;
259 color: #eff0f1;
260 border: 1px solid #3A3939;
261 border-radius: 2px;
262 }
263
264 QWidget:focus,
265 QMenuBar:focus {
266 border: 1px solid #3daee9;
267 }
268
269 QTabWidget:focus,
270 QCheckBox:focus,
271 QRadioButton:focus,
272 QSlider:focus {
273 border: none;
274 }
275
276 QLineEdit {
277 background-color: #232629;
278 padding: 5px;
279 border-style: solid;
280 border: 1px solid #76797C;
281 border-radius: 2px;
282 color: #eff0f1;
283 }
284
285 QAbstractItemView QLineEdit {
286 padding: 0;
287 }
288
289 QGroupBox {
290 border: 1px solid #76797C;
291 border-radius: 2px;
292 margin-top: 20px;
293 }
294
295 QGroupBox::title {
296 subcontrol-origin: margin;
297 subcontrol-position: top center;
298 padding-left: 10px;
299 padding-right: 10px;
300 padding-top: 10px;
301 }
302
303 QAbstractScrollArea {
304 border-radius: 2px;
305 border: 1px solid #76797C;
306 background-color: transparent;
307 }
308
309 QScrollBar:horizontal {
310 height: 15px;
311 margin: 3px 15px 3px 15px;
312 border: 1px transparent #2A2929;
313 border-radius: 4px;
314 background-color: #2A2929;
315 }
316
317 QScrollBar::handle:horizontal {
318 background-color: #605F5F;
319 min-width: 5px;
320 border-radius: 4px;
321 }
322
323 QScrollBar::add-line:horizontal {
324 margin: 0px 3px 0px 3px;
325 border-image: url(:/themes/qdarkstyle/rc/right_arrow_disabled.png);
326 width: 10px;
327 height: 10px;
328 subcontrol-position: right;
329 subcontrol-origin: margin;
330 }
331
332 QScrollBar::sub-line:horizontal {
333 margin: 0px 3px 0px 3px;
334 border-image: url(:/themes/qdarkstyle/rc/left_arrow_disabled.png);
335 height: 10px;
336 width: 10px;
337 subcontrol-position: left;
338 subcontrol-origin: margin;
339 }
340
341 QScrollBar::add-line:horizontal:hover,
342 QScrollBar::add-line:horizontal:on {
343 border-image: url(:/themes/qdarkstyle/rc/right_arrow.png);
344 height: 10px;
345 width: 10px;
346 subcontrol-position: right;
347 subcontrol-origin: margin;
348 }
349
350 QScrollBar::sub-line:horizontal:hover,
351 QScrollBar::sub-line:horizontal:on {
352 border-image: url(:/themes/qdarkstyle/rc/left_arrow.png);
353 height: 10px;
354 width: 10px;
355 subcontrol-position: left;
356 subcontrol-origin: margin;
357 }
358
359 QScrollBar::up-arrow:horizontal,
360 QScrollBar::down-arrow:horizontal {
361 background: none;
362 }
363
364 QScrollBar::add-page:horizontal,
365 QScrollBar::sub-page:horizontal {
366 background: none;
367 }
368
369 QScrollBar:vertical {
370 background-color: #2A2929;
371 width: 15px;
372 margin: 15px 3px 15px 3px;
373 border: 1px transparent #2A2929;
374 border-radius: 4px;
375 }
376
377 QScrollBar::handle:vertical {
378 background-color: #605F5F;
379 min-height: 5px;
380 border-radius: 4px;
381 }
382
383 QScrollBar::sub-line:vertical {
384 margin: 3px 0px 3px 0px;
385 border-image: url(:/themes/qdarkstyle/rc/up_arrow_disabled.png);
386 height: 10px;
387 width: 10px;
388 subcontrol-position: top;
389 subcontrol-origin: margin;
390 }
391
392 QScrollBar::add-line:vertical {
393 margin: 3px 0px 3px 0px;
394 border-image: url(:/themes/qdarkstyle/rc/down_arrow_disabled.png);
395 height: 10px;
396 width: 10px;
397 subcontrol-position: bottom;
398 subcontrol-origin: margin;
399 }
400
401 QScrollBar::sub-line:vertical:hover,
402 QScrollBar::sub-line:vertical:on {
403 border-image: url(:/themes/qdarkstyle/rc/up_arrow.png);
404 height: 10px;
405 width: 10px;
406 subcontrol-position: top;
407 subcontrol-origin: margin;
408 }
409
410 QScrollBar::add-line:vertical:hover,
411 QScrollBar::add-line:vertical:on {
412 border-image: url(:/themes/qdarkstyle/rc/down_arrow.png);
413 height: 10px;
414 width: 10px;
415 subcontrol-position: bottom;
416 subcontrol-origin: margin;
417 }
418
419 QScrollBar::up-arrow:vertical,
420 QScrollBar::down-arrow:vertical {
421 background: none;
422 }
423
424 QScrollBar::add-page:vertical,
425 QScrollBar::sub-page:vertical {
426 background: none;
427 }
428
429 QTextEdit {
430 background-color: #232629;
431 color: #eff0f1;
432 border: 1px solid #76797C;
433 }
434
435 QPlainTextEdit {
436 background-color: #232629;
437 ;
438 color: #eff0f1;
439 border-radius: 2px;
440 border: 1px solid #76797C;
441 }
442
443 QHeaderView::section {
444 background-color: #76797C;
445 color: #eff0f1;
446 padding: 5px;
447 border: 1px solid #76797C;
448 }
449
450 QSizeGrip {
451 image: url(:/themes/qdarkstyle/rc/sizegrip.png);
452 width: 12px;
453 height: 12px;
454 }
455
456 QMainWindow::separator {
457 background-color: #31363b;
458 color: white;
459 padding-left: 4px;
460 spacing: 2px;
461 border: 1px dashed #76797C;
462 }
463
464 QMainWindow::separator:hover {
465 background-color: #787876;
466 color: white;
467 padding-left: 4px;
468 border: 1px solid #76797C;
469 spacing: 2px;
470 }
471
472 QMenu::separator {
473 height: 1px;
474 background-color: #76797C;
475 color: white;
476 padding-left: 4px;
477 margin-left: 10px;
478 margin-right: 5px;
479 }
480
481 QFrame {
482 border-radius: 2px;
483 border: 1px solid #76797C;
484 }
485
486 QFrame[frameShape="0"] {
487 border-radius: 2px;
488 border: 1px transparent #76797C;
489 }
490
491 QStackedWidget {
492 border: 1px transparent black;
493 }
494
495 QToolBar {
496 border: 1px transparent #393838;
497 background: 1px solid #31363b;
498 font-weight: bold;
499 }
500
501 QToolBar::handle:horizontal {
502 image: url(:/themes/qdarkstyle/rc/Hmovetoolbar.png);
503 }
504
505 QToolBar::handle:vertical {
506 image: url(:/themes/qdarkstyle/rc/Vmovetoolbar.png);
507 }
508
509 QToolBar::separator:horizontal {
510 image: url(:/themes/qdarkstyle/rc/Hsepartoolbar.png);
511 }
512
513 QToolBar::separator:vertical {
514 image: url(:/themes/qdarkstyle/rc/Vsepartoolbar.png);
515 }
516
517 QToolButton#qt_toolbar_ext_button {
518 background: #58595a
519 }
520
521 QPushButton {
522 color: #eff0f1;
523 background-color: #31363b;
524 border-width: 1px;
525 border-color: #76797C;
526 border-style: solid;
527 padding: 5px;
528 border-radius: 2px;
529 outline: none;
530 }
531
532 QPushButton:disabled {
533 background-color: #31363b;
534 border-width: 1px;
535 border-color: #454545;
536 border-style: solid;
537 padding-top: 5px;
538 padding-bottom: 5px;
539 padding-left: 10px;
540 padding-right: 10px;
541 border-radius: 2px;
542 color: #454545;
543 }
544
545 QPushButton:focus {
546 background-color: #3daee9;
547 color: white;
548 }
549
550 QPushButton:pressed {
551 background-color: #3daee9;
552 padding-top: -15px;
553 padding-bottom: -17px;
554 }
555
556 QComboBox {
557 selection-background-color: #3daee9;
558 border-style: solid;
559 border: 1px solid #76797C;
560 border-radius: 2px;
561 padding: 5px;
562 min-width: 75px;
563 }
564
565 QPushButton:checked {
566 background-color: #76797C;
567 border-color: #6A6969;
568 }
569
570 QComboBox:hover,
571 QPushButton:hover,
572 QAbstractSpinBox:hover,
573 QLineEdit:hover,
574 QTextEdit:hover,
575 QPlainTextEdit:hover,
576 QAbstractView:hover,
577 QTreeView:hover {
578 border: 1px solid #3daee9;
579 color: #eff0f1;
580 }
581
582 QComboBox:on {
583 padding-top: 3px;
584 padding-left: 4px;
585 selection-background-color: #4a4a4a;
586 }
587
588 QComboBox QAbstractItemView {
589 background-color: #232629;
590 border-radius: 2px;
591 border: 1px solid #76797C;
592 selection-background-color: #18465d;
593 }
594
595 QComboBox::drop-down {
596 subcontrol-origin: padding;
597 subcontrol-position: top right;
598 width: 15px;
599 border-left-width: 0px;
600 border-left-color: darkgray;
601 border-left-style: solid;
602 border-top-right-radius: 3px;
603 border-bottom-right-radius: 3px;
604 }
605
606 QComboBox::down-arrow {
607 image: url(:/themes/qdarkstyle/rc/down_arrow_disabled.png);
608 }
609
610 QComboBox::down-arrow:on,
611 QComboBox::down-arrow:hover,
612 QComboBox::down-arrow:focus {
613 image: url(:/themes/qdarkstyle/rc/down_arrow.png);
614 }
615
616 QAbstractSpinBox {
617 padding: 5px;
618 border: 1px solid #76797C;
619 background-color: #232629;
620 color: #eff0f1;
621 border-radius: 2px;
622 min-width: 75px;
623 }
624
625 QAbstractSpinBox:up-button {
626 background-color: transparent;
627 subcontrol-origin: border;
628 subcontrol-position: center right;
629 }
630
631 QAbstractSpinBox:down-button {
632 background-color: transparent;
633 subcontrol-origin: border;
634 subcontrol-position: center left;
635 }
636
637 QAbstractSpinBox::up-arrow,
638 QAbstractSpinBox::up-arrow:disabled,
639 QAbstractSpinBox::up-arrow:off {
640 image: url(:/themes/qdarkstyle/rc/up_arrow_disabled.png);
641 width: 10px;
642 height: 10px;
643 }
644
645 QAbstractSpinBox::up-arrow:hover {
646 image: url(:/themes/qdarkstyle/rc/up_arrow.png);
647 }
648
649 QAbstractSpinBox::down-arrow,
650 QAbstractSpinBox::down-arrow:disabled,
651 QAbstractSpinBox::down-arrow:off {
652 image: url(:/themes/qdarkstyle/rc/down_arrow_disabled.png);
653 width: 10px;
654 height: 10px;
655 }
656
657 QAbstractSpinBox::down-arrow:hover {
658 image: url(:/themes/qdarkstyle/rc/down_arrow.png);
659 }
660
661 QLabel {
662 border: 0px solid black;
663 }
664
665 QTabWidget {
666 border: 0px transparent black;
667 }
668
669 QTabWidget::pane {
670 border: 1px solid #76797C;
671 padding: 5px;
672 margin: 0px;
673 }
674
675 QTabWidget::tab-bar {
676 /* left: 5px; move to the right by 5px */
677 }
678
679 QTabBar {
680 qproperty-drawBase: 0;
681 border-radius: 3px;
682 }
683
684 QTabBar:focus {
685 border: 0px transparent black;
686 }
687
688 QTabBar::close-button {
689 image: url(:/themes/qdarkstyle/rc/close.png);
690 background: transparent;
691 }
692
693 QTabBar::close-button:hover {
694 image: url(:/themes/qdarkstyle/rc/close-hover.png);
695 background: transparent;
696 }
697
698 QTabBar::close-button:pressed {
699 image: url(:/themes/qdarkstyle/rc/close-pressed.png);
700 background: transparent;
701 }
702
703
704 /* TOP TABS */
705
706 QTabBar::tab:top {
707 color: #eff0f1;
708 border: 1px solid #76797C;
709 border-bottom: 1px transparent black;
710 background-color: #31363b;
711 padding: 5px;
712 min-width: 50px;
713 border-top-left-radius: 2px;
714 border-top-right-radius: 2px;
715 }
716
717 QTabBar::tab:top:selected {
718 color: #eff0f1;
719 background-color: #54575B;
720 border: 1px solid #76797C;
721 border-bottom: 2px solid #3daee9;
722 border-top-left-radius: 2px;
723 border-top-right-radius: 2px;
724 }
725
726 QTabBar::tab:top:!selected:hover {
727 background-color: #3daee9;
728 }
729
730
731 /* BOTTOM TABS */
732
733 QTabBar::tab:bottom {
734 color: #eff0f1;
735 border: 1px solid #76797C;
736 border-top: 1px transparent black;
737 background-color: #31363b;
738 padding: 5px;
739 border-bottom-left-radius: 2px;
740 border-bottom-right-radius: 2px;
741 min-width: 50px;
742 }
743
744 QTabBar::tab:bottom:selected {
745 color: #eff0f1;
746 background-color: #54575B;
747 border: 1px solid #76797C;
748 border-top: 2px solid #3daee9;
749 border-bottom-left-radius: 2px;
750 border-bottom-right-radius: 2px;
751 }
752
753 QTabBar::tab:bottom:!selected:hover {
754 background-color: #3daee9;
755 }
756
757
758 /* LEFT TABS */
759
760 QTabBar::tab:left {
761 color: #eff0f1;
762 border: 1px solid #76797C;
763 border-left: 1px transparent black;
764 background-color: #31363b;
765 padding: 5px;
766 border-top-right-radius: 2px;
767 border-bottom-right-radius: 2px;
768 min-height: 50px;
769 }
770
771 QTabBar::tab:left:selected {
772 color: #eff0f1;
773 background-color: #54575B;
774 border: 1px solid #76797C;
775 border-left: 2px solid #3daee9;
776 border-top-right-radius: 2px;
777 border-bottom-right-radius: 2px;
778 }
779
780 QTabBar::tab:left:!selected:hover {
781 background-color: #3daee9;
782 }
783
784
785 /* RIGHT TABS */
786
787 QTabBar::tab:right {
788 color: #eff0f1;
789 border: 1px solid #76797C;
790 border-right: 1px transparent black;
791 background-color: #31363b;
792 padding: 5px;
793 border-top-left-radius: 2px;
794 border-bottom-left-radius: 2px;
795 min-height: 50px;
796 }
797
798 QTabBar::tab:right:selected {
799 color: #eff0f1;
800 background-color: #54575B;
801 border: 1px solid #76797C;
802 border-right: 2px solid #3daee9;
803 border-top-left-radius: 2px;
804 border-bottom-left-radius: 2px;
805 }
806
807 QTabBar::tab:right:!selected:hover {
808 background-color: #3daee9;
809 }
810
811 QTabBar QToolButton::right-arrow:enabled {
812 image: url(:/themes/qdarkstyle/rc/right_arrow.png);
813 }
814
815 QTabBar QToolButton::left-arrow:enabled {
816 image: url(:/themes/qdarkstyle/rc/left_arrow.png);
817 }
818
819 QTabBar QToolButton::right-arrow:disabled {
820 image: url(:/themes/qdarkstyle/rc/right_arrow_disabled.png);
821 }
822
823 QTabBar QToolButton::left-arrow:disabled {
824 image: url(:/themes/qdarkstyle/rc/left_arrow_disabled.png);
825 }
826
827 QDockWidget {
828 background: #31363b;
829 border: 1px solid #403F3F;
830 titlebar-close-icon: url(:/themes/qdarkstyle/rc/close.png);
831 titlebar-normal-icon: url(:/themes/qdarkstyle/rc/undock.png);
832 }
833
834 QDockWidget::close-button,
835 QDockWidget::float-button {
836 border: 1px solid transparent;
837 border-radius: 2px;
838 background: transparent;
839 }
840
841 QDockWidget::close-button:hover,
842 QDockWidget::float-button:hover {
843 background: rgba(255, 255, 255, 10);
844 }
845
846 QDockWidget::close-button:pressed,
847 QDockWidget::float-button:pressed {
848 padding: 1px -1px -1px 1px;
849 background: rgba(255, 255, 255, 10);
850 }
851
852 QTreeView,
853 QListView {
854 border: 1px solid #76797C;
855 background-color: #232629;
856 }
857
858 QTreeView:branch:selected,
859 QTreeView:branch:hover {
860 background: url(:/themes/qdarkstyle/rc/transparent.png);
861 }
862
863 QTreeView::branch:has-siblings:!adjoins-item {
864 border-image: url(:/themes/qdarkstyle/rc/transparent.png);
865 }
866
867 QTreeView::branch:has-siblings:adjoins-item {
868 border-image: url(:/themes/qdarkstyle/rc/transparent.png);
869 }
870
871 QTreeView::branch:!has-children:!has-siblings:adjoins-item {
872 border-image: url(:/themes/qdarkstyle/rc/transparent.png);
873 }
874
875 QTreeView::branch:has-children:!has-siblings:closed,
876 QTreeView::branch:closed:has-children:has-siblings {
877 image: url(:/themes/qdarkstyle/rc/branch_closed.png);
878 }
879
880 QTreeView::branch:open:has-children:!has-siblings,
881 QTreeView::branch:open:has-children:has-siblings {
882 image: url(:/themes/qdarkstyle/rc/branch_open.png);
883 }
884
885 QTreeView::branch:has-children:!has-siblings:closed:hover,
886 QTreeView::branch:closed:has-children:has-siblings:hover {
887 image: url(:/themes/qdarkstyle/rc/branch_closed-on.png);
888 }
889
890 QTreeView::branch:open:has-children:!has-siblings:hover,
891 QTreeView::branch:open:has-children:has-siblings:hover {
892 image: url(:/themes/qdarkstyle/rc/branch_open-on.png);
893 }
894
895 QListView::item:!selected:hover,
896 QTreeView::item:!selected:hover {
897 background: #18465d;
898 outline: 0;
899 color: #eff0f1
900 }
901
902 QListView::item:selected:hover,
903 QTreeView::item:selected:hover {
904 background: #287399;
905 color: #eff0f1;
906 }
907
908 QTreeView::indicator:checked,
909 QListView::indicator:checked {
910 image: url(:/themes/qdarkstyle/rc/checkbox_checked.png);
911 }
912
913 QTreeView::indicator:unchecked,
914 QListView::indicator:unchecked {
915 image: url(:/themes/qdarkstyle/rc/checkbox_unchecked.png);
916 }
917
918 QTreeView::indicator:indeterminate,
919 QListView::indicator:indeterminate {
920 image: url(:/themes/qdarkstyle/rc/checkbox_indeterminate.png);
921 }
922
923 QTreeView::indicator:checked:hover,
924 QTreeView::indicator:checked:focus,
925 QTreeView::indicator:checked:pressed,
926 QListView::indicator:checked:hover,
927 QListView::indicator:checked:focus,
928 QListView::indicator:checked:pressed {
929 image: url(:/themes/qdarkstyle/rc/checkbox_checked_focus.png);
930 }
931
932 QTreeView::indicator:unchecked:hover,
933 QTreeView::indicator:unchecked:focus,
934 QTreeView::indicator:unchecked:pressed,
935 QListView::indicator:unchecked:hover,
936 QListView::indicator:unchecked:focus,
937 QListView::indicator:unchecked:pressed {
938 image: url(:/themes/qdarkstyle/rc/checkbox_unchecked_focus.png);
939 }
940
941 QTreeView::indicator:indeterminate:hover,
942 QTreeView::indicator:indeterminate:focus,
943 QTreeView::indicator:indeterminate:pressed,
944 QListView::indicator:indeterminate:hover,
945 QListView::indicator:indeterminate:focus,
946 QListView::indicator:indeterminate:pressed {
947 image: url(:/themes/qdarkstyle/rc/checkbox_indeterminate_focus.png);
948 }
949
950 QSlider::groove:horizontal {
951 border: 1px solid #565a5e;
952 height: 4px;
953 background: #565a5e;
954 margin: 0px;
955 border-radius: 2px;
956 }
957
958 QSlider::handle:horizontal {
959 background: #232629;
960 border: 1px solid #565a5e;
961 width: 16px;
962 height: 16px;
963 margin: -8px 0;
964 border-radius: 9px;
965 }
966
967 QSlider::sub-page:horizontal {
968 border: 1px solid #565a5e;
969 height: 4px;
970 background: #3daee9;
971 margin: 0px;
972 border-radius: 2px;
973 }
974
975 QSlider::groove:vertical {
976 border: 1px solid #565a5e;
977 width: 4px;
978 background: #565a5e;
979 margin: 0px;
980 border-radius: 3px;
981 }
982
983 QSlider::handle:vertical {
984 background: #232629;
985 border: 1px solid #565a5e;
986 width: 16px;
987 height: 16px;
988 margin: 0 -8px;
989 border-radius: 9px;
990 }
991
992 QSlider::sub-page:vertical {
993 border: 1px solid #565a5e;
994 width: 4px;
995 background: #3daee9;
996 margin: 0px;
997 border-radius: 3px;
998 }
999
1000 QToolButton {
1001 background-color: transparent;
1002 border: 1px transparent #76797C;
1003 border-radius: 2px;
1004 margin: 3px;
1005 padding: 5px;
1006 }
1007
1008 QToolButton[popupMode="1"] {
1009 /* only for MenuButtonPopup */
1010 padding-right: 20px;
1011 /* make way for the popup button */
1012 border: 1px #76797C;
1013 border-radius: 5px;
1014 }
1015
1016 QToolButton[popupMode="2"] {
1017 /* only for InstantPopup */
1018 padding-right: 10px;
1019 /* make way for the popup button */
1020 border: 1px #76797C;
1021 }
1022
1023 QToolButton:hover,
1024 QToolButton::menu-button:hover {
1025 background-color: transparent;
1026 border: 1px solid #3daee9;
1027 }
1028
1029 QToolButton:checked,
1030 QToolButton:pressed,
1031 QToolButton::menu-button:pressed {
1032 background-color: #3daee9;
1033 border: 1px solid #3daee9;
1034 /* padding: 5px; */
1035 }
1036
1037
1038 /* the subcontrol below is used only in the InstantPopup or DelayedPopup mode */
1039
1040 QToolButton::menu-indicator {
1041 image: url(:/themes/qdarkstyle/rc/down_arrow.png);
1042 top: -7px;
1043 }
1044
1045
1046 /* the subcontrols below are used only in the MenuButtonPopup mode */
1047
1048 QToolButton::menu-button {
1049 border: 1px transparent #76797C;
1050 border-top-right-radius: 6px;
1051 border-bottom-right-radius: 6px;
1052 /* 16px width + 4px for border = 20px allocated above */
1053 width: 16px;
1054 outline: none;
1055 }
1056
1057 QToolButton::menu-arrow {
1058 image: url(:/themes/qdarkstyle/rc/down_arrow.png);
1059 }
1060
1061 QToolButton::menu-arrow:open {
1062 border: 1px solid #76797C;
1063 }
1064
1065 QPushButton::menu-indicator {
1066 subcontrol-origin: padding;
1067 subcontrol-position: bottom right;
1068 bottom: 5px;
1069 }
1070
1071 QTableView {
1072 border: 1px solid #76797C;
1073 gridline-color: #31363b;
1074 background-color: #232629;
1075 }
1076
1077 QTableView,
1078 QHeaderView {
1079 border-radius: 0px;
1080 }
1081
1082 QTableView::item:pressed,
1083 QListView::item:pressed,
1084 QTreeView::item:pressed {
1085 background: #18465d;
1086 color: #eff0f1;
1087 }
1088
1089 QTableView::item:selected:active,
1090 QTreeView::item:selected:active,
1091 QListView::item:selected:active {
1092 background: #287399;
1093 color: #eff0f1;
1094 }
1095
1096 QHeaderView {
1097 background-color: #31363b;
1098 border: 1px transparent;
1099 border-radius: 0px;
1100 margin: 0px;
1101 padding: 0px;
1102 }
1103
1104 QHeaderView::section {
1105 background-color: #31363b;
1106 color: #eff0f1;
1107 padding: 5px;
1108 border: 1px solid #76797C;
1109 border-radius: 0px;
1110 text-align: center;
1111 }
1112
1113 QHeaderView::section::vertical::first,
1114 QHeaderView::section::vertical::only-one {
1115 border-top: 1px solid #76797C;
1116 }
1117
1118 QHeaderView::section::vertical {
1119 border-top: transparent;
1120 }
1121
1122 QHeaderView::section::horizontal::first,
1123 QHeaderView::section::horizontal::only-one {
1124 border-left: 1px solid #76797C;
1125 }
1126
1127 QHeaderView::section::horizontal {
1128 border-left: transparent;
1129 }
1130
1131 QHeaderView::section:checked {
1132 color: white;
1133 background-color: #334e5e;
1134 }
1135
1136
1137 /* style the sort indicator */
1138
1139 QHeaderView::down-arrow {
1140 image: url(:/themes/qdarkstyle/rc/down_arrow.png);
1141 }
1142
1143 QHeaderView::up-arrow {
1144 image: url(:/themes/qdarkstyle/rc/up_arrow.png);
1145 }
1146
1147 QTableCornerButton::section {
1148 background-color: #31363b;
1149 border: 1px transparent #76797C;
1150 border-radius: 0px;
1151 }
1152
1153 QToolBox {
1154 padding: 5px;
1155 border: 1px transparent black;
1156 }
1157
1158 QToolBox::tab {
1159 color: #eff0f1;
1160 background-color: #31363b;
1161 border: 1px solid #76797C;
1162 border-bottom: 1px transparent #31363b;
1163 border-top-left-radius: 5px;
1164 border-top-right-radius: 5px;
1165 }
1166
1167 QToolBox::tab:selected {
1168 /* italicize selected tabs */
1169 font: italic;
1170 background-color: #31363b;
1171 border-color: #3daee9;
1172 }
1173
1174 QStatusBar::item {
1175 border: 0px transparent dark;
1176 }
1177
1178 QFrame[height="3"],
1179 QFrame[width="3"] {
1180 background-color: #76797C;
1181 }
1182
1183 QSplitter::handle {
1184 border: 1px dashed #76797C;
1185 }
1186
1187 QSplitter::handle:hover {
1188 background-color: #787876;
1189 border: 1px solid #76797C;
1190 }
1191
1192 QSplitter::handle:horizontal {
1193 width: 1px;
1194 }
1195
1196 QSplitter::handle:vertical {
1197 height: 1px;
1198 }
1199
1200 QProgressBar {
1201 border: 1px solid #76797C;
1202 border-radius: 5px;
1203 text-align: center;
1204 }
1205
1206 QProgressBar::chunk {
1207 background-color: #05B8CC;
1208 }
1209
1210 QDateEdit {
1211 selection-background-color: #3daee9;
1212 border-style: solid;
1213 border: 1px solid #3375A3;
1214 border-radius: 2px;
1215 padding: 1px;
1216 min-width: 75px;
1217 }
1218
1219 QDateEdit:on {
1220 padding-top: 3px;
1221 padding-left: 4px;
1222 selection-background-color: #4a4a4a;
1223 }
1224
1225 QDateEdit QAbstractItemView {
1226 background-color: #232629;
1227 border-radius: 2px;
1228 border: 1px solid #3375A3;
1229 selection-background-color: #3daee9;
1230 }
1231
1232 QDateEdit::drop-down {
1233 subcontrol-origin: padding;
1234 subcontrol-position: top right;
1235 width: 15px;
1236 border-left-width: 0px;
1237 border-left-color: darkgray;
1238 border-left-style: solid;
1239 border-top-right-radius: 3px;
1240 border-bottom-right-radius: 3px;
1241 }
1242
1243 QDateEdit::down-arrow {
1244 image: url(:/themes/qdarkstyle/rc/down_arrow_disabled.png);
1245 }
1246
1247 QDateEdit::down-arrow:on,
1248 QDateEdit::down-arrow:hover,
1249 QDateEdit::down-arrow:focus {
1250 image: url(:/themes/qdarkstyle/rc/down_arrow.png);
1251 }