From 9eb36b33b97165e2a55c56cd03458a7cc378307f Mon Sep 17 00:00:00 2001 From: DreamSourceLab Date: Wed, 24 Sep 2014 18:43:42 +0800 Subject: [PATCH] v0.4 release --- DSLogic-gui/CMakeLists.txt | 201 ++- DSLogic-gui/DSLogic.qrc | 16 +- DSLogic-gui/icons/about.png | Bin 1058 -> 6091 bytes DSLogic-gui/icons/capture.png | Bin 2055 -> 6010 bytes DSLogic-gui/icons/decoder-hidden.png | Bin 0 -> 1440 bytes DSLogic-gui/icons/decoder-shown.png | Bin 0 -> 1041 bytes DSLogic-gui/icons/file.png | Bin 450 -> 1186 bytes DSLogic-gui/icons/file_dis.png | Bin 0 -> 811 bytes DSLogic-gui/icons/gear.png | Bin 0 -> 1576 bytes DSLogic-gui/icons/instant.png | Bin 0 -> 1246 bytes DSLogic-gui/icons/logo_color.png | Bin 364 -> 2401 bytes DSLogic-gui/icons/logo_noColor.png | Bin 364 -> 1906 bytes DSLogic-gui/icons/measure.png | Bin 1886 -> 1290 bytes DSLogic-gui/icons/measure_dis.png | Bin 0 -> 1169 bytes DSLogic-gui/icons/next.png | Bin 632 -> 5866 bytes DSLogic-gui/icons/open.png | Bin 1181 -> 5806 bytes DSLogic-gui/icons/params.png | Bin 1964 -> 1381 bytes DSLogic-gui/icons/params_dis.png | Bin 0 -> 1339 bytes DSLogic-gui/icons/pre.png | Bin 648 -> 5851 bytes DSLogic-gui/icons/protocol.png | Bin 2504 -> 1141 bytes DSLogic-gui/icons/protocol_dis.png | Bin 0 -> 1090 bytes DSLogic-gui/icons/save.png | Bin 1180 -> 5658 bytes DSLogic-gui/icons/search-bar.png | Bin 1976 -> 1694 bytes DSLogic-gui/icons/search-bar_dis.png | Bin 0 -> 1563 bytes DSLogic-gui/icons/start.png | Bin 1253 -> 1244 bytes DSLogic-gui/icons/stop.png | Bin 1110 -> 789 bytes DSLogic-gui/icons/trigger.png | Bin 4466 -> 1007 bytes DSLogic-gui/icons/trigger_dis.png | Bin 0 -> 978 bytes DSLogic-gui/icons/wiki.png | Bin 0 -> 2610 bytes DSLogic-gui/main.cpp | 16 +- DSLogic-gui/pv/data/analog.cpp | 15 +- DSLogic-gui/pv/data/analog.h | 7 +- DSLogic-gui/pv/data/analogsnapshot.cpp | 2 + DSLogic-gui/pv/data/decode/annotation.cpp | 74 + DSLogic-gui/pv/data/decode/annotation.h | 55 + DSLogic-gui/pv/data/decode/decoder.cpp | 185 +++ DSLogic-gui/pv/data/decode/decoder.h | 101 ++ DSLogic-gui/pv/data/decode/row.cpp | 72 + DSLogic-gui/pv/data/decode/row.h | 59 + DSLogic-gui/pv/data/decode/rowdata.cpp | 68 + DSLogic-gui/pv/data/decode/rowdata.h | 59 + DSLogic-gui/pv/data/decoderstack.cpp | 526 +++++++ DSLogic-gui/pv/data/decoderstack.h | 185 +++ DSLogic-gui/pv/data/dso.cpp | 9 +- DSLogic-gui/pv/data/dso.h | 4 +- DSLogic-gui/pv/data/dsosnapshot.cpp | 2 + DSLogic-gui/pv/data/group.cpp | 9 +- DSLogic-gui/pv/data/group.h | 4 +- DSLogic-gui/pv/data/groupsnapshot.cpp | 2 +- DSLogic-gui/pv/data/logic.cpp | 10 +- DSLogic-gui/pv/data/logic.h | 6 +- DSLogic-gui/pv/data/logicsnapshot.cpp | 27 +- DSLogic-gui/pv/data/logicsnapshot.h | 3 +- DSLogic-gui/pv/data/signaldata.cpp | 31 +- DSLogic-gui/pv/data/signaldata.h | 17 +- DSLogic-gui/pv/data/snapshot.cpp | 13 +- DSLogic-gui/pv/data/snapshot.h | 4 +- DSLogic-gui/pv/device/device.cpp | 105 ++ DSLogic-gui/pv/device/device.h | 52 + DSLogic-gui/pv/device/devinst.cpp | 203 +++ DSLogic-gui/pv/device/devinst.h | 132 ++ DSLogic-gui/pv/device/file.cpp | 67 + DSLogic-gui/pv/device/file.h | 50 + DSLogic-gui/pv/device/inputfile.cpp | 144 ++ DSLogic-gui/pv/device/inputfile.h | 69 + DSLogic-gui/pv/device/sessionfile.cpp | 76 + DSLogic-gui/pv/device/sessionfile.h | 48 + DSLogic-gui/pv/devicemanager.cpp | 164 +-- DSLogic-gui/pv/devicemanager.h | 27 +- DSLogic-gui/pv/dialogs/deviceoptions.cpp | 29 +- DSLogic-gui/pv/dialogs/deviceoptions.h | 4 - DSLogic-gui/pv/dialogs/storeprogress.cpp | 84 ++ DSLogic-gui/pv/dialogs/storeprogress.h | 65 + DSLogic-gui/pv/dock/dsotriggerdock.cpp | 28 +- DSLogic-gui/pv/dock/dsotriggerdock.h | 1 + DSLogic-gui/pv/dock/measuredock.cpp | 120 +- DSLogic-gui/pv/dock/measuredock.h | 14 +- DSLogic-gui/pv/dock/protocoldock.cpp | 71 +- DSLogic-gui/pv/dock/protocoldock.h | 5 +- DSLogic-gui/pv/dock/searchdock.cpp | 4 +- DSLogic-gui/pv/dock/searchdock.h | 8 +- DSLogic-gui/pv/dock/triggerdock.cpp | 5 +- DSLogic-gui/pv/mainwindow.cpp | 528 +++---- DSLogic-gui/pv/mainwindow.h | 42 +- DSLogic-gui/pv/prop/binding/binding.cpp | 59 + DSLogic-gui/pv/prop/binding/binding.h | 15 + .../pv/prop/binding/decoderoptions.cpp | 148 ++ DSLogic-gui/pv/prop/binding/decoderoptions.h | 66 + DSLogic-gui/pv/prop/binding/deviceoptions.cpp | 274 ++++ DSLogic-gui/pv/prop/bool.cpp | 12 +- DSLogic-gui/pv/prop/bool.h | 7 +- DSLogic-gui/pv/prop/double.cpp | 11 +- DSLogic-gui/pv/prop/double.h | 7 +- DSLogic-gui/pv/prop/enum.cpp | 16 +- DSLogic-gui/pv/prop/enum.h | 7 +- DSLogic-gui/pv/prop/int.cpp | 165 ++- DSLogic-gui/pv/prop/int.h | 8 +- DSLogic-gui/pv/prop/property.h | 7 +- DSLogic-gui/pv/prop/string.cpp | 77 + DSLogic-gui/pv/prop/string.h | 52 + DSLogic-gui/pv/sigsession.cpp | 1257 ++++++----------- DSLogic-gui/pv/sigsession.h | 146 +- DSLogic-gui/pv/storesession.cpp | 195 +++ DSLogic-gui/pv/storesession.h | 83 ++ DSLogic-gui/pv/toolbars/filebar.cpp | 42 +- DSLogic-gui/pv/toolbars/filebar.h | 4 +- DSLogic-gui/pv/toolbars/logobar.cpp | 17 + DSLogic-gui/pv/toolbars/logobar.h | 3 +- DSLogic-gui/pv/toolbars/samplingbar.cpp | 462 ++++-- DSLogic-gui/pv/toolbars/samplingbar.h | 68 +- DSLogic-gui/pv/toolbars/trigbar.cpp | 31 + DSLogic-gui/pv/toolbars/trigbar.h | 2 + DSLogic-gui/pv/view/analogsignal.cpp | 55 +- DSLogic-gui/pv/view/analogsignal.h | 30 +- DSLogic-gui/pv/view/decodetrace.cpp | 833 +++++++++++ DSLogic-gui/pv/view/decodetrace.h | 204 +++ DSLogic-gui/pv/view/devmode.cpp | 194 +++ DSLogic-gui/pv/view/devmode.h | 90 ++ DSLogic-gui/pv/view/dsosignal.cpp | 539 ++++++- DSLogic-gui/pv/view/dsosignal.h | 101 +- DSLogic-gui/pv/view/groupsignal.cpp | 74 +- DSLogic-gui/pv/view/groupsignal.h | 30 +- DSLogic-gui/pv/view/header.cpp | 507 +++---- DSLogic-gui/pv/view/header.h | 16 +- DSLogic-gui/pv/view/logicsignal.cpp | 201 +-- DSLogic-gui/pv/view/logicsignal.h | 38 +- DSLogic-gui/pv/view/ruler.cpp | 25 +- DSLogic-gui/pv/view/selectableitem.cpp | 57 + DSLogic-gui/pv/view/selectableitem.h | 69 + DSLogic-gui/pv/view/signal.cpp | 753 +--------- DSLogic-gui/pv/view/signal.h | 263 +--- DSLogic-gui/pv/view/trace.cpp | 474 +++++++ DSLogic-gui/pv/view/trace.h | 314 ++++ DSLogic-gui/pv/view/view.cpp | 305 ++-- DSLogic-gui/pv/view/view.h | 51 +- DSLogic-gui/pv/view/viewport.cpp | 292 ++-- DSLogic-gui/pv/view/viewport.h | 10 +- DSLogic-gui/pv/widgets/decodergroupbox.cpp | 69 + DSLogic-gui/pv/widgets/decodergroupbox.h | 54 + DSLogic-gui/pv/widgets/decodermenu.cpp | 75 + DSLogic-gui/pv/widgets/decodermenu.h | 58 + DSLogic-gui/pv/widgets/fakelineedit.cpp | 42 + DSLogic-gui/pv/widgets/fakelineedit.h | 53 + DSLogic-gui/res/DSLogic.fw | Bin 8120 -> 8120 bytes DSLogic-gui/res/DSLogic33.bin | Bin 0 -> 340604 bytes DSLogic-gui/res/DSLogic50.bin | Bin 0 -> 340604 bytes DSLogic-gui/stylesheet.qss | 10 +- INSTALL | 23 +- NEWS | 54 +- libsigrok4DSLogic/device.c | 42 +- libsigrok4DSLogic/hardware/DSLogic/command.c | 46 +- libsigrok4DSLogic/hardware/DSLogic/command.h | 11 +- libsigrok4DSLogic/hardware/DSLogic/dslogic.c | 519 ++++--- libsigrok4DSLogic/hardware/DSLogic/dslogic.h | 28 +- libsigrok4DSLogic/hardware/demo/demo.c | 270 ++-- libsigrok4DSLogic/hwdriver.c | 80 +- libsigrok4DSLogic/input/in_binary.c | 8 +- libsigrok4DSLogic/input/in_vcd.c | 6 +- libsigrok4DSLogic/input/in_wav.c | 8 +- libsigrok4DSLogic/libsigrok-internal.h | 4 +- libsigrok4DSLogic/libsigrok.h | 158 ++- libsigrok4DSLogic/output/out_analog.c | 6 +- libsigrok4DSLogic/output/out_csv.c | 12 +- libsigrok4DSLogic/output/out_vcd.c | 12 +- libsigrok4DSLogic/output/text/text.c | 10 +- libsigrok4DSLogic/proto.h | 27 +- libsigrok4DSLogic/session.c | 28 + libsigrok4DSLogic/session_driver.c | 83 +- libsigrok4DSLogic/session_file.c | 305 +++- libsigrok4DSLogic/strutil.c | 34 +- 170 files changed, 10523 insertions(+), 4305 deletions(-) create mode 100644 DSLogic-gui/icons/decoder-hidden.png create mode 100644 DSLogic-gui/icons/decoder-shown.png create mode 100644 DSLogic-gui/icons/file_dis.png create mode 100644 DSLogic-gui/icons/gear.png create mode 100644 DSLogic-gui/icons/instant.png create mode 100644 DSLogic-gui/icons/measure_dis.png create mode 100644 DSLogic-gui/icons/params_dis.png create mode 100644 DSLogic-gui/icons/protocol_dis.png create mode 100644 DSLogic-gui/icons/search-bar_dis.png create mode 100644 DSLogic-gui/icons/trigger_dis.png create mode 100644 DSLogic-gui/icons/wiki.png create mode 100644 DSLogic-gui/pv/data/decode/annotation.cpp create mode 100644 DSLogic-gui/pv/data/decode/annotation.h create mode 100644 DSLogic-gui/pv/data/decode/decoder.cpp create mode 100644 DSLogic-gui/pv/data/decode/decoder.h create mode 100644 DSLogic-gui/pv/data/decode/row.cpp create mode 100644 DSLogic-gui/pv/data/decode/row.h create mode 100644 DSLogic-gui/pv/data/decode/rowdata.cpp create mode 100644 DSLogic-gui/pv/data/decode/rowdata.h create mode 100644 DSLogic-gui/pv/data/decoderstack.cpp create mode 100644 DSLogic-gui/pv/data/decoderstack.h create mode 100644 DSLogic-gui/pv/device/device.cpp create mode 100644 DSLogic-gui/pv/device/device.h create mode 100644 DSLogic-gui/pv/device/devinst.cpp create mode 100644 DSLogic-gui/pv/device/devinst.h create mode 100644 DSLogic-gui/pv/device/file.cpp create mode 100644 DSLogic-gui/pv/device/file.h create mode 100644 DSLogic-gui/pv/device/inputfile.cpp create mode 100644 DSLogic-gui/pv/device/inputfile.h create mode 100644 DSLogic-gui/pv/device/sessionfile.cpp create mode 100644 DSLogic-gui/pv/device/sessionfile.h create mode 100644 DSLogic-gui/pv/dialogs/storeprogress.cpp create mode 100644 DSLogic-gui/pv/dialogs/storeprogress.h create mode 100644 DSLogic-gui/pv/prop/binding/decoderoptions.cpp create mode 100644 DSLogic-gui/pv/prop/binding/decoderoptions.h create mode 100644 DSLogic-gui/pv/prop/binding/deviceoptions.cpp create mode 100644 DSLogic-gui/pv/prop/string.cpp create mode 100644 DSLogic-gui/pv/prop/string.h create mode 100644 DSLogic-gui/pv/storesession.cpp create mode 100644 DSLogic-gui/pv/storesession.h create mode 100644 DSLogic-gui/pv/view/decodetrace.cpp create mode 100644 DSLogic-gui/pv/view/decodetrace.h create mode 100644 DSLogic-gui/pv/view/devmode.cpp create mode 100644 DSLogic-gui/pv/view/devmode.h create mode 100644 DSLogic-gui/pv/view/selectableitem.cpp create mode 100644 DSLogic-gui/pv/view/selectableitem.h create mode 100644 DSLogic-gui/pv/view/trace.cpp create mode 100644 DSLogic-gui/pv/view/trace.h create mode 100644 DSLogic-gui/pv/widgets/decodergroupbox.cpp create mode 100644 DSLogic-gui/pv/widgets/decodergroupbox.h create mode 100644 DSLogic-gui/pv/widgets/decodermenu.cpp create mode 100644 DSLogic-gui/pv/widgets/decodermenu.h create mode 100644 DSLogic-gui/pv/widgets/fakelineedit.cpp create mode 100644 DSLogic-gui/pv/widgets/fakelineedit.h create mode 100644 DSLogic-gui/res/DSLogic33.bin create mode 100644 DSLogic-gui/res/DSLogic50.bin diff --git a/DSLogic-gui/CMakeLists.txt b/DSLogic-gui/CMakeLists.txt index f2e563d..6e7dd10 100644 --- a/DSLogic-gui/CMakeLists.txt +++ b/DSLogic-gui/CMakeLists.txt @@ -19,10 +19,13 @@ ## along with this program. If not, see . ## -cmake_minimum_required(VERSION 2.6) +cmake_minimum_required(VERSION 2.8.6) + include(FindPkgConfig) include(GNUInstallDirs) +set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMake") + project(DSLogic) #=============================================================================== @@ -31,22 +34,27 @@ project(DSLogic) option(DISABLE_WERROR "Build without -Werror" TRUE) option(ENABLE_SIGNALS "Build with UNIX signals" TRUE) +option(ENABLE_DECODE "Build with libsigrokdecode" TRUE) +option(ENABLE_COTIRE "Enable cotire" FALSE) option(ENABLE_TESTS "Enable unit tests" FALSE) -option(STATIC_PKGDEPS_LIBS "Statically link to (pkgconfig) libraries" FALSE) +option(STATIC_PKGDEPS_LIBS "Statically link to (pkg-config) libraries" FALSE) +option(FORCE_QT4 "Force use of Qt4 even if Qt5 is available" FALSE) if(WIN32) # On Windows/MinGW we need to statically link to libraries. # This option is user configurable, but enable it by default on win32. set(STATIC_PKGDEPS_LIBS TRUE) - # For boost-thread we need two additional settings on win32: - set(Boost_USE_STATIC_LIBS on) - add_definitions(-DBOOST_THREAD_USE_LIB) - - # Windsws does not support UNIX signals + # Windows does not support UNIX signals. set(ENABLE_SIGNALS FALSE) endif() +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING + "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." + FORCE) +endif() + #=============================================================================== #= Dependencies #------------------------------------------------------------------------------- @@ -55,26 +63,35 @@ list(APPEND PKGDEPS "libsigrok4DSLogic >= 0.2.0" "libusb-1.0 >= 1.0.16" ) +if(ENABLE_DECODE) + list(APPEND PKGDEPS "libsigrokdecode>=0.3.0") +endif() -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules/") - find_package(PkgConfig) pkg_check_modules(PKGDEPS REQUIRED ${PKGDEPS}) -find_package(Qt4 REQUIRED) - -# Find the platform's thread library (needed for boost-thread). -# This will set ${CMAKE_THREAD_LIBS_INIT} to the correct, OS-specific value. -find_package(Threads) +if(FORCE_QT4) + set(Qt5Core_FOUND FALSE) +else() + find_package(Qt5Core QUIET) +endif() -if(WIN32) -# On Windows/MinGW the we need to use 'thread_win32' instead of 'thread'. -# The library is named libboost_thread_win32* (not libboost_thread*). -find_package(Boost 1.42 COMPONENTS system thread_win32 REQUIRED) +if(Qt5Core_FOUND) + message("-- Using Qt5") + find_package(Qt5Widgets REQUIRED) + find_package(Qt5Gui REQUIRED) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}") + set(QT_INCLUDE_DIRS ${Qt5Gui_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS}) + set(QT_LIBRARIES Qt5::Gui Qt5::Widgets) + add_definitions(${Qt5Gui_DEFINITIONS} ${Qt5Widgets_DEFINITIONS}) else() -find_package(Boost 1.42 COMPONENTS system thread REQUIRED) + find_program(QT_QMAKE_EXECUTABLE NAMES qmake4 qmake-qt4 qmake-mac) + find_package(Qt4 REQUIRED) endif() +find_package(Threads) + +find_package(Boost 1.42 COMPONENTS filesystem system thread REQUIRED) find_package(libusb-1.0 REQUIRED) #=============================================================================== @@ -85,7 +102,7 @@ set(DS_TITLE DSLogic) set(DS_DESCRIPTION "A GUI for DSLogic") set(DS_VERSION_MAJOR 0) -set(DS_VERSION_MINOR 3) +set(DS_VERSION_MINOR 4) set(DS_VERSION_MICRO 0) set(DS_VERSION_STRING ${DS_VERSION_MAJOR}.${DS_VERSION_MINOR}.${DS_VERSION_MICRO} @@ -105,31 +122,28 @@ set(DSLogic_SOURCES pv/devicemanager.cpp pv/mainwindow.cpp pv/sigsession.cpp + pv/storesession.cpp pv/data/analog.cpp pv/data/analogsnapshot.cpp + pv/data/dso.cpp + pv/data/dsosnapshot.cpp pv/data/group.cpp pv/data/groupsnapshot.cpp pv/data/logic.cpp pv/data/logicsnapshot.cpp pv/data/signaldata.cpp pv/data/snapshot.cpp - pv/data/dso.cpp - pv/data/dsosnapshot.cpp - pv/decoder/decoder.cpp - pv/decoder/decoderfactory.cpp - pv/decoder/democonfig.cpp - pv/decoder/ds1wire.cpp - pv/decoder/dsdmx512.cpp - pv/decoder/dsi2c.cpp - pv/decoder/dsserial.cpp - pv/decoder/dsspi.cpp + pv/device/devinst.cpp + pv/device/device.cpp + pv/device/file.cpp + pv/device/inputfile.cpp + pv/device/sessionfile.cpp pv/dialogs/about.cpp - pv/dialogs/connect.cpp pv/dialogs/deviceoptions.cpp pv/dialogs/search.cpp - pv/dock/fakelineedit.cpp + pv/dialogs/storeprogress.cpp + pv/dock/dsotriggerdock.cpp pv/dock/measuredock.cpp - pv/dock/protocoldock.cpp pv/dock/searchdock.cpp pv/dock/triggerdock.cpp pv/prop/bool.cpp @@ -137,80 +151,116 @@ set(DSLogic_SOURCES pv/prop/enum.cpp pv/prop/int.cpp pv/prop/property.cpp + pv/prop/string.cpp pv/prop/binding/binding.cpp - pv/prop/binding/binding_deviceoptions.cpp - pv/toolbars/devicebar.cpp + pv/prop/binding/deviceoptions.cpp + pv/prop/binding/decoderoptions.cpp pv/toolbars/filebar.cpp pv/toolbars/logobar.cpp pv/toolbars/samplingbar.cpp pv/toolbars/trigbar.cpp pv/view/analogsignal.cpp pv/view/cursor.cpp + pv/view/devmode.cpp + pv/view/dsldial.cpp + pv/view/dsosignal.cpp pv/view/groupsignal.cpp pv/view/header.cpp pv/view/logicsignal.cpp - pv/view/protocolsignal.cpp pv/view/ruler.cpp + pv/view/selectableitem.cpp pv/view/signal.cpp pv/view/timemarker.cpp + pv/view/trace.cpp pv/view/view.cpp pv/view/viewport.cpp - pv/view/dsosignal.cpp - pv/view/dsldial.cpp - pv/dock/dsotriggerdock.cpp + pv/widgets/fakelineedit.cpp ) set(DSLogic_HEADERS - pv/sigsession.h pv/mainwindow.h - pv/decoder/democonfig.h - pv/dock/fakelineedit.h - pv/dock/measuredock.h - pv/dock/protocoldock.h - pv/dock/searchdock.h - pv/dock/triggerdock.h + pv/sigsession.h + pv/storesession.h + pv/device/devinst.h pv/dialogs/about.h - pv/dialogs/connect.h pv/dialogs/deviceoptions.h pv/dialogs/search.h - pv/toolbars/samplingbar.h - pv/toolbars/devicebar.h + pv/dialogs/storeprogress.h + pv/dock/dsotriggerdock.h + pv/dock/measuredock.h + pv/dock/searchdock.h + pv/dock/triggerdock.h + pv/prop/bool.h + pv/prop/double.h + pv/prop/enum.h + pv/prop/int.h + pv/prop/property.h + pv/prop/string.h pv/toolbars/filebar.h pv/toolbars/logobar.h + pv/toolbars/samplingbar.h pv/toolbars/trigbar.h - pv/data/dso.h - pv/data/dsosnapshot.h pv/view/cursor.h + pv/view/devmode.h pv/view/header.h pv/view/ruler.h + pv/view/selectableitem.h pv/view/timemarker.h - pv/view/groupsignal.h - pv/view/protocolsignal.h + pv/view/trace.h pv/view/view.h - pv/view/dsosignal.h pv/view/viewport.h - pv/view/dsldial.h - pv/dock/dsotriggerdock.h + pv/widgets/fakelineedit.h ) set(DSLogic_FORMS pv/dialogs/about.ui - pv/decoder/dmx512config.ui - pv/decoder/i2cconfig.ui - pv/decoder/serialconfig.ui - pv/decoder/spiconfig.ui - pv/decoder/wire1config.ui ) set(DSLogic_RESOURCES DSLogic.qrc ) -qt4_wrap_cpp(DSLogic_HEADERS_MOC ${DSLogic_HEADERS}) -qt4_wrap_ui(DSLogic_FORMS_HEADERS ${DSLogic_FORMS}) -qt4_add_resources(DSLogic_RESOURCES_RCC ${DSLogic_RESOURCES}) -include(${QT_USE_FILE}) +if(ENABLE_DECODE) + list(APPEND DSLogic_SOURCES + pv/dock/protocoldock.cpp + pv/data/decoderstack.cpp + pv/data/decode/annotation.cpp + pv/data/decode/decoder.cpp + pv/data/decode/row.cpp + pv/data/decode/rowdata.cpp + pv/prop/binding/decoderoptions.cpp + pv/view/decodetrace.cpp + pv/widgets/decodergroupbox.cpp + pv/widgets/decodermenu.cpp + ) + + list(APPEND DSLogic_HEADERS + pv/dock/protocoldock.h + pv/data/decoderstack.h + pv/view/decodetrace.h + pv/widgets/decodergroupbox.h + pv/widgets/decodermenu.h + ) +endif() + +if(WIN32) + # Use the DSLogic icon for the DSLogic.exe executable. + set(CMAKE_RC_COMPILE_OBJECT "${CMAKE_RC_COMPILER} -O coff -I${CMAKE_CURRENT_SOURCE_DIR} ") + enable_language(RC) + list(APPEND DSLogic_SOURCES DSLogic.rc) +endif() + +if(Qt5Core_FOUND) + qt5_wrap_cpp(DSLogic_HEADERS_MOC ${DSLogic_HEADERS}) + qt5_wrap_ui(DSLogic_FORMS_HEADERS ${DSLogic_FORMS}) + qt5_add_resources(DSLogic_RESOURCES_RCC ${DSLogic_RESOURCES}) +else() + qt4_wrap_cpp(DSLogic_HEADERS_MOC ${DSLogic_HEADERS}) + qt4_wrap_ui(DSLogic_FORMS_HEADERS ${DSLogic_FORMS}) + qt4_add_resources(DSLogic_RESOURCES_RCC ${DSLogic_RESOURCES}) + include(${QT_USE_FILE}) +endif() #=============================================================================== #= Global Definitions @@ -219,8 +269,12 @@ include(${QT_USE_FILE}) add_definitions(${QT_DEFINITIONS}) add_definitions(-Wall -Wextra -Wno-return-type -Wno-ignored-qualifiers) +if(ENABLE_DECODE) + add_definitions(-DENABLE_DECODE) +endif() + if(NOT DISABLE_WERROR) - add_definitions(-Werror) + add_definitions(-Werror) endif() #=============================================================================== @@ -231,6 +285,7 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${Boost_INCLUDE_DIRS} + ${QT_INCLUDE_DIRS} ) if(STATIC_PKGDEPS_LIBS) @@ -255,11 +310,16 @@ set(DSLOGIC_LINK_LIBS if(STATIC_PKGDEPS_LIBS) link_directories(${PKGDEPS_STATIC_LIBRARY_DIRS}) list(APPEND DSLOGIC_LINK_LIBS ${PKGDEPS_STATIC_LIBRARIES}) +if(WIN32) + # Workaround for a MinGW linking issue. + list(APPEND PULSEVIEW_LINK_LIBS "-llzma -llcms2") +endif() else() link_directories(${PKGDEPS_LIBRARY_DIRS}) list(APPEND DSLOGIC_LINK_LIBS ${PKGDEPS_LIBRARIES}) endif() + add_executable(${PROJECT_NAME} ${DSLogic_SOURCES} ${DSLogic_HEADERS_MOC} @@ -273,6 +333,12 @@ if(WIN32) # Pass -mwindows so that no "DOS box" will open when PulseView is started. set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-mwindows") endif() + + +if(ENABLE_COTIRE) + include(cotire) + cotire(${PROJECT_NAME}) +endif() set_target_properties(${PROJECT_NAME} PROPERTIES INSTALL_RPATH "/usr/local/lib") #=============================================================================== @@ -282,7 +348,8 @@ set_target_properties(${PROJECT_NAME} PROPERTIES INSTALL_RPATH "/usr/local/lib") # Install the executable. install(TARGETS ${PROJECT_NAME} DESTINATION bin/) install(FILES res/DSLogic.fw DESTINATION bin/res/) -install(FILES res/DSLogic.bin DESTINATION bin/res/) +install(FILES res/DSLogic33.bin DESTINATION bin/res/) +install(FILES res/DSLogic50.bin DESTINATION bin/res/) #=============================================================================== #= Packaging (handled by CPack) diff --git a/DSLogic-gui/DSLogic.qrc b/DSLogic-gui/DSLogic.qrc index d7b5e8a..1efbf6b 100644 --- a/DSLogic-gui/DSLogic.qrc +++ b/DSLogic-gui/DSLogic.qrc @@ -4,14 +4,12 @@ icons/next.png icons/pre.png icons/file.png - icons/photo.png icons/save.png icons/open.png icons/params.png stylesheet.qss icons/down-arrow.png icons/slider-handle.png - icons/set.png icons/add.png icons/del.png icons/trigger.png @@ -20,14 +18,22 @@ icons/protocol.png icons/logo_noColor.png icons/logo_color.png - icons/logo_muColor.png icons/about.png icons/capture.png icons/stop.png icons/start.png icons/dsl_logo.png icons/logo.png - icons/checkbox.png - icons/radiobutton.png + icons/decoder-hidden.png + icons/decoder-shown.png + icons/instant.png + icons/trigger_dis.png + icons/file_dis.png + icons/measure_dis.png + icons/protocol_dis.png + icons/search-bar_dis.png + icons/params_dis.png + icons/gear.png + icons/wiki.png diff --git a/DSLogic-gui/icons/about.png b/DSLogic-gui/icons/about.png index b2a3208e8d92c5ee7301f03d3b5586322005c49c..b249edb13e0c36023c71e0f8b116f25fa23d931e 100644 GIT binary patch literal 6091 zcmcIo2{@GP`hP{%L@FxujUh_RYRNR#8C!NnXdg4LF`0##vByWHC?&}rX(d~sNGM7u zTa*?=SyK9NqEZRxosm9uI_H0#b6tPeES~qd@B8s#Ulr4v$(4K5OXnzAeX|S`|{{)7K|U0?8)}$8N%VvJnVaL zUvD;#&Glyc{(xX{zpDo6hx{>(L?ckhg^t#~JcxP3wfb`vWM`T=`?R1_lMh0Y=g+Y0w9jS=2y@E?6Yuf z5R9GOe>P__e+tj#nFm11e&40vlH!treL=nsu?L^2N~a6sr2 zN=Xa~jnYP;q1P`~kOh?vf$?*J21Q|jUStZ0fVS%wLsNgAH=DzCWOM2K6o8VG!-Em& z9FW3ea|E^vw+Ls=;(-hXH0e)fSb{A6w))Z`QyPan7a5WTX9wjwmCPgasV?A%C1KDw z9TX0W-H1ZtP$)rkfs9aCE|1KjfYwAqIP?TTr&DoQqCQa{UQdR!Oy|xx66yoaM0kO)+1=>Tyg+No$ChkQ}q9}-9oKy zE_k3f2%Qx_j-TJQ_8-O+Z>aG3#}$%)6XKr?@VVacJ$>ibfk*z!O6RtB!5sx!{@gR@ z?gL%BkU#ES(BQ|-4P-%J9Oz!weI_jmy3UUIhS>4s-&+(Q*E2 z1G6R&V7?r-7o7nDKu~9f3ytLRR^D*3=YyF|bi^qew)0AHn6uf@h!yebnko{qTcTCY zK40lnv&`IFM6n?&TKM?!h}e}*T1$>dJ`)>IId}ZP`RF4Lre1}f_jyt|`MP=L`ctE! z{E3W`RdrFQKY%r&Wc3&*6CdMtHT@ zEkG#fk(4-4ZI@n+QaU^$Pxke zG*UeK0Y`0MkH(*`9{~{=pHoAHfbFMLRtjas1F+>_G!gLL4CJ?NJz@^Hqkt7&Hnj%8 zGi?B6?dD|#TrC5tTV$n*0r4dO$|*W(10WI#>}gO{4FV3P0xK-W-SF=>7RnB3LQ16; z<@&mW98QPuQ)o#;_FX8xalOJE+>Qjrv z?2NP&`TX)u(+As&O06d)4tjRKjYoB0R?I z zd&f-HOp+R*#M!eUf4|xKL(Nx}HTNJNn<(p4J9!^U@Lh_y<9cJMp|8wWvUq0A`pQTN z5z_|o9Q`#1EzU_jr1H>daq?N|4>{M?7{=D=WnXGnd1BCq-L14#pUA~6 zN6Lmq6KeI<sYN(O}O7l^(huJIHd*K*bp_4 zF>rSPJD@nAGQM3h!_&m?!e^Jh_0A9WZdpAjJE$`#3Z`I8UC*aEzsNaDTBaMf&8*;T zsq+=5!#T@!{L>7Y56D~FTr4=3_-Ku{g7+l-${XIzWiJxf+9yABYj1pO`6l^|@Q1)9 z(uYY@@ z+{iYUry{7;>64&HS4E(Q=8qXvq>a(XJ&etMK=9{n&VA zWPR6rC&JFVCWMRRL~`=0gYo5U`12hW$1G%G>2*8$)%uxr%)0aHqUtK@Hg>~yqiN1| z<(D>G+HAYow!XQk`CRjpRNZt#^i#}G`cV4Q^xm>P+k&=fmFAS*0IiD$wq5cVEv;ll zZ8Pw==T?MD)=ZoBfT5WFthnV%>{|s(~%MkA6E}g|vUnNH0mh zGm_JFWmIpJK6<$OOq0%;4VfNT?!&tcgqKc(ZN^e(5>YAyp&=8Shr` z^lI6~z}!jln1azKBhHzAqkg=852nXscawf~SwmT)!(Ddf?xbAUk56 z5KTBH(lm1WqN&dz`&4cpH^y~=pNgN4n8s$6o31pC+h4GMu%D(Hth5b zKHGBVLsYi>WrUm-F3H3;+J4iif0)W@=E%pc8($u{?zm*|9pyVTII>NzE&3T@hvr)Q zaVt%`kduGKSLz0<-NRng8Z1Akars20t+efnbX=xYSX3TVs+ql#zlv5uA@*CvYH_bEA%zt}@tvefL-8`8G*Pc_AgX`q|e?dF&V21$9D z8=FU(N*;8b&_6wPW_CpGj@+x0r56v`xM?f9zpl#PYfy~$3+|HaU%7bWp{=6%_!~z` zHJKd_c_VJgh2d4|A$C5IyJ{ZnuemPd^G?cDaxg0FUH8+Goeu6ukKXI#uZJ=ejaJ4S zDO}N|ki7EGXY0Fz1{DvpAKXsuK6UMKUTrzG)vMj>?du-x%}q+O4luIp3vB?b??os{UETq}+142TWQeje3AFLn z*o?%CuyDke?mb=SN<)VzExmhX*kdiqb_T;_E zkG=9Q!Z{k#EsYh=12skqDjh0!e=r#_eNK3BxWjn+O!u>~?mzcd2WCyQbz4q~MjdFK ziT@ZULs%S_8rOJ8`p~<~O-2R=ea4*Wx8Je}t<+ZaOof?qGmV7`^2qSd9q*pDw57D2 zO&(89xXs*maeBD=-Rdh{b$9C4-nt)zMeq8sb!wz0uscwB?0m0`!@xe+pX+D6zZ89R zd6+$wusR`ORzx^u;u{64-38q@VH7K8I{*k-2LKTV0N~3k^!*k9{Gs0=hxY&gE(HJ- z*e7@2vj6~LF>9hJDX6o1n+>baL^kZ0l2JU|Tar&!kVqRWTw77NB3OR%vfU3I@ z`}x)xv4=ci-rnf-+*N_H5v-v5ldNUj}FP1^1K; z1*H0YB^j}-tq$>FW+q(7WOqNj(o@KBoi*p+qM*=Fm;Lt?KFOvF1H3q?3?pijuef9KN{uabD% zTFlFvg^Q{;oeSKGO>a(9EPu$*s}SF%gBQvZyLm#CrX&qK=9Zr>c@h;Jt>5(~J3E_v zyXuU6_F|wwn<>9v`b$x8F4AnD*&h|BrL*SPxakEAb4ePF zMxfIt(Ii-F5Os7|74a1zVdis&SZc1m#1 z2HZHU7oaQ#AHmke&hE$|X-Y3v`eLNBGyv#!Osjree|}rgd!NQ?TmbVGZGkU3nc%S9%(x_efZ+HNn0a?I@}#lZ!3V1I};F k^9$Qpb8@D#7&#pno=#2HU(4hV44OB zLRk?T5TyP&3Le}ZipFaXg7*eQx(DOIt9pwUY5hYb1VKfmX{jw(8ZALnl5U#a>~7vZ z9|I4DM@Qph_jLpNg&)J)ee8Zezu#|WcHRol^VrFfbd!1j@qeoa;IK{2&COi^O0-91 zpacjknwpwwF91-QoSZ}mfs|5@egFhxSa_Znj6u+9wV0ouFZl-Xli~qFn~kG|0`q1l zrGn86^SvMf_;8jzA0!rlu#qr?^|f#S7Kj)sV6>10$nu&257;1L0SM0nTc)k|0d0ej z01+YVVXu@9pnq);&i^zY9?jVHB48Jot5drHtmeaDx7V&vYg7R6fZ_g8(oSBR_m@!s z{v*rfh!u#g+SWQ>-~5=bZp^ajR**^{H5@dXEuPv_qa2$c>!Ft67T#`vaUrv$9NKz@uw@V*V$UMgQ{+ENS8>Tf#yZ{TgKhv34 zNr6%Vx8X6EKFq0S-XWXl=lb6Zy0y}MHFDVm{e2FfUwNOSdy9HrJYY`6dmi}Z&P9aM zy$&rHN`H?sed1G|-uFBsyC;}BGR<4ZK4PQlB84QIN{~!7_;vZBxzs*9fZtXYkP?&> zxJ{2!qdN1LIy7{g1O4MP8ZDGi8_@zUBN>0|XS^(30F2BVi`7EoI8p|sUULX5_%ui=}|ad)*!Mm)~B zlYg@eW{11~vC7YPzNF?YGmuZSQQe}D9MhYdVZOU7@W;KYq!J1#6l?42y!NcQxphB) z?*G+FT?e3lQUE&#pfoW{NIMJQi)%BO8rgMBS7FBh%w0Xt&_G_#0VQQe+ep$Th5sno zTNq$?R{?>v+#72J?9V&R!@hZ~q=O?Iq<;|Ry(WYik-?m(<^#hv{dDtp0*&qF-vGji z>3ju(u<61}w+P7wU`8wz&|x0N&5A^;rv5AwqN4z&`QTAVsGTAwq~ieEFc3Bm5VZnf z2pyfQKmcJiA00vibhH9x{m3sKmD#3x|I|NR^#FPRJ%E1z#R(U4t$qI&00000Ne4wv IM6N<$g3gxt)Bpeg diff --git a/DSLogic-gui/icons/capture.png b/DSLogic-gui/icons/capture.png index 7b7db85b0b07fee20fb85f609b7b342a97737bd9..15e44bf8998b6843e87968d4fc4bd6e72f3aa875 100644 GIT binary patch literal 6010 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 Gz5kzI^Nu(G literal 2055 zcmV+i2>ADjP)xFWmso_y}#d#!%Su-l5_ITx##Wge%`(Jz2Ex@ zpiRl*Dh7e3%$O$ER>Abj`i)Fm7H9`NoW~eb+^(qz{3Io&jH<&VDws*Sb(6$5*t`Co z_&L%8$plW7f1|okm=j%IlqKex%t7b?67#RKn1B~hezOHqnF4YJCrT(teo6}b!*e=_ zPcGK0FPzDaxm0{2-yDPvAVGAE#RYod=G|U{0aBUVcm^}9%L$lsM`!nd4uFRk8i%;7 zUR`>YgUB`sp##{T`xV>0=_P!9uTQ7M!a|B^mR%%$u(Fi!dev$N$d%4eC|#juHoAo* zBV*|7>Bq*k&l&J?5Cx_GQyO#U>rI~f4@ukkQ)OCLd`pS`Jy4_>j3s#PR|MgaRxRIt5U zmg82F2Eb1EHB{>9kSZ3L^tH{jMp#-5V?zEcZ;9dIF?96y*Hkw9IWT-uvhh65e{11K{GI)(9Xry__8g zeHFJ`2VwtMIYy<+$^Sw;8YYXlu@iy66{Ko zJAZhD-51V6bZQxr-(GDVAo5r#JG6Z@8apI-%({xqKcAxCmHeMX(n7mCqg%LtG#UdV z(R4JsNZKe6jc-tWgDjfkfL$r2_%LFvd4PA0l(54))}gs`n0$Mm?f?mydMO>pE=;!- z(N53v>l8WWoSo`=RsfSfn>vx<~lB za9TAjKAjd{Y;qxM%X@e**pGcFgQ&mP58vlI;O_1YE30YbGHVEJgs`!(LB3ebkKgsq zPKfeFI9ojjYUMmDy$4@gy(Ob#E#%)$UnYc3t&Y-E96BVAd&#`31H*4-H3eu0vhhN+-yg??1zjr)7W1!#PnPKk!t>fk_Kk3yoY5xsxm) z62UJlj$hp;NW=jI>i~k|PqVB&8_+vAVP4}8s9yBV#o3$yzP&IraY{k>l3yf#Io%q* zn+rIAP#wVLy(d}rK0o@7OD7fSDbzLTy?i{^=iqdjv1AyAXTdAzEDH1Vid@X zjx>XcCaP(G+Y@&9>lU#gK+x_2mbZT+21jI%+V5x+Roo3E|A6F@-7Nkf2uSP{!V5J(>zIesj)UJOBZT^=d0MELa- z#-0&~H@Q(enVb4Jvy(A&Ip>OfCAYC?wmrNSI3hUwv>||B)CqPnc?-s+rr&@~rJHHz z1^nVl8p7pKHEBpc_6ENEb`amwqa#1a4L6$yOwM<5ut&n@ckqOxEmkaaLRiEZLx49T zKW3jE`a7g@-v8j9SycKo+n^JMdIaY9Y^5dRkRaCJaUmxMot9xjgjn6g9Hkd*v7itBn|N$pww zOZej^)B2p-HTSBQYb|p4;vG7Q!0St9BRcvYh5$a{X{`8o7=lV_^tkme1Hojgw)*em zRY*wmK}mfdUD;DQ1bpy%|JoUGvF8i{yu*&O^J(E&Uw8vv^Jd~&dq1y|FMr3C1$c4p z3|yynuET5YOl0i-0`Unx1{b7&N=zUydyfmqoLtx5xbubp>q1jlN&4G3@Kpn@wrO6* zFVFvlw#=}6zB4LYHUA7;xq@QjzNmg`+cuL70M>>aWuIq8{GKtx`;Be8s86Jj#km8njIl0=@ocNLFcc lM-r)4e$LcHa#5S}>HmbA?ShE21^WO1002ovPDHLkV1jSx&ei|` diff --git a/DSLogic-gui/icons/decoder-hidden.png b/DSLogic-gui/icons/decoder-hidden.png new file mode 100644 index 0000000000000000000000000000000000000000..215b5ada038497ff658eedb6b5887706700644b7 GIT binary patch literal 1440 zcmXxkdsNbA7zgklKD1M}1bv>wA2yOKq2462$7i5ZG1P1{0hBXUz@f$si8g;moK(dYuiGbT9VBgj~ z{zBw%u6HUofyD;e1QI_rZRe_qMCZh^87W)_D;Y3AVy)D!t8pJzN(P$|pTGq=^d+up z)%&7sdW$bfx{|&q`HIDbf_$ln;yfa15qjeXyRXgxtzg?WyYwZY@l!OcDb4n z8V-wLCo_`cfkDh0%@cI~JD^t@iwX8nUE6Cp}e(>3?q?g%_Tf1 z%i!)*+E&EdmfcSfwzcT5DtSZ_4^z_`fiB~H)Twjg2l&5{%T~;ca**X*SzU&Bm`~K3 zo^+E>E8I9sLV3eZ$NQ^xf*F8ddrIq>{V6R1_0M_b;KBkEOA||6*_Eby*RMh5{KiH_ z(Bnk6@ywAdPY9CRRiTFR@iRa9H=~Aba+av_=|#Xx`TT6phEI$4?^mO2lt!VrPKb&( zn7DQ|Sd`@%plmc=dw1s6(^-*VanNen>b9ti}3f|B}sli%;}?kLOSd+0__OB&L~qNc$wjNe|Y#p#a4^A*ML(u(H~J^%DO4t zTa`}!P1S0;EUtanhgs|QQ%->hrxo*x?c~u?zo;C?vYJzb^fb;RyEDwse_= zv7$>v79fFoLM(YzUbTr~ZeA^sI1lx{x?@mpAcldrG<aBN%0 zyxKTx6w!Eeo?Q(HYNcUgLVXF-NQ{+7Zh22w;amVipazvS4jjSaa#>nyP@LFVGre&1 zePLl?!{N*vkl>|Eu2R)Xui#n!82#ipI?S^JdM_Nk72I|^*3qk{K2LS-=Qwxe#E;gX znUAGOPJI;2BDF4OQCo)2-g3UPMtsi2(yK2?jy>OR krm~0#mEy*eg-ggS9rul4{7h4*ulgebDC8iLfEZoyFN5!Xr2qf` literal 0 HcmV?d00001 diff --git a/DSLogic-gui/icons/decoder-shown.png b/DSLogic-gui/icons/decoder-shown.png new file mode 100644 index 0000000000000000000000000000000000000000..d745e24fc1cef63e7b5d73b97dc2fe3da3282a0d GIT binary patch literal 1041 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?4jBOuH;Rhv&5D9B#o z>FdgVkCTH*m+QU#uYX`6*N76w(vpn)B8HXg&UgTYcuIgmE~&-IMVSR9nfZAP!kmiA zKyfaRxO09%Wl?5&MhU|j{xeoUQ9iI}QEDPcsX|F+ZYqQ6cYaQw3@1n?I6tkVJh3R1 zA-3Q@NI5sy@XX@moJ63~B8Ke;w)6vqIY7eUiA8ytdFc!Xq@RJjzz)%0T9lm1@b!Q5 zYnU?U{M_8syb^|QXQu53im`!|g=CiGq%y2{cS)Urf$5H?i(^Pew(yu!LkQBM}d&iE*%m!;_9xO<7~7ju{Y4!K=_(-TtbRS*a_t>YyBcA18`Kn*8F4q{pFk5IU>6SiMn<;fLUtDaTf zE1Yy$syBV#`~Hb;Cq_oAq^^Sn;0hi-WPTQ&P_j0oqX$xn-RDr?;@ zy=!~z)^0;3v0g**f1M2Xzn`BJ)2H}ia(b*o{je0p=#L~%`hi_gJ6x=#HS0am6X{3^%cr2-yNVa zhi{i&!%w!)(!?)iI`_LnidH8d^WqzW#hrwE?imW3!Yn*rdvEA)&;EF9Mz@N%)BXSE$!DHkn3c7qBR%wMfKZpQcw$xH{{3rL#(UlE;}9 zuV$>0;{gSz;jtr;ozqp?FMqnZLi+87Yi35p`JttHD`&0_(JJpc`sn)>mw*1&f6bNj zOztw2u`9m*kvw(#wbo^FCOd+dGT1j5ORQu1(Ydy2=^3Hh|JJ+cDP%uk+T?~p9n0s8 Xjkvok!?*yL@)FdgVkCThjSWw(udpTIhHKN3^v?L?Hh+*ZrGaf)8o)VytOKNd)QD#9&W_})nFsGt2 zP@D@S?wnsxS(KTcQNpl>|BMw-ln*Rgl$r=qs!)=do62DNou3mZ!wHfJ&QB{TPb^Ah zh%NXJQqB!FJhM1CClRQ$h++GIE&V`Y4v=toVo_dZUOK}8>1QA>utW5h7A2=LeEr}2 z8m7!SKQ}iuuY}>-nQ8lhVr(E~A(e5v@3xyjt+9}* zmTATXc4@{XQe{<}*4t_P?YJkTU@MXl@MVH?^*7CT{347|3zjokvT9Vlv1j?&5W}*= z?uw9B$h5awYjiuW`0xpeb}fA~WjE(TC;o_n@~wAj^V8GHHG)F#q!e6a@!2R6U^0!V z%gyq|+0S=I#x=$;F)WB$yXa!ZnOjOs zX>5XLf3YeWz&8JiDndB^7XfX(rPtB7mh+OXdr-@JKKe*e4rvDeRQCw8Q%wsoBG+VlJ5qsn{k z``*_-wK%tZ-RrtbFH5{yIZp^p%n`n*qc-X2=RLC?Tf_qm$*_^TpSd;4O=*qH;%=5D zzg<`oB@UI|l@hd-;cLI&aboVriaE2}19_IjYIIldq#esJaT0MY{O`3i=%*FW;r$FY zj0PtTAGGM0oU-}EasTV32hRvp7XMhGb?w@i)mDAYMLTW8 zuP_wU<(2J^>}>oJJ4tQ6NSd@8(__)QP*d&b~SBe?d$8CG4u4(qMcFeug4$# z`?l;h^Mn%qvny72xCDyyZri@S`0bP4?FasUuV46mrsKcxn5U}d_n5A+cASV6tkhKc zn)%b&MecT-x?3Kj(xc83#dD9_DyoTsiw(z3iIZ z6IR^|zFI7}bM~A5yPBE0(yt}C6&5jvUR}9f|4yOOFS!HfU0hcETDi(@TcVFVdQ&MBb@07t_Evj6}9 literal 450 zcmV;z0X_bSP)A#ViKifnLl!gE3>}I?7x8O!aO)@NXXpnJgigiLuhOAI79FG%aZ8EN=1&Jx zkeG|f^&~=na7$k9eV_Z?y+;lz)>X#0JK*{cIX?gwcvnp@iJ~YD!?5)5JTKmEx88g{ zpC$`2PZi5@U=jquOBjZw$=B;Owr#W9?bvKKZ;saC{ zby>1QJBvyP!E82Txm+H_27^HhunGgHxX|nM7>z~@hr^>-r_;%tlL;W((^C>c(C_y% zV~s}R`~Z29<o1v# zF7qDv_gzpsqXS20E)7rz#2EoZQX`N72%y_;x3v}rNOE?Q8lN%%fR$kwPk^DB;Tu?` sKc?S?b)d!m6{Gu(h}gdoeGazZ2OfcU*_$LnRR91007*qoM6N<$g57b!_y7O^ diff --git a/DSLogic-gui/icons/file_dis.png b/DSLogic-gui/icons/file_dis.png new file mode 100644 index 0000000000000000000000000000000000000000..71eb703df680221c17dd72973143e4d06bb2e956 GIT binary patch literal 811 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?4jBOuH;Rhv&5D9B#o z>FdgVk5ib7!KA(3W)4`$HKN3^v?L?Hh+*ZrGaf)8o)VytOKNd)QD#9&W_})nFsGt2 zP@D@S?wnsxS(KTcQNpl>|BMw-ln*Rgl$r=qs!)=do62DNou3mZ!wHfJ&QB{TPb^Ah zh%NXJQqB!FJhM1CClRQ$h++GIE&V`Y4v=toVo_dZUOK}8>1QA>utW5h7A2=LeEr}2 z8m7!SKQ}iuuY}>-nQ8lhVr(E~A(RwJU(oeLiS`K*BG3_o)7hKM9L_%EcM!&#PKaatI6>TkWlsmk#nxE|q!Z*T-f1a?Nar$H zA7Grx@!G;^9rc+%wRkRbWJCdlQ3(`F|Pv&1`aN9KlzTe z%{rYf8v6C4?=vSCv)uSHp@1V{JsnqK{#31QbW6Th|L)YsX^jCE`q6cwc?|rnDM1_T znY(T)>sIRw)2`6xY#YF+fLFVMiaUvC#AA2P*-}s z?q7{aVSH$C=wG|kqxKW8I`Kv@-}`xD!gt*zt5TE08u>OXUG?{l*}1pV_0Fx{)!2KI z>93a3-puCog<6dJPgq^qQ1Dhy``^sY@`a!nfcW{Dx%rAoVOP(e=>nz+22WQ%mvv4F FO#n<_K-vHR literal 0 HcmV?d00001 diff --git a/DSLogic-gui/icons/gear.png b/DSLogic-gui/icons/gear.png new file mode 100644 index 0000000000000000000000000000000000000000..2c52b8df040f5a7f65386cd77baf5679c0552836 GIT binary patch literal 1576 zcmV+@2G{wCP)U%nu zFJF$ncI{exZf@?bl$4Zc{_iCvC9(PW`Mb}aJsW%a^l8!m0+-2T`#*g6@Y3t`x^+68 z77py?|JT~uTHy2f7_C-2QD0wQn3R+h_stzpC={s-!w}vY8XDwcu{h@T?c3X(PN!{Y zX^Bwl@9$TiIB_E3tATg#-raic+_}xk$;qsw4<0-a^Kb-9tX8YNs;cUGUteDf5BGYP zNN;cNC>;2KK9ilDExMkcpK#{PnRWjPE|p667>&k{)6>)Ag@uJ;ydbu^x;kwc?(>@; z%)-5_y?gggjTGzzc-zZYuj*Ycw?{6Q7a?VlrRC<$n_CSA!^c3hK4x%m@Xf-)g7ZsY zkB6FvEG~MOp`oFEv)OD~SXd-@j=0=zUs+jM&dHM}*M@ZrZb67?eQ4v?o$pEg%iRHW3_)=H$(DzQY8x-kfj zc8h-h{{72H@;p91Zp_KaDS@O1U^`$t)6&v@e)jD7b0>1`ba7x7?jUduW+yhAomg!) zGH7c4=LT@@=W2s-~hTQR6$4_|LD;pxr2+^?%=>I+=jWq!pZ!+ zmE6BCmEj$Mlwe@&!Wh+t$uq-6zG<~IVrXp{8TosZMr{j@ngv_t=LzP^Qh9l~0`G|v zdLNC4_hOFAEf$MC5cQ(JttKZYEmZb!9aLUkku{`JO<4s|Q?S`$VLCcGhEb5&GdKWX zy8&f_z;3ReU4edXZf<^#MH9c81Er;<2LSW%QO{901<%b9gAryh61~Ad6m@k^pk`ne zK$cjIW@=(ZFyGG(wz8Rrjg4~rehapnzlbhgyeKLyD?2bTF*&n3*hqAG158hFqq+kJ z0xLQ(F;VopLQ#lauI}#c9_{IQs|J_}^s^(-&oJDKM$s`vm~N7~y1Ip4;A|RYSZJ#&7pY~%00`;{nx;N1zmBg9I~s& z#_ZFdW(azJIypJz2pm`wkqRinIDBm6QiNbYneOg4GivpiotK+N z{Gz>mge$ff46cLKGFc<`%#nzq7alyYR#>fAp%S6ot!-_?bhh8PaU%m@hL^j&qeIPK z%enW&pv~8O^>@O(d%uZk)KqTBx`edQt+pN?CvnQo&es%9<$m#^efXPvn1mjHHUgT6 zmXhI$tq^WTTxbr})Yd-1ec=urG7;T=|I0ai_^=3auBVrYW5vcT79vQO$00008!n+z0`!r4S&H5KKNqxe^d0AQlJ$ zfwVOYBLYE~YDZ<9gitAwF|~yj1w<}CV+Rc>Vz|VlU+naccb}Qx^XxnC?(9}YMI5)p z?8N{8EXgEd45Hq~GTVWy2__qrh@f*xG%f&EL&iciermN3fjc2GHGJm`8iRJip6kr= z{fNR7p^!W%lg$BLy($udEFc6C>D*KfBNt+@bHHx2^Ct+$7%3NsOfIkKeB^skOQaImCwYMK^Wjt_59W>d{Vo06El5LwByWv^(v(-RrLh zi{ZOSf_cSYS{XXG)aJo&0%78AY2{vO^?bK;Ca#Gfih(vt0=+MqP6ny0ynB|KDXSXs znruV9@nJ@#z0&D94))zQi2uRm`)M|wy znCIE(?^yn9bQGmLJ5}T3fn~U#G_~=n*b{7_xH&jDXsuK#v$M0ZFv5a@f^Iw>Kieaf zzPArnYoNFhp4|y*ZE3ksm(6sk=f610M%^R0L`oOgDSc<&UG8aX zYiosXESAA%a6vR0t*X9A40mX!-%U*=>4t}g_u(AZHwUzfiv?aaKYC-MKy6%J0@0HQ zoA75kIyz2+%J!(|6BFwP`61Z;zcH@-BYdUvk@9MOT~P%bD7-JfzbSw{M+SU28FQ1>rT*p%gX*D4{o}p2r(e#niixx;iy+MBN5^Qa5Zr+zJWk!zkE0OuK zaoTk%Y%)^F<#PMRkqe{Fk}MLNTu0L0RzUu;x<}hacqBc1^znG&p{Co1uMF`kmzI_? z$R!sh%Ef-O6e?BreSTS|B?xA-wJXWV$=B+qmxr`(-b}ZMMCV8DXvzx99wKf=#;fZ( zosJ=_C71XH1WXs<7HB>IAk#IIP*>t3jY}1*=`lT-p2<_Ma*F<{|2>@c6#> zm^;iDy$zHDYLwxt3EtxaJpQ%F)R`EU%2JxpZ-h-QS<)XQ`3MPKpGl*dQ!~VWvK;5^ z4%@g_#N|!e-}Z2ky4*s~?cj^Q-ey&cXPAm?>$Pz@k9e7w{n#lfpghTRSQMYC`W)m4 z9ul;@RRV&`**7mar{bGstI=xRvU#?9%(1wA5j>#d2f z9(!ZvRbjT1Bj;rM%P*Ea+K@MVH9sx79nk>Pi3)e`*adbb@{IvGJc8I8`c2utV5Tl( literal 0 HcmV?d00001 diff --git a/DSLogic-gui/icons/logo_color.png b/DSLogic-gui/icons/logo_color.png index 730f3b247319914815fec91ead000b73b2ac5fb0..1b820058a26601ca0248f70f6e4bcf698af92700 100644 GIT binary patch delta 2390 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 D#?xc4 delta 337 zcmV-X0j~bx66^wyB!2;OQb$4nuFf3k00004XF*Lt006O%3;baP0000WV@Og>004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw0002$NklEWv~ z2(@WZLLgJOPfCkt1mtPZS3pE&000SeMObuGZ)S9N zVRB^vU2y+80000BbVXQnL}_zlY+-3_WpV(wz_gD5000PdMObuKVRCM1Zf5|%8|H@q z000McMObuGZ*_8GWdQa6gX;hQ00?wNSad^gZEa<4bO83umcIZ100wkLSaeirbZlh+ zsP57y000I(NqS?JWF-vu#!7)IC*p}&g=&CSh?xqL^D9{qelgpjW5zqPvx zfTn4`15j1klF#gM)*88Jb}jZ{>!Nh;a7o*&Y#*p`oFPb?equ zFNhEVNF)*}KwVwkJxk&G`ueW{Ow$}aaNxlI^6``<3#qF50|3)B7Zt*9Syr~J2m}Jp zRDV=dG$xbD|HNXk;gW~TjvYH*Ieq%{gos@1?Cg9|M7VL|#=9aS6B84Ey;lf{$Tks? z{{H??MTC}?mS5*)rqT*LvgUd9{Et;m0N~N@7^eBp=07RqF(My*uy$?LIcJ11aYinyKOMjk5 zJ32c4>u@-30%)4%dRTpKw|g;wh`8Vpt;9=~EOF+eTb7mk{!E1?olZM!J{{&d zP*I>wS-$S>Zm+E{Um08h;Bwvl;VsLW$-{fwzGfFU(=?|uA{m;dX*U2I4#yPsiy0pu z4~d9ey?S+5CI@fYv`He7h=jvo$$!vJojUb55fQ^M{*UHLuVNlg4Y>yXbJl(u`b7JYzr8OfXBiT<>Sy@>%djwoA*JA*RqGV|vkHr=5(F)s;&jVBdP16MKE@>^xvVQ=krlyKg zxOMARMc4HjfLph2dFIX1Z-Ls>)Kqoh!iARr#>U283IqaAK8PZR!;uD9y?XVBD^{#n z2C!(+qSv=<*|PZFk`D)U<;s;Et5&Ug9H6nWvBvB5?zNv4b?U1qO1iJF@9`^FuDk#c z4u@9&R99F3a$YT-1yw|9;eSqnq$tX=2T>#Ha+Qy?Xi$qxIO@~u*NV49|5 z20+*K>HG1P0(onp_3PJHClU!4Kz)7v;d?0%k*5JFD=VLyfAb1JzUBGw;ln3#w{FeN z&CiPn_I6L~Lnvag*xR{$y}iAGQkM?1+nf;y27`4X@?*R20PuJ`FXr-O4xw~iUu%;x zhg54y`nB_5jsuZMWPjY_@qA(TQvilxe3Z+RnXsm5zGss%hg3&PX>2~^-H{zSbm&*d zj~{;rs51;>=e~XW`e(5y0P%P{*w@!*sH*ylR4R4Q@Atn`YJ=yXn0U*X18+U*STA@E z+y_Yjr_))Ml$j3&JJ8kDRdep#xhDZMO)GhoSuhG6s*@*A{#-LRW%l&+tS@)?6dXdK zP{>hTUHvorRsnE2oj)zvS1krbc-xyZ0H*!pblFFsDCW`8(Tf8E1LF7lUoE%cnWl(* d|8JGD;{PtKgN;OnxTpXC002ovPDHLkV1m4ZiJ$-g delta 337 zcmV-X0j~b?4(tMuB!2;OQb$4nuFf3k00004XF*Lt006O%3;baP0000WV@Og>004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw0002$Nkl?fcc8a*Z zK9m}i{wSwVZ%Th#Bzq~;y*k}9wQldg%F9?P;gyF;hjv7ewE+38Lk`Gqoe=@i1-f3c jZk+*1qSaIp_+tWZsy!SXb%&u600000NkvXXu0mjfpm>ih diff --git a/DSLogic-gui/icons/measure.png b/DSLogic-gui/icons/measure.png index a00a19e810e5d95c05c50c4b19faf23c834dfe4a..f0b28b7fbb7158669868980c12cd9cb5f67afcfd 100644 GIT binary patch literal 1290 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 literal 0 HcmV?d00001 diff --git a/DSLogic-gui/icons/next.png b/DSLogic-gui/icons/next.png index 95ec60ff20e0121141a0efe46b0f8519f11bcacd..9e86ecf6472b9b0332d72dd9a042bab6aa62be12 100644 GIT binary patch literal 5866 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 PU4Vm)3!&6{NAy1d_2NA+ delta 608 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 EFNt<4xBvhE literal 1181 zcmV;O1Y-M%P)>LIfe8 zD*OFojT>W6a1X2&!>0!BEAjy#=cAFs|jih-nd}+=8(dm*@P^Y7>Ie zY&HpkkOzq{2nw$X6Wg|-#_#}9mJlq!gKx!()AI1}KM~+X>BTvJu0~v2T;==gOT2r& zMF~R2h$Qz!nIm(`AkB$04{8VkOO$vPRt7Yhd)P6RjZeTJb^P8*2`o(20`@eTG^Py| z;8sVtdb7u&1AExr6nR8Sxn z0l*2QnPO24kU1H*0HNU0^1>>hT9^|h%>`=|FN(+}B~ZX@z0biEWR&GFlyeFJu?QA$ z-ctcgV8&1aFCbJfRG`Q@@rroC7$x^wt$Hm5sIo9 z&mBP!u-YBq0Y6#;CxY|EzNv|jD0hf>+MS$MR|ze6z}6uQ!>S8%=hzkm{2Nr1L6p$# zMR+g84HuBjv0?Jv^AU6^}*?#kP9 zht>e{2R(`1+NbBbzkb-+c)GW269-=yC2;)IYmffZp5~D=+xMS5_u|y- vo4Yn!|IeT2%_Faxu=dFA)hi#g*aUw8bB-x8C@7F-00000NkvXXu0mjfbiOFj diff --git a/DSLogic-gui/icons/params.png b/DSLogic-gui/icons/params.png index 9432007e39dec4e45bf999556c22f925bbe02361..5b5af1745e41cdc0e01c37a6ccc94f40bd91cfb9 100644 GIT binary patch literal 1381 zcmXxkdoPpeMt9N-=RT8r z5ab#SF}Qr7q@n|_AazJVAT5f@XYwE>mjmoT8X74arSxN5UL2oEXFx#t_6{e7YJOHe zEle3TfS7C=aByWiQZYd)C;r?BC^n2w1IWBhWnAs^;mjy1D@+mP1C64(bOoy_IWUaR zVRGnz2s5F)!nQB_qxn=Cu(Wx3`v2H5E}Ko`K)`ZGR)IA|j1laW2Nl-PKQPRdKah3eo;Wk)g%JCL?S)#|Wzkj9 zGoLuhQ)#^w)~e|;y|mjXJOK+zi<*h@kasT{xVJtOib25_NjslJI(T~Rk&e0_pFySYOQN5`N7e*-*vUiYPwgC z!i|@(P|xx%ionS%Ozu`gp6R)0;DT6q!6@P4c`l3Ik(GY8j9TUoObgHdLsf#6TZ`z>W>~B}B8JC~)24~2k{zT9_D>rK&VnTjs%x4O+sD8d;9;5K(%#R%>J^wMjo9G`_JqK)dn~ lbkD<+{?L%Ji2L$fHxbp3+@2HG|5bi+0KxqPSm8!V{tqg4Zp{Dy literal 1964 zcmV;d2UGZoP)YFm(Y{|y3 zxJt*Gxl&?FC2tmjWLickEvyw8)(1<=TxF!-O+?I9&fSB#wRS#)?$8ex#uP~ z_nhDFe&_qnpDzGqluW_OZggwujI-PzrK0ZF#+1a@OZvM5BJrL+L`?b-kUUA}zzy|ZV}p0BK|{7e+ZV*mhx z@TNMl3;>`g%7(Hm%TFXpx^wsL-CLI~UHT8+2tis!Q7DA~q?y}>hK3HFIdkTM%jIel z1i>K)f<{(P+6bxuilV5;%F@!(%|s$`rLC>)AIKM>C?Sd!KqG$q_;E)p7W>xY@pL1E z902Qt_2gM1kmS{~TE-YJzkdC?KO7EUotT(dhJ1@C2*n7Xn!9aubhNgvuI@Kc6pyH+ zdC05Rr1_KA6iWa_Q9zdE>Bo;Bf8Es7G!5$l6f8oa0%+u)K7AVU`~5=*Atx_+BP*c! z0V5GG#>+2Wyl4*u0;5ob2!#lsk{6#nd-l!#{re+=ARs+?qrT7E1<10VJaOPxuU>W4 z)YSX}05TLHf>i;uH$OW&+u`&1uBb#2LK{e1?f)vw`~YDAT6tk%;k)YU>Om;L608W2 zXZD8=AO2l^^CbB~&0hu0ADDCl%@UqJfByN=qetUlQ3TeoRDUlT8yh>~_xrD#kvBV3 z5qQ2cyH{5?^85YQ$HvBvz{b&LNd!Oufa>e(9l>Dm213YTg*=TI4OYnOML-BSg2CX8 z`uci@6%ly4fch|TZf@?P*X#YBsFdmWXoPv9fc`Txhp2W1j4`BAslKYJs(x5^ip+r$ zuLanm(da>s$J1@a-1U9mss-pj<2gJY&&6modJs70IV=IRVQyn%W4CJhEYsGL>2#it zL?S-mumUa%2!%o|f*@3|92AL}nHiXwnPIc!0E;xGO%#Z)s^MoTUZCVli@pM0JMQZ1#!Q>-`uQBSfYJ3=9nTL{SXTyt|pF-)xw* zLl6WIMe$fP8hsxa9L@R&0suID`g9$wQd)yXMn<&n&Ye34006gd-{vC33w>B5>sJ&- zI|BfmIC0_=02t13p;`dTNg$WY6*PL=W^cZ>ww4;#OAV4RDS{vf;BvV>0)U%lzc&&< z3n6y9y_&2{Up5kY~+6Yr7uIE`Sj7ZncvuMfl7`C@=FqgU{6vSdmVrB{KY(jsKgQn~QEZ4@O2t0Kj-& zK!!uN+g%9&D_{@`txSjCIVg&<#=3`T0g@!m@7lG?Ne+Nm3n0aU6+&4HBZEbPS zo;@Fc$zX`(12D!or7n1G6-p68bD}J=?qg0CWLciG*=&CO!%0ENP*e(`%tENqK!_%o zEX#AOx{Q2)w(GLGx;jg%#Hb-Gn1wj2ptXT4=h|+%gT&mb&SWx=^b3=;%6L)AS)x&U z)b?gFnZJOsB4pMDFaSU@nVf8GZPgxZWa^5dSegy0j;;{IVzCXM(O%HQhY$bA6%9pT zT0lof$AT=&Q>?Z6t%g}Ua@%K9ZEbD;LXj4L0RT&qG_r5sz5vYv$Z{+eV}`^+XNjyX zs24<%q&vXq{{VdHDmWgG#}q|bG25iz@yY<}A+DyThEZ)kj5bU3braVs@p$|eaI6Zm zVwScfK0iM{;C8#eqOAzgmZkD4DKIqh>=MydgwpBsuLlks=md^kA}$L60Gvo9`Z31K zCaRk@+$zkTVb%tW@p2-O=m*Z+f%3)(hX7iVQaBvGD$DXTtr4`hLN-`WipPiWkS7mW zmZ!tv@K0bNkBE0RhyegkOiV0KPELM_F<#R5KORA7M2&>uA*_4*OZV^J4-=^~T)V?o zx5p6xh*q}8tvW;=$47C11*xbxrxAzPLNQsqf=y5XqGTFHTCJCLx+BTSCga) zT@q7c>{=iY7#$xUug_+))9QMheqD|>U}0VB&*YKKX8(Th;6X#NUDB0B1% z_Es{Pywcj*`VzKLsw|1TWa&sG;tPdBEtQp(&7vp<018T$o{}VKBp#2)y1Kg5%<*lP zEKP!%EfFDZ*Bk+2_Yi@000VfMObu0Z*X~XX=iA30IUzpIsgCw4s=CWbVG7w zVRUJ4ZXk4NZDjy8_YVmG000SeMObuGZ)S9NVRB^vU2y+80000BbVXQnL}_zlY+-3_ zWpV(wz_gD5000PdMObuKVRCM1Zf5|%8|H@q000McMObuGZ*_8GWdQa6gX;hQ00?wN zSad^gZEa<4bO83umcIZ100wkLSaeirbZlh+sP57y000CANkle1-uObRUMpvRp5y1~C;;uE#!j&k33!#cT7a2uF1qRZ%X>1j1i>?nArd@ z46Fq%x_F(Ltp(;J{pGTG;DQsFPgHCHp3SL!+S`wSQUt2xz@5NX1g{Re>}}v{;K|(F zc@`QEEz)lR_xkU1mzBQPR0*MB0*N``5b!s!0odg2a>%?rDCrvjGkdgx9YD1mh#s9F zcyBB4y2~yWt|-Quq(4x>4xqCno|W`FfSH}Fus$W553{z>X%#B!FNYZC_nFx$U=X+~ zH`X>fA#fq-&lQZHCm57@KO&NS!haNuwT&tSViG=XW*fbKi?>ZP8wBnE?xq#no6Rf( zegZxPeg$qLoFiv}-N0$!NNHorzLA;P2=EE8npPP-2b==Nf#ZaO?fTr(S>Po}A0&GA z0niYN)wPY1MKrq ziFt59(jd?h%bPHgoEro zLZUxUD6=P$d2vnU5(=FWI0w8#c%}R~3MEYg(`NPu;m)4{dnNsrTmK7z;GeFm5=x)M z|23@mGTJB5Yjo`(GqWwg9^gSqQ{8$_AptXc4H!#AbxDn-6q#8K_!8JIX+Kc&-+G{H z1g;1nH+}~&A}Isvz=WBNE~9Hb;c5+lq=uv$oJ`lSpp2T?5W$O6lIle=_w=M29Pu)N zbd=S--2*&CsQKxdF-f}%be;0~HNsbq%X1eakl%qJUwk+U{Qf@$)SOI2!*{3wi~`%; z(2-oY4Y@JjB|EyqbL6rv!rR^VK#YJfN!e1y7ZHfY-W~;Rbd9hBGs)aYvf<+?xfFS) z#C)iwx)MjtILT+stmz@$9syUf7q~z1&;x|;nfWA+BuA46M`Kguof5|vO>9Y1k~UDh zTMKYS74p~wnDiEktI(*KHEG4|VL}K@m{~oU7d<-Z^P|4~o&$N*%7?9a`E|ZV8x%r{o81-(I?Pr^a=DDmjD%_QZI;**}DJ$002ovPDHLkV1ga7O5Okf literal 0 HcmV?d00001 diff --git a/DSLogic-gui/icons/pre.png b/DSLogic-gui/icons/pre.png index f09cdc9a879384266b38de008884a4004199fc8e..1eac2f4e8105f121ae3820514bb59bd97821980b 100644 GIT binary patch literal 5851 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 KMNUMnLSTZJ-6-|| diff --git a/DSLogic-gui/icons/protocol.png b/DSLogic-gui/icons/protocol.png index 23aff7725b83b0eb156b677e1294b3cb5a4783ac..b1a9cf44b8ff9e6409db8e25da051ffcab193ff6 100644 GIT binary patch literal 1141 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?4jBOuH;Rhv&5D9B#o z>FdgVkCTg2-l#{=uo^7n8d2g{T9T1p#ISPR84sWkPYF=SCAB!YD6^m>Ge3_(m{U<1 zD9!~Ecg`=UEXqvJC}CK`f5r+Z$_ExLN=*bQRVc~KO=U3s&d&*y;RMM9=ckpFCl;kL z#1{MqDdz?oo>`onlL%B=#IXIqmVTfx2S_+Pu_!MyFP-6l^fQnb*dh8$i;`0rzW#51 z4O8ZvpPQSSSHke^%(VSLF*cC0kj#>tRE8DrE~zsxFgtjnRxISXZ}QifDRMT0CK zjvlFG{|8wUPo{;1$~@Q5dfm$LrFrU9(QFf`>y0PQ6qGZci&}f__1Uv$6Z5a9-FbhN zf7aP&x}N;1st>DFUOG-vx!x|@znlL*OT+8~S_acM4 zp1-mB!VO^wi&iml@x?1wuKeDx?#_zqudi=1JMjAJudPd$E|re_{qp6@*KgmxZH!LR zUbOqA&Aq#K{}$xsy|W8ow(^sykl&G=CO^mh$%xefxJ=UEM#|tUN`g z)UF?51@bdaO75QY^MBxs*+&&W$bL{tU!y-IRMC62Z@;IYs=nAf5rLTx({V}?rCLPyYv;3 z|Etucv#k2{FT~aS)q$D2_xs#DR}tOV)}+Pphp)zY_tJ(k+c&`ncQZ_R(W-ao&><#c zW8<4`e(mk;>DJcP|99@oSS6}dAZhqb%jDpHsY%yV-gN%?~%u)V-XuXlAWk z$JGY?#;gkjYEZ?poBpSi|ZB_p1A0N@km-3e>m0G%UD%H(8-`(F>Iu*$!{s z*|R0FFMQvB_Lm1&n!-})-c8IsOG-0!F&dvqud3* zjZqP0OfoZWz77Xcw8!yIDPhYkL`B0OJ39NIbWK# zL|yUu?waLU4=!66a~FfBtDnm{r-UW|E+G1r literal 2504 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 MwR9vF;(ZSP6H6b~X#fBK literal 1180 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 literal 1976 zcmV;p2S@mcP)Px#1ZP1_K>z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBU(Hc3Q5R9Hvtn0stfbri=7BBB%Kh5{lojXF_L5jC19Vl?=dPsC?T zOc0n5W1@+P@saxGr4iJK7?lJO-GDd>TUX}RuHCwpzV7Y2>${`vw!5|KI(pmD-s3s# zijSz7@fQDK$;oc+ZGQdyzUO<+@7zixXaCMB@J9szV0u$i(@dF6c7alpwIztkz<4L`^nV(rkB+S!}M9*=pzTzEiI^eBag8^;BzX>mpM0OsF~S(4j-~ zdU|@+8jL2D+2$HGd4dHdI3QU26JU#ug5$&}*ds$=_4Ny^)5DuttEacOcRQwjsZ=VR zQBlao#>QH$R{N5PVI2mSf5H;tpl_Im=y(C*8GzVHfN&ZhlolX3IsttH27@fnPH}t2#pUOjm2S`tpVi2!GML>YaIKYW<0m360a0H@Y zVeK3$|Djwizf^Qj(t^0~wR(f$N1fH3Rr`~`3>TmuA>ydoz@&QqF@U~N0Qygm^gKq8 zi454hVZmr(99TS$qLSx{nrc!Hi{e=WV-2a?G3e}{0Mjslbp(LHU@>e706T^&iI6FP z076o_qqqUGfH$55tHX0rr#t>7R`vo>0ddEdXmz@eb*!5|=1ak`69TB>0BVe$K>+;_ zfQc)?5~vGRJPnB|5@a};LqCS|SlxQ;0=HwqOcw=`(u+I3T5mKqYwUqSi$4SMet`BU zKxgcakVb+;Wl2dlLV8ijAS&5U=^iV9Js1-ht0U6c+4(s2QBeSCr`T|B)*DP3l_Mg^ z0y$`j0JNO|_yr-A1UUepMJ11;l4gXY)H^WM9)v`}A%u_-tZWCiAFOB&eYoCkx6c#} z5O=$*SEujku!V(Y{{+awP=r(vBmz>pyAiS%Aq@yL*uq1AKITxt z2GI(IQryQF@^rkVU$PIa;EARrrv zfstXOn2~3UM&r4nC`gMmH#aZDM*Ee9^-LV~rr`(z668^Yl%t9SNq!{AV$q#WYhCA*pB(v|Kc0Kaz^% z!@wB&FP;X*?aLiMZro2pgs9D$3Iz}S<#?_tw9Zhz%{Pu76iTTs`w=0jXjG^q`H@-= zwH)%Goy!58*Q~=8p>v!@Gtty5FNA5Hjl18(Iy^y*J64bf_#z~E@yMhfsc1S7vYcw_ zjVyNxERh7TE^n^8yZgsBnQVzvx>B4L^ZQ*+j#-OMCi6C%#~;;t2MP-RI2;L`g2N%< zG~_W#HA1Rm1<*!wUqb)6t$5yWyxOLf$@%L6Q zG`ZfHcVuAa<&RVZlKe;mD8n#o`+UBi`+||MGZfFTp}{;G<_gyEP{A3F=e(hQ&gTzk z?M~v@w;!Bzwg1@FXg{kyGV$9zKl=qyF-=ORbLrMOwBYhy2)g^&StYcC_NXPt)s;Q{`X>Mh6{ zwnL_K1D`s0?<8b}l$;7AeTLgAnVsBHyC(hF6`Jf1tFrkbN-Ec9Q-=t$a)mP`$fA<35oGo1^o}}pQAuS<$*j78 zlvHuMuX?y*g)>!3N_SDo&#&mt?k7*aSt^_^^yGmRo!stg9~LUn%%=)St%{T!*;4al z3L*I-WJ$@4YCWGka8G;U>uXj(ljMxN@c%70DLL{9RyQemx^T7%nfB*n+;_J$2s^J9 z^$n1IEc$cM+*b}#qZ~9a%(zt|Ik)kJIkmxeYMx2%Sgg%_zbcniyqX!6z1R}kb?045 zspLNo7QY7~5CWh@>79die?A5c2GoeFF)qTW#kd$_9>#o(g%8%xTq55zry=;!qNBb~ zmLF(*W%;xB-CTPaKBLK7v|q_+p)zC{l)j)0J8Ke{=D{-bY`mtZetxB7!QqWnH|<|n zbHhVRs|cJvb4EP)SE>KYds3-ny7V_oh^E}XDBiOlJ*&V!D)1|Gx+1nV71E6W0000< KMNUMnLSTYTa;ppg diff --git a/DSLogic-gui/icons/search-bar_dis.png b/DSLogic-gui/icons/search-bar_dis.png new file mode 100644 index 0000000000000000000000000000000000000000..81805a6041d02d6b38861343f436062a22ebeb54 GIT binary patch literal 1563 zcmXw(do-J87{I?Tp^*@=YN$(FdT|*Vt#)x~sU%vumb#SCnp;ESmNbI3ol4Zb9i@y> zL+dWdLao}mB&RckSVjpAt@OfX?o`pux-Z??bI$WV=lq`E^Pcnm@jhAS+)trV`X~SZ z&~C0IFQ{58Q*9e``oT6cpn_z$`o{r)W|uO-LJLg;2qIW+XI&64k=iH_*{$I$`Gj1^ zEa!L@mCgj}`4U$MX|NzfqQr$UX$%&P9t&tAjrTzurQ}39;|h}&L1h74jKm(Inx80> z5~5U%ShN@laC9AmggQuN$D1C`N(^CAfI!AarLO+za#~zibO@v}fmTsVE`-&T*e`?` zON)&FLEQuZ0= z@35;+UR;5FgQ4EsC*y4*9yce1U6l@HSD)jLcuAck38*a3-nE|9i@W;r64%p8$2(dV z7p_TjrR!VQ9m}Pw{X5iD9+igV;5p(-mdj1ck~SmOzO66u-W%Hn3k4Cv$C?no;bRs zCNQhAXRkc96Hb2grO{^mPHLeuGUF;9J+iH?uAYhy-m_$zy@V6FDWVj3BP=^^# zX;{4l=}wn{=UdQ`*Vq`f^7S#lltSOewKq9AIeBw)bL(uFLVUi_7fa^J%99)?3&6Xz z#`mWWadif&b{b$>mI=x8u6(cVY9QHxp4sRX56G1B#Cs)z`=uF?Y7c9aB{QzA5EqRZm zx7SQOFwik@(%bu4=!56&dIuC9ic`VvIuEJgl_&*#*k=YFDIXs{E;>KVek+fDh`RZU zSH{ZI=gZ5>extk#i9_T+QJ<;TH8k|`x!hy0c6RpSs;EemSU2Bg!v+?>?!T{p|YloWAvIfldOv8W~Z@7>+qABqkJen2lBj67})76BRK^9xM- zvNxBpfPnU_Ce6;y4x@|3@&p0Abr63iKmTAte05G*z znXLF{a}%(#S1QHBHPGm-Ho32lPt}M_CT9np*xjdI(cbcGJ|WSe>{l_lGpd-)FvH4VgH>1LK>KEcD@Fvo-4$_XMi9OmF}W@mpDXKPqL zC@+u0sCWhhJn4;XrGs-KR87V+2SVnJ!LhPdn`MzmL`q89$`Bcd)>|2_QPI)2visYw z7-i!6HY;w?m3Uq zd`{26WN?F2EmLA&#JgJs%}A<@GRj8RH5O@$%pwN^8YdC#+Hh)-uH5Ne+bin}+l47l zVjPUCHC)Z{vn#$8-fAI3sU5B<>trPJSDK zHA>xyC2&Vbf3a_P^}&W`8vIY|o%mMHRCuy_X|UFu3IDB9{&=!UYB5dyIJY1761nNG znrf_dCj_Vb E3z+u5nE(I) literal 0 HcmV?d00001 diff --git a/DSLogic-gui/icons/start.png b/DSLogic-gui/icons/start.png index f3c624d64baef634edb1284c1e121223497277bd..badd73e5d6b54eab43d63ea2571e3250d59577d1 100644 GIT binary patch delta 1225 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 delta 1089 zcmV-H1it%~2G$6WB!32COGiWi{{a60|De66lK=n!32;bRa{vGUNB{r;NB~C3Yd!z~ z1IS54K~!ko?O981+b|TIC&xDO0gSZp35qUyhX6TK57f(K-$iys(H9~X;-8ifEeU#!>b z_xJbrAF8TCjelwI7|5g_)JkO60SF5MhxVFM3SYl|5fPb{U@Ia3K!`|Yvl*&t)&*S~ zA=)K^Yd~cHrM?FlK@kCq06Pt$^3*IPP`_v(;=u*w1nP6CLa4zTW8s|#nrs+R0mN6Q zpa;N5wrRxCu~wgeh`?G0YaIjt(F6oi1yBI?=~|0$f)b zV=TP)O@AS%A2F?kDE9lTk_>^5<3uFMBJkeBXbbCHQ+?X`A**T-7;B-m#&Wqlh^?&U zQET^Z9q*40oRIX)&^v?eb_Zi^ldQdQ1MLw2e!l#q=jZ3~6bO@M)VJwrnIhd0!C8-; zHgL{&qn$;4Z+9O(2O4MOk#7}&2)q|)ZJ>>XFn{;$a*)7ejSR!KK|57?=b?5wj07pL z%;PPQtYatSN>>qq+uK{Yxw#Q$Mi{3v@FK9zK`9OI{NAW{Ne!qHS%satnDZMM5Q z@_&C3?Y#tw``EDxv+opqp-?u~kp zDqIUbdJeP}GN!KcBF6#zQ}arKQwshV0umK)&O<33HXy)pUp9}MF_F@XXs0;{6$ z|29d72((1P{$PR}5Qtc7p^YnuYANXME`KfEW0$V7aheG18NYt1q^d;lS*1BZBF~$g z46}!bjIIE`yu5sSeEhtssPe5U_4h!~r292^u6w!JCsbOi??mK^f?FaPGvn#$X=RL= zV-W7Ri-`Psz5a1|d3o?%MFjrk_3HmWa3*l-btZ7?btZ7?_38=y2CBbZ0anE}uQ~t# z03v!+SaefwW^{L9a%BJjc-kv3FW1Y=%Pvk%EJ)SMFG>dhHrNJO5L3!r00000NkvXX Hu0mjf(+BaF diff --git a/DSLogic-gui/icons/trigger.png b/DSLogic-gui/icons/trigger.png index 6b36974db65e896dc4ab61e5c11a4762f19e1ec3..9eeba7f33b8c68f6fde0dc6629c87e0e149ba5ff 100644 GIT binary patch literal 1007 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?4jBOuH;Rhv&5D9B#o z>FdgVkCTg2Q%fw-A_6Sr8d2g{T9T1p#ISPR84sWkPYF=SCAB!YD6^m>Ge3_(m{U<1 zD9!~Ecg`=UEXqvJC}CK`f5r+Z$_ExLN=*bQRVc~KO=U3s&d&*y;RMM9=ckpFCl;kL z#1{MqDdz?oo>`onlL%B=#IXIqmVTfx2S_+Pu_!MyFP-6l^fQnb*dh8$i;`0rzW#51 z4O8ZvpPQSSSHke^%(VSLF*cC0kj#>tRE8DrE~zsxFzxbmaSX|5d^^K1Lo`yPZGZig z4uwD+k(1pC+Dq=ix4h*(Fz?IS&M3rjPy9!ywrKX*Sn+oZId_xJTa1{`j9 zyZOMzNyqnlF-0)^+xXYK;K;Yq2aj34?49x@+%MtXuJ=5Gh3CG_>)0{Xr%DWwWK=1bI1Kbac1O7hBuwycw#K+tmQ*_z#1Fxr-+C1jv4g5K4PpxGN zZ#-Q(Nj*(LF~RqekwEm{j<-{H?X>@EWzf^8nZWiVp?bA*#;&|iM|f_SZD2cd^WXIK zlPhwVCtb_={_0seuZPuy(;NS4n*NEr6qm4WUBe`^>-n2=uWK%@_mbK<;pRWrOP&uF zyp>Ft|MvO;UC&-|BTxO!o;zEWis$-spWEG0G4-9i$)9Ht;Xr2{`&OTm^K2ete~y3d z^*WDt|88VW2Zr053g)Gu8rxg%`K{tQ+xNcl7@Ja(osyip%iF)FRWwZn1ih8}D!30< zPO6$zb>IzuXq4McAzlH&%Yqv-ewS%KGLp*eH*Zl|BrfQkc|yn4<$T%wZwgA!g3s-F zd$3&A+~aUg@SU>y70Z7wzO~}gmZg>oFEay0+`AV=$QfDi-eGvR%=pvR5}O)pt-Rk# z1;$7HZp1NEh35JkX?Uxy+P`dagY|2rQ;#O4sh;)Tm3DiQ*yWm8XSZCuJA?air)>D{KDub?E>rX3KvXJz9hX5a1Nx72nir-G8vGj7$1CHZZqr?LRkHG`+C KpUXO@geCwoV6}n( literal 4466 zcmV-&5smJNP)=7;wt&A=Rre-0^8iVdNQB5NNI+&HpfZYpGBmR-DAHnsC=h6nMr@iv zMW$eb3%B$y{`0N9Yo9u` zckMIODU@Z(GG!T+GK61=sM9m1XH3tS@22%H=wHyk;M|r9d&KX4?T2)m#<*CsWQb=LZTX}wY_B?gox4Fx5SLS}zyzw)=o_*q_ ztP{miApwogU)CNESsQh#RfryNvTpYAb&KKooTgU*;qL131LP+^r^Gz$Ape;N+R~P` z&_HRRG*G~`#ATI|V!krSHi6|kdTjq-%YX*`1NJ#` z?e*$CAL-VX9N9cJt=*nxf3wA1kXpd-_b8ax^g2@p0KDvF)+i<^u2cvbMZ#Zqr|q`Nc(PGpQ>Z>nEL8xWu^d#6B04 z)f@sa`@xaJF}(o7x^`&<@I=uUAN(?al9K)V|LF^^5QjzAKO`V*TG+I(X^=lTe{%k0 z?3YGdUUg*wr)*Pf2Lfw?Se+u=GQ+vsayRQEo zjqb{^pg!m%0x&`Mzm>V7jdIAiQgZUpXOunJb6-Z;1u+~`x{c4M>ux^jQ<01VA!^w! zU}K#!)9+IZQKXHENJE-u%UYiZ=Q(|L(fS>a$aWl;{$Q3W)BWdF8;WMUQSwOMDs=Ip zeFA{y+veNm+jcJ@vfhHapVXa>W*_d~A^3B+GWUx2{5tvtbaM1`ybkhDGJ{X`L}j3; zM)iQfN{V7&>nzlcYBzkHqqQO0yCU8EYd~peb6fVsmYV86SJ`(YHhNrCk$ESpDb;1| zyGKtQFZs*9Gr@f?PB$(VX|%poaz1**W)7CBryJky1}5$H4r{_eB9*L=1%SIo^?CgP z$Ydu2e^CLue^CMT5x^-x0(F4LXf6N|Wp43&lDA6c3hV%i)xy+dqD+*DKsxE9!}0JF z5yxXO)!kZXKp@;{Rn(`bJwfF?$`i{Qf-+ddgL+(MlguH$Ba_Sc%sn*cS)P{Tl`%>S z`vUb3N?mnMKuWDon4?8H`qna(RI$RZw0|y7i;*@z-W9Kl1nnc=e)V#wzdF^qp<%D@ z;+4>!=QPY0PmA{pUgH20mBzOhvsKJf>fbGKefDaEHm9U5op5ULp4JqIxyle@zUZdR z@*ir96PoRARozj|>{LGL!l;-Y#@i(gGZ)=hgNLhZUfPlz=?_<-me>j0Ae8PvjAqNX z<=gV_XEfG`HDYzJ&9=`Dnhgb>TZ69^f-~>-<)3eWROKz8RJ5TDZFrA09OXl0GSkKP z;(HX{Q~b3{c=?;?XPYJah#AVOekaUN7I%-R2a@%7Vc4 zF^M8a?d0efBk8IL$6?}_s!Z^&!)(z~33oIGMv7d&Nq|pD@&5~mJKE9Ea0W3957AUJ z-L9LbZ^}J#h!ItqymAdAjc_)9w!`>q>3X>OqZ%2y2$-+D0B|D_fQ}1L#AMDZ$sp>9 zk?Jmb@;k9j-6MLUGgH~1f|qP%Ia6sM5=46@%X7Rc7V@FU7A@H>JIb4Uq@>9vB1`;P zOJtB}B{KXD$eA<~d;PN5DWlXkLobt|WNNv=uP6(p(=+E<5An27wWRC83)?69hWz}& zfltS_(t3w{4d+7Ch!BTgB8E1pKo0qM#qKkCW>(L}sVFvyFX;uLVg8czwLgO~+qyoM8qAc7nqf=1#H z5JZHC0sOEjBT+>pBa|~ts?^ahq|j)N@loQ3{j-rQm)7o*LU|>{IVRleE1rY6MPkX?J4nVs6Rt^O2vXgp1A*W=0;5MgZJ;2Q&`8ZHarhF9e z-BuO+b=&89p!~KZv*3%&6YMyAVo&OlLua#(ZrIag%iIxN zeJ!*4n>BViucf^o|7xP&0D#A0c{V_8Sh8!a-t|sVW<4-H%9HlRKuhdA0rvtI_bR~a z^g6xH`|IY_p;w1qeFanfoh>G?JrryxI9AYr$MiS#QTiAV!D0sc5%LIG90m>(3Lxjv z*_;K^l)a5YV5uBv{s5ezpZPT~fFI2iV7r`eya2LX&NPBRBymAx^KnPU;m?3+~ z$v}qu+Bgr?lT(abkS)z@y*tR6(y1qaIH$BUUj*A3Em(KsGj{1O->lE@tJAO7Dk+s; zUEOtiW_YPNBXCn#=SHzYZ}UuxcOx1ptHY9mtE$o3W_^V2^hFYFZ18-yT)~m>xQ9Cv0n=`;mUwZ1J3oQmfvQ-Eq=Rz)v^b*dw0r^1flCs}b=G62XBrfHhJ|39K(IJ@7W*&ssb9dQ)CE#M?Rgj-U zeQ%ti;qq%0+xn$vJgbPs6%reRX$(_^YP(e5gSqT*~M{XF-*9gkQR`+-_y>SbgQ~E$#?A5=CF*= zFa6tGp4)oQQ(O7*d`3;>@jiu{f1GVhFE8*_J5!wf?VM}&yu2q}LoQs`>&o8sj*hrx zCKDGpwni9Gs3tIaa0>K) zvmT-JuxA1?^73Fi0J|!7x&XdRAh=@lt@-Ns>iFv1p9JmC+S}UWF_e7mV|ymJ6WlG_ z^C(k4Qx~d#1#^Ho3T|zY#qtPG=Cm-*8%ePHm^aX;VeV?QH0PW1q@DXAW$=z|!SDIT zNh!CMJcmmN(W`zUDe1GfPHR0AYWA-3UDS5$>U!ITDuH-gj8ArlJjxAE`L*c9acU?1 z%hHUK)7WFSFaPpxA2asE#ky0ncA7VzZdcy&rz$MDowDpAAi`I4-ks}4c3HH_3Sn4| z6{wwmM*_PBb`9)$Pjw`2O8h?YXEFVmcb-XUHwkLZs5QUV-t?}K-pHKDIQdz~D#zmx zH=(kDUrSpOi01*6WU$5_Qc9_9=gSTDh53G&O7HopNz1~Qszc9YY)B7ZsZQ{{leNHq zuJUF1^7Pk1^e@WWV&fg!7meTj8bE2SeJugSH7Ro|z)km`tN&X8{ZTg{exspLa--x%$p^KlD^ZuDa`p2SZdKl{Y%u1{0j)q=zh%X|A`r*k zNgwW~*`>?m8m9?^QUcLN@K;<|4sfQilzY2 zonj|QQ<~DGwr`Pdv2Q7tD%|8a%YUv#Riud>G8)!JeE00b4(c~0z)Um~&BXhL$ib0= zBL`y;NhFcroZ_6~oI;!$tH!Fal(LnrY!z`2+()3IqCV_!e!D29YO3b_2N!uG@8N!4MMj1z!R4#v9{*VMtffm2?c{CFLdMCD2p! z6g`E!Um#r%sH15%g-%t3j{gqah7x2<~!sxw~A{_vCnT zJUJ{TiYTIJY_*?e@dfFUF6p9%rN>%y-0P<@Q58}-gba&5i{1z5d4TQ*SUKyI{|nvy zzBkmMgWRX=`jE;_esb)p9?qwA`GpmArmR#D**~)M1(yDr#V0I#cZ+u| zc6+b_EXT^_&6ZNZ5bM9MZH(3M4a={-mB-o@RF7J3zSv4gGs~~?+n{o6T9qLmwYbYl z&@_wBS@g4LJ+OJqV&$9P^y@^J>n&P8OU%m_t5_Un_4kd%r~U`9MR(FdgVk5ib_THpBH5<#$#Yeb1-X-P(Y5yQ%LXFPyHJS9LOm(=3qqRfJl%=|nCVNOM5 zpg0#u+&RCXvM4h>ql94%{~0TwC?8m~C^ZqJRG}m@H-L1 z5L@scq?{XUcxG{OP9jig5ySQaTl#^*93bKF#G<^+ymW>G($7F%V29{0ElN&h`1-&3 zHB6aver|4RUJ1juGt>41#n?c~LNZHoQW;jfyQI#*z%#W5tK@$HO_*};J#$LcQz z399Jwb(smqu$q5NNN*2gSGV3-ChQ_o7$Eh(!7tCNpkPyM%#ue6d`XtgP0e{-86^c1 zo-7m#%9c6Dd)58ZJ>Pf6=O6rj;8;|2&-VH5=exh(nH{wJa^jqg%I{g60tHS;vo`L5 zQik7H)STkNt{NYxOqed6&0N75BRa9HqUt4w62lv2Kc@Q)Y6i?cy;_wGLAMh9LeDd{ z|Nf(MAoGDyf&3138HQH}(%63P6`r`;lh00c2ZN0F$Dd3l4eJj$KG+pr^|FGIML47C zhY!Q#1AaNOdsw$MOgg^1FefSK&U<0&65c<3LT-KD>v41+*wkuY|S@^>046teYpq11?@djk+Yn$)cu-P=`n5Jz;^A3*Z&xo zOH%P^9CusonEN*}-*JDy`5`1!o%#4zw*c0EtUa~z8XQl0n;h;*R5Kk+R}8gfyuW=JT^02aD^?O(db8c}+JCPFtGsXcG~I5DNv*o&{8&_c7A$>yfeIO{0PD`sD ztj=^gR+)~)2Rf8I0HvUlA)x~#ifCShB!o@&{mN#uue*19{(qElre%uOf9KBKoxS%z z-}%mWzH_fkfty~kFAoXd;Zg!XI2QZw5oI!HR4NuU?exbVukm`lhP7+fyamABJJ25+ z9Oy@BsRd(WSCMHjz~ykDWY#Ps#5monKq4tZ4h6)72!&h$xk8RaA^|z=t4m2miP;P} zZoF7<-N{q`+zH+^3;%HL!ugI}FTBuLURsuO_mA&Z{{IZzU}*&*+vaq> zZ!{X00l=Q;cVX4)JCT=HFl+t#^?$u{^_@MhzxLW5OKE8b0ROq3Zs|J#K!IhOw`?h! zjz+r#LC6>$8O5HRJD^f2F~52NY~$lZEs&d=Tm8(A=Z-WsHl2Fqm6u=mUUki904Dne z2a>q?^`9kJd+)s^Fa7bwPpM8BBNSYx)j$+Q{N?q7AV5gZ$Uq_eFZK3beBdW&P7&MHlmRT zLen9LatU%tqy)6a+v@6G{N&=N%^#iq`1y)5OB;Z|=e=KC?-=fc^($8vwI4q|=J9&r z5BMP^Bor1D;Md!?W5uc;Ael^{wfz_t)GWYb8y|w2Xt$C^><%}|Epu_~*fB29*|TS( zsHljGHb&+q#^W5c#E%3#9#3~$OY4q(d-rw$2m(mT8Hn9`_pSqgP-q${+7x7x(tSQZ ze)ZJTSi9yYnDYC$dzBOygV11f^eTeEDMmNnymU?}&Yt}j4j+CG=g*&~|AIWAMyrNC zJq_t;bfoJs(>$~0kw+grex~!xk;gV|D*K87VQk#muGeR*v=RMXXF71l9jka21c;2s z#A*sZsh$Li;M5d+fk|Yfr$eI_co$IZIsMa-D3#EGz~m$)qC;GaQzBku=rb@yppvB8 zVFG{qvfGf7qWh@QQofooAf%?ITB6Y?q_~I?x?p^Kf@MZ^&4gB~#Rn%&!0YpY1jVT5 z;KGqG`UeMz>NrsygWYC_&2FdGOhF_PgHlj&u;FM7L={2=Zs^JtT)uJ{f?9*@EJG=a ztpuP{DplDLx-c4#Vp-iXWMyYzk|9ne>B4NvIDpST`;1VhASWjWc_tGeOCn0at>la) zCB>AT3_-fCFh3vp`6fyr$=s3y3#1fgN_MS=IWy;GZh9XeNx0wj#Q4NGMn{IRX;U)`#7f4^DjWzQ zNgpMZB$1TFuZ&2H#i9W@xsF<`h9TQPicLq17|9~rCAxVAKHnt7M1pKs&x6OPTw1M; z>%~U`?d|OYTkpUB9+oV*4NI5Sv2NhOBTQeCL_&?y5k_iS3Tx8b+8 zGO8F0%;!cjk>o%xUA)M}P2h%EYz83abUHdzN(C1gvtor(MQjL2(dh^|;P?8`(sC5X z+S<|4(Sh?9F7Wy$C1AofK{}a4rXh2hFB=^W&n z@?f{yJ6LQ6K%zE#mrpvPw{qS*9-P{ewI0tPRiBDgt5)Oo`W2|JUkS6>%z;vwxM5{e zaHa#lKR}en$S<_q5z|xC0bV{oc}j@()!+`gpXuP_2OqMS5}yS-o_V%!!-kDRr104a zvL=~a&YdnMiXe|bcsk6_27`f5yfCTQ?RK%fd3y~E4spK#l3bJ*&YdH-9On0zS;`5J znzJOR1k}u*&n$Up&+grQU!R84U0t2Kv|0_#029u3b`rHV_Ao+ZZR@5_2}yC+QnjC2jKR3 zlWW&B9Cf?h7tQ7w$RPviBUQWIKFV0ZHKEh!;BeS5F=6BNy?XT;wX25%HBh(kET*R+ zs0FHX8Kay}O(pq)&+9{OjuG|Cmb0$8*!aK$N3UA1C9eYzMF|eKJGAu|TN_xm4QuXU z4;G;5$esmxI|&+r_jpfFH+4$@-Q5>RNH!2_JVUq317>E|tYlDjCu=acqC$$p#7@`q~2t_Yl609yv17-qzL-jYhC|VJ(UZ^AV4U#DoW4%A%AqD=)8r znH;TVK`o5NTxM3>Ze1Q1FNKUOIueg^mWD@1kd~rD^W%>|OC@S;X>Dk0Z5_GcGa<1c zH8nQ1oIZ8>VS0%m!Q4vLY~U{wG-&u)p;YqGOkF_76wi|Uf(;Zt1^uJ}8ubVcj}ekf1*W`50Bqg6Z(sTR>gpvX zlc|PU&Io|Hri&g(x3VL*j$QyEM)wojpTz%K /* First, so we avoid a _POSIX_C_SOURCE warning. */ #endif @@ -33,12 +33,15 @@ #include #include #include +#include #include "pv/devicemanager.h" #include "pv/mainwindow.h" #include "config.h" +char decoders_path[256]; + void usage() { fprintf(stdout, @@ -85,7 +88,7 @@ int main(int argc, char *argv[]) const int loglevel = atoi(optarg); sr_log_loglevel_set(loglevel); -#ifdef ENABLE_SIGROKDECODE +#ifdef ENABLE_DECODE srd_log_loglevel_set(loglevel); #endif @@ -118,7 +121,12 @@ int main(int argc, char *argv[]) do { -#ifdef ENABLE_SIGROKDECODE +#ifdef ENABLE_DECODE + QDir dir(QCoreApplication::applicationDirPath()); + assert(dir.cd("decoders")); + std::string str = dir.absolutePath().toStdString() + "/"; + strcpy(decoders_path, str.c_str()); + // Initialise libsigrokdecode if (srd_init(NULL) != SRD_OK) { qDebug() << "ERROR: libsigrokdecode init failed."; @@ -148,7 +156,7 @@ int main(int argc, char *argv[]) qDebug() << e.what(); } -#ifdef ENABLE_SIGROKDECODE +#ifdef ENABLE_DECODE // Destroy libsigrokdecode srd_exit(); #endif diff --git a/DSLogic-gui/pv/data/analog.cpp b/DSLogic-gui/pv/data/analog.cpp index d4e6304..0d55909 100644 --- a/DSLogic-gui/pv/data/analog.cpp +++ b/DSLogic-gui/pv/data/analog.cpp @@ -30,11 +30,17 @@ using namespace std; namespace pv { namespace data { -Analog::Analog(unsigned int num_probes, uint64_t samplerate) : - SignalData(num_probes, samplerate) +Analog::Analog(unsigned int num_probes) : + SignalData(), + _num_probes(num_probes) { } +int Analog::get_num_probes() const +{ + return _num_probes; +} + void Analog::push_snapshot(boost::shared_ptr &snapshot) { _snapshots.push_front(snapshot); @@ -45,5 +51,10 @@ deque< boost::shared_ptr >& Analog::get_snapshots() return _snapshots; } +void Analog::clear() +{ + _snapshots.clear(); +} + } // namespace data } // namespace pv diff --git a/DSLogic-gui/pv/data/analog.h b/DSLogic-gui/pv/data/analog.h index 281b936..1009d01 100644 --- a/DSLogic-gui/pv/data/analog.h +++ b/DSLogic-gui/pv/data/analog.h @@ -37,7 +37,9 @@ class AnalogSnapshot; class Analog : public SignalData { public: - Analog(unsigned int num_probes, uint64_t samplerate); + Analog(unsigned int num_probes); + + int get_num_probes() const; void push_snapshot( boost::shared_ptr &snapshot); @@ -45,7 +47,10 @@ public: std::deque< boost::shared_ptr >& get_snapshots(); + void clear(); + private: + const unsigned int _num_probes; std::deque< boost::shared_ptr > _snapshots; }; diff --git a/DSLogic-gui/pv/data/analogsnapshot.cpp b/DSLogic-gui/pv/data/analogsnapshot.cpp index f613983..d526015 100644 --- a/DSLogic-gui/pv/data/analogsnapshot.cpp +++ b/DSLogic-gui/pv/data/analogsnapshot.cpp @@ -75,6 +75,8 @@ void AnalogSnapshot::append_payload( const uint16_t* AnalogSnapshot::get_samples( int64_t start_sample, int64_t end_sample) const { + (void)end_sample; + assert(start_sample >= 0); assert(start_sample < (int64_t)get_sample_count()); assert(end_sample >= 0); diff --git a/DSLogic-gui/pv/data/decode/annotation.cpp b/DSLogic-gui/pv/data/decode/annotation.cpp new file mode 100644 index 0000000..c3c7440 --- /dev/null +++ b/DSLogic-gui/pv/data/decode/annotation.cpp @@ -0,0 +1,74 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +extern "C" { +#include +} + +#include +#include + +#include "annotation.h" + +namespace pv { +namespace data { +namespace decode { + +Annotation::Annotation(const srd_proto_data *const pdata) : + _start_sample(pdata->start_sample), + _end_sample(pdata->end_sample) +{ + assert(pdata); + const srd_proto_data_annotation *const pda = + (const srd_proto_data_annotation*)pdata->data; + assert(pda); + + _format = pda->ann_format; + + const char *const *annotations = (char**)pda->ann_text; + while(*annotations) { + _annotations.push_back(QString::fromUtf8(*annotations)); + annotations++; + } +} + +uint64_t Annotation::start_sample() const +{ + return _start_sample; +} + +uint64_t Annotation::end_sample() const +{ + return _end_sample; +} + +int Annotation::format() const +{ + return _format; +} + +const std::vector& Annotation::annotations() const +{ + return _annotations; +} + +} // namespace decode +} // namespace data +} // namespace pv diff --git a/DSLogic-gui/pv/data/decode/annotation.h b/DSLogic-gui/pv/data/decode/annotation.h new file mode 100644 index 0000000..2ca15d7 --- /dev/null +++ b/DSLogic-gui/pv/data/decode/annotation.h @@ -0,0 +1,55 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2013 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DSLOGIC_PV_VIEW_DECODE_ANNOTATION_H +#define DSLOGIC_PV_VIEW_DECODE_ANNOTATION_H + +#include + +#include + +struct srd_proto_data; + +namespace pv { +namespace data { +namespace decode { + +class Annotation +{ +public: + Annotation(const srd_proto_data *const pdata); + + uint64_t start_sample() const; + uint64_t end_sample() const; + int format() const; + const std::vector& annotations() const; + +private: + uint64_t _start_sample; + uint64_t _end_sample; + int _format; + std::vector _annotations; +}; + +} // namespace decode +} // namespace data +} // namespace pv + +#endif // DSLOGIC_PV_VIEW_DECODE_ANNOTATION_H diff --git a/DSLogic-gui/pv/data/decode/decoder.cpp b/DSLogic-gui/pv/data/decode/decoder.cpp new file mode 100644 index 0000000..8ae072f --- /dev/null +++ b/DSLogic-gui/pv/data/decode/decoder.cpp @@ -0,0 +1,185 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2013 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + +#include "decoder.h" + +#include + +using boost::shared_ptr; +using std::set; +using std::map; +using std::string; + +namespace pv { +namespace data { +namespace decode { + +Decoder::Decoder(const srd_decoder *const dec) : + _decoder(dec), + _shown(true), + _shown_back(true), + _shown_setted(false), + _setted(true) +{ +} + +Decoder::~Decoder() +{ + for (map::const_iterator i = _options.begin(); + i != _options.end(); i++) + g_variant_unref((*i).second); +} + +const srd_decoder* Decoder::decoder() const +{ + return _decoder; +} + +bool Decoder::shown() const +{ + return _shown; +} + +void Decoder::show(bool show) +{ + _shown_back = show; + _shown_setted = true; +} + +void Decoder::commit_show() +{ + if (_shown_setted) { + _shown = _shown_back; + _shown_setted = false; + } +} + +const map >& +Decoder::channels() const +{ + return _probes; +} + +void Decoder::set_probes(std::map > probes) +{ + _probes_back = probes; + _setted = true; +} + +const std::map& Decoder::options() const +{ + return _options; +} + +void Decoder::set_option(const char *id, GVariant *value) +{ + assert(value); + g_variant_ref(value); + _options_back[id] = value; + _setted = true; +} + +bool Decoder::commit() +{ + if (_setted) { + _probes = _probes_back; + _options = _options_back; + _setted = false; + return true; + } else { + return false; + } +} + +bool Decoder::have_required_probes() const +{ + for (GSList *l = _decoder->channels; l; l = l->next) { + const srd_channel *const pdch = (const srd_channel*)l->data; + assert(pdch); + if (_probes.find(pdch) == _probes.end()) + return false; + } + + return true; +} + +set< shared_ptr > Decoder::get_data() +{ + set< shared_ptr > data; + for(map >:: + const_iterator i = _probes.begin(); + i != _probes.end(); i++) + { + shared_ptr signal((*i).second); + assert(signal); + data.insert(signal->logic_data()); + } + + return data; +} + +srd_decoder_inst* Decoder::create_decoder_inst(srd_session *session, int unit_size) const +{ + GHashTable *const opt_hash = g_hash_table_new_full(g_str_hash, + g_str_equal, g_free, (GDestroyNotify)g_variant_unref); + + for (map::const_iterator i = _options.begin(); + i != _options.end(); i++) + { + GVariant *const value = (*i).second; + g_variant_ref(value); + g_hash_table_replace(opt_hash, (void*)g_strdup( + (*i).first.c_str()), value); + } + + srd_decoder_inst *const decoder_inst = srd_inst_new( + session, _decoder->id, opt_hash); + g_hash_table_destroy(opt_hash); + + if(!decoder_inst) + return NULL; + + // Setup the probes + GHashTable *const probes = g_hash_table_new_full(g_str_hash, + g_str_equal, g_free, (GDestroyNotify)g_variant_unref); + + for(map >:: + const_iterator i = _probes.begin(); + i != _probes.end(); i++) + { + shared_ptr signal((*i).second); + GVariant *const gvar = g_variant_new_int32( + signal->probe()->index); + g_variant_ref_sink(gvar); + g_hash_table_insert(probes, (*i).first->id, gvar); + } + + srd_inst_channel_set_all(decoder_inst, probes, unit_size); + + return decoder_inst; +} + +} // decode +} // data +} // pv diff --git a/DSLogic-gui/pv/data/decode/decoder.h b/DSLogic-gui/pv/data/decode/decoder.h new file mode 100644 index 0000000..f1b4b08 --- /dev/null +++ b/DSLogic-gui/pv/data/decode/decoder.h @@ -0,0 +1,101 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2013 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DSLOGIC_PV_DATA_DECODE_DECODER_H +#define DSLOGIC_PV_DATA_DECODE_DECODER_H + +#include +#include + +#include + +#include + +struct srd_decoder; +struct srd_decoder_inst; +struct srd_channel; +struct srd_session; + +namespace pv { + +namespace view { +class LogicSignal; +} + +namespace data { + +class Logic; + +namespace decode { + +class Decoder +{ +public: + Decoder(const srd_decoder *const decoder); + + virtual ~Decoder(); + + const srd_decoder* decoder() const; + + bool shown() const; + void show(bool show = true); + void commit_show(); + + const std::map >& channels() const; + void set_probes(std::map > probes); + + const std::map& options() const; + + void set_option(const char *id, GVariant *value); + + bool have_required_probes() const; + + srd_decoder_inst* create_decoder_inst( + srd_session *session, int unit_size) const; + + std::set< boost::shared_ptr > get_data(); + + bool commit(); + +private: + const srd_decoder *const _decoder; + + bool _shown; + bool _shown_back; + bool _shown_setted; + + std::map > + _probes; + std::map _options; + + std::map > + _probes_back; + std::map _options_back; + + bool _setted; +}; + +} // namespace decode +} // namespace data +} // namespace pv + +#endif // DSLOGIC_PV_DATA_DECODE_DECODER_H diff --git a/DSLogic-gui/pv/data/decode/row.cpp b/DSLogic-gui/pv/data/decode/row.cpp new file mode 100644 index 0000000..2aabf0f --- /dev/null +++ b/DSLogic-gui/pv/data/decode/row.cpp @@ -0,0 +1,72 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2014 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "row.h" + +#include + +namespace pv { +namespace data { +namespace decode { + +Row::Row() : + _decoder(NULL), + _row(NULL) +{ +} + +Row::Row(const srd_decoder *decoder, const srd_decoder_annotation_row *row) : + _decoder(decoder), + _row(row) +{ +} + +const srd_decoder* Row::decoder() const +{ + return _decoder; +} + +const srd_decoder_annotation_row* Row::row() const +{ + return _row; +} + +const QString Row::title() const +{ + if (_decoder && _decoder->name && _row && _row->desc) + return QString("%1: %2") + .arg(QString::fromUtf8(_decoder->name)) + .arg(QString::fromUtf8(_row->desc)); + if (_decoder && _decoder->name) + return QString::fromUtf8(_decoder->name); + if (_row && _row->desc) + return QString::fromUtf8(_row->desc); + return QString(); +} + +bool Row::operator<(const Row &other) const +{ + return (_decoder < other._decoder) || + (_decoder == other._decoder && _row < other._row); +} + +} // decode +} // data +} // pv diff --git a/DSLogic-gui/pv/data/decode/row.h b/DSLogic-gui/pv/data/decode/row.h new file mode 100644 index 0000000..6d73c41 --- /dev/null +++ b/DSLogic-gui/pv/data/decode/row.h @@ -0,0 +1,59 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2014 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DSLOGIC_PV_DATA_DECODE_ROW_H +#define DSLOGIC_PV_DATA_DECODE_ROW_H + +#include + +#include "annotation.h" + +struct srd_decoder; +struct srd_decoder_annotation_row; + +namespace pv { +namespace data { +namespace decode { + +class Row +{ +public: + Row(); + + Row(const srd_decoder *decoder, + const srd_decoder_annotation_row *row = NULL); + + const srd_decoder* decoder() const; + const srd_decoder_annotation_row* row() const; + + const QString title() const; + + bool operator<(const Row &other) const; + +private: + const srd_decoder *_decoder; + const srd_decoder_annotation_row *_row; +}; + +} // decode +} // data +} // pv + +#endif // DSLOGIC_PV_DATA_DECODE_ROW_H diff --git a/DSLogic-gui/pv/data/decode/rowdata.cpp b/DSLogic-gui/pv/data/decode/rowdata.cpp new file mode 100644 index 0000000..1d3ed27 --- /dev/null +++ b/DSLogic-gui/pv/data/decode/rowdata.cpp @@ -0,0 +1,68 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2014 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "rowdata.h" + +using std::max; +using std::vector; + +namespace pv { +namespace data { +namespace decode { + +RowData::RowData() : + _max_annotation(0) +{ +} + +uint64_t RowData::get_max_sample() const +{ + if (_annotations.empty()) + return 0; + return _annotations.back().end_sample(); +} + +uint64_t RowData::get_max_annotation() const +{ + return _max_annotation; +} + +void RowData::get_annotation_subset( + vector &dest, + uint64_t start_sample, uint64_t end_sample) const +{ + for (vector::const_iterator i = _annotations.begin(); + i != _annotations.end(); i++) + if ((*i).end_sample() > start_sample && + (*i).start_sample() <= end_sample) + dest.push_back(*i); +} + +void RowData::push_annotation(const Annotation &a) +{ + _annotations.push_back(a); + _max_annotation = max(_max_annotation, a.end_sample() - a.start_sample()); +} + +} // decode +} // data +} // pv diff --git a/DSLogic-gui/pv/data/decode/rowdata.h b/DSLogic-gui/pv/data/decode/rowdata.h new file mode 100644 index 0000000..5fddd95 --- /dev/null +++ b/DSLogic-gui/pv/data/decode/rowdata.h @@ -0,0 +1,59 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2014 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DSLOGIC_PV_DATA_DECODE_ROWDATA_H +#define DSLOGIC_PV_DATA_DECODE_ROWDATA_H + +#include + +#include "annotation.h" + +namespace pv { +namespace data { +namespace decode { + +class RowData +{ +public: + RowData(); + +public: + uint64_t get_max_sample() const; + + uint64_t get_max_annotation() const; + /** + * Extracts sorted annotations between two period into a vector. + */ + void get_annotation_subset( + std::vector &dest, + uint64_t start_sample, uint64_t end_sample) const; + + void push_annotation(const Annotation &a); + +private: + uint64_t _max_annotation; + std::vector _annotations; +}; + +} +} // data +} // pv + +#endif // DSLOGIC_PV_DATA_DECODE_ROWDATA_H diff --git a/DSLogic-gui/pv/data/decoderstack.cpp b/DSLogic-gui/pv/data/decoderstack.cpp new file mode 100644 index 0000000..5e5ca87 --- /dev/null +++ b/DSLogic-gui/pv/data/decoderstack.cpp @@ -0,0 +1,526 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include + +#include +#include + +#include + +#include + +#include "decoderstack.h" + +#include +#include +#include +#include +#include +#include + +using boost::lock_guard; +using boost::mutex; +using boost::optional; +using boost::shared_ptr; +using boost::unique_lock; +using std::deque; +using std::make_pair; +using std::max; +using std::min; +using std::list; +using std::map; +using std::pair; +using std::vector; + +using namespace pv::data::decode; + +namespace pv { +namespace data { + +const double DecoderStack::DecodeMargin = 1.0; +const double DecoderStack::DecodeThreshold = 0.2; +const int64_t DecoderStack::DecodeChunkLength = 1024 * 1024; +const unsigned int DecoderStack::DecodeNotifyPeriod = 1; + +mutex DecoderStack::_global_decode_mutex; + +DecoderStack::DecoderStack(pv::SigSession &session, + const srd_decoder *const dec) : + _session(session), + _sample_count(0), + _frame_complete(false), + _samples_decoded(0), + _decode_state(Stopped), + _options_changed(false) +{ + connect(&_session, SIGNAL(frame_began()), + this, SLOT(on_new_frame())); + connect(&_session, SIGNAL(data_received()), + this, SLOT(on_data_received())); + connect(&_session, SIGNAL(frame_ended()), + this, SLOT(on_frame_ended())); + + _stack.push_back(shared_ptr( + new decode::Decoder(dec))); +} + +DecoderStack::~DecoderStack() +{ +// if (_decode_thread.joinable()) { +// _decode_thread.interrupt(); +// _decode_thread.join(); +// } + stop_decode(); +} + +const std::list< boost::shared_ptr >& +DecoderStack::stack() const +{ + return _stack; +} + +void DecoderStack::push(boost::shared_ptr decoder) +{ + assert(decoder); + _stack.push_back(decoder); +} + +void DecoderStack::remove(int index) +{ + assert(index >= 0); + assert(index < (int)_stack.size()); + + // Find the decoder in the stack + list< shared_ptr >::iterator iter = _stack.begin(); + for(int i = 0; i < index; i++, iter++) + assert(iter != _stack.end()); + + // Delete the element + _stack.erase(iter); +} + +int64_t DecoderStack::samples_decoded() const +{ + lock_guard decode_lock(_output_mutex); + return _samples_decoded; +} + +std::vector< std::pair > DecoderStack::get_visible_rows() const +{ + lock_guard lock(_output_mutex); + + std::vector< std::pair > rows; + + BOOST_FOREACH (const shared_ptr &dec, _stack) + { + assert(dec); + + const srd_decoder *const decc = dec->decoder(); + assert(dec->decoder()); + + // Add a row for the decoder if it doesn't have a row list + if (!decc->annotation_rows) + rows.push_back(make_pair(Row(decc), dec->shown())); + + // Add the decoder rows + for (const GSList *l = decc->annotation_rows; l; l = l->next) + { + const srd_decoder_annotation_row *const ann_row = + (srd_decoder_annotation_row *)l->data; + assert(ann_row); + rows.push_back(make_pair(Row(decc, ann_row), dec->shown())); + } + } + + return rows; +} + +void DecoderStack::get_annotation_subset( + std::vector &dest, + const Row &row, uint64_t start_sample, + uint64_t end_sample) const +{ + lock_guard lock(_output_mutex); + + std::map::const_iterator iter = + _rows.find(row); + if (iter != _rows.end()) + (*iter).second.get_annotation_subset(dest, + start_sample, end_sample); +} + +uint64_t DecoderStack::get_max_annotation(const Row &row) +{ + lock_guard lock(_output_mutex); + + std::map::const_iterator iter = + _rows.find(row); + if (iter != _rows.end()) + return (*iter).second.get_max_annotation(); + + return 0; +} + +bool DecoderStack::has_annotations(const Row &row) const +{ + lock_guard lock(_output_mutex); + + std::map::const_iterator iter = + _rows.find(row); + if (iter != _rows.end()) + if(0 == (*iter).second.get_max_sample()) + return false; + else + return true; + else + return false; +} + +QString DecoderStack::error_message() +{ + lock_guard lock(_output_mutex); + return _error_message; +} + +void DecoderStack::clear() +{ + _sample_count = 0; + _frame_complete = false; + _samples_decoded = 0; + _error_message = QString(); + _rows.clear(); + _class_rows.clear(); +} + +void DecoderStack::stop_decode() +{ + if(_decode_state == Stopped) + return; + + if (_decode_thread.get()) { + _decode_thread->interrupt(); + _decode_thread->join(); + _decode_state = Stopped; + } + _decode_thread.reset(); +} + +void DecoderStack::begin_decode() +{ + shared_ptr logic_signal; + shared_ptr data; + + if (!_options_changed) + return; +// if (_decode_thread.joinable()) { +// _decode_thread.interrupt(); +// _decode_thread.join(); +// } + stop_decode(); + + clear(); + + // Check that all decoders have the required channels + BOOST_FOREACH(const shared_ptr &dec, _stack) + if (!dec->have_required_probes()) { + _error_message = tr("One or more required channels " + "have not been specified"); + return; + } + + // Add classes + BOOST_FOREACH (const shared_ptr &dec, _stack) + { + assert(dec); + const srd_decoder *const decc = dec->decoder(); + assert(dec->decoder()); + + // Add a row for the decoder if it doesn't have a row list + if (!decc->annotation_rows) + _rows[Row(decc)] = decode::RowData(); + + // Add the decoder rows + for (const GSList *l = decc->annotation_rows; l; l = l->next) + { + const srd_decoder_annotation_row *const ann_row = + (srd_decoder_annotation_row *)l->data; + assert(ann_row); + + const Row row(decc, ann_row); + + // Add a new empty row data object + _rows[row] = decode::RowData(); + + // Map out all the classes + for (const GSList *ll = ann_row->ann_classes; + ll; ll = ll->next) + _class_rows[make_pair(decc, + GPOINTER_TO_INT(ll->data))] = row; + } + } + + // We get the logic data of the first channel in the list. + // This works because we are currently assuming all + // LogicSignals have the same data/snapshot + BOOST_FOREACH (const shared_ptr &dec, _stack) + if (dec && !dec->channels().empty() && + ((logic_signal = (*dec->channels().begin()).second)) && + ((data = logic_signal->logic_data()))) + break; + + if (!data) + return; + + // Check we have a snapshot of data + const deque< shared_ptr > &snapshots = + data->get_snapshots(); + if (snapshots.empty()) + return; + _snapshot = snapshots.front(); + + // Get the samplerate and start time + _start_time = data->get_start_time(); + _samplerate = data->samplerate(); + if (_samplerate == 0.0) + _samplerate = 1.0; + + //_decode_thread = boost::thread(&DecoderStack::decode_proc, this); + _decode_thread.reset(new boost::thread(&DecoderStack::decode_proc, this)); +} + +uint64_t DecoderStack::get_max_sample_count() const +{ + uint64_t max_sample_count = 0; + + for (map::const_iterator i = _rows.begin(); + i != _rows.end(); i++) + max_sample_count = max(max_sample_count, + (*i).second.get_max_sample()); + + return max_sample_count; +} + +boost::optional DecoderStack::wait_for_data() const +{ + unique_lock input_lock(_input_mutex); + while(!boost::this_thread::interruption_requested() && + !_frame_complete && (uint64_t)_samples_decoded >= _sample_count) + _input_cond.wait(input_lock); + return boost::make_optional( + !boost::this_thread::interruption_requested() && + ((uint64_t)_samples_decoded < _sample_count || !_frame_complete), + _sample_count); +} + +void DecoderStack::decode_data( + const uint64_t sample_count, const unsigned int unit_size, + srd_session *const session) +{ + //uint8_t chunk[DecodeChunkLength]; + uint8_t *chunk = NULL; + chunk = (uint8_t *)realloc(chunk, DecodeChunkLength); + + const uint64_t chunk_sample_count = + DecodeChunkLength / _snapshot->unit_size(); + + for (uint64_t i = 0; + !boost::this_thread::interruption_requested() && + i < sample_count; + i += chunk_sample_count) + { + //lock_guard decode_lock(_global_decode_mutex); + + const uint64_t chunk_end = min( + i + chunk_sample_count, sample_count); + _snapshot->get_samples(chunk, i, chunk_end); + + if (srd_session_send(session, i, i + sample_count, chunk, + (chunk_end - i) * unit_size) != SRD_OK) { + _error_message = tr("Decoder reported an error"); + break; + } + + { + lock_guard lock(_output_mutex); + _samples_decoded = chunk_end; + } + + if (i % DecodeNotifyPeriod == 0) + new_decode_data(); + + } + _options_changed = false; + decode_done(); + //new_decode_data(); +} + +void DecoderStack::decode_proc() +{ + lock_guard decode_lock(_global_decode_mutex); + + optional sample_count; + srd_session *session; + srd_decoder_inst *prev_di = NULL; + + assert(_snapshot); + + // Create the session + srd_session_new(&session); + assert(session); + + _decode_state = Running; + + // Create the decoders + const unsigned int unit_size = _snapshot->unit_size(); + + BOOST_FOREACH(const shared_ptr &dec, _stack) + { + srd_decoder_inst *const di = dec->create_decoder_inst(session, unit_size); + + if (!di) + { + _error_message = tr("Failed to create decoder instance"); + srd_session_destroy(session); + return; + } + + if (prev_di) + srd_inst_stack (session, prev_di, di); + + prev_di = di; + } + + // Get the intial sample count + { + unique_lock input_lock(_input_mutex); + sample_count = _sample_count = _snapshot->get_sample_count(); + } + + // Start the session + srd_session_metadata_set(session, SRD_CONF_SAMPLERATE, + g_variant_new_uint64((uint64_t)_samplerate)); + + srd_pd_output_callback_add(session, SRD_OUTPUT_ANN, + DecoderStack::annotation_callback, this); + + srd_session_start(session); + +// do { +// decode_data(*sample_count, unit_size, session); +// } while(_error_message.isEmpty() && (sample_count = wait_for_data())); + decode_data(*sample_count, unit_size, session); + + // Destroy the session + srd_session_destroy(session); + + _decode_state = Stopped; +} + +void DecoderStack::annotation_callback(srd_proto_data *pdata, void *decoder) +{ + assert(pdata); + assert(decoder); + + DecoderStack *const d = (DecoderStack*)decoder; + assert(d); + + lock_guard lock(d->_output_mutex); + + const Annotation a(pdata); + + // Find the row + assert(pdata->pdo); + assert(pdata->pdo->di); + const srd_decoder *const decc = pdata->pdo->di->decoder; + assert(decc); + + map::iterator row_iter = d->_rows.end(); + + // Try looking up the sub-row of this class + const map, Row>::const_iterator r = + d->_class_rows.find(make_pair(decc, a.format())); + if (r != d->_class_rows.end()) + row_iter = d->_rows.find((*r).second); + else + { + // Failing that, use the decoder as a key + row_iter = d->_rows.find(Row(decc)); + } + + assert(row_iter != d->_rows.end()); + if (row_iter == d->_rows.end()) { + qDebug() << "Unexpected annotation: decoder = " << decc << + ", format = " << a.format(); + assert(0); + return; + } + + // Add the annotation + (*row_iter).second.push_annotation(a); +} + +void DecoderStack::on_new_frame() +{ + //begin_decode(); +} + +void DecoderStack::on_data_received() +{ +// { +// unique_lock lock(_input_mutex); +// if (_snapshot) +// _sample_count = _snapshot->get_sample_count(); +// } +// _input_cond.notify_one(); +} + +void DecoderStack::on_frame_ended() +{ +// { +// unique_lock lock(_input_mutex); +// if (_snapshot) +// _frame_complete = true; +// } +// _input_cond.notify_one(); + _options_changed = true; + begin_decode(); +} + +int DecoderStack::cur_rows_size() +{ + int rows_size = 0; + for (map::const_iterator i = _rows.begin(); + i != _rows.end(); i++) + if ((*i).second.get_max_sample() != 0) + rows_size++; + + if (rows_size == 0) + return 1; + else + return rows_size; +} + +void DecoderStack::options_changed(bool changed) +{ + _options_changed = changed; +} + +} // namespace data +} // namespace pv diff --git a/DSLogic-gui/pv/data/decoderstack.h b/DSLogic-gui/pv/data/decoderstack.h new file mode 100644 index 0000000..8ea1d26 --- /dev/null +++ b/DSLogic-gui/pv/data/decoderstack.h @@ -0,0 +1,185 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DSLOGIC_PV_DATA_DECODERSTACK_H +#define DSLOGIC_PV_DATA_DECODERSTACK_H + +#include "signaldata.h" + +#include + +#include +#include +#include + +#include +#include + +#include +#include + +struct srd_decoder; +struct srd_decoder_annotation_row; +struct srd_channel; +struct srd_proto_data; +struct srd_session; + +namespace DecoderStackTest { +class TwoDecoderStack; +} + +namespace pv { + +class SigSession; + +namespace view { +class LogicSignal; +} + +namespace data { + +class LogicSnapshot; + +namespace decode { +class Annotation; +class Decoder; +} + +class Logic; + +class DecoderStack : public QObject, public SignalData +{ + Q_OBJECT + +private: + static const double DecodeMargin; + static const double DecodeThreshold; + static const int64_t DecodeChunkLength; + static const unsigned int DecodeNotifyPeriod; + +public: + enum decode_state { + Stopped, + Running + }; + +public: + DecoderStack(pv::SigSession &_session, + const srd_decoder *const decoder); + + virtual ~DecoderStack(); + + const std::list< boost::shared_ptr >& stack() const; + void push(boost::shared_ptr decoder); + void remove(int index); + + int64_t samples_decoded() const; + + std::vector< std::pair > get_visible_rows() const; + + /** + * Extracts sorted annotations between two period into a vector. + */ + void get_annotation_subset( + std::vector &dest, + const decode::Row &row, uint64_t start_sample, + uint64_t end_sample) const; + + uint64_t get_max_annotation(const decode::Row &row); + + bool has_annotations(const decode::Row &row) const; + + QString error_message(); + + void clear(); + + uint64_t get_max_sample_count() const; + + void begin_decode(); + + void stop_decode(); + + int cur_rows_size(); + + void options_changed(bool changed); + +private: + boost::optional wait_for_data() const; + + void decode_data(const uint64_t sample_count, + const unsigned int unit_size, srd_session *const session); + + void decode_proc(); + + static void annotation_callback(srd_proto_data *pdata, + void *decoder); + +private slots: + void on_new_frame(); + + void on_data_received(); + + void on_frame_ended(); + +signals: + void new_decode_data(); + void decode_done(); + +private: + pv::SigSession &_session; + + /** + * This mutex prevents more than one decode operation occuring + * concurrently. + * @todo A proper solution should be implemented to allow multiple + * decode operations. + */ + static boost::mutex _global_decode_mutex; + + std::list< boost::shared_ptr > _stack; + + boost::shared_ptr _snapshot; + + mutable boost::mutex _input_mutex; + mutable boost::condition_variable _input_cond; + uint64_t _sample_count; + bool _frame_complete; + + mutable boost::mutex _output_mutex; + int64_t _samples_decoded; + + std::map _rows; + + std::map, decode::Row> _class_rows; + + QString _error_message; + + std::auto_ptr _decode_thread; + decode_state _decode_state; + + bool _options_changed; + + friend class DecoderStackTest::TwoDecoderStack; +}; + +} // namespace data +} // namespace pv + +#endif // DSLOGIC_PV_DATA_DECODERSTACK_H diff --git a/DSLogic-gui/pv/data/dso.cpp b/DSLogic-gui/pv/data/dso.cpp index fc967ff..a72f3b2 100644 --- a/DSLogic-gui/pv/data/dso.cpp +++ b/DSLogic-gui/pv/data/dso.cpp @@ -29,8 +29,8 @@ using namespace std; namespace pv { namespace data { -Dso::Dso(unsigned int num_probes, uint64_t samplerate) : - SignalData(num_probes, samplerate) +Dso::Dso(int num_probes) : + SignalData(num_probes) { } @@ -44,5 +44,10 @@ deque< boost::shared_ptr >& Dso::get_snapshots() return _snapshots; } +void Dso::clear() +{ + _snapshots.clear(); +} + } // namespace data } // namespace pv diff --git a/DSLogic-gui/pv/data/dso.h b/DSLogic-gui/pv/data/dso.h index 591fa27..8ab5137 100644 --- a/DSLogic-gui/pv/data/dso.h +++ b/DSLogic-gui/pv/data/dso.h @@ -36,7 +36,7 @@ class DsoSnapshot; class Dso : public SignalData { public: - Dso(unsigned int num_probes, uint64_t samplerate); + Dso(int num_probes); void push_snapshot( boost::shared_ptr &snapshot); @@ -44,6 +44,8 @@ public: std::deque< boost::shared_ptr >& get_snapshots(); + void clear(); + private: std::deque< boost::shared_ptr > _snapshots; }; diff --git a/DSLogic-gui/pv/data/dsosnapshot.cpp b/DSLogic-gui/pv/data/dsosnapshot.cpp index fb05e5d..f5f46fb 100644 --- a/DSLogic-gui/pv/data/dsosnapshot.cpp +++ b/DSLogic-gui/pv/data/dsosnapshot.cpp @@ -73,6 +73,8 @@ void DsoSnapshot::append_payload(const sr_datafeed_dso &dso) const uint8_t *DsoSnapshot::get_samples( int64_t start_sample, int64_t end_sample, uint16_t index) const { + (void)end_sample; + assert(start_sample >= 0); assert(start_sample < (int64_t)get_sample_count()); assert(end_sample >= 0); diff --git a/DSLogic-gui/pv/data/group.cpp b/DSLogic-gui/pv/data/group.cpp index 6df3ee8..362d595 100644 --- a/DSLogic-gui/pv/data/group.cpp +++ b/DSLogic-gui/pv/data/group.cpp @@ -30,8 +30,8 @@ using namespace std; namespace pv { namespace data { -Group::Group(unsigned int num_probes, uint64_t samplerate) : - SignalData(num_probes, samplerate) +Group::Group() : + SignalData() { } @@ -45,5 +45,10 @@ deque< boost::shared_ptr >& Group::get_snapshots() return _snapshots; } +void Group::clear() +{ + _snapshots.clear(); +} + } // namespace data } // namespace pv diff --git a/DSLogic-gui/pv/data/group.h b/DSLogic-gui/pv/data/group.h index c55f6e9..0ee105f 100644 --- a/DSLogic-gui/pv/data/group.h +++ b/DSLogic-gui/pv/data/group.h @@ -37,7 +37,7 @@ class GroupSnapshot; class Group : public SignalData { public: - Group(unsigned int num_probes, uint64_t samplerate); + Group(); void push_snapshot( boost::shared_ptr &snapshot); @@ -45,6 +45,8 @@ public: std::deque< boost::shared_ptr >& get_snapshots(); + void clear(); + private: std::deque< boost::shared_ptr > _snapshots; }; diff --git a/DSLogic-gui/pv/data/groupsnapshot.cpp b/DSLogic-gui/pv/data/groupsnapshot.cpp index 939ffca..085f697 100644 --- a/DSLogic-gui/pv/data/groupsnapshot.cpp +++ b/DSLogic-gui/pv/data/groupsnapshot.cpp @@ -59,7 +59,7 @@ GroupSnapshot::GroupSnapshot(const boost::shared_ptr &_logic_snap memset(_envelope_levels, 0, sizeof(_envelope_levels)); _data = _logic_snapshot->get_data(); _sample_count = _logic_snapshot->get_sample_count(); - _unit_size = _logic_snapshot->get_unit_size(); + _unit_size = _logic_snapshot->unit_size(); _index_list = index_list; append_payload(); } diff --git a/DSLogic-gui/pv/data/logic.cpp b/DSLogic-gui/pv/data/logic.cpp index 4cdfe2a..f45aac6 100644 --- a/DSLogic-gui/pv/data/logic.cpp +++ b/DSLogic-gui/pv/data/logic.cpp @@ -30,10 +30,9 @@ using namespace std; namespace pv { namespace data { -Logic::Logic(unsigned int num_probes, uint64_t samplerate) : - SignalData(num_probes, samplerate) +Logic::Logic(int num_probes) : + SignalData(num_probes) { - assert(_num_probes > 0); } void Logic::push_snapshot( @@ -47,5 +46,10 @@ deque< boost::shared_ptr >& Logic::get_snapshots() return _snapshots; } +void Logic::clear() +{ + _snapshots.clear(); +} + } // namespace data } // namespace pv diff --git a/DSLogic-gui/pv/data/logic.h b/DSLogic-gui/pv/data/logic.h index d7e23d3..b6b958b 100644 --- a/DSLogic-gui/pv/data/logic.h +++ b/DSLogic-gui/pv/data/logic.h @@ -37,7 +37,9 @@ class LogicSnapshot; class Logic : public SignalData { public: - Logic(unsigned int num_probes, uint64_t samplerate); + Logic(int num_probes); + + int get_num_probes() const; void push_snapshot( boost::shared_ptr &snapshot); @@ -45,6 +47,8 @@ public: std::deque< boost::shared_ptr >& get_snapshots(); + void clear(); + private: std::deque< boost::shared_ptr > _snapshots; }; diff --git a/DSLogic-gui/pv/data/logicsnapshot.cpp b/DSLogic-gui/pv/data/logicsnapshot.cpp index 9a6b2d2..621b4aa 100644 --- a/DSLogic-gui/pv/data/logicsnapshot.cpp +++ b/DSLogic-gui/pv/data/logicsnapshot.cpp @@ -74,6 +74,22 @@ void LogicSnapshot::append_payload( append_payload_to_mipmap(); } +void LogicSnapshot::get_samples(uint8_t *const data, + int64_t start_sample, int64_t end_sample) const +{ + assert(data); + assert(start_sample >= 0); + assert(start_sample <= (int64_t)_sample_count); + assert(end_sample >= 0); + assert(end_sample <= (int64_t)_sample_count); + assert(start_sample <= end_sample); + + //lock_guard lock(_mutex); + + const size_t size = (end_sample - start_sample) * _unit_size; + memcpy(data, (const uint8_t*)_data + start_sample * _unit_size, size); +} + void LogicSnapshot::reallocate_mipmap_level(MipMapLevel &m) { const uint64_t new_data_length = ((m.length + MipMapDataUnit - 1) / @@ -170,14 +186,6 @@ void LogicSnapshot::append_payload_to_mipmap() } } -uint64_t LogicSnapshot::get_sample(uint64_t index) const -{ - assert(_data); - assert(index < _sample_count); - - return *(uint64_t*)((uint8_t*)_data + index * _unit_size); -} - void LogicSnapshot::get_subsampled_edges( std::vector &edges, uint64_t start, uint64_t end, @@ -194,6 +202,9 @@ void LogicSnapshot::get_subsampled_edges( assert(sig_index >= 0); assert(sig_index < 64); + if (!_data) + return; + boost::lock_guard lock(_mutex); const uint64_t block_length = (uint64_t)max(min_length, 1.0f); diff --git a/DSLogic-gui/pv/data/logicsnapshot.h b/DSLogic-gui/pv/data/logicsnapshot.h index 5a3bff8..f618c10 100644 --- a/DSLogic-gui/pv/data/logicsnapshot.h +++ b/DSLogic-gui/pv/data/logicsnapshot.h @@ -67,7 +67,8 @@ public: void append_payload(const sr_datafeed_logic &logic); - uint64_t get_sample(uint64_t index) const; + void get_samples(uint8_t *const data, + int64_t start_sample, int64_t end_sample) const; private: void reallocate_mipmap_level(MipMapLevel &m); diff --git a/DSLogic-gui/pv/data/signaldata.cpp b/DSLogic-gui/pv/data/signaldata.cpp index 355640a..0dede45 100644 --- a/DSLogic-gui/pv/data/signaldata.cpp +++ b/DSLogic-gui/pv/data/signaldata.cpp @@ -20,27 +20,31 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include #include "signaldata.h" namespace pv { namespace data { -SignalData::SignalData(unsigned int num_probes, double samplerate) : - _num_probes(num_probes), - _samplerate(samplerate), - _start_time(0) +SignalData::SignalData(int num_probes) : + _samplerate(0), + _start_time(0), + _num_probes(num_probes) { + assert(num_probes >= 0); } -int SignalData::get_num_probes() const +double SignalData::samplerate() const { - return _num_probes; + return _samplerate; } -double SignalData::get_samplerate() const +void SignalData::set_samplerate(double samplerate) { - return _samplerate; + assert(samplerate > 0); + _samplerate = samplerate; + clear(); } double SignalData::get_start_time() const @@ -48,5 +52,16 @@ double SignalData::get_start_time() const return _start_time; } +int SignalData::get_num_probes() const +{ + return _num_probes; +} + +void SignalData::set_num_probes(int num) +{ + assert(num >= 0); + _num_probes = num; +} + } // namespace data } // namespace pv diff --git a/DSLogic-gui/pv/data/signaldata.h b/DSLogic-gui/pv/data/signaldata.h index be5280a..0d8fdd7 100644 --- a/DSLogic-gui/pv/data/signaldata.h +++ b/DSLogic-gui/pv/data/signaldata.h @@ -32,17 +32,24 @@ namespace data { class SignalData { public: - SignalData(unsigned int num_probes, double samplerate); + SignalData(int num_probes = 1); public: - double get_samplerate() const; + double samplerate() const; + void set_samplerate(double samplerate); + + virtual void clear() = 0; + double get_start_time() const; + int get_num_probes() const; + void set_num_probes(int num); + protected: - const unsigned int _num_probes; - const double _samplerate; - const double _start_time; + double _samplerate; + double _start_time; + int _num_probes; }; } // namespace data diff --git a/DSLogic-gui/pv/data/snapshot.cpp b/DSLogic-gui/pv/data/snapshot.cpp index f05452a..6c2480d 100644 --- a/DSLogic-gui/pv/data/snapshot.cpp +++ b/DSLogic-gui/pv/data/snapshot.cpp @@ -54,6 +54,7 @@ Snapshot::~Snapshot() int Snapshot::init(uint64_t _total_sample_len) { + boost::lock_guard lock(_mutex); _data = malloc(_total_sample_len * _unit_size + sizeof(uint64_t)); @@ -83,7 +84,7 @@ void* Snapshot::get_data() const return _data; } -int Snapshot::get_unit_size() const +int Snapshot::unit_size() const { boost::lock_guard lock(_mutex); return _unit_size; @@ -95,6 +96,16 @@ unsigned int Snapshot::get_channel_num() const return _channel_num; } +uint64_t Snapshot::get_sample(uint64_t index) const +{ + boost::lock_guard lock(_mutex); + + assert(_data); + assert(index < _sample_count); + + return *(uint64_t*)((uint8_t*)_data + index * _unit_size); +} + void Snapshot::append_data(void *data, uint64_t samples) { // boost::lock_guard lock(_mutex); diff --git a/DSLogic-gui/pv/data/snapshot.h b/DSLogic-gui/pv/data/snapshot.h index 5968c4b..784f333 100644 --- a/DSLogic-gui/pv/data/snapshot.h +++ b/DSLogic-gui/pv/data/snapshot.h @@ -44,12 +44,14 @@ public: void * get_data() const; - int get_unit_size() const; + int unit_size() const; bool buf_null() const; unsigned int get_channel_num() const; + uint64_t get_sample(uint64_t index) const; + protected: void append_data(void *data, uint64_t samples); diff --git a/DSLogic-gui/pv/device/device.cpp b/DSLogic-gui/pv/device/device.cpp new file mode 100644 index 0000000..6b1063c --- /dev/null +++ b/DSLogic-gui/pv/device/device.cpp @@ -0,0 +1,105 @@ +/* + * This file is part of the DSLogic-gui project. + * DSLogic-gui is based on PulseView. + * + * Copyright (C) 2014 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include + +#include "device.h" + +using std::ostringstream; +using std::string; + +namespace pv { +namespace device { + +Device::Device(sr_dev_inst *sdi) : + _sdi(sdi) +{ + assert(_sdi); +} + +sr_dev_inst* Device::dev_inst() const +{ + return _sdi; +} + +void Device::use(SigSession *owner) throw(QString) +{ + DevInst::use(owner); + + sr_session_new(); + + assert(_sdi); + sr_dev_open(_sdi); + if (sr_session_dev_add(_sdi) != SR_OK) + throw QString(tr("Failed to use device.")); +} + +void Device::release() +{ + if (_owner) { + DevInst::release(); + sr_session_destroy(); + } + + sr_dev_close(_sdi); +} + +std::string Device::format_device_title() const +{ + ostringstream s; + + assert(_sdi); + + if (_sdi->vendor && _sdi->vendor[0]) { + s << _sdi->vendor; + if ((_sdi->model && _sdi->model[0]) || + (_sdi->version && _sdi->version[0])) + s << ' '; + } + + if (_sdi->model && _sdi->model[0]) { + s << _sdi->model; + if (_sdi->version && _sdi->version[0]) + s << ' '; + } + + if (_sdi->version && _sdi->version[0]) + s << _sdi->version; + + return s.str(); +} + +bool Device::is_trigger_enabled() const +{ + assert(_sdi); + for (const GSList *l = _sdi->channels; l; l = l->next) { + const sr_channel *const p = (const sr_channel *)l->data; + assert(p); + if (p->trigger && p->trigger[0] != '\0') + return true; + } + return false; +} + +} // device +} // pv diff --git a/DSLogic-gui/pv/device/device.h b/DSLogic-gui/pv/device/device.h new file mode 100644 index 0000000..a5aa5f4 --- /dev/null +++ b/DSLogic-gui/pv/device/device.h @@ -0,0 +1,52 @@ +/* + * This file is part of the DSLogic-gui project. + * DSLogic-gui is based on PulseView. + * + * Copyright (C) 2014 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DSLOGIC_PV_DEVICE_DEVICE_H +#define DSLOGIC_PV_DEVICE_DEVICE_H + +#include "devinst.h" + +namespace pv { +namespace device { + +class Device : public DevInst +{ +public: + Device(sr_dev_inst *dev_inst); + + sr_dev_inst* dev_inst() const; + + void use(SigSession *owner) throw(QString); + + void release(); + + std::string format_device_title() const; + + bool is_trigger_enabled() const; + +private: + sr_dev_inst *const _sdi; +}; + +} // device +} // pv + +#endif // DSLOGIC_PV_DEVICE_DEVICE_H diff --git a/DSLogic-gui/pv/device/devinst.cpp b/DSLogic-gui/pv/device/devinst.cpp new file mode 100644 index 0000000..c0ba087 --- /dev/null +++ b/DSLogic-gui/pv/device/devinst.cpp @@ -0,0 +1,203 @@ +/* + * This file is part of the DSLogic-gui project. + * DSLogic-gui is based on PulseView. + * + * Copyright (C) 2014 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include + +#include + +#include "devinst.h" + +#include + +namespace pv { +namespace device { + +DevInst::DevInst() : + _owner(NULL) +{ + _id = malloc(1); +} + +DevInst::~DevInst() +{ + assert(_id); + free(_id); +} + +void* DevInst::get_id() const +{ + assert(_id); + + return _id; +} + +void DevInst::use(SigSession *owner) throw(QString) +{ + assert(owner); + assert(!_owner); + _owner = owner; +} + +void DevInst::release() +{ + if (_owner) { + _owner->release_device(this); + _owner = NULL; + } +} + +SigSession* DevInst::owner() const +{ + return _owner; +} + +GVariant* DevInst::get_config(const sr_channel *ch, const sr_channel_group *group, int key) +{ + GVariant *data = NULL; + assert(_owner); + sr_dev_inst *const sdi = dev_inst(); + assert(sdi); + if (sr_config_get(sdi->driver, sdi, ch, group, key, &data) != SR_OK) + return NULL; + return data; +} + +bool DevInst::set_config(const sr_channel *ch, const sr_channel_group *group, int key, GVariant *data) +{ + assert(_owner); + sr_dev_inst *const sdi = dev_inst(); + assert(sdi); + if(sr_config_set(sdi, ch, group, key, data) == SR_OK) { + config_changed(); + return true; + } + return false; +} + +GVariant* DevInst::list_config(const sr_channel_group *group, int key) +{ + GVariant *data = NULL; + assert(_owner); + sr_dev_inst *const sdi = dev_inst(); + assert(sdi); + if (sr_config_list(sdi->driver, sdi, group, key, &data) != SR_OK) + return NULL; + return data; +} + +void DevInst::enable_probe(const sr_channel *probe, bool enable) +{ + assert(_owner); + sr_dev_inst *const sdi = dev_inst(); + assert(sdi); + for (const GSList *p = sdi->channels; p; p = p->next) + if (probe == p->data) { + const_cast(probe)->enabled = enable; + config_changed(); + return; + } + + // Probe was not found in the device + assert(0); +} + +uint64_t DevInst::get_sample_limit() +{ + uint64_t sample_limit; + GVariant* gvar = get_config(NULL, NULL, SR_CONF_LIMIT_SAMPLES); + if (gvar != NULL) { + sample_limit = g_variant_get_uint64(gvar); + g_variant_unref(gvar); + } else { + sample_limit = 0U; + } + return sample_limit; +} + +uint64_t DevInst::get_sample_rate() +{ + uint64_t sample_rate; + GVariant* gvar = get_config(NULL, NULL, SR_CONF_SAMPLERATE); + if (gvar != NULL) { + sample_rate = g_variant_get_uint64(gvar); + g_variant_unref(gvar); + } else { + sample_rate = 0U; + } + return sample_rate; +} + +uint64_t DevInst::get_time_base() +{ + uint64_t time_base; + GVariant* gvar = get_config(NULL, NULL, SR_CONF_TIMEBASE); + if (gvar != NULL) { + time_base = g_variant_get_uint64(gvar); + g_variant_unref(gvar); + } else { + time_base = 0U; + } + return time_base; +} + + +double DevInst::get_sample_time() +{ + uint64_t sample_rate = get_sample_rate(); + uint64_t sample_limit = get_sample_limit(); + double sample_time; + + if (sample_rate == 0) + sample_time = 0; + else + sample_time = sample_limit * 1.0f / sample_rate; + + return sample_time; +} + +GSList* DevInst::get_dev_mode_list() +{ + assert(_owner); + sr_dev_inst *const sdi = dev_inst(); + assert(sdi); + return sr_dev_mode_list(sdi->driver); +} + +bool DevInst::is_trigger_enabled() const +{ + return false; +} + +void DevInst::start() +{ + if (sr_session_start() != SR_OK) + throw tr("Failed to start session."); +} + +void DevInst::run() +{ + sr_session_run(); +} + +} // device +} // pv diff --git a/DSLogic-gui/pv/device/devinst.h b/DSLogic-gui/pv/device/devinst.h new file mode 100644 index 0000000..aab7d57 --- /dev/null +++ b/DSLogic-gui/pv/device/devinst.h @@ -0,0 +1,132 @@ +/* + * This file is part of the DSLogic-gui project. + * DSLogic-gui is based on PulseView. + * + * Copyright (C) 2014 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DSLOGIC_PV_DEVICE_DEVINST_H +#define DSLOGIC_PV_DEVICE_DEVINST_H + +#include + +#include + +#include + +#include + +#include + +struct sr_dev_inst; +struct sr_channel; +struct sr_channel_group; + +namespace pv { + +class SigSession; + +namespace device { + +class DevInst : public QObject +{ + Q_OBJECT + +protected: + DevInst(); + ~DevInst(); + +public: + virtual sr_dev_inst* dev_inst() const = 0; + + virtual void use(SigSession *owner) throw(QString); + + virtual void release(); + + SigSession* owner() const; + + virtual std::string format_device_title() const = 0; + + GVariant* get_config(const sr_channel *ch, const sr_channel_group *group, int key); + + bool set_config(const sr_channel *ch, const sr_channel_group *group, int key, GVariant *data); + + GVariant* list_config(const sr_channel_group *group, int key); + + void enable_probe(const sr_channel *probe, bool enable = true); + + /** + * @brief Gets the sample limit from the driver. + * + * @return The returned sample limit from the driver, or 0 if the + * sample limit could not be read. + */ + uint64_t get_sample_limit(); + + /** + * @brief Gets the sample rate from the driver. + * + * @return The returned sample rate from the driver, or 0 if the + * sample rate could not be read. + */ + uint64_t get_sample_rate(); + + /** + * @brief Gets the sample time from the driver. + * + * @return The returned sample time from the driver, or 0 if the + * sample time could not be read. + */ + double get_sample_time(); + + /** + * @brief Gets the time base from the driver. + * + * @return The returned time base from the driver, or 0 if the + * time base could not be read. + */ + uint64_t get_time_base(); + + /** + * @brief Gets the device mode list from the driver. + * + * @return The returned device mode list from the driver, or NULL if the + * mode list could not be read. + */ + GSList* get_dev_mode_list(); + + virtual bool is_trigger_enabled() const; + +public: + virtual void start(); + + virtual void run(); + + virtual void* get_id() const; + +signals: + void config_changed(); + +protected: + SigSession *_owner; + void *_id; +}; + +} // device +} // pv + +#endif // DSLOGIC_PV_DEVICE_DEVINST_H diff --git a/DSLogic-gui/pv/device/file.cpp b/DSLogic-gui/pv/device/file.cpp new file mode 100644 index 0000000..1e864b0 --- /dev/null +++ b/DSLogic-gui/pv/device/file.cpp @@ -0,0 +1,67 @@ +/* + * This file is part of the DSLogic-gui project. + * DSLogic-gui is based on PulseView. + * + * Copyright (C) 2014 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "file.h" +#include "inputfile.h" +#include "sessionfile.h" + +#include + +#include + +using std::string; + +namespace pv { +namespace device { + +File::File(const std::string path) : + _path(path) +{ +} + +std::string File::format_device_title() const +{ + return boost::filesystem::path(_path).filename().string(); +} + +File* File::create(const string &name) +{ + if (sr_session_load(name.c_str()) == SR_OK) { + GSList *devlist = NULL; + sr_session_dev_list(&devlist); + sr_session_destroy(); + + if (devlist) { + sr_dev_inst *const sdi = (sr_dev_inst*)devlist->data; + g_slist_free(devlist); + if (sdi) { + sr_dev_close(sdi); + sr_dev_clear(sdi->driver); + return new SessionFile(name); + } + } + } + + return new InputFile(name); +} + +} // device +} // pv diff --git a/DSLogic-gui/pv/device/file.h b/DSLogic-gui/pv/device/file.h new file mode 100644 index 0000000..e4d5796 --- /dev/null +++ b/DSLogic-gui/pv/device/file.h @@ -0,0 +1,50 @@ +/* + * This file is part of the DSLogic-gui project. + * DSLogic-gui is based on PulseView. + * + * Copyright (C) 2014 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DSLOGIC_PV_DEVICE_FILE_H +#define DSLOGIC_PV_DEVICE_FILE_H + +#include + +#include "devinst.h" + +namespace pv { +namespace device { + +class File : public DevInst +{ +protected: + File(const std::string path); + +public: + static File* create(const std::string &name); + +public: + std::string format_device_title() const; + +protected: + const std::string _path; +}; + +} // device +} // pv + +#endif // DSLOGIC_PV_DEVICE_FILE_H diff --git a/DSLogic-gui/pv/device/inputfile.cpp b/DSLogic-gui/pv/device/inputfile.cpp new file mode 100644 index 0000000..13a3d18 --- /dev/null +++ b/DSLogic-gui/pv/device/inputfile.cpp @@ -0,0 +1,144 @@ +/* + * This file is part of the DSLogic-gui project. + * DSLogic-gui is based on PulseView. + * + * Copyright (C) 2014 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include +#include +#include + +#include "inputfile.h" + +#include + +using std::string; + +namespace pv { +namespace device { + +InputFile::InputFile(const std::string &path) : + File(path), + _input(NULL) +{ +} + +sr_dev_inst* InputFile::dev_inst() const +{ + assert(_input); + return _input->sdi; +} + +void InputFile::use(SigSession *owner) throw(QString) +{ + assert(!_input); + + _input = load_input_file_format(_path, NULL); + File::use(owner); + + sr_session_new(); + + if (sr_session_dev_add(_input->sdi) != SR_OK) + throw tr("Failed to add session device."); +} + +void InputFile::release() +{ + if (!_owner) + return; + + assert(_input); + File::release(); + sr_dev_close(_input->sdi); + sr_session_destroy(); + _input = NULL; +} + +sr_input_format* InputFile::determine_input_file_format( + const string &filename) +{ + int i; + + /* If there are no input formats, return NULL right away. */ + sr_input_format *const *const inputs = sr_input_list(); + if (!inputs) { + g_critical("No supported input formats available."); + return NULL; + } + + /* Otherwise, try to find an input module that can handle this file. */ + for (i = 0; inputs[i]; i++) { + if (inputs[i]->format_match(filename.c_str())) + break; + } + + /* Return NULL if no input module wanted to touch this. */ + if (!inputs[i]) { + g_critical("Error: no matching input module found."); + return NULL; + } + + return inputs[i]; +} + +sr_input* InputFile::load_input_file_format(const string &filename, + sr_input_format *format) +{ + struct stat st; + sr_input *in; + + if (!format && !(format = + determine_input_file_format(filename.c_str()))) { + /* The exact cause was already logged. */ + throw tr("Failed to load file"); + } + + if (stat(filename.c_str(), &st) == -1) + throw tr("Failed to load file"); + + /* Initialize the input module. */ + if (!(in = new sr_input)) { + throw tr("Failed to allocate input module."); + } + + in->format = format; + in->param = NULL; + if (in->format->init && + in->format->init(in, filename.c_str()) != SR_OK) { + throw tr("Failed to load file"); + } + + return in; +} + +void InputFile::start() +{ +} + +void InputFile::run() +{ + assert(_input); + assert(_input->format); + assert(_input->format->loadfile); + _input->format->loadfile(_input, _path.c_str()); +} + +} // device +} // pv diff --git a/DSLogic-gui/pv/device/inputfile.h b/DSLogic-gui/pv/device/inputfile.h new file mode 100644 index 0000000..1ad3b34 --- /dev/null +++ b/DSLogic-gui/pv/device/inputfile.h @@ -0,0 +1,69 @@ +/* + * This file is part of the DSLogic-gui project. + * DSLogic-gui is based on PulseView. + * + * Copyright (C) 2014 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DSLOGIC_PV_DEVICE_INPUTFILE_H +#define DSLOGIC_PV_DEVICE_INPUTFILE_H + +#include "file.h" + +#include + +struct sr_input; +struct sr_input_format; + +namespace pv { +namespace device { + +class InputFile : public File +{ +public: + InputFile(const std::string &path); + + sr_dev_inst* dev_inst() const; + + virtual void use(SigSession *owner) throw(QString); + + virtual void release(); + + virtual void start(); + + virtual void run(); + +private: + /** + * Attempts to autodetect the format. Failing that + * @param filename The filename of the input file. + * @return A pointer to the 'struct sr_input_format' that should be used, + * or NULL if no input format was selected or auto-detected. + */ + static sr_input_format* determine_input_file_format( + const std::string &filename); + + static sr_input* load_input_file_format(const std::string &filename, + sr_input_format *format); +private: + sr_input *_input; +}; + +} // device +} // pv + +#endif // DSLOGIC_PV_DEVICE_INPUTFILE_H diff --git a/DSLogic-gui/pv/device/sessionfile.cpp b/DSLogic-gui/pv/device/sessionfile.cpp new file mode 100644 index 0000000..c996436 --- /dev/null +++ b/DSLogic-gui/pv/device/sessionfile.cpp @@ -0,0 +1,76 @@ +/* + * This file is part of the DSLogic-gui project. + * DSLogic-gui is based on PulseView. + * + * Copyright (C) 2014 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "sessionfile.h" + +#include + +namespace pv { +namespace device { + +SessionFile::SessionFile(const std::string &path) : + File(path), + _sdi(NULL) +{ +} + +sr_dev_inst* SessionFile::dev_inst() const +{ + return _sdi; +} + +void SessionFile::use(SigSession *owner) throw(QString) +{ + assert(!_sdi); + + if (sr_session_load(_path.c_str()) != SR_OK) + throw tr("Failed to open file.\n"); + + GSList *devlist = NULL; + sr_session_dev_list(&devlist); + + if (!devlist || !devlist->data) { + if (devlist) + g_slist_free(devlist); + throw tr("Failed to start session."); + } + + _sdi = (sr_dev_inst*)devlist->data; + g_slist_free(devlist); + + File::use(owner); +} + +void SessionFile::release() +{ + if (!_owner) + return; + + assert(_sdi); + File::release(); + sr_dev_close(_sdi); + sr_dev_clear(_sdi->driver); + sr_session_destroy(); + _sdi = NULL; +} + +} // device +} // pv diff --git a/DSLogic-gui/pv/device/sessionfile.h b/DSLogic-gui/pv/device/sessionfile.h new file mode 100644 index 0000000..ef1845e --- /dev/null +++ b/DSLogic-gui/pv/device/sessionfile.h @@ -0,0 +1,48 @@ +/* + * This file is part of the DSLogic-gui project. + * DSLogic-gui is based on PulseView. + * + * Copyright (C) 2014 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DSLOGIC_PV_DEVICE_SESSIONFILE_H +#define DSLOGIC_PV_DEVICE_SESSIONFILE_H + +#include "file.h" + +namespace pv { +namespace device { + +class SessionFile : public File +{ +public: + SessionFile(const std::string &path); + + sr_dev_inst* dev_inst() const; + + virtual void use(SigSession *owner) throw(QString); + + virtual void release(); + +private: + sr_dev_inst *_sdi; +}; + +} // device +} // pv + +#endif // DSLOGIC_PV_DEVICE_SESSIONFILE_H diff --git a/DSLogic-gui/pv/devicemanager.cpp b/DSLogic-gui/pv/devicemanager.cpp index 66e3500..a6c1f1b 100644 --- a/DSLogic-gui/pv/devicemanager.cpp +++ b/DSLogic-gui/pv/devicemanager.cpp @@ -22,6 +22,8 @@ #include "devicemanager.h" +#include "device/devinst.h" +#include "device/device.h" #include "sigsession.h" #include @@ -34,9 +36,17 @@ #include #include +#include + #include -using namespace std; +using boost::shared_ptr; +using std::list; +using std::map; +using std::ostringstream; +using std::runtime_error; +using std::string; + char config_path[256]; namespace pv { @@ -53,57 +63,47 @@ DeviceManager::~DeviceManager() release_devices(); } -const std::list& DeviceManager::devices() const +const std::list > &DeviceManager::devices() const { return _devices; } -int DeviceManager::use_device(sr_dev_inst *sdi, SigSession *owner) -{ - assert(sdi); - assert(owner); - - if (sr_dev_open(sdi) != SR_OK) - return SR_ERR; - - _used_devices[sdi] = owner; - return SR_OK; -} - -void DeviceManager::release_device(sr_dev_inst *sdi) +void DeviceManager::add_device(boost::shared_ptr device) { - assert(sdi); - - // Notify the owner, and removed the device from the used device list - _used_devices[sdi]->release_device(sdi); - _used_devices.erase(sdi); + assert(device); - sr_dev_close(sdi); + if (std::find(_devices.begin(), _devices.end(), device) == + _devices.end()) + _devices.push_front(device); } -list DeviceManager::driver_scan( +std::list > DeviceManager::driver_scan( struct sr_dev_driver *const driver, GSList *const drvopts) { - list driver_devices; + list< shared_ptr > driver_devices; assert(driver); // Remove any device instances from this driver from the device // list. They will not be valid after the scan. - list::iterator i = _devices.begin(); + list< shared_ptr >::iterator i = _devices.begin(); while (i != _devices.end()) { - if ((*i)->driver == driver) + if ((*i)->dev_inst() && + (*i)->dev_inst()->driver == driver) { + (*i)->release(); i = _devices.erase(i); - else + } else { i++; + } } - // Release this driver and all it's attached devices - release_driver(driver); + // Clear all the old device instances from this driver + sr_dev_clear(driver); + //release_driver(driver); // Check If DSLogic driver if (strcmp(driver->name, "DSLogic") == 0) { - QDir dir(QApplication::applicationDirPath()); + QDir dir(QCoreApplication::applicationDirPath()); if (!dir.cd("res")) return driver_devices; std::string str = dir.absolutePath().toStdString() + "/"; @@ -113,43 +113,19 @@ list DeviceManager::driver_scan( // Do the scan GSList *const devices = sr_driver_scan(driver, drvopts); for (GSList *l = devices; l; l = l->next) - driver_devices.push_back((sr_dev_inst*)l->data); + driver_devices.push_front(shared_ptr( + new device::Device((sr_dev_inst*)l->data))); g_slist_free(devices); - driver_devices.sort(compare_devices); + //driver_devices.sort(compare_devices); // Add the scanned devices to the main list _devices.insert(_devices.end(), driver_devices.begin(), driver_devices.end()); - _devices.sort(compare_devices); + //_devices.sort(compare_devices); return driver_devices; } -string DeviceManager::format_device_title(const sr_dev_inst *const sdi) -{ - ostringstream s; - - assert(sdi); - - if (sdi->vendor && sdi->vendor[0]) { - s << sdi->vendor; - if ((sdi->model && sdi->model[0]) || - (sdi->version && sdi->version[0])) - s << ' '; - } - - if (sdi->model && sdi->model[0]) { - s << sdi->model; - if (sdi->version && sdi->version[0]) - s << ' '; - } - - if (sdi->version && sdi->version[0]) - s << sdi->version; - - return s.str(); -} - void DeviceManager::init_drivers() { // Initialise all libsigrok drivers @@ -165,17 +141,16 @@ void DeviceManager::init_drivers() void DeviceManager::release_devices() { - // Release all the used devices - for (map::iterator i = _used_devices.begin(); - i != _used_devices.end();) - release_device((*i++).first); - - _used_devices.clear(); + // Release all the used devices + BOOST_FOREACH(shared_ptr dev, _devices) { + assert(dev); + dev->release(); + } - // Clear all the drivers - sr_dev_driver **const drivers = sr_driver_list(); - for (sr_dev_driver **driver = drivers; *driver; driver++) - sr_dev_clear(*driver); + // Clear all the drivers + sr_dev_driver **const drivers = sr_driver_list(); + for (sr_dev_driver **driver = drivers; *driver; driver++) + sr_dev_clear(*driver); } void DeviceManager::scan_all_drivers() @@ -188,53 +163,22 @@ void DeviceManager::scan_all_drivers() void DeviceManager::release_driver(struct sr_dev_driver *const driver) { - assert(driver); -// map::iterator i = _used_devices.begin(); -// while(i != _used_devices.end()) { -// if((*i).first->driver == driver) -// { -// // Notify the current owner of the device -// (*i).second->release_device((*i).first); - -// // Close the device instance -// sr_dev_close((*i).first); - -// // Remove it from the used device list -// i = _used_devices.erase(i); -// } else { -// i++; -// } -// } - for (map::iterator i = _used_devices.begin(); - i != _used_devices.end();) - if((*i).first->driver == driver) - { - // Notify the current owner of the device - (*i).second->release_device((*i).first); - - // Close the device instance - sr_dev_close((*i).first); - - // Remove it from the used device list - _used_devices.erase(i++); - } else { - i++; - } - - // Clear all the old device instances from this driver - sr_dev_clear(driver); -} + BOOST_FOREACH(shared_ptr dev, _devices) { + assert(dev); + if(dev->dev_inst()->driver == driver) + dev->release(); + } -bool DeviceManager::compare_devices(const sr_dev_inst *const a, - const sr_dev_inst *const b) -{ - return format_device_title(a).compare(format_device_title(b)) < 0; + // Clear all the old device instances from this driver + sr_dev_clear(driver); } -int DeviceManager::test_device(sr_dev_inst *sdi) +bool DeviceManager::compare_devices(boost::shared_ptr a, + boost::shared_ptr b) { - assert(sdi); - return sdi->driver->dev_test(sdi); + assert(a); + assert(b); + return a->format_device_title().compare(b->format_device_title()) <= 0; } } // namespace pv diff --git a/DSLogic-gui/pv/devicemanager.h b/DSLogic-gui/pv/devicemanager.h index 0fc6396..94bd896 100644 --- a/DSLogic-gui/pv/devicemanager.h +++ b/DSLogic-gui/pv/devicemanager.h @@ -47,6 +47,10 @@ namespace pv { class SigSession; +namespace device { +class DevInst; +} + class DeviceManager { public: @@ -54,36 +58,29 @@ public: ~DeviceManager(); - const std::list& devices() const; - - int use_device(sr_dev_inst *sdi, SigSession *owner); + const std::list< boost::shared_ptr >& devices() const; - void release_device(sr_dev_inst *sdi); + void add_device(boost::shared_ptr device); - std::list driver_scan( + std::list< boost::shared_ptr > driver_scan( struct sr_dev_driver *const driver, GSList *const drvopts = NULL); - static std::string format_device_title(const sr_dev_inst *const sdi); - - void scan_all_drivers(); - - int test_device(sr_dev_inst *sdi); - private: void init_drivers(); void release_devices(); + void scan_all_drivers(); + void release_driver(struct sr_dev_driver *const driver); - static bool compare_devices(const sr_dev_inst *const a, - const sr_dev_inst *const b); + static bool compare_devices(boost::shared_ptr a, + boost::shared_ptr b); private: struct sr_context *const _sr_ctx; - std::list _devices; - std::map _used_devices; + std::list< boost::shared_ptr > _devices; }; } // namespace pv diff --git a/DSLogic-gui/pv/dialogs/deviceoptions.cpp b/DSLogic-gui/pv/dialogs/deviceoptions.cpp index 726fc92..a09dca2 100644 --- a/DSLogic-gui/pv/dialogs/deviceoptions.cpp +++ b/DSLogic-gui/pv/dialogs/deviceoptions.cpp @@ -42,7 +42,6 @@ DeviceOptions::DeviceOptions(QWidget *parent, struct sr_dev_inst *sdi) : _layout(this), _probes_box(tr("Channels"), this), _props_box(tr("Mode"), this), - _mode_comboBox(this), _button_box(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this), _device_options_binding(sdi) @@ -50,11 +49,6 @@ DeviceOptions::DeviceOptions(QWidget *parent, struct sr_dev_inst *sdi) : setWindowTitle(tr("Configure Device")); setLayout(&_layout); - _last_mode = sdi->mode; - _mode_comboBox.addItem(mode_strings[LOGIC]); - _mode_comboBox.addItem(mode_strings[DSO]); - _mode_comboBox.addItem(mode_strings[ANALOG]); - _mode_comboBox.setCurrentIndex(_sdi->mode); _props_box.setLayout(&_props_box_layout); _props_box_layout.addWidget(get_property_form()); _layout.addWidget(&_props_box); @@ -68,9 +62,6 @@ DeviceOptions::DeviceOptions(QWidget *parent, struct sr_dev_inst *sdi) : connect(&_button_box, SIGNAL(accepted()), this, SLOT(accept())); connect(&_button_box, SIGNAL(rejected()), this, SLOT(reject())); - - connect(&_mode_comboBox, SIGNAL(currentIndexChanged(QString)), - this, SLOT(mode_changed(QString))); } void DeviceOptions::accept() @@ -79,7 +70,6 @@ void DeviceOptions::accept() QDialog::accept(); - _last_mode = _sdi->mode; // Commit the properties const vector< boost::shared_ptr > &properties = _device_options_binding.properties(); @@ -90,8 +80,8 @@ void DeviceOptions::accept() // Commit the probes int index = 0; - for (const GSList *l = _sdi->probes; l; l = l->next) { - sr_probe *const probe = (sr_probe*)l->data; + for (const GSList *l = _sdi->channels; l; l = l->next) { + sr_channel *const probe = (sr_channel*)l->data; assert(probe); probe->enabled = (_probes_checkBox_list.at(index)->checkState() == Qt::Checked); @@ -104,8 +94,6 @@ void DeviceOptions::reject() using namespace Qt; QDialog::reject(); - // Mode Recovery - sr_config_set(_sdi, SR_CONF_DEVICE_MODE, g_variant_new_string(_mode_comboBox.itemText(_last_mode).toLocal8Bit())); } QWidget* DeviceOptions::get_property_form() @@ -114,7 +102,6 @@ QWidget* DeviceOptions::get_property_form() QFormLayout *const layout = new QFormLayout(form); form->setLayout(layout); - layout->addRow("Device Mode", &_mode_comboBox); const vector< boost::shared_ptr > &properties = _device_options_binding.properties(); BOOST_FOREACH(boost::shared_ptr p, properties) @@ -144,8 +131,8 @@ void DeviceOptions::setup_probes() _probes_label_list.clear(); _probes_checkBox_list.clear(); - for (const GSList *l = _sdi->probes; l; l = l->next) { - sr_probe *const probe = (sr_probe*)l->data; + for (const GSList *l = _sdi->channels; l; l = l->next) { + sr_channel *const probe = (sr_channel*)l->data; assert(probe); QLabel *probe_label = new QLabel(QString::number(probe->index), this); @@ -192,13 +179,5 @@ void DeviceOptions::disable_all_probes() set_all_probes(false); } -void DeviceOptions::mode_changed(QString mode) -{ - (void)mode; - // Commit mode - sr_config_set(_sdi, SR_CONF_DEVICE_MODE, g_variant_new_string(_mode_comboBox.currentText().toLocal8Bit())); - setup_probes(); -} - } // namespace dialogs } // namespace pv diff --git a/DSLogic-gui/pv/dialogs/deviceoptions.h b/DSLogic-gui/pv/dialogs/deviceoptions.h index ad804d2..3db2e92 100644 --- a/DSLogic-gui/pv/dialogs/deviceoptions.h +++ b/DSLogic-gui/pv/dialogs/deviceoptions.h @@ -64,12 +64,9 @@ private: private slots: void enable_all_probes(); void disable_all_probes(); - void mode_changed(QString mode); private: struct sr_dev_inst *const _sdi; - int _last_mode; - QVBoxLayout _layout; QGroupBox _probes_box; @@ -78,7 +75,6 @@ private: QVector _probes_checkBox_list; QGroupBox _props_box; - QComboBox _mode_comboBox; QVBoxLayout _props_box_layout; QDialogButtonBox _button_box; diff --git a/DSLogic-gui/pv/dialogs/storeprogress.cpp b/DSLogic-gui/pv/dialogs/storeprogress.cpp new file mode 100644 index 0000000..f608f54 --- /dev/null +++ b/DSLogic-gui/pv/dialogs/storeprogress.cpp @@ -0,0 +1,84 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2014 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "storeprogress.h" + +#include + +namespace pv { +namespace dialogs { + +StoreProgress::StoreProgress(const QString &file_name, + SigSession &session, QWidget *parent) : + QProgressDialog(tr("Saving..."), tr("Cancel"), 0, 0, parent), + _session(file_name.toStdString(), session) +{ + connect(&_session, SIGNAL(progress_updated()), + this, SLOT(on_progress_updated())); +} + +StoreProgress::~StoreProgress() +{ + _session.wait(); +} + +void StoreProgress::run() +{ + if (_session.start()) + show(); + else + show_error(); +} + +void StoreProgress::show_error() +{ + QMessageBox msg(parentWidget()); + msg.setText(tr("Failed to save session.")); + msg.setInformativeText(_session.error()); + msg.setStandardButtons(QMessageBox::Ok); + msg.setIcon(QMessageBox::Warning); + msg.exec(); +} + +void StoreProgress::closeEvent(QCloseEvent*) +{ + _session.cancel(); +} + +void StoreProgress::on_progress_updated() +{ + const std::pair p = _session.progress(); + assert(p.first <= p.second); + + setValue(p.first); + setMaximum(p.second); + + const QString err = _session.error(); + if (!err.isEmpty()) { + show_error(); + close(); + } + + if (p.first == p.second) + close(); +} + +} // dialogs +} // pv diff --git a/DSLogic-gui/pv/dialogs/storeprogress.h b/DSLogic-gui/pv/dialogs/storeprogress.h new file mode 100644 index 0000000..dde071c --- /dev/null +++ b/DSLogic-gui/pv/dialogs/storeprogress.h @@ -0,0 +1,65 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2014 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DSLOGIC_PV_DIALOGS_SAVEPROGRESS_H +#define DSLOGIC_PV_DIALOGS_SAVEPROGRESS_H + +#include + +#include + +#include + +#include + +namespace pv { + +class SigSession; + +namespace dialogs { + +class StoreProgress : public QProgressDialog +{ + Q_OBJECT + +public: + StoreProgress(const QString &file_name, SigSession &session, + QWidget *parent = 0); + + virtual ~StoreProgress(); + + void run(); + +private: + void show_error(); + + void closeEvent(QCloseEvent*); + +private slots: + void on_progress_updated(); + +private: + pv::StoreSession _session; +}; + +} // dialogs +} // pv + +#endif // PULSEVIEW_PV_DIALOGS_SAVEPROGRESS_H diff --git a/DSLogic-gui/pv/dock/dsotriggerdock.cpp b/DSLogic-gui/pv/dock/dsotriggerdock.cpp index d8f8277..b2b22e8 100644 --- a/DSLogic-gui/pv/dock/dsotriggerdock.cpp +++ b/DSLogic-gui/pv/dock/dsotriggerdock.cpp @@ -23,6 +23,7 @@ #include "dsotriggerdock.h" #include "../sigsession.h" +#include "../device/devinst.h" #include #include @@ -135,11 +136,10 @@ void DsoTriggerDock::paintEvent(QPaintEvent *) void DsoTriggerDock::pos_changed(int pos) { int ret; - quint32 real_pos; - real_pos = pos*_session.get_total_sample_len()/100.0f; - real_pos = (_session.get_last_sample_rate() > SR_MHZ(100)) ? real_pos/2 : real_pos; - ret = sr_config_set(_session.get_device(), SR_CONF_HORIZ_TRIGGERPOS, g_variant_new_uint32(real_pos)); - if (ret != SR_OK) { + ret = _session.get_device()->set_config(NULL, NULL, + SR_CONF_HORIZ_TRIGGERPOS, + g_variant_new_uint16((uint16_t)pos)); + if (!ret) { QMessageBox msg(this); msg.setText("Trigger Setting Issue"); msg.setInformativeText("Change horiz trigger position failed!"); @@ -147,6 +147,10 @@ void DsoTriggerDock::pos_changed(int pos) msg.setIcon(QMessageBox::Warning); msg.exec(); } + + uint64_t sample_limit = _session.get_device()->get_sample_limit(); + uint64_t trig_pos = sample_limit * pos / 100; + set_trig_pos(trig_pos); } void DsoTriggerDock::source_changed() @@ -154,8 +158,10 @@ void DsoTriggerDock::source_changed() int id = source_group->checkedId(); int ret; - ret = sr_config_set(_session.get_device(), SR_CONF_TRIGGER_SOURCE, g_variant_new_byte(id)); - if (ret != SR_OK) { + ret = _session.get_device()->set_config(NULL, NULL, + SR_CONF_TRIGGER_SOURCE, + g_variant_new_byte(id)); + if (!ret) { QMessageBox msg(this); msg.setText("Trigger Setting Issue"); msg.setInformativeText("Change trigger source failed!"); @@ -170,8 +176,10 @@ void DsoTriggerDock::type_changed() int id = type_group->checkedId(); int ret; - ret = sr_config_set(_session.get_device(), SR_CONF_TRIGGER_SLOPE, g_variant_new_byte(id)); - if (ret != SR_OK) { + ret = _session.get_device()->set_config(NULL, NULL, + SR_CONF_TRIGGER_SLOPE, + g_variant_new_byte(id)); + if (!ret) { QMessageBox msg(this); msg.setText("Trigger Setting Issue"); msg.setInformativeText("Change trigger type failed!"); @@ -183,7 +191,7 @@ void DsoTriggerDock::type_changed() void DsoTriggerDock::device_change() { - if (strcmp(_session.get_device()->driver->name, "DSLogic") != 0) { + if (strcmp(_session.get_device()->dev_inst()->driver->name, "DSLogic") != 0) { position_spinBox->setDisabled(true); position_slider->setDisabled(true); } else { diff --git a/DSLogic-gui/pv/dock/dsotriggerdock.h b/DSLogic-gui/pv/dock/dsotriggerdock.h index 5c135c4..72095d0 100644 --- a/DSLogic-gui/pv/dock/dsotriggerdock.h +++ b/DSLogic-gui/pv/dock/dsotriggerdock.h @@ -52,6 +52,7 @@ public: void device_change(); signals: + void set_trig_pos(quint64 trig_pos); private slots: void pos_changed(int pos); diff --git a/DSLogic-gui/pv/dock/measuredock.cpp b/DSLogic-gui/pv/dock/measuredock.cpp index 43c43cf..26dcd6f 100644 --- a/DSLogic-gui/pv/dock/measuredock.cpp +++ b/DSLogic-gui/pv/dock/measuredock.cpp @@ -20,6 +20,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include #include "measuredock.h" #include "../sigsession.h" @@ -27,6 +28,9 @@ #include "../view/view.h" #include "../view/timemarker.h" #include "../view/ruler.h" +#include "../view/logicsignal.h" +#include "../data/signaldata.h" +#include "../data/snapshot.h" #include #include @@ -34,6 +38,8 @@ #include "libsigrok4DSLogic/libsigrok.h" +using boost::shared_ptr; + namespace pv { namespace dock { @@ -71,23 +77,42 @@ MeasureDock::MeasureDock(QWidget *parent, View &view, SigSession &session) : _cursor_groupBox = new QGroupBox("Cursor measurement", this); _t1_comboBox = new QComboBox(this); _t2_comboBox = new QComboBox(this); - _delta_label = new QLabel("#####", this); - _cnt_label = new QLabel("#####", this); + _t3_comboBox = new QComboBox(this); + _delta_label_t1t2 = new QLabel("#####", this); + _cnt_label_t1t2 = new QLabel("#####", this); + _delta_label_t2t3 = new QLabel("#####", this); + _cnt_label_t2t3 = new QLabel("#####", this); + _delta_label_t1t3 = new QLabel("#####", this); + _cnt_label_t1t3 = new QLabel("#####", this); + _t1_last_index = 0; + _t2_last_index = 0; + _t3_last_index = 0; _cursor_layout = new QGridLayout(); _cursor_layout->addWidget(new QLabel("T1: ", this), 0, 0); _cursor_layout->addWidget(_t1_comboBox, 0, 1); _cursor_layout->addWidget(new QLabel("T2: ", this), 1, 0); _cursor_layout->addWidget(_t2_comboBox, 1, 1); - _cursor_layout->addWidget(new QLabel("|T2 - T1|: ", this), 2, 0); - _cursor_layout->addWidget(_delta_label, 2, 1); - _cursor_layout->addWidget(new QLabel("Delta Samples: ", this), 2, 2); - _cursor_layout->addWidget(_cnt_label, 2, 3); + _cursor_layout->addWidget(new QLabel("T3: ", this), 2, 0); + _cursor_layout->addWidget(_t3_comboBox, 2, 1); + + _cursor_layout->addWidget(new QLabel("|T2 - T1|: ", this), 3, 0); + _cursor_layout->addWidget(_delta_label_t1t2, 3, 1); + _cursor_layout->addWidget(new QLabel("Delta Samples: ", this), 3, 2); + _cursor_layout->addWidget(_cnt_label_t1t2, 3, 3); + + _cursor_layout->addWidget(new QLabel("|T3 - T2|: ", this), 4, 0); + _cursor_layout->addWidget(_delta_label_t2t3, 4, 1); + _cursor_layout->addWidget(new QLabel("Delta Samples: ", this), 4, 2); + _cursor_layout->addWidget(_cnt_label_t2t3, 4, 3); + + _cursor_layout->addWidget(new QLabel("|T3 - T1|: ", this), 5, 0); + _cursor_layout->addWidget(_delta_label_t1t3, 5, 1); + _cursor_layout->addWidget(new QLabel("Delta Samples: ", this), 5, 2); + _cursor_layout->addWidget(_cnt_label_t1t3, 5, 3); - _cursor_layout->addWidget(new QLabel("Cursors", this), 4, 0); - _cursor_layout->addWidget(new QLabel("Time/Samples", this), 4, 1); - _cursor_layout->addWidget(new QLabel("Sample Value", this), 4, 2); - _cursor_layout->addWidget(new QLabel("Value Radix", this), 4, 3); + _cursor_layout->addWidget(new QLabel("Cursors", this), 6, 0); + _cursor_layout->addWidget(new QLabel("Time/Samples", this), 6, 1); _cursor_layout->addWidget(new QLabel(this), 0, 4); _cursor_layout->addWidget(new QLabel(this), 1, 4); @@ -106,6 +131,7 @@ MeasureDock::MeasureDock(QWidget *parent, View &view, SigSession &session) : connect(_t1_comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(delta_update())); connect(_t2_comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(delta_update())); + connect(_t3_comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(delta_update())); connect(_fen_checkBox, SIGNAL(stateChanged(int)), &_view, SLOT(set_measure_en(int))); } @@ -124,10 +150,16 @@ void MeasureDock::paintEvent(QPaintEvent *) void MeasureDock::cursor_update() { + using namespace pv::data; + int index = 1; + disconnect(_t1_comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(delta_update())); + disconnect(_t2_comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(delta_update())); + disconnect(_t3_comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(delta_update())); _t1_comboBox->clear(); _t2_comboBox->clear(); + _t3_comboBox->clear(); if (!_cursor_pushButton_list.empty()) { for (QVector::Iterator i = _cursor_pushButton_list.begin(); @@ -136,20 +168,12 @@ void MeasureDock::cursor_update() for (QVector::Iterator i = _curpos_label_list.begin(); i != _curpos_label_list.end(); i++) delete (*i); - for (QVector::Iterator i = _curvalue_label_list.begin(); - i != _curvalue_label_list.end(); i++) - delete (*i); - for (QVector::Iterator i = _radix_comboBox_list.begin(); - i != _radix_comboBox_list.end(); i++) - delete (*i); for (QVector::Iterator i = _space_label_list.begin(); i != _space_label_list.end(); i++) delete (*i); _cursor_pushButton_list.clear(); _curpos_label_list.clear(); - _curvalue_label_list.clear(); - _radix_comboBox_list.clear(); _space_label_list.clear(); } @@ -158,33 +182,37 @@ void MeasureDock::cursor_update() QString curCursor = "Cursor "+QString::number(index); _t1_comboBox->addItem(curCursor); _t2_comboBox->addItem(curCursor); + _t3_comboBox->addItem(curCursor); QPushButton *_cursor_pushButton = new QPushButton(curCursor, this); - QLabel *_curpos_label = new QLabel(_view.get_cm_time(index - 1), this); - QLabel *_curvalue_label = new QLabel("####", this); - QComboBox *_radix_comboBox = new QComboBox(this); - _radix_comboBox->addItem("Bin"); - _radix_comboBox->addItem("Oct"); - _radix_comboBox->addItem("Dec"); - _radix_comboBox->addItem("Hex"); + QString _cur_text = _view.get_cm_time(index - 1) + "/" + QString::number(_view.get_cursor_samples(index - 1)); + QLabel *_curpos_label = new QLabel(_cur_text, this); QLabel *_space_label = new QLabel(this); _cursor_pushButton_list.push_back(_cursor_pushButton); _curpos_label_list.push_back(_curpos_label); - _curvalue_label_list.push_back(_curvalue_label); - _radix_comboBox_list.push_back(_radix_comboBox); _space_label_list.push_back(_space_label); - _cursor_layout->addWidget(_cursor_pushButton, 4 + index, 0); - _cursor_layout->addWidget(_curpos_label, 4 + index, 1); - _cursor_layout->addWidget(_curvalue_label, 4 + index, 2); - _cursor_layout->addWidget(_radix_comboBox, 4 + index, 3); - _cursor_layout->addWidget(_space_label, 4 + index, 4); + _cursor_layout->addWidget(_cursor_pushButton, 6 + index, 0); + _cursor_layout->addWidget(_curpos_label, 6 + index, 1); + _cursor_layout->addWidget(_space_label, 6 + index, 2); connect(_cursor_pushButton, SIGNAL(clicked()), this, SLOT(goto_cursor())); index++; } + if (_t1_last_index < _t1_comboBox->count()) + _t1_comboBox->setCurrentIndex(_t1_last_index); + if (_t2_last_index < _t2_comboBox->count()) + _t2_comboBox->setCurrentIndex(_t2_last_index); + if (_t3_last_index < _t3_comboBox->count()) + _t3_comboBox->setCurrentIndex(_t3_last_index); + + connect(_t1_comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(delta_update())); + connect(_t2_comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(delta_update())); + connect(_t3_comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(delta_update())); + + delta_update(); update(); } @@ -202,7 +230,8 @@ void MeasureDock::cursor_moved() int index = 0; for(std::list::iterator i = _view.get_cursorList().begin(); i != _view.get_cursorList().end(); i++) { - _curpos_label_list.at(index)->setText(_view.get_cm_time(index)); + QString _cur_text = _view.get_cm_time(index) + "/" + QString::number(_view.get_cursor_samples(index)); + _curpos_label_list.at(index)->setText(_cur_text); //_curvalue_label_list.at(index)->setText(_view.get_cm_value(index)); index++; } @@ -212,11 +241,28 @@ void MeasureDock::cursor_moved() void MeasureDock::delta_update() { + _t1_last_index = std::max(_t1_comboBox->currentIndex(), 0); + _t2_last_index = std::max(_t2_comboBox->currentIndex(), 0); + _t3_last_index = std::max(_t3_comboBox->currentIndex(), 0); if (_t1_comboBox->count() != 0 && _t2_comboBox->count() != 0) { - int t1_index = _t1_comboBox->currentIndex(); - int t2_index = _t2_comboBox->currentIndex(); - _delta_label->setText(_view.get_cm_delta(t1_index, t2_index)); - _cnt_label->setText(_view.get_cm_delta_cnt(t1_index, t2_index)); + uint64_t delta = abs(_view.get_cursor_samples(_t1_last_index) - + _view.get_cursor_samples(_t2_last_index)); + _delta_label_t1t2->setText(_view.get_cm_delta(_t1_last_index, _t2_last_index)); + _cnt_label_t1t2->setText(QString::number(delta)); + } + + if (_t2_comboBox->count() != 0 && _t2_comboBox->count() != 0) { + uint64_t delta = abs(_view.get_cursor_samples(_t2_last_index) - + _view.get_cursor_samples(_t3_last_index)); + _delta_label_t2t3->setText(_view.get_cm_delta(_t2_last_index, _t3_last_index)); + _cnt_label_t2t3->setText(QString::number(delta)); + } + + if (_t1_comboBox->count() != 0 && _t3_comboBox->count() != 0) { + uint64_t delta = abs(_view.get_cursor_samples(_t1_last_index) - + _view.get_cursor_samples(_t3_last_index)); + _delta_label_t1t3->setText(_view.get_cm_delta(_t1_last_index, _t3_last_index)); + _cnt_label_t1t3->setText(QString::number(delta)); } } diff --git a/DSLogic-gui/pv/dock/measuredock.h b/DSLogic-gui/pv/dock/measuredock.h index 738de43..e360774 100644 --- a/DSLogic-gui/pv/dock/measuredock.h +++ b/DSLogic-gui/pv/dock/measuredock.h @@ -91,13 +91,19 @@ private: QGroupBox *_cursor_groupBox; QComboBox *_t1_comboBox; QComboBox *_t2_comboBox; - QLabel *_delta_label; - QLabel *_cnt_label; + QComboBox *_t3_comboBox; + QLabel *_delta_label_t1t2; + QLabel *_cnt_label_t1t2; + QLabel *_delta_label_t2t3; + QLabel *_cnt_label_t2t3; + QLabel *_delta_label_t1t3; + QLabel *_cnt_label_t1t3; + int _t1_last_index; + int _t2_last_index; + int _t3_last_index; QVector _cursor_pushButton_list; QVector _curpos_label_list; - QVector _curvalue_label_list; - QVector _radix_comboBox_list; QVector _space_label_list; }; diff --git a/DSLogic-gui/pv/dock/protocoldock.cpp b/DSLogic-gui/pv/dock/protocoldock.cpp index ed8c572..3b05627 100644 --- a/DSLogic-gui/pv/dock/protocoldock.cpp +++ b/DSLogic-gui/pv/dock/protocoldock.cpp @@ -22,13 +22,17 @@ #include "protocoldock.h" -#include "../decoder/democonfig.h" #include "../sigsession.h" +#include "../view/decodetrace.h" +#include "../device/devinst.h" #include #include #include #include +#include + +#include namespace pv { namespace dock { @@ -49,10 +53,21 @@ ProtocolDock::ProtocolDock(QWidget *parent, SigSession &session) : QIcon(":/icons/del.png"))); _del_all_button->setCheckable(true); _protocol_combobox = new QComboBox(this); - for (int i = 0; decoder::protocol_list[i] != NULL;) { - _protocol_combobox->addItem(decoder::protocol_list[i]); - i++; + + GSList *l = g_slist_sort(g_slist_copy( + (GSList*)srd_decoder_list()), decoder_name_cmp); + for(; l; l = l->next) + { + const srd_decoder *const d = (srd_decoder*)l->data; + assert(d); + + const bool have_probes = (d->channels || d->opt_channels) != 0; + if (true == have_probes) { + _protocol_combobox->addItem(QString::fromUtf8(d->name), qVariantFromValue(l->data)); + } } + g_slist_free(l); + hori_layout->addWidget(_add_button); hori_layout->addWidget(_del_all_button); hori_layout->addWidget(_protocol_combobox); @@ -74,6 +89,12 @@ ProtocolDock::~ProtocolDock() { } +int ProtocolDock::decoder_name_cmp(const void *a, const void *b) +{ + return strcmp(((const srd_decoder*)a)->name, + ((const srd_decoder*)b)->name); +} + void ProtocolDock::paintEvent(QPaintEvent *) { QStyleOption opt; @@ -84,7 +105,7 @@ void ProtocolDock::paintEvent(QPaintEvent *) void ProtocolDock::add_protocol() { - if (_session.get_device()->mode != LOGIC) { + if (_session.get_device()->dev_inst()->mode != LOGIC) { QMessageBox msg(this); msg.setText("Protocol Analyzer"); msg.setInformativeText("Protocol Analyzer is only valid in Digital Mode!"); @@ -92,11 +113,12 @@ void ProtocolDock::add_protocol() msg.setIcon(QMessageBox::Warning); msg.exec(); } else { - pv::decoder::DemoConfig dlg(this, _session.get_device(), _protocol_combobox->currentIndex()); - if (dlg.exec()) { - std::list _sel_probes = dlg.get_sel_probes(); - QMap & _options = dlg.get_options(); - QMap _options_index = dlg.get_options_index(); + srd_decoder *const decoder = + (srd_decoder*)(_protocol_combobox->itemData(_protocol_combobox->currentIndex())).value(); + if (_session.add_decoder(decoder)) { + //std::list _sel_probes = dlg.get_sel_probes(); + //QMap & _options = dlg.get_options(); + //QMap _options_index = dlg.get_options_index(); QPushButton *_del_button = new QPushButton(this); QPushButton *_set_button = new QPushButton(this); @@ -105,7 +127,7 @@ void ProtocolDock::add_protocol() QIcon(":/icons/del.png"))); _set_button->setFlat(true); _set_button->setIcon(QIcon::fromTheme("protocol", - QIcon(":/icons/set.png"))); + QIcon(":/icons/gear.png"))); QLabel *_protocol_label = new QLabel(this); _del_button->setCheckable(true); @@ -129,7 +151,7 @@ void ProtocolDock::add_protocol() _hori_layout_list.push_back(hori_layout); _layout->insertLayout(_del_button_list.size(), hori_layout); - _session.add_protocol_analyzer(_protocol_combobox->currentIndex(), _sel_probes, _options, _options_index); + //_session.add_protocol_analyzer(_protocol_combobox->currentIndex(), _sel_probes, _options, _options_index); } } } @@ -141,15 +163,16 @@ void ProtocolDock::rst_protocol() i != _set_button_list.end(); i++) { QPushButton *button = qobject_cast(sender()); if ((*i) == button) { - pv::decoder::DemoConfig dlg(this, _session.get_device(), _protocol_index_list.at(rst_index)); - dlg.set_config(_session.get_decode_probes(rst_index), _session.get_decode_options_index(rst_index)); - if (dlg.exec()) { - std::list _sel_probes = dlg.get_sel_probes(); - QMap & _options = dlg.get_options(); - QMap _options_index = dlg.get_options_index(); - - _session.rst_protocol_analyzer(rst_index, _sel_probes, _options, _options_index); - } + //pv::decoder::DemoConfig dlg(this, _session.get_device(), _protocol_index_list.at(rst_index)); + //dlg.set_config(_session.get_decode_probes(rst_index), _session.get_decode_options_index(rst_index)); + //if (dlg.exec()) { + //std::list _sel_probes = dlg.get_sel_probes(); + //QMap & _options = dlg.get_options(); + //QMap _options_index = dlg.get_options_index(); + + //_session.rst_protocol_analyzer(rst_index, _sel_probes, _options, _options_index); + //} + _session.rst_decoder(rst_index); break; } rst_index++; @@ -170,7 +193,7 @@ void ProtocolDock::del_protocol() delete _set_button_list.at(del_index); delete _protocol_label_list.at(del_index); - _session.del_protocol_analyzer(0); + _session.remove_decode_signal(0); del_index++; } _hori_layout_list.clear(); @@ -204,7 +227,7 @@ void ProtocolDock::del_protocol() _protocol_label_list.remove(del_index); _protocol_index_list.remove(del_index); - _session.del_protocol_analyzer(del_index); + _session.remove_decode_signal(del_index); break; } @@ -225,7 +248,7 @@ void ProtocolDock::del_all_protocol() delete _set_button_list.at(del_index); delete _protocol_label_list.at(del_index); - _session.del_protocol_analyzer(0); + _session.remove_decode_signal(0); del_index++; } _hori_layout_list.clear(); diff --git a/DSLogic-gui/pv/dock/protocoldock.h b/DSLogic-gui/pv/dock/protocoldock.h index 2165af1..c60e6cd 100644 --- a/DSLogic-gui/pv/dock/protocoldock.h +++ b/DSLogic-gui/pv/dock/protocoldock.h @@ -24,6 +24,8 @@ #ifndef DSLOGIC_PV_PROTOCOLDOCK_H #define DSLOGIC_PV_PROTOCOLDOCK_H +#include + #include #include #include @@ -36,8 +38,6 @@ #include -#include "../decoder/decoder.h" - namespace pv { class SigSession; @@ -64,6 +64,7 @@ private slots: void del_protocol(); private: + static int decoder_name_cmp(const void *a, const void *b); private: SigSession &_session; diff --git a/DSLogic-gui/pv/dock/searchdock.cpp b/DSLogic-gui/pv/dock/searchdock.cpp index fc079b7..04b9191 100644 --- a/DSLogic-gui/pv/dock/searchdock.cpp +++ b/DSLogic-gui/pv/dock/searchdock.cpp @@ -30,6 +30,7 @@ #include "../dialogs/search.h" #include "../data/snapshot.h" #include "../data/logicsnapshot.h" +#include "../device/devinst.h" #include #include @@ -45,6 +46,7 @@ namespace pv { namespace dock { using namespace pv::view; +using namespace pv::widgets; SearchDock::SearchDock(QWidget *parent, View &view, SigSession &session) : QWidget(parent), @@ -197,7 +199,7 @@ void SearchDock::on_next() void SearchDock::on_set() { - dialogs::Search dlg(this, _session.get_device(), _pattern); + dialogs::Search dlg(this, _session.get_device()->dev_inst(), _pattern); if (dlg.exec()) { _pattern = dlg.get_pattern(); _pattern.remove(QChar(' '), Qt::CaseInsensitive); diff --git a/DSLogic-gui/pv/dock/searchdock.h b/DSLogic-gui/pv/dock/searchdock.h index c29f061..7d748fc 100644 --- a/DSLogic-gui/pv/dock/searchdock.h +++ b/DSLogic-gui/pv/dock/searchdock.h @@ -45,7 +45,7 @@ #include -#include "fakelineedit.h" +#include "../widgets/fakelineedit.h" namespace pv { @@ -55,6 +55,10 @@ namespace view { class View; } +namespace widgets { + class FakeLineEdit; +} + namespace dock { class SearchDock : public QWidget @@ -86,7 +90,7 @@ private: QPushButton _pre_button; QPushButton _nxt_button; - FakeLineEdit* _search_value; + widgets::FakeLineEdit* _search_value; }; } // namespace dock diff --git a/DSLogic-gui/pv/dock/triggerdock.cpp b/DSLogic-gui/pv/dock/triggerdock.cpp index d5fc890..4d5e78c 100644 --- a/DSLogic-gui/pv/dock/triggerdock.cpp +++ b/DSLogic-gui/pv/dock/triggerdock.cpp @@ -23,6 +23,7 @@ #include "triggerdock.h" #include "../sigsession.h" +#include "../device/devinst.h" #include #include @@ -209,7 +210,7 @@ void TriggerDock::simple_trigger() void TriggerDock::adv_trigger() { - if (strcmp(_session.get_device()->driver->name, "DSLogic") == 0) { + if (strcmp(_session.get_device()->dev_inst()->driver->name, "DSLogic") == 0) { widget_enable(); ds_trigger_set_mode(ADV_TRIGGER); _session.set_adv_trigger(true); @@ -341,7 +342,7 @@ void TriggerDock::pos_changed(int pos) void TriggerDock::device_change() { - if (strcmp(_session.get_device()->driver->name, "DSLogic") != 0) { + if (strcmp(_session.get_device()->dev_inst()->driver->name, "DSLogic") != 0) { position_spinBox->setDisabled(true); position_slider->setDisabled(true); } else { diff --git a/DSLogic-gui/pv/mainwindow.cpp b/DSLogic-gui/pv/mainwindow.cpp index 9a7bb84..db501b6 100644 --- a/DSLogic-gui/pv/mainwindow.cpp +++ b/DSLogic-gui/pv/mainwindow.cpp @@ -21,12 +21,14 @@ */ -#ifdef ENABLE_SIGROKDECODE +#ifdef ENABLE_DECODE #include +#include "dock/protocoldock.h" #endif #include #include +#include #include #include @@ -41,27 +43,32 @@ #include #include #include +#include +#include #include "mainwindow.h" #include "devicemanager.h" +#include "device/device.h" +#include "device/file.h" #include "dialogs/about.h" -#include "dialogs/connect.h" +#include "dialogs/storeprogress.h" #include "toolbars/samplingbar.h" -#include "toolbars/devicebar.h" #include "toolbars/trigbar.h" #include "toolbars/filebar.h" #include "toolbars/logobar.h" -#include "dock/protocoldock.h" #include "dock/triggerdock.h" #include "dock/dsotriggerdock.h" #include "dock/measuredock.h" #include "dock/searchdock.h" #include "view/view.h" +#include "view/trace.h" +#include "view/signal.h" +#include "view/dsosignal.h" /* __STDC_FORMAT_MACROS is required for PRIu64 and friends (in C++). */ #define __STDC_FORMAT_MACROS @@ -69,9 +76,13 @@ #include #include #include +#include #include -using namespace std; +using boost::shared_ptr; +using boost::dynamic_pointer_cast; +using std::list; +using std::vector; namespace pv { @@ -110,103 +121,10 @@ void MainWindow::setup_ui() _vertical_layout->setContentsMargins(0, 0, 0, 0); setCentralWidget(_central_widget); -// // Setup the menu bar -// _menu_bar = new QMenuBar(this); -// _menu_bar->setGeometry(QRect(0, 0, 400, 25)); - -// // File Menu -// _menu_file = new QMenu(_menu_bar); -// _menu_file->setTitle(QApplication::translate( -// "MainWindow", "&File", 0, QApplication::UnicodeUTF8)); - -// _action_open = new QAction(this); -// _action_open->setText(QApplication::translate( -// "MainWindow", "&Open...", 0, QApplication::UnicodeUTF8)); -// _action_open->setIcon(QIcon::fromTheme("document-open", -// QIcon(":/icons/document-open.png"))); -// _action_open->setObjectName(QString::fromUtf8("actionOpen")); -// _menu_file->addAction(_action_open); - -// _menu_file->addSeparator(); - -// _action_connect = new QAction(this); -// _action_connect->setText(QApplication::translate( -// "MainWindow", "&Connect to Device...", 0, -// QApplication::UnicodeUTF8)); -// _action_connect->setObjectName(QString::fromUtf8("actionConnect")); -// _menu_file->addAction(_action_connect); - -// _menu_file->addSeparator(); - -// _action_quit = new QAction(this); -// _action_quit->setText(QApplication::translate( -// "MainWindow", "&Quit", 0, QApplication::UnicodeUTF8)); -// _action_quit->setIcon(QIcon::fromTheme("application-exit", -// QIcon(":/icons/application-exit.png"))); -// _action_quit->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q)); -// _action_quit->setObjectName(QString::fromUtf8("actionQuit")); -// _menu_file->addAction(_action_quit); - -// // View Menu -// _menu_view = new QMenu(_menu_bar); -// _menu_view->setTitle(QApplication::translate( -// "MainWindow", "&View", 0, QApplication::UnicodeUTF8)); - -// _action_view_zoom_in = new QAction(this); -// _action_view_zoom_in->setText(QApplication::translate( -// "MainWindow", "Zoom &In", 0, QApplication::UnicodeUTF8)); -// _action_view_zoom_in->setIcon(QIcon::fromTheme("zoom-in", -// QIcon(":/icons/zoom-in.png"))); -// _action_view_zoom_in->setObjectName( -// QString::fromUtf8("actionViewZoomIn")); -// _menu_view->addAction(_action_view_zoom_in); - -// _action_view_zoom_out = new QAction(this); -// _action_view_zoom_out->setText(QApplication::translate( -// "MainWindow", "Zoom &Out", 0, QApplication::UnicodeUTF8)); -// _action_view_zoom_out->setIcon(QIcon::fromTheme("zoom-out", -// QIcon(":/icons/zoom-out.png"))); -// _action_view_zoom_out->setObjectName( -// QString::fromUtf8("actionViewZoomOut")); -// _menu_view->addAction(_action_view_zoom_out); - -// _menu_view->addSeparator(); - -// _action_view_show_cursors = new QAction(this); -// _action_view_show_cursors->setCheckable(true); -// _action_view_show_cursors->setChecked(_view->cursors_shown()); -// _action_view_show_cursors->setShortcut(QKeySequence(Qt::Key_C)); -// _action_view_show_cursors->setObjectName( -// QString::fromUtf8("actionViewShowCursors")); -// _action_view_show_cursors->setText(QApplication::translate( -// "MainWindow", "Show &Cursors", 0, QApplication::UnicodeUTF8)); -// _menu_view->addAction(_action_view_show_cursors); - -// // Help Menu -// _menu_help = new QMenu(_menu_bar); -// _menu_help->setTitle(QApplication::translate( -// "MainWindow", "&Help", 0, QApplication::UnicodeUTF8)); - -// _action_about = new QAction(this); -// _action_about->setObjectName(QString::fromUtf8("actionAbout")); -// _action_about->setText(QApplication::translate( -// "MainWindow", "&About...", 0, QApplication::UnicodeUTF8)); -// _menu_help->addAction(_action_about); - -// _menu_bar->addAction(_menu_file->menuAction()); -// _menu_bar->addAction(_menu_view->menuAction()); -// _menu_bar->addAction(_menu_help->menuAction()); - - //setMenuBar(_menu_bar); - //QMenuBar *_void_menu = new QMenuBar(this); - //setMenuBar(_void_menu); - //QMetaObject::connectSlotsByName(this); - // Setup the sampling bar - _sampling_bar = new toolbars::SamplingBar(this); + _sampling_bar = new toolbars::SamplingBar(_session, this); _trig_bar = new toolbars::TrigBar(this); _file_bar = new toolbars::FileBar(_session, this); - _device_bar = new toolbars::DeviceBar(this); _logo_bar = new toolbars::LogoBar(_session, this); connect(_trig_bar, SIGNAL(on_protocol(bool)), this, @@ -217,9 +135,14 @@ void MainWindow::setup_ui() SLOT(on_measure(bool))); connect(_trig_bar, SIGNAL(on_search(bool)), this, SLOT(on_search(bool))); + connect(_file_bar, SIGNAL(load_file(QString)), this, + SLOT(load_file(QString))); + connect(_file_bar, SIGNAL(save()), this, + SLOT(on_save())); connect(_file_bar, SIGNAL(on_screenShot()), this, SLOT(on_screenShot())); +#ifdef ENABLE_DECODE // protocol dock _protocol_dock=new QDockWidget(tr("Protocol"),this); _protocol_dock->setFeatures(QDockWidget::NoDockWidgetFeatures); @@ -228,6 +151,8 @@ void MainWindow::setup_ui() //dock::ProtocolDock *_protocol_widget = new dock::ProtocolDock(_protocol_dock, _session); _protocol_widget = new dock::ProtocolDock(_protocol_dock, _session); _protocol_dock->setWidget(_protocol_widget); + qDebug() << "Protocol decoder enabled!\n"; +#endif // trigger dock _trigger_dock=new QDockWidget(tr("Trigger Setting..."),this); _trigger_dock->setFeatures(QDockWidget::NoDockWidgetFeatures); @@ -248,41 +173,25 @@ void MainWindow::setup_ui() _view = new pv::view::View(_session, this); _vertical_layout->addWidget(_view); - // Populate the device list and select the initially selected device - update_device_list(); - - connect(_device_bar, SIGNAL(device_selected()), this, - SLOT(device_selected())); -// connect(_device_bar, SIGNAL(device_selected()), this, -// SLOT(init())); - connect(_device_bar, SIGNAL(device_updated()), this, - SLOT(update())); - connect(_sampling_bar, SIGNAL(device_reload()), this, - SLOT(init())); + connect(_sampling_bar, SIGNAL(device_selected()), this, + SLOT(update_device_list())); + connect(_sampling_bar, SIGNAL(device_updated()), &_session, + SLOT(reload())); connect(_sampling_bar, SIGNAL(run_stop()), this, SLOT(run_stop())); + connect(_sampling_bar, SIGNAL(instant_stop()), this, + SLOT(instant_stop())); + connect(_sampling_bar, SIGNAL(update_scale()), _view, + SLOT(update_scale())); + connect(_dso_trigger_widget, SIGNAL(set_trig_pos(quint64)), _view, + SLOT(set_trig_pos(quint64))); + addToolBar(_sampling_bar); addToolBar(_trig_bar); - addToolBar(_device_bar); addToolBar(_file_bar); addToolBar(_logo_bar); // Setup the dockWidget - // protocol dock -// _protocol_dock=new QDockWidget(tr("Protocol"),this); -// _protocol_dock->setFeatures(QDockWidget::NoDockWidgetFeatures); -// _protocol_dock->setAllowedAreas(Qt::RightDockWidgetArea); -// _protocol_dock->setVisible(false); -// //dock::ProtocolDock *_protocol_widget = new dock::ProtocolDock(_protocol_dock, _session); -// _protocol_widget = new dock::ProtocolDock(_protocol_dock, _session); -// _protocol_dock->setWidget(_protocol_widget); -// // trigger dock -// _trigger_dock=new QDockWidget(tr("Trigger Setting..."),this); -// _trigger_dock->setFeatures(QDockWidget::NoDockWidgetFeatures); -// _trigger_dock->setAllowedAreas(Qt::RightDockWidgetArea); -// _trigger_dock->setVisible(false); -// dock::TriggerDock *_trigger_widget = new dock::TriggerDock(_trigger_dock, _session); -// _trigger_dock->setWidget(_trigger_widget); // measure dock _measure_dock=new QDockWidget(tr("Measurement"),this); _measure_dock->setFeatures(QDockWidget::NoDockWidgetFeatures); @@ -300,10 +209,9 @@ void MainWindow::setup_ui() _search_widget = new dock::SearchDock(_search_dock, *_view, _session); _search_dock->setWidget(_search_widget); - - _protocol_dock->setObjectName(tr("protocolDock")); - _trigger_dock->setObjectName(tr("triggerDock")); +#ifdef ENABLE_DECODE addDockWidget(Qt::RightDockWidgetArea,_protocol_dock); +#endif addDockWidget(Qt::RightDockWidgetArea,_trigger_dock); addDockWidget(Qt::RightDockWidgetArea,_dso_trigger_dock); addDockWidget(Qt::RightDockWidgetArea, _measure_dock); @@ -322,8 +230,8 @@ void MainWindow::setup_ui() SLOT(device_detach())); connect(&_session, SIGNAL(test_data_error()), this, SLOT(test_data_error())); - connect(&_session, SIGNAL(dso_ch_changed(uint16_t)), this, - SLOT(dso_ch_changed(uint16_t))); + connect(&_session, SIGNAL(sample_rate_changed(uint64_t)), _sampling_bar, + SLOT(set_sample_rate(uint64_t))); connect(_view, SIGNAL(cursor_update()), _measure_widget, SLOT(cursor_update())); @@ -331,28 +239,28 @@ void MainWindow::setup_ui() SLOT(cursor_moved())); connect(_view, SIGNAL(mouse_moved()), _measure_widget, SLOT(mouse_moved())); -} - -void MainWindow::init() -{ - _protocol_widget->del_all_protocol(); - _trigger_widget->device_change(); - if (_session.get_device()) - _session.init_signals(_session.get_device()); - if (_session.get_device()->mode == DSO) { - _sampling_bar->set_record_length(DefaultDSODepth*2); - _sampling_bar->set_sample_rate(DefaultDSORate*2); - _sampling_bar->enable_toggle(false); - _view->hDial_changed(0); - } else if(_session.get_device()->mode == LOGIC) { - _sampling_bar->enable_toggle(true); - } -} + connect(_view, SIGNAL(mode_changed()), this, + SLOT(update_device_list())); + + // event filter + _view->installEventFilter(this); + _sampling_bar->installEventFilter(this); + _trig_bar->installEventFilter(this); + _file_bar->installEventFilter(this); + _logo_bar->installEventFilter(this); + _dso_trigger_dock->installEventFilter(this); + _trigger_dock->installEventFilter(this); +#ifdef ENABLE_DECODE + _protocol_dock->installEventFilter(this); +#endif + _measure_dock->installEventFilter(this); + _search_dock->installEventFilter(this); -void MainWindow::update() -{ - if (_session.get_device()) - _session.update_signals(_session.get_device()); + // Populate the device list and select the initially selected device + _session.set_default_device(); + update_device_list(); + _session.start_hotplug_proc(boost::bind(&MainWindow::session_error, this, + QString("Hotplug failed"), _1)); } void MainWindow::session_error( @@ -363,92 +271,55 @@ void MainWindow::session_error( Q_ARG(QString, info_text)); } -void MainWindow::update_device_list(struct sr_dev_inst *selected_device) +void MainWindow::update_device_list() { - assert(_device_bar); - - const list &devices = _device_manager.devices(); - _device_bar->set_device_list(devices); - - if (!selected_device && !devices.empty()) { - // Fall back to the first device in the list. - selected_device = devices.front(); + assert(_sampling_bar); - // Try and find the demo device and select that by default - BOOST_FOREACH (struct sr_dev_inst *sdi, devices) - if (strcmp(sdi->driver->name, "DSLogic") == 0) { - selected_device = sdi; - } - } - - if (selected_device) { - if (_session.set_device(selected_device) == SR_OK) { - _device_bar->set_selected_device(selected_device, false); - _sampling_bar->set_device(selected_device); - _sampling_bar->update_sample_rate_selector(); - _logo_bar->dslogic_connected(strcmp(selected_device->driver->name, "DSLogic") == 0); - init(); - } else { - show_session_error("Open Device Failed", - "the selected device can't be opened!"); - } - } - -// #ifdef HAVE_LA_DSLOGIC - _session.start_hotplug_proc(boost::bind(&MainWindow::session_error, this, - QString("Hotplug failed"), _1)); - _session.start_dso_ctrl_proc(boost::bind(&MainWindow::session_error, this, - QString("Hotplug failed"), _1)); -// #endif -} - -void MainWindow::device_change() -{ - assert(_device_bar); - - struct sr_dev_inst *selected_device; - - const list &devices = _device_manager.devices(); - _device_bar->set_device_list(devices); + _view->show_trig_cursor(false); + _trigger_widget->device_change(); +#ifdef ENABLE_DECODE + _protocol_widget->del_all_protocol(); +#endif + _trig_bar->close_all(); - // Fall back to the first device in the list. - selected_device = devices.front(); - // Try and find the demo device and select that by default - BOOST_FOREACH (struct sr_dev_inst *sdi, devices) - if (strcmp(sdi->driver->name, "DSLogic") == 0) { - selected_device = sdi; - } - - if (_session.set_device(selected_device) == SR_OK) {; - _device_bar->set_selected_device(selected_device, true); - _sampling_bar->set_device(selected_device); - _sampling_bar->update_sample_rate_selector(); - _logo_bar->dslogic_connected(strcmp(selected_device->driver->name, "DSLogic") == 0); - init(); + if (_session.get_device()->dev_inst()->mode == DSO) { + _sampling_bar->enable_toggle(false); } else { -// show_session_error("Open Device Failed", -// "the selected device can't be opened!"); - device_detach(); + _sampling_bar->enable_toggle(true); } -// #ifdef HAVE_LA_DSLOGIC - _session.stop_hotplug_proc(); - _session.start_hotplug_proc(boost::bind(&MainWindow::session_error, this, - QString("Hotplug failed"), _1)); -// #endif - + shared_ptr selected_device = _session.get_device(); + _device_manager.add_device(selected_device); + _sampling_bar->set_device_list(_device_manager.devices(), selected_device); + _session.init_signals(); + + if(dynamic_pointer_cast(selected_device)) { + const QString errorMessage( + QString("Failed to capture file data!")); + const QString infoMessage; + _session.start_capture(true, boost::bind(&MainWindow::session_error, this, + errorMessage, infoMessage)); + } + if (strcmp(selected_device->dev_inst()->driver->name, "DSLogic") == 0) + _logo_bar->dslogic_connected(true); + else + _logo_bar->dslogic_connected(false); } void MainWindow::load_file(QString file_name) { - const QString errorMessage( - QString("Failed to load file %1").arg(file_name)); - const QString infoMessage; - _session.load_file(file_name.toStdString(), - boost::bind(&MainWindow::session_error, this, - errorMessage, infoMessage)); + try { + _session.set_file(file_name.toStdString()); + } catch(QString e) { + show_session_error(tr("Failed to load ") + file_name, e); + _session.set_default_device(); + update_device_list(); + return; + } + + update_device_list(); } void MainWindow::show_session_error( @@ -462,50 +333,9 @@ void MainWindow::show_session_error( msg.exec(); } -void MainWindow::device_selected() -{ - if (_session.set_device(_device_bar->get_selected_device()) == SR_OK) {; - _sampling_bar->set_device(_device_bar->get_selected_device()); - _sampling_bar->update_sample_rate_selector(); - _view->show_trig_cursor(false); - _trigger_dock->setVisible(false); - _dso_trigger_dock->setVisible(false); - _protocol_dock->setVisible(false); - _measure_dock->setVisible(false); - _search_dock->setVisible(false); - init(); - } else { - show_session_error("Open Device Failed", - "the selected device can't be opened!"); - } - - if (_device_bar->get_selected_device()->mode == DSO) { - QMessageBox msg(this); - msg.setText("Zero Adjustment"); - msg.setInformativeText("Please left both of channels unconnect for zero adjustment!"); - msg.setStandardButtons(QMessageBox::Ok); - msg.setIcon(QMessageBox::Warning); - msg.exec(); - - int ret = sr_config_set(_device_bar->get_selected_device(), SR_CONF_ZERO, g_variant_new_boolean(TRUE)); - if (ret != SR_OK) { - QMessageBox msg(this); - msg.setText("Zero Adjustment Issue"); - msg.setInformativeText("Can't send out the command of zero adjustment!"); - msg.setStandardButtons(QMessageBox::Ok); - msg.setIcon(QMessageBox::Warning); - msg.exec(); - } else { - run_stop(); - g_usleep(100000); - run_stop(); - } - } -} - void MainWindow::device_attach() { - _session.stop_hotplug_proc(); + //_session.stop_hot_plug_proc(); if (_session.get_capture_state() == SigSession::Running) _session.stop_capture(); @@ -517,12 +347,13 @@ void MainWindow::device_attach() if (*driver) _device_manager.driver_scan(*driver); - device_change(); + _session.set_default_device(); + update_device_list(); } void MainWindow::device_detach() { - _session.stop_hotplug_proc(); + //_session.stop_hot_plug_proc(); if (_session.get_capture_state() == SigSession::Running) _session.stop_capture(); @@ -534,18 +365,19 @@ void MainWindow::device_detach() if (*driver) _device_manager.driver_scan(*driver); - device_change(); + _session.set_default_device(); + update_device_list(); } void MainWindow::run_stop() { _sampling_bar->enable_run_stop(false); + _sampling_bar->enable_instant(false); switch(_session.get_capture_state()) { case SigSession::Init: case SigSession::Stopped: _view->show_trig_cursor(false); - _session.set_total_sample_len(_sampling_bar->get_record_length()); - _session.start_capture(_sampling_bar->get_record_length(), + _session.start_capture(false, boost::bind(&MainWindow::session_error, this, QString("Capture failed"), _1)); break; @@ -558,29 +390,25 @@ void MainWindow::run_stop() _sampling_bar->enable_run_stop(true); } -void MainWindow::dso_ch_changed(uint16_t num) +void MainWindow::instant_stop() { - if(num == 1) { - _sampling_bar->set_record_length(DefaultDSODepth*2); - _sampling_bar->set_sample_rate(DefaultDSORate*2); - _session.set_total_sample_len(_sampling_bar->get_record_length()); - if (_session.get_capture_state() == SigSession::Running) { - _session.stop_capture(); - _session.start_capture(_sampling_bar->get_record_length(), - boost::bind(&MainWindow::session_error, this, - QString("Capture failed"), _1)); - } - } else { - _sampling_bar->set_record_length(DefaultDSODepth); - _sampling_bar->set_sample_rate(DefaultDSORate); - _session.set_total_sample_len(_sampling_bar->get_record_length()); - if (_session.get_capture_state() == SigSession::Running) { - _session.stop_capture(); - _session.start_capture(_sampling_bar->get_record_length(), - boost::bind(&MainWindow::session_error, this, - QString("Capture failed"), _1)); - } + _sampling_bar->enable_instant(false); + _sampling_bar->enable_run_stop(false); + switch(_session.get_capture_state()) { + case SigSession::Init: + case SigSession::Stopped: + _view->show_trig_cursor(false); + _session.start_capture(true, + boost::bind(&MainWindow::session_error, this, + QString("Capture failed"), _1)); + break; + + case SigSession::Running: + _session.stop_capture(); + break; } + g_usleep(1000); + _sampling_bar->enable_instant(true); } void MainWindow::test_data_error() @@ -596,12 +424,11 @@ void MainWindow::test_data_error() void MainWindow::capture_state_changed(int state) { - if (_session.get_device()->mode != DSO) { + if (_session.get_device()->dev_inst()->mode != DSO) { _sampling_bar->enable_toggle(state != SigSession::Running); _trig_bar->enable_toggle(state != SigSession::Running); _measure_dock->widget()->setEnabled(state != SigSession::Running); } - _device_bar->enable_toggle(state != SigSession::Running); _file_bar->enable_toggle(state != SigSession::Running); _sampling_bar->set_sampling(state == SigSession::Running); _view->on_state_changed(state != SigSession::Running); @@ -609,15 +436,20 @@ void MainWindow::capture_state_changed(int state) void MainWindow::on_protocol(bool visible) { +#ifdef ENABLE_DECODE _protocol_dock->setVisible(visible); +#endif } void MainWindow::on_trigger(bool visible) { - if (_session.get_device()->mode != DSO) + if (_session.get_device()->dev_inst()->mode != DSO) { _trigger_dock->setVisible(visible); - else + _dso_trigger_dock->setVisible(false); + } else { + _trigger_dock->setVisible(false); _dso_trigger_dock->setVisible(visible); + } } void MainWindow::on_measure(bool visible) @@ -648,4 +480,104 @@ void MainWindow::on_screenShot() pixmap.save(fileName, format.toAscii()); } +void MainWindow::on_save() +{ + using pv::dialogs::StoreProgress; + + // Stop any currently running capture session + _session.stop_capture(); + + // Show the dialog + const QString file_name = QFileDialog::getSaveFileName( + this, tr("Save File"), "", tr("DSLogic Sessions (*.dsl)")); + + if (file_name.isEmpty()) + return; + + StoreProgress *dlg = new StoreProgress(file_name, _session, this); + dlg->run(); +} + + +bool MainWindow::eventFilter(QObject *object, QEvent *event) +{ + (void) object; + + if( event->type() == QEvent::KeyPress ) { + const vector< shared_ptr > sigs( + _session.get_signals()); + + QKeyEvent *ke = (QKeyEvent *) event; + switch(ke->key()) { + case Qt::Key_S: + run_stop(); + break; + case Qt::Key_I: + instant_stop(); + break; + case Qt::Key_T: + if (_session.get_device()->dev_inst()->mode == DSO) + on_trigger(!_dso_trigger_dock->isVisible()); + else + on_trigger(!_trigger_dock->isVisible()); + break; + case Qt::Key_D: + on_protocol(!_protocol_dock->isVisible()); + break; + case Qt::Key_M: + on_measure(!_measure_dock->isVisible()); + break; + case Qt::Key_R: + on_search(!_search_dock->isVisible()); + break; + case Qt::Key_O: + _sampling_bar->on_configure(); + break; + case Qt::Key_PageUp: + _view->set_scale_offset(_view->scale(), + _view->offset() - _view->scale()*_view->viewport()->width()); + break; + case Qt::Key_PageDown: + _view->set_scale_offset(_view->scale(), + _view->offset() + _view->scale()*_view->viewport()->width()); + + break; + case Qt::Key_Left: + _view->zoom(1); + break; + case Qt::Key_Right: + _view->zoom(-1); + break; + case Qt::Key_Up: + BOOST_FOREACH(const shared_ptr s, sigs) { + shared_ptr dsoSig; + if (dsoSig = dynamic_pointer_cast(s)) { + if (dsoSig->get_vDialActive()) { + dsoSig->go_vDialNext(); + update(); + break; + } + } + } + break; + case Qt::Key_Down: + BOOST_FOREACH(const shared_ptr s, sigs) { + shared_ptr dsoSig; + if (dsoSig = dynamic_pointer_cast(s)) { + if (dsoSig->get_vDialActive()) { + dsoSig->go_vDialPre(); + update(); + break; + } + } + } + break; + default: + QWidget::keyPressEvent((QKeyEvent *)event); + } + return true; + } + return false; +} + } // namespace pv diff --git a/DSLogic-gui/pv/mainwindow.h b/DSLogic-gui/pv/mainwindow.h index 9be748a..c1bd1cc 100644 --- a/DSLogic-gui/pv/mainwindow.h +++ b/DSLogic-gui/pv/mainwindow.h @@ -45,7 +45,6 @@ class DeviceManager; namespace toolbars { class SamplingBar; -class DeviceBar; class TrigBar; class FileBar; class LogoBar; @@ -67,10 +66,6 @@ class MainWindow : public QMainWindow { Q_OBJECT -private: - static const int DefaultDSODepth = 8192; - static const int DefaultDSORate = 100000000; - public: explicit MainWindow(DeviceManager &device_manager, const char *open_file_name = NULL, @@ -81,35 +76,29 @@ private: void session_error(const QString text, const QString info_text); - /** - * Updates the device list in the sampling bar, and updates the - * selection. - * @param selected_device The device to select, or NULL if the - * first device in the device list should be selected. - */ - void update_device_list( - struct sr_dev_inst *selected_device = NULL); - - void device_change(); + bool eventFilter(QObject *object, QEvent *event); private slots: void load_file(QString file_name); + /** + * Updates the device list in the sampling bar, and updates the + * selection. + * @param selected_device The device to select, or NULL if the + * first device in the device list should be selected. + */ + void update_device_list(); void show_session_error( const QString text, const QString info_text); - void device_selected(); - void run_stop(); - void test_data_error(); + void instant_stop(); - void capture_state_changed(int state); - - void init(); + void test_data_error(); - void update(); + void capture_state_changed(int state); void on_protocol(bool visible); @@ -121,15 +110,14 @@ private slots: void on_screenShot(); + void on_save(); + /* * hotplug slot function */ void device_attach(); void device_detach(); - /* */ - void dso_ch_changed(uint16_t num); - private: DeviceManager &_device_manager; @@ -155,13 +143,15 @@ private: QVBoxLayout *_vertical_layout; toolbars::SamplingBar *_sampling_bar; - toolbars::DeviceBar *_device_bar; toolbars::TrigBar *_trig_bar; toolbars::FileBar *_file_bar; toolbars::LogoBar *_logo_bar; +#ifdef ENABLE_DECODE QDockWidget *_protocol_dock; dock::ProtocolDock *_protocol_widget; +#endif + QDockWidget *_trigger_dock; QDockWidget *_dso_trigger_dock; dock::TriggerDock *_trigger_widget; diff --git a/DSLogic-gui/pv/prop/binding/binding.cpp b/DSLogic-gui/pv/prop/binding/binding.cpp index e2ddd6a..6fcacf8 100644 --- a/DSLogic-gui/pv/prop/binding/binding.cpp +++ b/DSLogic-gui/pv/prop/binding/binding.cpp @@ -20,9 +20,16 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include + +#include + +#include #include "binding.h" +using boost::shared_ptr; + namespace pv { namespace prop { namespace binding { @@ -32,6 +39,58 @@ const std::vector< boost::shared_ptr >& Binding::properties() return _properties; } +void Binding::commit() +{ + BOOST_FOREACH(shared_ptr p, _properties) { + assert(p); + p->commit(); + } +} + +void Binding::add_properties_to_form(QFormLayout *layout, + bool auto_commit) const +{ + assert(layout); + + BOOST_FOREACH(shared_ptr p, _properties) + { + assert(p); + + QWidget *const widget = p->get_widget(layout->parentWidget(), + auto_commit); + if (p->labeled_widget()) + layout->addRow(widget); + else + layout->addRow(p->name(), widget); + } +} + +QWidget* Binding::get_property_form(QWidget *parent, + bool auto_commit) const +{ + QWidget *const form = new QWidget(parent); + QFormLayout *const layout = new QFormLayout(form); + form->setLayout(layout); + add_properties_to_form(layout, auto_commit); + return form; +} + +QString Binding::print_gvariant(GVariant *const gvar) +{ + QString s; + + if (g_variant_is_of_type(gvar, G_VARIANT_TYPE("s"))) + s = QString::fromUtf8(g_variant_get_string(gvar, NULL)); + else + { + gchar *const text = g_variant_print(gvar, FALSE); + s = QString::fromUtf8(text); + g_free(text); + } + + return s; +} + } // binding } // prop } // pv diff --git a/DSLogic-gui/pv/prop/binding/binding.h b/DSLogic-gui/pv/prop/binding/binding.h index fdf4fe8..dd52171 100644 --- a/DSLogic-gui/pv/prop/binding/binding.h +++ b/DSLogic-gui/pv/prop/binding/binding.h @@ -24,9 +24,14 @@ #ifndef DSLOGIC_PV_PROP_BINDING_BINDING_H #define DSLOGIC_PV_PROP_BINDING_BINDING_H +#include + #include #include +#include + +class QFormLayout; class QWidget; namespace pv { @@ -41,6 +46,16 @@ class Binding public: const std::vector< boost::shared_ptr >& properties(); + void commit(); + + void add_properties_to_form(QFormLayout *layout, + bool auto_commit = false) const; + + QWidget* get_property_form(QWidget *parent, + bool auto_commit = false) const; + + static QString print_gvariant(GVariant *const gvar); + protected: std::vector< boost::shared_ptr > _properties; diff --git a/DSLogic-gui/pv/prop/binding/decoderoptions.cpp b/DSLogic-gui/pv/prop/binding/decoderoptions.cpp new file mode 100644 index 0000000..c9005bb --- /dev/null +++ b/DSLogic-gui/pv/prop/binding/decoderoptions.cpp @@ -0,0 +1,148 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2013 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "decoderoptions.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using boost::bind; +using boost::none; +using boost::shared_ptr; +using std::make_pair; +using std::map; +using std::pair; +using std::string; +using std::vector; + +namespace pv { +namespace prop { +namespace binding { + +DecoderOptions::DecoderOptions( + shared_ptr decoder_stack, + shared_ptr decoder) : + _decoder_stack(decoder_stack), + _decoder(decoder) +{ + assert(_decoder); + + const srd_decoder *const dec = _decoder->decoder(); + assert(dec); + + for (GSList *l = dec->options; l; l = l->next) + { + const srd_decoder_option *const opt = + (srd_decoder_option*)l->data; + + const QString name = QString::fromUtf8(opt->desc); + + const Property::Getter getter = bind( + &DecoderOptions::getter, this, opt->id); + const Property::Setter setter = bind( + &DecoderOptions::setter, this, opt->id, _1); + + shared_ptr prop; + + if (opt->values) + prop = bind_enum(name, opt, getter, setter); + else if (g_variant_is_of_type(opt->def, G_VARIANT_TYPE("d"))) + prop = shared_ptr(new Double(name, 2, "", + none, none, getter, setter)); + else if (g_variant_is_of_type(opt->def, G_VARIANT_TYPE("x"))) + prop = shared_ptr( + new Int(name, "", none, getter, setter)); + else if (g_variant_is_of_type(opt->def, G_VARIANT_TYPE("s"))) + prop = shared_ptr( + new String(name, getter, setter)); + else + continue; + + _properties.push_back(prop); + } +} + +shared_ptr DecoderOptions::bind_enum( + const QString &name, const srd_decoder_option *option, + Property::Getter getter, Property::Setter setter) +{ + vector< pair > values; + for (GSList *l = option->values; l; l = l->next) { + GVariant *const var = (GVariant*)l->data; + assert(var); + values.push_back(make_pair(var, print_gvariant(var))); + } + + return shared_ptr(new Enum(name, values, getter, setter)); +} + +GVariant* DecoderOptions::getter(const char *id) +{ + GVariant *val = NULL; + + assert(_decoder); + + // Get the value from the hash table if it is already present + const map& options = _decoder->options(); + map::const_iterator iter = options.find(id); + + if (iter != options.end()) + val = (*iter).second; + else + { + assert(_decoder->decoder()); + + // Get the default value if not + for (GSList *l = _decoder->decoder()->options; l; l = l->next) + { + const srd_decoder_option *const opt = + (srd_decoder_option*)l->data; + if (strcmp(opt->id, id) == 0) { + val = opt->def; + break; + } + } + } + + if (val) + g_variant_ref(val); + + return val; +} + +void DecoderOptions::setter(const char *id, GVariant *value) +{ + assert(_decoder); + _decoder->set_option(id, value); +} + +} // binding +} // prop +} // pv diff --git a/DSLogic-gui/pv/prop/binding/decoderoptions.h b/DSLogic-gui/pv/prop/binding/decoderoptions.h new file mode 100644 index 0000000..eaae51d --- /dev/null +++ b/DSLogic-gui/pv/prop/binding/decoderoptions.h @@ -0,0 +1,66 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2013 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DSLOGIC_PV_PROP_BINDING_DECODEROPTIONS_H +#define DSLOGIC_PV_PROP_BINDING_DECODEROPTIONS_H + +#include "binding.h" + +#include + +struct srd_decoder_option; + +namespace pv { + +namespace data { +class DecoderStack; +namespace decode { +class Decoder; +} +} + +namespace prop { +namespace binding { + +class DecoderOptions : public Binding +{ +public: + DecoderOptions(boost::shared_ptr decoder_stack, + boost::shared_ptr decoder); + +private: + static boost::shared_ptr bind_enum(const QString &name, + const srd_decoder_option *option, + Property::Getter getter, Property::Setter setter); + + GVariant* getter(const char *id); + + void setter(const char *id, GVariant *value); + +private: + boost::shared_ptr _decoder_stack; + boost::shared_ptr _decoder; +}; + +} // binding +} // prop +} // pv + +#endif // DSLOGIC_PV_PROP_BINDING_DECODEROPTIONS_H diff --git a/DSLogic-gui/pv/prop/binding/deviceoptions.cpp b/DSLogic-gui/pv/prop/binding/deviceoptions.cpp new file mode 100644 index 0000000..8ffc1f7 --- /dev/null +++ b/DSLogic-gui/pv/prop/binding/deviceoptions.cpp @@ -0,0 +1,274 @@ +/* + * This file is part of the DSLogic-gui project. + * DSLogic-gui is based on PulseView. + * + * Copyright (C) 2012 Joel Holdsworth + * Copyright (C) 2013 DreamSourceLab + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include +#include + +#include + +#include "deviceoptions.h" + +#include +#include +#include +#include + +using namespace boost; +using namespace std; + +namespace pv { +namespace prop { +namespace binding { + +DeviceOptions::DeviceOptions(struct sr_dev_inst *sdi) : + _sdi(sdi) +{ + GVariant *gvar_opts, *gvar_list; + gsize num_opts; + + if ((sr_config_list(sdi->driver, sdi, NULL, SR_CONF_DEVICE_CONFIGS, + &gvar_opts) != SR_OK)) + /* Driver supports no device instance options. */ + return; + + const int *const options = (const int32_t *)g_variant_get_fixed_array( + gvar_opts, &num_opts, sizeof(int32_t)); + for (unsigned int i = 0; i < num_opts; i++) { + const struct sr_config_info *const info = + sr_config_info_get(options[i]); + + if (!info) + continue; + + const int key = info->key; + + if(sr_config_list(_sdi->driver, _sdi, NULL, key, &gvar_list) != SR_OK) + gvar_list = NULL; + + const QString name(info->name); + + switch(key) + { + case SR_CONF_SAMPLERATE: + bind_samplerate(name, gvar_list); + break; + + case SR_CONF_CAPTURE_RATIO: + bind_int(name, key, "%", pair(0, 100)); + break; + + case SR_CONF_PATTERN_MODE: + case SR_CONF_BUFFERSIZE: + case SR_CONF_TRIGGER_SOURCE: + case SR_CONF_FILTER: + case SR_CONF_COUPLING: + case SR_CONF_EN_CH: + case SR_CONF_OPERATION_MODE: + case SR_CONF_THRESHOLD: + case SR_CONF_ZERO: + bind_enum(name, key, gvar_list); + break; + + case SR_CONF_RLE: + bind_bool(name, key); + break; + + case SR_CONF_CLOCK_TYPE: + case SR_CONF_CLOCK_EDGE: + bind_bool(name, key); + break; + + case SR_CONF_TIMEBASE: + bind_enum(name, key, gvar_list, print_timebase); + break; + + case SR_CONF_VDIV: + bind_enum(name, key, gvar_list, print_vdiv); + break; + default: + gvar_list = NULL; + } + + if (gvar_list) + g_variant_unref(gvar_list); + } + g_variant_unref(gvar_opts); +} + +GVariant* DeviceOptions::config_getter( + const struct sr_dev_inst *sdi, int key) +{ + GVariant *data = NULL; + if (sr_config_get(sdi->driver, sdi, NULL, NULL, key, &data) != SR_OK) { + qDebug() << + "WARNING: Failed to get value of config id" << key; + return NULL; + } + return data; +} + +void DeviceOptions::config_setter( + const struct sr_dev_inst *sdi, int key, GVariant* value) +{ + if (sr_config_set(sdi, NULL, NULL, key, value) != SR_OK) + qDebug() << "WARNING: Failed to set value of sample rate"; +} + +void DeviceOptions::bind_bool(const QString &name, int key) +{ + _properties.push_back(boost::shared_ptr( + new Bool(name, bind(config_getter, _sdi, key), + bind(config_setter, _sdi, key, _1)))); +} + +void DeviceOptions::bind_enum(const QString &name, int key, + GVariant *const gvar_list, boost::function printer) +{ + GVariant *gvar; + GVariantIter iter; + vector< pair > values; + + assert(gvar_list); + + g_variant_iter_init (&iter, gvar_list); + while ((gvar = g_variant_iter_next_value (&iter))) + values.push_back(make_pair(gvar, printer(gvar))); + + _properties.push_back(boost::shared_ptr( + new Enum(name, values, + bind(config_getter, _sdi, key), + bind(config_setter, _sdi, key, _1)))); +} + +void DeviceOptions::bind_int(const QString &name, int key, QString suffix, + optional< std::pair > range) +{ + _properties.push_back(boost::shared_ptr( + new Int(name, suffix, range, + bind(config_getter, _sdi, key), + bind(config_setter, _sdi, key, _1)))); +} + +QString DeviceOptions::print_gvariant(GVariant *const gvar) +{ + QString s; + + if (g_variant_is_of_type(gvar, G_VARIANT_TYPE("s"))) + s = QString(g_variant_get_string(gvar, NULL)); + else + { + gchar *const text = g_variant_print(gvar, FALSE); + s = QString(text); + g_free(text); + } + + return s; +} + +void DeviceOptions::bind_samplerate(const QString &name, + GVariant *const gvar_list) +{ + GVariant *gvar_list_samplerates; + + assert(gvar_list); + + if ((gvar_list_samplerates = g_variant_lookup_value(gvar_list, + "samplerate-steps", G_VARIANT_TYPE("at")))) + { + gsize num_elements; + const uint64_t *const elements = + (const uint64_t *)g_variant_get_fixed_array( + gvar_list_samplerates, &num_elements, sizeof(uint64_t)); + + assert(num_elements == 3); + + _properties.push_back(boost::shared_ptr( + new Double(name, 0, QObject::tr("Hz"), + make_pair((double)elements[0], (double)elements[1]), + (double)elements[2], + bind(samplerate_double_getter, _sdi), + bind(samplerate_double_setter, _sdi, _1)))); + + g_variant_unref(gvar_list_samplerates); + } + else if ((gvar_list_samplerates = g_variant_lookup_value(gvar_list, + "samplerates", G_VARIANT_TYPE("at")))) + { + bind_enum(name, SR_CONF_SAMPLERATE, + gvar_list_samplerates, print_samplerate); + g_variant_unref(gvar_list_samplerates); + } +} + +QString DeviceOptions::print_samplerate(GVariant *const gvar) +{ + char *const s = sr_samplerate_string( + g_variant_get_uint64(gvar)); + const QString qstring(s); + g_free(s); + return qstring; +} + +GVariant* DeviceOptions::samplerate_double_getter( + const struct sr_dev_inst *sdi) +{ + GVariant *const gvar = config_getter(sdi, SR_CONF_SAMPLERATE); + + if(!gvar) + return NULL; + + GVariant *const gvar_double = g_variant_new_double( + g_variant_get_uint64(gvar)); + + g_variant_unref(gvar); + + return gvar_double; +} + +void DeviceOptions::samplerate_double_setter( + struct sr_dev_inst *sdi, GVariant *value) +{ + GVariant *const gvar = g_variant_new_uint64( + g_variant_get_double(value)); + config_setter(sdi, SR_CONF_SAMPLERATE, gvar); +} + +QString DeviceOptions::print_timebase(GVariant *const gvar) +{ + uint64_t p, q; + g_variant_get(gvar, "(tt)", &p, &q); + return QString(sr_period_string(p * q)); +} + +QString DeviceOptions::print_vdiv(GVariant *const gvar) +{ + uint64_t p, q; + g_variant_get(gvar, "(tt)", &p, &q); + return QString(sr_voltage_string(p, q)); +} + +} // binding +} // prop +} // pv + diff --git a/DSLogic-gui/pv/prop/bool.cpp b/DSLogic-gui/pv/prop/bool.cpp index 996cbde..12f2f45 100644 --- a/DSLogic-gui/pv/prop/bool.cpp +++ b/DSLogic-gui/pv/prop/bool.cpp @@ -43,7 +43,7 @@ Bool::~Bool() { } -QWidget* Bool::get_widget(QWidget *parent) +QWidget* Bool::get_widget(QWidget *parent, bool auto_commit) { if (_check_box) return _check_box; @@ -58,6 +58,10 @@ QWidget* Bool::get_widget(QWidget *parent) g_variant_unref(value); } + if (auto_commit) + connect(_check_box, SIGNAL(stateChanged(int)), + this, SLOT(on_state_changed(int))); + return _check_box; } @@ -77,5 +81,11 @@ void Bool::commit() _check_box->checkState() == Qt::Checked)); } + +void Bool::on_state_changed(int) +{ + commit(); +} + } // prop } // pv diff --git a/DSLogic-gui/pv/prop/bool.h b/DSLogic-gui/pv/prop/bool.h index a48f52f..7c65608 100644 --- a/DSLogic-gui/pv/prop/bool.h +++ b/DSLogic-gui/pv/prop/bool.h @@ -33,16 +33,21 @@ namespace prop { class Bool : public Property { + Q_OBJECT; + public: Bool(QString name, Getter getter, Setter setter); virtual ~Bool(); - QWidget* get_widget(QWidget *parent); + QWidget* get_widget(QWidget *parent, bool auto_commit); bool labeled_widget() const; void commit(); +private slots: + void on_state_changed(int); + private: QCheckBox *_check_box; }; diff --git a/DSLogic-gui/pv/prop/double.cpp b/DSLogic-gui/pv/prop/double.cpp index b804575..ebc1ba1 100644 --- a/DSLogic-gui/pv/prop/double.cpp +++ b/DSLogic-gui/pv/prop/double.cpp @@ -53,7 +53,7 @@ Double::~Double() { } -QWidget* Double::get_widget(QWidget *parent) +QWidget* Double::get_widget(QWidget *parent, bool auto_commit) { if (_spin_box) return _spin_box; @@ -73,6 +73,10 @@ QWidget* Double::get_widget(QWidget *parent) g_variant_unref(value); } + if (auto_commit) + connect(_spin_box, SIGNAL(valueChanged(double)), + this, SLOT(on_value_changed(double))); + return _spin_box; } @@ -86,5 +90,10 @@ void Double::commit() _setter(g_variant_new_double(_spin_box->value())); } +void Double::on_value_changed(double) +{ + commit(); +} + } // prop } // pv diff --git a/DSLogic-gui/pv/prop/double.h b/DSLogic-gui/pv/prop/double.h index 47ef17d..79b1100 100644 --- a/DSLogic-gui/pv/prop/double.h +++ b/DSLogic-gui/pv/prop/double.h @@ -37,6 +37,8 @@ namespace prop { class Double : public Property { + Q_OBJECT; + public: Double(QString name, int decimals, QString suffix, boost::optional< std::pair > range, @@ -46,10 +48,13 @@ public: virtual ~Double(); - QWidget* get_widget(QWidget *parent); + QWidget* get_widget(QWidget *parent, bool auto_commit); void commit(); +private slots: + void on_value_changed(double); + private: const int _decimals; const QString _suffix; diff --git a/DSLogic-gui/pv/prop/enum.cpp b/DSLogic-gui/pv/prop/enum.cpp index edf6584..8e96c5e 100644 --- a/DSLogic-gui/pv/prop/enum.cpp +++ b/DSLogic-gui/pv/prop/enum.cpp @@ -40,6 +40,9 @@ Enum::Enum(QString name, _values(values), _selector(NULL) { + for (vector< pair >::const_iterator i = + _values.begin(); i != _values.end(); i++) + g_variant_ref((*i).first); } Enum::~Enum() @@ -48,12 +51,14 @@ Enum::~Enum() g_variant_unref(_values[i].first); } -QWidget* Enum::get_widget(QWidget *parent) +QWidget* Enum::get_widget(QWidget *parent, bool auto_commit) { if (_selector) return _selector; GVariant *const value = _getter ? _getter() : NULL; + if (!value) + return NULL; _selector = new QComboBox(parent); for (unsigned int i = 0; i < _values.size(); i++) { @@ -65,6 +70,10 @@ QWidget* Enum::get_widget(QWidget *parent) g_variant_unref(value); + if (auto_commit) + connect(_selector, SIGNAL(currentIndexChanged(int)), + this, SLOT(on_current_item_changed(int))); + return _selector; } @@ -82,5 +91,10 @@ void Enum::commit() _setter((GVariant*)_selector->itemData(index).value()); } +void Enum::on_current_item_changed(int) +{ + commit(); +} + } // prop } // pv diff --git a/DSLogic-gui/pv/prop/enum.h b/DSLogic-gui/pv/prop/enum.h index c26e512..0e14a1d 100644 --- a/DSLogic-gui/pv/prop/enum.h +++ b/DSLogic-gui/pv/prop/enum.h @@ -36,16 +36,21 @@ namespace prop { class Enum : public Property { + Q_OBJECT; + public: Enum(QString name, std::vector > values, Getter getter, Setter setter); virtual ~Enum(); - QWidget* get_widget(QWidget *parent); + QWidget* get_widget(QWidget *parent, bool auto_commit); void commit(); +private slots: + void on_current_item_changed(int); + private: const std::vector< std::pair > _values; diff --git a/DSLogic-gui/pv/prop/int.cpp b/DSLogic-gui/pv/prop/int.cpp index 741b879..cf5fd5b 100644 --- a/DSLogic-gui/pv/prop/int.cpp +++ b/DSLogic-gui/pv/prop/int.cpp @@ -21,14 +21,31 @@ */ +#include #include +#include #include #include "int.h" +using boost::optional; using namespace std; -using namespace boost; + +#define INT8_MIN (-0x7f - 1) +#define INT16_MIN (-0x7fff - 1) +#define INT32_MIN (-0x7fffffff - 1) +#define INT64_MIN (-0x7fffffffffffffff - 1) + +#define INT8_MAX 0x7f +#define INT16_MAX 0x7fff +#define INT32_MAX 0x7fffffff +#define INT64_MAX 0x7fffffffffffffff + +#define UINT8_MAX 0xff +#define UINT16_MAX 0xffff +#define UINT32_MAX 0xffffffff +#define UINT64_MAX 0xffffffffffffffff namespace pv { namespace prop { @@ -41,41 +58,145 @@ Int::Int(QString name, Property(name, getter, setter), _suffix(suffix), _range(range), + _value(NULL), _spin_box(NULL) { } Int::~Int() { + if (_value) + g_variant_unref(_value); } -QWidget* Int::get_widget(QWidget *parent) +QWidget* Int::get_widget(QWidget *parent, bool auto_commit) { - if (_spin_box) - return _spin_box; - - _spin_box = new QSpinBox(parent); - _spin_box->setSuffix(_suffix); - if (_range) - _spin_box->setRange((int)_range->first, (int)_range->second); - - GVariant *const value = _getter ? _getter() : NULL; - if (value) { - _spin_box->setValue((int)g_variant_get_int64(value)); - g_variant_unref(value); - } - - return _spin_box; + int64_t int_val = 0, range_min = 0, range_max = 0; + + if (_spin_box) + return _spin_box; + + if (_value) + g_variant_unref(_value); + + _value = _getter ? _getter() : NULL; + if (!_value) + return NULL; + + _spin_box = new QSpinBox(parent); + _spin_box->setSuffix(_suffix); + + const GVariantType *const type = g_variant_get_type(_value); + assert(type); + + if (g_variant_type_equal(type, G_VARIANT_TYPE_BYTE)) + { + int_val = g_variant_get_byte(_value); + range_min = 0, range_max = UINT8_MAX; + } + else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT16)) + { + int_val = g_variant_get_int16(_value); + range_min = INT16_MIN, range_max = INT16_MAX; + } + else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT16)) + { + int_val = g_variant_get_uint16(_value); + range_min = 0, range_max = UINT16_MAX; + } + else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT32)) + { + int_val = g_variant_get_int32(_value); + range_min = INT32_MIN, range_max = INT32_MAX; + } + else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT32)) + { + int_val = g_variant_get_uint32(_value); + range_min = 0, range_max = UINT32_MAX; + } + else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT64)) + { + int_val = g_variant_get_int64(_value); + range_min = INT64_MIN, range_max = INT64_MAX; + } + else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT64)) + { + int_val = g_variant_get_uint64(_value); + range_min = 0, range_max = UINT64_MAX; + } + else + { + // Unexpected value type. + assert(0); + } + + // @todo Sigrok supports 64-bit quantities, but Qt does not have a + // standard widget to allow the values to be modified over the full + // 64-bit range on 32-bit machines. To solve the issue we need a + // custom widget. + + range_min = max(range_min, (int64_t)INT_MIN); + range_max = min(range_max, (int64_t)INT_MAX); + + if (_range) + _spin_box->setRange((int)_range->first, (int)_range->second); + else + _spin_box->setRange((int)range_min, (int)range_max); + + _spin_box->setValue((int)int_val); + + if (auto_commit) + connect(_spin_box, SIGNAL(valueChanged(int)), + this, SLOT(on_value_changed(int))); + + return _spin_box; } void Int::commit() { - assert(_setter); - - if (!_spin_box) - return; + assert(_setter); + + if (!_spin_box) + return; + + assert(_value); + + GVariant *new_value = NULL; + const GVariantType *const type = g_variant_get_type(_value); + assert(type); + + if (g_variant_type_equal(type, G_VARIANT_TYPE_BYTE)) + new_value = g_variant_new_byte(_spin_box->value()); + else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT16)) + new_value = g_variant_new_int16(_spin_box->value()); + else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT16)) + new_value = g_variant_new_uint16(_spin_box->value()); + else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT32)) + new_value = g_variant_new_int32(_spin_box->value()); + else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT32)) + new_value = g_variant_new_int32(_spin_box->value()); + else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT64)) + new_value = g_variant_new_int64(_spin_box->value()); + else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT64)) + new_value = g_variant_new_uint64(_spin_box->value()); + else + { + // Unexpected value type. + assert(0); + } + + assert(new_value); + + g_variant_unref(_value); + g_variant_ref(new_value); + _value = new_value; + + _setter(new_value); +} - _setter(g_variant_new_int64(_spin_box->value())); +void Int::on_value_changed(int) +{ + commit(); } } // prop diff --git a/DSLogic-gui/pv/prop/int.h b/DSLogic-gui/pv/prop/int.h index b9ae22b..81ea7dd 100644 --- a/DSLogic-gui/pv/prop/int.h +++ b/DSLogic-gui/pv/prop/int.h @@ -37,6 +37,8 @@ namespace prop { class Int : public Property { + Q_OBJECT; + public: Int(QString name, QString suffix, boost::optional< std::pair > range, @@ -44,14 +46,18 @@ public: virtual ~Int(); - QWidget* get_widget(QWidget *parent); + QWidget* get_widget(QWidget *parent, bool auto_commit); void commit(); +private slots: + void on_value_changed(int); + private: const QString _suffix; const boost::optional< std::pair > _range; + GVariant *_value; QSpinBox *_spin_box; }; diff --git a/DSLogic-gui/pv/prop/property.h b/DSLogic-gui/pv/prop/property.h index 61e880c..d1f3e51 100644 --- a/DSLogic-gui/pv/prop/property.h +++ b/DSLogic-gui/pv/prop/property.h @@ -36,8 +36,10 @@ class QWidget; namespace pv { namespace prop { -class Property +class Property : public QObject { + Q_OBJECT; + public: typedef boost::function Getter; typedef boost::function Setter; @@ -48,7 +50,8 @@ protected: public: const QString& name() const; - virtual QWidget* get_widget(QWidget *parent) = 0; + virtual QWidget* get_widget(QWidget *parent, + bool auto_commit = false) = 0; virtual bool labeled_widget() const; virtual void commit() = 0; diff --git a/DSLogic-gui/pv/prop/string.cpp b/DSLogic-gui/pv/prop/string.cpp new file mode 100644 index 0000000..7fcc656 --- /dev/null +++ b/DSLogic-gui/pv/prop/string.cpp @@ -0,0 +1,77 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2013 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include +#include + +#include "string.h" + +namespace pv { +namespace prop { + +String::String(QString name, + Getter getter, + Setter setter) : + Property(name, getter, setter), + _line_edit(NULL) +{ +} + +QWidget* String::get_widget(QWidget *parent, bool auto_commit) +{ + if (_line_edit) + return _line_edit; + + GVariant *const value = _getter ? _getter() : NULL; + if (!value) + return NULL; + + _line_edit = new QLineEdit(parent); + _line_edit->setText(QString::fromUtf8( + g_variant_get_string(value, NULL))); + g_variant_unref(value); + + if (auto_commit) + connect(_line_edit, SIGNAL(textEdited(const QString&)), + this, SLOT(on_text_edited(const QString&))); + + return _line_edit; +} + +void String::commit() +{ + assert(_setter); + + if (!_line_edit) + return; + + QByteArray ba = _line_edit->text().toLocal8Bit(); + _setter(g_variant_new_string(ba.data())); +} + +void String::on_text_edited(const QString&) +{ + commit(); +} + +} // prop +} // pv diff --git a/DSLogic-gui/pv/prop/string.h b/DSLogic-gui/pv/prop/string.h new file mode 100644 index 0000000..0caf565 --- /dev/null +++ b/DSLogic-gui/pv/prop/string.h @@ -0,0 +1,52 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2013 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DSLOGIC_PV_PROP_STRING_H +#define DSLOGIC_PV_PROP_STRING_H + +#include "property.h" + +class QLineEdit; + +namespace pv { +namespace prop { + +class String : public Property +{ + Q_OBJECT; + +public: + String(QString name, Getter getter, Setter setter); + + QWidget* get_widget(QWidget *parent, bool auto_commit); + + void commit(); + +private slots: + void on_text_edited(const QString&); + +private: + QLineEdit *_line_edit; +}; + +} // prop +} // pv + +#endif // DSLOGIC_PV_PROP_STRING_H diff --git a/DSLogic-gui/pv/sigsession.cpp b/DSLogic-gui/pv/sigsession.cpp index 27d7946..bb6f238 100644 --- a/DSLogic-gui/pv/sigsession.cpp +++ b/DSLogic-gui/pv/sigsession.cpp @@ -20,10 +20,17 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef ENABLE_DECODE +#include +#endif #include "sigsession.h" +#include "mainwindow.h" #include "devicemanager.h" +#include "device/device.h" +#include "device/file.h" + #include "data/analog.h" #include "data/analogsnapshot.h" #include "data/dso.h" @@ -32,23 +39,36 @@ #include "data/logicsnapshot.h" #include "data/group.h" #include "data/groupsnapshot.h" +#include "data/decoderstack.h" +#include "data/decode/decoder.h" + #include "view/analogsignal.h" #include "view/dsosignal.h" #include "view/logicsignal.h" #include "view/groupsignal.h" -#include "view/protocolsignal.h" -#include "decoder/decoder.h" -#include "decoder/decoderfactory.h" +#include "view/decodetrace.h" #include +#include +#include #include #include #include -using namespace boost; -using namespace std; +using boost::dynamic_pointer_cast; +using boost::function; +using boost::lock_guard; +using boost::mutex; +using boost::shared_ptr; +using std::list; +using std::map; +using std::set; +using std::string; +using std::vector; +using std::deque; +using std::min; namespace pv { @@ -59,10 +79,8 @@ SigSession* SigSession::_session = NULL; SigSession::SigSession(DeviceManager &device_manager) : _device_manager(device_manager), - _sdi(NULL), _capture_state(Init), - _last_sample_rate(1), - _total_sample_len(1) + _instant(false) { // TODO: This should not be necessary _session = this; @@ -70,134 +88,107 @@ SigSession::SigSession(DeviceManager &device_manager) : _hot_detach = false; _adv_trigger = false; _group_cnt = 0; - _protocol_cnt = 0; - _decoderFactory = new decoder::DecoderFactory(); ds_trigger_init(); - - _vDial_changed = false; - _hDial_changed = false; - _dso_ctrl_channel = 0; - register_hotplug_callback(); + register_hotplug_callback(); } SigSession::~SigSession() { - stop_capture(); - - if (_sampling_thread.get()) - _sampling_thread->join(); - _sampling_thread.reset(); - + stop_capture(); if (_hotplug_handle) { stop_hotplug_proc(); deregister_hotplug_callback(); } - ds_trigger_destroy(); - stop_dso_ctrl_proc(); + + _dev_inst->release(); // TODO: This should not be necessary _session = NULL; } -quint64 SigSession::get_last_sample_rate() const -{ - return _last_sample_rate; -} - -quint64 SigSession::get_total_sample_len() const +boost::shared_ptr SigSession::get_device() const { - return _total_sample_len; + return _dev_inst; } -void SigSession::set_total_sample_len(quint64 length) +void SigSession::set_device(shared_ptr dev_inst) throw(QString) { - _total_sample_len = length; -} + using pv::device::Device; -struct sr_dev_inst* SigSession::get_device() const -{ - return _sdi; -} + // Ensure we are not capturing before setting the device + stop_capture(); -int SigSession::set_device(struct sr_dev_inst *sdi) -{ - int ret = SR_ERR; - - if (_sdi == NULL) { - ret = _device_manager.use_device(sdi, this); - _sdi = sdi; - set_capture_state(Init); - } else if (sdi != _sdi) { - ret = _device_manager.use_device(sdi, this); - _device_manager.release_device(_sdi); - _sdi = sdi; - set_capture_state(Init); - } else { - ret = SR_OK; + if (_dev_inst) { + sr_session_datafeed_callback_remove_all(); + _dev_inst->release(); } - return ret; + _dev_inst = dev_inst; + _decode_traces.clear(); + _group_traces.clear(); + + if (_dev_inst) { + _dev_inst->use(this); + sr_session_datafeed_callback_add(data_feed_in_proc, NULL); + device_setted(); + } } -void SigSession::release_device(struct sr_dev_inst *sdi) -{ - (void)sdi; - assert(_capture_state != Running); - _sdi = NULL; +void SigSession::set_file(const string &name) throw(QString) +{ + // Deslect the old device, because file type detection in File::create + // destorys the old session inside libsigrok. + set_device(shared_ptr()); + set_device(shared_ptr(device::File::create(name))); } void SigSession::save_file(const std::string &name){ - if (_sdi->mode == LOGIC) { - const deque< boost::shared_ptr > &snapshots = - _logic_data->get_snapshots(); - if (snapshots.empty()) - return; - - const boost::shared_ptr &snapshot = - snapshots.front(); - - sr_session_save(name.c_str(), _sdi, - (unsigned char*)snapshot->get_data(), - snapshot->get_unit_size(), - snapshot->get_sample_count()); - } else if (_sdi->mode == DSO){ - const deque< boost::shared_ptr > &snapshots = - _dso_data->get_snapshots(); - if (snapshots.empty()) - return; + const deque< boost::shared_ptr > &snapshots = + _logic_data->get_snapshots(); + if (snapshots.empty()) + return; - const boost::shared_ptr &snapshot = - snapshots.front(); + const boost::shared_ptr &snapshot = + snapshots.front(); - sr_session_save(name.c_str(), _sdi, - (unsigned char*)snapshot->get_data(), - snapshot->get_unit_size(), - snapshot->get_sample_count()); - } else { - const deque< boost::shared_ptr > &snapshots = - _analog_data->get_snapshots(); - if (snapshots.empty()) - return; - - const boost::shared_ptr &snapshot = - snapshots.front(); + sr_session_save(name.c_str(), _dev_inst->dev_inst(), + (unsigned char*)snapshot->get_data(), + snapshot->unit_size(), + snapshot->get_sample_count()); +} - sr_session_save(name.c_str(), _sdi, - (unsigned char*)snapshot->get_data(), - snapshot->get_unit_size(), - snapshot->get_sample_count()); +void SigSession::set_default_device() +{ + shared_ptr default_device; + const list< shared_ptr > &devices = + _device_manager.devices(); + + if (!devices.empty()) { + // Fall back to the first device in the list. + default_device = devices.front(); + + // Try and find the DSLogic device and select that by default + BOOST_FOREACH (shared_ptr dev, devices) + if (dev->dev_inst() && + strcmp(dev->dev_inst()->driver->name, + "DSLogic") == 0) { + default_device = dev; + break; + } + set_device(default_device); } } -void SigSession::load_file(const string &name, - boost::function error_handler) +void SigSession::release_device(device::DevInst *dev_inst) { - stop_capture(); - _sampling_thread.reset(new boost::thread( - &SigSession::load_thread_proc, this, name, - error_handler)); + (void)dev_inst; + assert(_dev_inst.get() == dev_inst); + + assert(_capture_state != Running); + _dev_inst = shared_ptr(); + //_dev_inst.reset(); } SigSession::capture_state SigSession::get_capture_state() const @@ -206,21 +197,23 @@ SigSession::capture_state SigSession::get_capture_state() const return _capture_state; } -void SigSession::start_capture(uint64_t record_length, - boost::function error_handler) +void SigSession::start_capture(bool instant, + boost::function error_handler) { stop_capture(); // Check that a device instance has been selected. - if (!_sdi) { + if (!_dev_inst) { qDebug() << "No device selected"; return; } + assert(_dev_inst->dev_inst()); + // Check that at least one probe is enabled const GSList *l; - for (l = _sdi->probes; l; l = l->next) { - sr_probe *const probe = (sr_probe*)l->data; + for (l = _dev_inst->dev_inst()->channels; l; l = l->next) { + sr_channel *const probe = (sr_channel*)l->data; assert(probe); if (probe->enabled) break; @@ -230,32 +223,16 @@ void SigSession::start_capture(uint64_t record_length, return; } - // Check that at least one signal channel is active under DSO mode - if (_sdi->mode == DSO) { - bool active = false; - BOOST_FOREACH(const shared_ptr s, _signals) - { - assert(s); - if (s->get_active()) { - active = true; - break; - } - } - if (!active) { - error_handler(tr("No channels enabled.")); - return; - } - } - // Begin the session + _instant = instant; _sampling_thread.reset(new boost::thread( - &SigSession::sample_thread_proc, this, _sdi, - record_length, error_handler)); + &SigSession::sample_thread_proc, this, _dev_inst, + error_handler)); } void SigSession::stop_capture() { - if (get_capture_state() == Stopped) + if (get_capture_state() != Running) return; sr_session_stop(); @@ -271,75 +248,27 @@ vector< boost::shared_ptr > SigSession::get_signals() return _signals; } -vector< boost::shared_ptr > SigSession::get_pro_signals() +vector< boost::shared_ptr > SigSession::get_group_signals() { boost::lock_guard lock(_signals_mutex); - return _protocol_signals; + return _group_traces; } -int SigSession::get_logic_probe_cnt(const sr_dev_inst *sdi) +set< shared_ptr > SigSession::get_data() const { - unsigned int logic_probe_cnt = 0; - // Detect what data types we will receive - for (const GSList *l = sdi->probes; l; l = l->next) { - const sr_probe *const probe = (const sr_probe *)l->data; - if (!probe->enabled) - continue; - - switch(probe->type) { - case SR_PROBE_LOGIC: - logic_probe_cnt++; - break; - } - } - - return logic_probe_cnt; -} - -int SigSession::get_dso_probe_cnt(const sr_dev_inst *sdi) -{ - unsigned int dso_probe_cnt = 0; - for (const GSList *l = sdi->probes; l; l = l->next) { - const sr_probe *const probe = (const sr_probe *)l->data; - if (!probe->enabled) - continue; - - switch(probe->type) { - case SR_PROBE_DSO: - dso_probe_cnt++; - break; - } + lock_guard lock(_signals_mutex); + set< shared_ptr > data; + BOOST_FOREACH(const shared_ptr sig, _signals) { + assert(sig); + data.insert(sig->data()); } - return dso_probe_cnt; -} - -int SigSession::get_analog_probe_cnt(const sr_dev_inst *sdi) -{ - unsigned int analog_probe_cnt = 0; - for (const GSList *l = sdi->probes; l; l = l->next) { - const sr_probe *const probe = (const sr_probe *)l->data; - if (!probe->enabled) - continue; - - switch(probe->type) { - case SR_PROBE_ANALOG: - analog_probe_cnt++; - break; - } - } - - return analog_probe_cnt; -} - -boost::shared_ptr SigSession::get_data() -{ - return _logic_data; + return data; } void* SigSession::get_buf(int& unit_size, uint64_t &length) { - if (_sdi->mode == LOGIC) { + if (_dev_inst->dev_inst()->mode == LOGIC) { const deque< boost::shared_ptr > &snapshots = _logic_data->get_snapshots(); if (snapshots.empty()) @@ -348,10 +277,10 @@ void* SigSession::get_buf(int& unit_size, uint64_t &length) const boost::shared_ptr &snapshot = snapshots.front(); - unit_size = snapshot->get_unit_size(); + unit_size = snapshot->unit_size(); length = snapshot->get_sample_count(); return snapshot->get_data(); - } else if (_sdi->mode == DSO) { + } else if (_dev_inst->dev_inst()->mode == DSO) { const deque< boost::shared_ptr > &snapshots = _dso_data->get_snapshots(); if (snapshots.empty()) @@ -360,7 +289,7 @@ void* SigSession::get_buf(int& unit_size, uint64_t &length) const boost::shared_ptr &snapshot = snapshots.front(); - unit_size = snapshot->get_unit_size(); + unit_size = snapshot->unit_size(); length = snapshot->get_sample_count(); return snapshot->get_data(); } else { @@ -372,12 +301,18 @@ void* SigSession::get_buf(int& unit_size, uint64_t &length) const boost::shared_ptr &snapshot = snapshots.front(); - unit_size = snapshot->get_unit_size(); + unit_size = snapshot->unit_size(); length = snapshot->get_sample_count(); return snapshot->get_data(); } } +void SigSession::set_sample_rate(uint64_t sample_rate) +{ + if (_capture_state != Stopped) + sample_rate_changed(sample_rate); +} + void SigSession::set_capture_state(capture_state state) { boost::lock_guard lock(_sampling_mutex); @@ -386,42 +321,17 @@ void SigSession::set_capture_state(capture_state state) capture_state_changed(state); } -void SigSession::load_thread_proc(const string name, - boost::function error_handler) -{ - if (sr_session_load(name.c_str()) != SR_OK) { - error_handler(tr("Failed to load file.")); - return; - } - - sr_session_datafeed_callback_add(data_feed_in_proc, NULL); - - if (sr_session_start() != SR_OK) { - error_handler(tr("Failed to start session.")); - return; - } - - set_capture_state(Running); - - sr_session_run(); - sr_session_destroy(); - - set_capture_state(Stopped); - - // Confirm that SR_DF_END was received - assert(!_cur_logic_snapshot); - assert(!_cur_dso_snapshot); - assert(!_cur_analog_snapshot); -} - -void SigSession::sample_thread_proc(struct sr_dev_inst *sdi, - uint64_t record_length, - boost::function error_handler) +void SigSession::sample_thread_proc(shared_ptr dev_inst, + boost::function error_handler) { - assert(sdi); + assert(dev_inst); + assert(dev_inst->dev_inst()); assert(error_handler); - if (!_adv_trigger) { + if (_instant) { + /* disable trigger under instant mode */ + ds_trigger_set_en(false); + } else if (!_adv_trigger) { /* simple trigger check trigger_enable */ ds_trigger_set_en(false); BOOST_FOREACH(const boost::shared_ptr s, _signals) @@ -437,36 +347,17 @@ void SigSession::sample_thread_proc(struct sr_dev_inst *sdi, ds_trigger_set_en(true); } - sr_session_new(); - sr_session_datafeed_callback_add(data_feed_in_proc, NULL); - - if (sr_session_dev_add(sdi) != SR_OK) { - error_handler(tr("Failed to use device.")); - sr_session_destroy(); - return; - } - - // Set the sample limit - if (sr_config_set(sdi, SR_CONF_LIMIT_SAMPLES, - g_variant_new_uint64(record_length)) != SR_OK) { - error_handler(tr("Failed to configure " - "time-based sample limit.")); - sr_session_destroy(); + try { + dev_inst->start(); + } catch(const QString e) { + error_handler(e); return; } receive_data(0); set_capture_state(Running); - if (sr_session_start() != SR_OK) { - error_handler(tr("Failed to start session.")); - set_capture_state(Stopped); - return; - } - - sr_session_run(); - sr_session_destroy(); - + dev_inst->run(); set_capture_state(Stopped); // Confirm that SR_DF_END was received @@ -475,133 +366,57 @@ void SigSession::sample_thread_proc(struct sr_dev_inst *sdi, assert(!_cur_analog_snapshot); } -void SigSession::feed_in_header(const sr_dev_inst *sdi) +void SigSession::read_sample_rate(const sr_dev_inst *const sdi) { - boost::shared_ptr signal; - GVariant *gvar; - uint64_t sample_rate = 0; - unsigned int logic_probe_count = 0; - unsigned int dso_probe_count = 0; - unsigned int dso_channel_count = 0; - unsigned int analog_probe_count = 0; - - // Detect what data types we will receive - for (const GSList *l = sdi->probes; l; l = l->next) { - const sr_probe *const probe = (const sr_probe *)l->data; - if (!probe->enabled) - continue; - - switch(probe->type) { - case SR_PROBE_LOGIC: - logic_probe_count++; - break; - - case SR_PROBE_DSO: - dso_probe_count++; - break; - - case SR_PROBE_ANALOG: - analog_probe_count++; - break; - } - } - - // Read out the sample rate - assert(sdi->driver); - - int ret = sr_config_get(sdi->driver, SR_CONF_SAMPLERATE, - &gvar, sdi); - if (ret != SR_OK) { - qDebug("Failed to get samplerate\n"); - return; - } - sample_rate = g_variant_get_uint64(gvar); - g_variant_unref(gvar); - -// ret = sr_config_get(sdi->driver, SR_CONF_LIMIT_SAMPLES, -// &gvar, sdi); -// if (ret != SR_OK) { -// qDebug("Failed to get total samples"); -// return; -// } -// if (g_variant_get_uint64(gvar) != 0) -// _total_sample_len = g_variant_get_uint64(gvar); -// g_variant_unref(gvar); - - if (sample_rate != _last_sample_rate) { - _last_sample_rate = sample_rate; - sample_rate_changed(sample_rate); - } - ret = sr_config_get(sdi->driver, SR_CONF_EN_CH0, - &gvar, sdi); - if (ret != SR_OK) { - qDebug("Failed to get ENABLE of DSO channel 0\n"); - return; - } - dso_channel_count += g_variant_get_boolean(gvar); - ret = sr_config_get(sdi->driver, SR_CONF_EN_CH1, - &gvar, sdi); - if (ret != SR_OK) { - qDebug("Failed to get ENABLE of DSO channel 1\n"); - return; - } - dso_channel_count += g_variant_get_boolean(gvar); - - - // Create data containers for the coming data snapshots - { - boost::lock_guard data_lock(_data_mutex); - - if (logic_probe_count != 0) { - _logic_data.reset(new data::Logic( - logic_probe_count, sample_rate)); - assert(_logic_data); - - _group_data.reset(new data::Group(logic_probe_count, sample_rate)); - assert(_group_data); - } - - if (dso_probe_count != 0) { - _dso_data.reset(new data::Dso(dso_channel_count, sample_rate)); - assert(_dso_data); - } - - if (analog_probe_count != 0) { - _analog_data.reset(new data::Analog(analog_probe_count, sample_rate)); - assert(_analog_data); - } - } + GVariant *gvar; + uint64_t sample_rate = 0; - // Set Signal data + // Read out the sample rate + if(sdi->driver) { - BOOST_FOREACH(const boost::shared_ptr s, _signals) - { - assert(s); - s->set_data(_logic_data, _dso_data, _analog_data, _group_data); + const int ret = sr_config_get(sdi->driver, sdi, NULL, NULL, SR_CONF_SAMPLERATE, &gvar); + if (ret != SR_OK) { + qDebug("Failed to get samplerate\n"); + return; } - receive_data(0); - //signals_changed(); + sample_rate = g_variant_get_uint64(gvar); + g_variant_unref(gvar); + } + + // Set the sample rate of all data + const set< shared_ptr > data_set = get_data(); + BOOST_FOREACH(shared_ptr data, data_set) { + assert(data); + data->set_samplerate(sample_rate); } } +void SigSession::feed_in_header(const sr_dev_inst *sdi) +{ + read_sample_rate(sdi); + //receive_data(0); +} + void SigSession::add_group() { std::list probe_index_list; std::vector< boost::shared_ptr >::iterator i = _signals.begin(); while (i != _signals.end()) { - if ((*i)->get_type() == view::Signal::DS_LOGIC && (*i)->selected()) + if ((*i)->get_type() == view::Trace::DS_LOGIC && (*i)->selected()) probe_index_list.push_back((*i)->get_index()); i++; } if (probe_index_list.size() > 1) { //_group_data.reset(new data::Group(_last_sample_rate)); - const boost::shared_ptr signal = boost::shared_ptr( + if (_group_data->get_snapshots().empty()) + _group_data->set_samplerate(_dev_inst->get_sample_rate()); + const boost::shared_ptr signal( new view::GroupSignal("New Group", - _group_data, probe_index_list, _signals.size(), _group_cnt)); - _signals.push_back(signal); + _group_data, probe_index_list, _group_cnt)); + _group_traces.push_back(signal); _group_cnt++; if (_capture_state == Stopped) { @@ -623,82 +438,26 @@ void SigSession::add_group() void SigSession::del_group() { - std::vector< boost::shared_ptr >::iterator i = _signals.begin(); - while (i != _signals.end()) { - if ((*i)->get_type() == view::Signal::DS_GROUP) { - if ((*i)->selected()) { - std::vector< boost::shared_ptr >::iterator j = _signals.begin(); - while(j != _signals.end()) { - if ((*j)->get_order() > (*i)->get_order()) - (*j)->set_order((*j)->get_order() - 1); - if ((*j)->get_sec_index() > (*i)->get_sec_index()) - (*j)->set_sec_index((*j)->get_sec_index() - 1); - j++; - } - - _group_data->get_snapshots().at((*i)->get_sec_index()).reset(); - std::deque< boost::shared_ptr >::iterator k = _group_data->get_snapshots().begin(); - k += (*i)->get_sec_index(); - _group_data->get_snapshots().erase(k); - - (*i).reset(); - i = _signals.erase(i); - - _group_cnt--; - continue; + std::vector< boost::shared_ptr >::iterator i = _group_traces.begin(); + while (i != _group_traces.end()) { + if ((*i)->selected()) { + std::vector< boost::shared_ptr >::iterator j = _group_traces.begin(); + while(j != _group_traces.end()) { + if ((*j)->get_sec_index() > (*i)->get_sec_index()) + (*j)->set_sec_index((*j)->get_sec_index() - 1); + j++; } - } - i++; - } - - signals_changed(); - data_updated(); -} - -void SigSession::add_protocol(std::list probe_index_list, decoder::Decoder *decoder) -{ - assert(_logic_data); - std::vector< boost::shared_ptr >::iterator i = _signals.begin(); - while (i != _signals.end()) { - (*i)->set_order((*i)->get_order() + 1); - i++; - } - - if (probe_index_list.size() > 0) { - //_group_data.reset(new data::Group(_last_sample_rate)); - const boost::shared_ptr signal = boost::shared_ptr( - new view::ProtocolSignal(decoder->get_decode_name(), - _logic_data, decoder, probe_index_list, 0, _protocol_cnt)); - _signals.push_back(signal); - _protocol_cnt++; + _group_data->get_snapshots().at((*i)->get_sec_index()).reset(); + std::deque< boost::shared_ptr >::iterator k = _group_data->get_snapshots().begin(); + k += (*i)->get_sec_index(); + _group_data->get_snapshots().erase(k); - signals_changed(); - data_updated(); - } -} + (*i).reset(); + i = _group_traces.erase(i); -void SigSession::del_protocol(int protocol_index) -{ - std::vector< boost::shared_ptr >::iterator i = _signals.begin(); - while (i != _signals.end()) { - if ((*i)->get_type() == view::Signal::DS_PROTOCOL) { - if ((*i)->get_sec_index() == protocol_index) { - std::vector< boost::shared_ptr >::iterator j = _signals.begin(); - while(j != _signals.end()) { - if ((*j)->get_order() > (*i)->get_order()) - (*j)->set_order((*j)->get_order() - 1); - if ((*j)->get_sec_index() > (*i)->get_sec_index()) - (*j)->set_sec_index((*j)->get_sec_index() - 1); - j++; - } - - (*i).reset(); - i = _signals.erase(i); - - _protocol_cnt--; - break; - } + _group_cnt--; + continue; } i++; } @@ -707,105 +466,63 @@ void SigSession::del_protocol(int protocol_index) data_updated(); } -void SigSession::del_signal(std::vector< boost::shared_ptr >::iterator i) +void SigSession::init_signals() { - std::vector< boost::shared_ptr >::iterator j = _signals.begin(); - while(j != _signals.end()) { - if ((*j)->get_order() > (*i)->get_order()) - (*j)->set_order((*j)->get_order() - 1); - j++; - } - - (*i).reset(); - _signals.erase(i); -} + assert(_dev_inst); + stop_capture(); -void SigSession::init_signals(const sr_dev_inst *sdi) -{ boost::shared_ptr signal; - GVariant *gvar; - uint64_t sample_rate = 0; unsigned int logic_probe_count = 0; unsigned int dso_probe_count = 0; - unsigned int dso_channel_count = 0; unsigned int analog_probe_count = 0; - uint64_t vdiv; - uint64_t timebase; - bool coupling; - bool active; - int ret; - - // Detect what data types we will receive - for (const GSList *l = sdi->probes; l; l = l->next) { - const sr_probe *const probe = (const sr_probe *)l->data; - if (!probe->enabled) - continue; - switch(probe->type) { - case SR_PROBE_LOGIC: - logic_probe_count++; - break; - case SR_PROBE_DSO: - dso_probe_count++; - break; + // Clear the decode traces + _decode_traces.clear(); - case SR_PROBE_ANALOG: - analog_probe_count++; - break; - } - } - - // Read out the sample rate - assert(sdi->driver); + // Detect what data types we will receive + if(_dev_inst) { + assert(_dev_inst->dev_inst()); + for (const GSList *l = _dev_inst->dev_inst()->channels; + l; l = l->next) { + const sr_channel *const probe = (const sr_channel *)l->data; - ret = sr_config_get(sdi->driver, SR_CONF_SAMPLERATE, - &gvar, sdi); - if (ret != SR_OK) { - qDebug("Failed to get samplerate\n"); - return; - } - sample_rate = g_variant_get_uint64(gvar); + switch(probe->type) { + case SR_CHANNEL_LOGIC: + if(probe->enabled) + logic_probe_count++; + break; - if (sample_rate != _last_sample_rate) { - _last_sample_rate = sample_rate; - sample_rate_changed(sample_rate); - } + case SR_CHANNEL_DSO: + dso_probe_count++; + break; - ret = sr_config_get(sdi->driver, SR_CONF_EN_CH0, - &gvar, sdi); - if (ret != SR_OK) { - qDebug("Failed to get ENABLE of DSO channel 0\n"); - return; - } - dso_channel_count += g_variant_get_boolean(gvar); - ret = sr_config_get(sdi->driver, SR_CONF_EN_CH1, - &gvar, sdi); - if (ret != SR_OK) { - qDebug("Failed to get ENABLE of DSO channel 1\n"); - return; + case SR_CHANNEL_ANALOG: + if(probe->enabled) + analog_probe_count++; + break; + } + } } - dso_channel_count += g_variant_get_boolean(gvar); // Create data containers for the coming data snapshots { if (logic_probe_count != 0) { - _logic_data.reset(new data::Logic( - logic_probe_count, sample_rate)); + _logic_data.reset(new data::Logic(logic_probe_count)); assert(_logic_data); - _group_data.reset(new data::Group(logic_probe_count, sample_rate)); + _group_data.reset(new data::Group()); assert(_group_data); _group_cnt = 0; } if (dso_probe_count != 0) { - _dso_data.reset(new data::Dso(dso_channel_count, sample_rate)); + _dso_data.reset(new data::Dso(dso_probe_count)); assert(_dso_data); } if (analog_probe_count != 0) { - _analog_data.reset(new data::Analog(analog_probe_count, sample_rate)); + _analog_data.reset(new data::Analog(analog_probe_count)); assert(_analog_data); } } @@ -814,240 +531,79 @@ void SigSession::init_signals(const sr_dev_inst *sdi) { _signals.clear(); - for (const GSList *l = sdi->probes; l; l = l->next) { - const sr_probe *const probe = - (const sr_probe *)l->data; + for (const GSList *l = _dev_inst->dev_inst()->channels; l; l = l->next) { + const sr_channel *const probe = + (const sr_channel *)l->data; assert(probe); - if (!probe->enabled) - continue; - + signal.reset(); switch(probe->type) { - case SR_PROBE_LOGIC: - signal = boost::shared_ptr( - new view::LogicSignal(probe->name, - _logic_data, probe->index, _signals.size())); + case SR_CHANNEL_LOGIC: + if (probe->enabled) + signal = boost::shared_ptr( + new view::LogicSignal(_dev_inst, _logic_data, probe)); break; - case SR_PROBE_DSO: - if (probe->index == 0) { - ret = sr_config_get(sdi->driver, SR_CONF_VDIV0, - &gvar, sdi); - if (ret != SR_OK) { - qDebug("Failed to get VDIV of channel 0\n"); - return; - } - vdiv = g_variant_get_uint64(gvar); - ret = sr_config_get(sdi->driver, SR_CONF_TIMEBASE, - &gvar, sdi); - if (ret != SR_OK) { - qDebug("Failed to get TIMEBASE\n"); - return; - } - timebase = g_variant_get_uint64(gvar); - ret = sr_config_get(sdi->driver, SR_CONF_COUPLING0, - &gvar, sdi); - if (ret != SR_OK) { - qDebug("Failed to get AC COUPLING of channel 0\n"); - return; - } - coupling = g_variant_get_boolean(gvar); - ret = sr_config_get(sdi->driver, SR_CONF_EN_CH0, - &gvar, sdi); - if (ret != SR_OK) { - qDebug("Failed to get ENABLE of channel 0\n"); - return; - } - active = g_variant_get_boolean(gvar); - } else if (probe->index == 1) { - ret = sr_config_get(sdi->driver, SR_CONF_VDIV1, - &gvar, sdi); - if (ret != SR_OK) { - qDebug("Failed to get VDIV of channel 1\n"); - return; - } - vdiv = g_variant_get_uint64(gvar); - ret = sr_config_get(sdi->driver, SR_CONF_TIMEBASE, - &gvar, sdi); - if (ret != SR_OK) { - qDebug("Failed to get TIMEBASE\n"); - return; - } - timebase = g_variant_get_uint64(gvar); - ret = sr_config_get(sdi->driver, SR_CONF_COUPLING1, - &gvar, sdi); - if (ret != SR_OK) { - qDebug("Failed to get AC COUPLING of channel 1\n"); - return; - } - coupling = g_variant_get_boolean(gvar); - ret = sr_config_get(sdi->driver, SR_CONF_EN_CH1, - &gvar, sdi); - if (ret != SR_OK) { - qDebug("Failed to get ENABLE of channel 1\n"); - return; - } - active = g_variant_get_boolean(gvar); - } - signal = boost::shared_ptr( - new view::DsoSignal(probe->name, - _dso_data, probe->index, _signals.size(), vdiv, timebase, coupling, active)); + case SR_CHANNEL_DSO: + signal = shared_ptr( + new view::DsoSignal(_dev_inst, _dso_data, probe)); break; - case SR_PROBE_ANALOG: - signal = boost::shared_ptr( - new view::AnalogSignal(probe->name, - _analog_data, probe->index, _signals.size())); + case SR_CHANNEL_ANALOG: + if (probe->enabled) + signal = shared_ptr( + new view::AnalogSignal(_dev_inst, _analog_data, probe)); break; } - - _signals.push_back(signal); + if(signal.get()) + _signals.push_back(signal); } signals_changed(); data_updated(); } - g_variant_unref(gvar); } -void SigSession::update_signals(const sr_dev_inst *sdi) +void SigSession::reload() { - boost::shared_ptr signal; - QMap probes_en_table; - QMap signals_en_table; - int index = 0; - GVariant *gvar; - uint64_t vdiv; - uint64_t timebase; - bool coupling; - bool active; - int ret; + assert(_dev_inst); - std::vector< boost::shared_ptr >::iterator i = _signals.begin(); - while (i != _signals.end()) { - if (((*i)->get_type() == view::Signal::DS_LOGIC || - (*i)->get_type() == view::Signal::DS_DSO || - (*i)->get_type() == view::Signal::DS_ANALOG)) - signals_en_table.insert((*i)->get_index(), 1); - i++; - } + if (_capture_state == Running) + stop_capture(); - index = 0; - for (const GSList *l = sdi->probes; l; l = l->next) { - const sr_probe *const probe = - (const sr_probe *)l->data; - assert(probe); - probes_en_table.insert(index, probe->enabled); - if (probe->enabled && !signals_en_table.contains(index)) { - i = _signals.begin(); - while (i != _signals.end()) { - (*i)->set_order((*i)->get_order() + 1); - i++; - } + boost::shared_ptr signal; + + // Make the logic probe list + { + _signals.clear(); + for (const GSList *l = _dev_inst->dev_inst()->channels; l; l = l->next) { + const sr_channel *const probe = + (const sr_channel *)l->data; + assert(probe); + signal.reset(); switch(probe->type) { - case SR_PROBE_LOGIC: - signal = boost::shared_ptr( - new view::LogicSignal(probe->name, - _logic_data, probe->index, 0)); + case SR_CHANNEL_LOGIC: + if (probe->enabled) + signal = boost::shared_ptr( + new view::LogicSignal(_dev_inst, _logic_data, probe)); break; - case SR_PROBE_DSO: - if (probe->index == 0) { - ret = sr_config_get(sdi->driver, SR_CONF_VDIV0, - &gvar, sdi); - if (ret != SR_OK) { - qDebug("Failed to get VDIV of channel 0\n"); - return; - } - vdiv = g_variant_get_uint64(gvar); - ret = sr_config_get(sdi->driver, SR_CONF_TIMEBASE, - &gvar, sdi); - if (ret != SR_OK) { - qDebug("Failed to get TIMEBASE\n"); - return; - } - timebase = g_variant_get_uint64(gvar); - ret = sr_config_get(sdi->driver, SR_CONF_COUPLING0, - &gvar, sdi); - if (ret != SR_OK) { - qDebug("Failed to get AC COUPLING of channel 0\n"); - return; - } - coupling = g_variant_get_boolean(gvar); - ret = sr_config_get(sdi->driver, SR_CONF_EN_CH0, - &gvar, sdi); - if (ret != SR_OK) { - qDebug("Failed to get ENABLE of channel 0\n"); - return; - } - active = g_variant_get_boolean(gvar); - } else if (probe->index == 1) { - ret = sr_config_get(sdi->driver, SR_CONF_VDIV1, - &gvar, sdi); - if (ret != SR_OK) { - qDebug("Failed to get VDIV of channel 1\n"); - return; - } - vdiv = g_variant_get_uint64(gvar); - ret = sr_config_get(sdi->driver, SR_CONF_TIMEBASE, - &gvar, sdi); - if (ret != SR_OK) { - qDebug("Failed to get TIMEBASE\n"); - return; - } - timebase = g_variant_get_uint64(gvar); - ret = sr_config_get(sdi->driver, SR_CONF_COUPLING1, - &gvar, sdi); - if (ret != SR_OK) { - qDebug("Failed to get AC COUPLING of channel 1\n"); - return; - } - coupling = g_variant_get_boolean(gvar); - ret = sr_config_get(sdi->driver, SR_CONF_EN_CH1, - &gvar, sdi); - if (ret != SR_OK) { - qDebug("Failed to get ENABLE of channel 1\n"); - return; - } - active = g_variant_get_boolean(gvar); - } - signal = boost::shared_ptr( - new view::DsoSignal(probe->name, - _dso_data, probe->index, _signals.size(), vdiv, timebase, coupling, active)); + case SR_CHANNEL_DSO: + signal = shared_ptr( + new view::DsoSignal(_dev_inst,_dso_data, probe)); break; - case SR_PROBE_ANALOG: - signal = boost::shared_ptr( - new view::AnalogSignal(probe->name, - _analog_data, probe->index, 0)); + case SR_CHANNEL_ANALOG: + if (probe->enabled) + signal = shared_ptr( + new view::AnalogSignal(_dev_inst, _analog_data, probe)); break; } - _signals.push_back(signal); + if (signal.get()) + _signals.push_back(signal); } - index++; - } - - i = _signals.begin(); - while (i != _signals.end()) { - if (((*i)->get_type() == view::Signal::DS_LOGIC || - (*i)->get_type() == view::Signal::DS_DSO || - (*i)->get_type() == view::Signal::DS_ANALOG) && - probes_en_table.value((*i)->get_index()) == false) { - std::vector< boost::shared_ptr >::iterator j = _signals.begin(); - while(j != _signals.end()) { - if ((*j)->get_order() > (*i)->get_order()) - (*j)->set_order((*j)->get_order() - 1); - j++; - } - - (*i).reset(); - i = _signals.erase(i); - continue; - } - i++; } signals_changed(); - data_updated(); } void SigSession::feed_in_meta(const sr_dev_inst *sdi, @@ -1092,11 +648,19 @@ void SigSession::feed_in_logic(const sr_datafeed_logic &logic) { // Create a new data snapshot _cur_logic_snapshot = boost::shared_ptr( - new data::LogicSnapshot(logic, _total_sample_len, 1)); + new data::LogicSnapshot(logic, _dev_inst->get_sample_limit(), 1)); if (_cur_logic_snapshot->buf_null()) + { stop_capture(); - else + } else { _logic_data->push_snapshot(_cur_logic_snapshot); + } + + // @todo Putting this here means that only listeners querying + // for logic will be notified. Currently the only user of + // frame_began is DecoderStack, but in future we need to signal + // this after both analog and logic sweeps have begun. + frame_began(); } else { @@ -1105,6 +669,7 @@ void SigSession::feed_in_logic(const sr_datafeed_logic &logic) } receive_data(logic.length/logic.unitsize); + data_received(); //data_updated(); } @@ -1122,7 +687,7 @@ void SigSession::feed_in_dso(const sr_datafeed_dso &dso) { // Create a new data snapshot _cur_dso_snapshot = boost::shared_ptr( - new data::DsoSnapshot(dso, _total_sample_len, _dso_data->get_num_probes())); + new data::DsoSnapshot(dso, _dev_inst->get_sample_limit(), _dso_data->get_num_probes())); if (_cur_dso_snapshot->buf_null()) stop_capture(); else @@ -1152,7 +717,7 @@ void SigSession::feed_in_analog(const sr_datafeed_analog &analog) { // Create a new data snapshot _cur_analog_snapshot = boost::shared_ptr( - new data::AnalogSnapshot(analog, _total_sample_len, _analog_data->get_num_probes())); + new data::AnalogSnapshot(analog, _dev_inst->get_sample_limit(), _analog_data->get_num_probes())); if (_cur_analog_snapshot->buf_null()) stop_capture(); else @@ -1209,24 +774,24 @@ void SigSession::data_feed_in(const struct sr_dev_inst *sdi, { { boost::lock_guard lock(_data_mutex); - BOOST_FOREACH(const boost::shared_ptr s, _signals) + BOOST_FOREACH(const boost::shared_ptr g, _group_traces) { - assert(s); - if (s->get_type() == view::Signal::DS_GROUP) { - _cur_group_snapshot = boost::shared_ptr( - new data::GroupSnapshot(_logic_data->get_snapshots().front(), s->get_index_list())); - //_cur_group_snapshot->append_payload(); - _group_data->push_snapshot(_cur_group_snapshot); - _cur_group_snapshot.reset(); - } - if (s->get_type() == view::Signal::DS_PROTOCOL) { - s->get_decoder()->decode(); - } + assert(g); + + _cur_group_snapshot = boost::shared_ptr( + new data::GroupSnapshot(_logic_data->get_snapshots().front(), g->get_index_list())); + _group_data->push_snapshot(_cur_group_snapshot); } - _cur_logic_snapshot.reset(); + _cur_logic_snapshot.reset(); _cur_dso_snapshot.reset(); _cur_analog_snapshot.reset(); } + for (vector< shared_ptr >::iterator i = + _decode_traces.begin(); + i != _decode_traces.end(); + i++) + (*i)->decoder()->stop_decode(); + frame_ended(); break; } } @@ -1240,81 +805,6 @@ void SigSession::data_feed_in_proc(const struct sr_dev_inst *sdi, _session->data_feed_in(sdi, packet); } -QVector > > SigSession::get_decoders() const -{ - return _decoders; -} - -void SigSession::add_protocol_analyzer(int decoder_index, std::list _sel_probes, - QMap & _options, QMap _options_index) -{ - decoder::Decoder *decoder; - - // new different docoder according to protocol_list in decoder.h - decoder = _decoderFactory->createDecoder(decoder_index, _logic_data, _sel_probes, _options, _options_index); - - // if current data is valid, do decode - if (_logic_data) - decoder->decode(); - _decoders.push_back(std::pair >(decoder, _sel_probes)); - -// // config signal's attribute for display -// BOOST_FOREACH(const int _index, _sel_probes) { -// _signals.at(_index)->set_decoder(decoder); -// } - - // add protocol decoder signal - add_protocol(_sel_probes, decoder); -} - -void SigSession::rst_protocol_analyzer(int rst_index, std::list _sel_probes, - QMap & _options, QMap _options_index) -{ - // if current data is valid, redo decode - if (_logic_data) - _decoders.at(rst_index).first->recode(_sel_probes, _options, _options_index); - - BOOST_FOREACH(const boost::shared_ptr s, _signals) - { - assert(s); - if (s->get_decoder() == _decoders.at(rst_index).first) { - s->set_index_list(s->get_decoder()->get_probes()); - break; - } - } - - // update protocol signal - signals_changed(); - data_updated(); -} - -void SigSession::del_protocol_analyzer(int protocol_index) -{ - assert(protocol_index < _decoders.size()); - //delete (_decoders.at(protocol_index)).first; - -// BOOST_FOREACH(const int _index, (_decoders.at(protocol_index)).second) { -// _signals.at(_index)->del_decoder(); -// } - del_protocol(protocol_index); - - _decoders.remove(protocol_index); -} - -std::list SigSession::get_decode_probes(int decode_index) -{ - assert(decode_index >= 0); - assert(decode_index < _decoders.size()); - return _decoders.at(decode_index).first->get_probes(); -} - -QMap SigSession::get_decode_options_index(int decode_index) -{ - assert(decode_index >= 0); - assert(decode_index < _decoders.size()); - return _decoders.at(decode_index).first->get_options_index(); -} - /* * hotplug function */ @@ -1344,7 +834,7 @@ void SigSession::hotplug_proc(boost::function error_handle (void)error_handler; - if (!_sdi) + if (!_dev_inst) return; tv.tv_sec = tv.tv_usec = 0; @@ -1355,7 +845,6 @@ void SigSession::hotplug_proc(boost::function error_handle qDebug("DSLogic hardware attached!"); device_attach(); _hot_attach = false; - break; } if (_hot_detach) { qDebug("DSLogic hardware detached!"); @@ -1364,7 +853,6 @@ void SigSession::hotplug_proc(boost::function error_handle _dso_data.reset(); _analog_data.reset(); _hot_detach = false; - break; } boost::this_thread::sleep(boost::posix_time::millisec(100)); } @@ -1424,91 +912,148 @@ void SigSession::set_adv_trigger(bool adv_trigger) } -/* - * oscilloscope control - */ -void SigSession::start_dso_ctrl_proc(boost::function error_handler) +uint16_t SigSession::get_dso_ch_num() { - - // Begin the dso control thread - _dso_ctrl_thread.reset(new boost::thread( - &SigSession::dso_ctrl_proc, this, error_handler)); - + uint16_t num_channels = 0; + BOOST_FOREACH(const shared_ptr s, _signals) + { + assert(s); + //if (dynamic_pointer_cast(s) && s->enabled()) { + if (dynamic_pointer_cast(s)) { + num_channels++; + } + } + return num_channels; } -void SigSession::stop_dso_ctrl_proc() +#ifdef ENABLE_DECODE +bool SigSession::add_decoder(srd_decoder *const dec) { + bool ret = false; + map > probes; + shared_ptr decoder_stack; - if (_dso_ctrl_thread.get()) { - _dso_ctrl_thread->interrupt(); - _dso_ctrl_thread->join(); + try + { + //lock_guard lock(_signals_mutex); + + // Create the decoder + decoder_stack = shared_ptr( + new data::DecoderStack(*this, dec)); + + // Make a list of all the probes + std::vector all_probes; + for(const GSList *i = dec->channels; i; i = i->next) + all_probes.push_back((const srd_channel*)i->data); + for(const GSList *i = dec->opt_channels; i; i = i->next) + all_probes.push_back((const srd_channel*)i->data); + + assert(decoder_stack); + assert(!decoder_stack->stack().empty()); + assert(decoder_stack->stack().front()); + decoder_stack->stack().front()->set_probes(probes); + + // Create the decode signal + shared_ptr d( + new view::DecodeTrace(*this, decoder_stack, + _decode_traces.size())); + if (d->create_popup()) { + _decode_traces.push_back(d); + ret = true; + } + } + catch(std::runtime_error e) + { + return false; } - _dso_ctrl_thread.reset(); -} - -int SigSession::set_dso_ctrl(int key) -{ - int ret; - if (key== SR_CONF_TIMEBASE) { - uint64_t timebase = _signals.at(0)->get_hDialValue(); - ret = sr_config_set(_sdi, key, g_variant_new_uint64(timebase)); - } else if (key == SR_CONF_VDIV0) { - uint64_t vdiv = _signals.at(0)->get_vDialValue(); - ret = sr_config_set(_sdi, key, g_variant_new_uint64(vdiv)); - } else if (key == SR_CONF_VDIV1) { - uint64_t vdiv = _signals.at(1)->get_vDialValue(); - ret = sr_config_set(_sdi, key, g_variant_new_uint64(vdiv)); - } else if (key == SR_CONF_COUPLING0) { - bool acdc = _signals.at(0)->get_acCoupling(); - ret = sr_config_set(_sdi, key, g_variant_new_boolean(acdc)); - } else if (key == SR_CONF_COUPLING1) { - bool acdc = _signals.at(1)->get_acCoupling(); - ret = sr_config_set(_sdi, key, g_variant_new_boolean(acdc)); - } else if (key == SR_CONF_EN_CH0) { - bool enable = _signals.at(0)->get_active(); - ret = sr_config_set(_sdi, key, g_variant_new_boolean(enable)); - dso_ch_changed(get_dso_ch_num()); - } else if (key == SR_CONF_EN_CH1) { - bool enable = _signals.at(1)->get_active(); - ret = sr_config_set(_sdi, key, g_variant_new_boolean(enable)); - dso_ch_changed(get_dso_ch_num()); + if (ret) { + signals_changed(); + // Do an initial decode + decoder_stack->begin_decode(); + data_updated(); } return ret; } -uint16_t SigSession::get_dso_ch_num() +vector< shared_ptr > SigSession::get_decode_signals() const { - uint16_t num_channels = 0; - BOOST_FOREACH(const shared_ptr s, _signals) + lock_guard lock(_signals_mutex); + return _decode_traces; +} + +void SigSession::remove_decode_signal(view::DecodeTrace *signal) +{ + for (vector< shared_ptr >::iterator i = + _decode_traces.begin(); + i != _decode_traces.end(); + i++) + if ((*i).get() == signal) + { + _decode_traces.erase(i); + signals_changed(); + return; + } +} + +void SigSession::remove_decode_signal(int index) +{ + int cur_index = 0; + for (vector< shared_ptr >::iterator i = + _decode_traces.begin(); + i != _decode_traces.end(); + i++) { - assert(s); - if (s->get_active()) { - num_channels++; + if (cur_index == index) + { + _decode_traces.erase(i); + signals_changed(); + return; } + cur_index++; } - return num_channels; } -void SigSession::dso_ctrl_proc(boost::function error_handler) +void SigSession::rst_decoder(int index) { - (void)error_handler; - try { - while(_session) { - if (!_sdi) { - // do nothing - } else if (strcmp(_sdi->driver->name, "Demo") == 0) { - - } else if (strcmp(_sdi->driver->name, "DSLogic") == 0) { - + int cur_index = 0; + for (vector< shared_ptr >::iterator i = + _decode_traces.begin(); + i != _decode_traces.end(); + i++) + { + if (cur_index == index) + { + if ((*i)->create_popup()) + { + (*i)->decoder()->stop_decode(); + (*i)->decoder()->begin_decode(); + data_updated(); } - boost::this_thread::sleep(boost::posix_time::millisec(100)); + return; } - } catch(...) { - qDebug("Interrupt exception for oscilloscope control thread was thrown."); + cur_index++; } - qDebug("Oscilloscope control thread exit!"); } +void SigSession::rst_decoder(view::DecodeTrace *signal) +{ + for (vector< shared_ptr >::iterator i = + _decode_traces.begin(); + i != _decode_traces.end(); + i++) + if ((*i).get() == signal) + { + if ((*i)->create_popup()) + { + (*i)->decoder()->stop_decode(); + (*i)->decoder()->begin_decode(); + data_updated(); + } + return; + } +} +#endif + } // namespace pv diff --git a/DSLogic-gui/pv/sigsession.h b/DSLogic-gui/pv/sigsession.h index a8d8cfb..28fd79f 100644 --- a/DSLogic-gui/pv/sigsession.h +++ b/DSLogic-gui/pv/sigsession.h @@ -31,6 +31,9 @@ #include #include +#include +#include +#include #include #include @@ -43,11 +46,15 @@ #include #include +struct srd_decoder; +struct srd_channel; + namespace pv { class DeviceManager; namespace data { +class SignalData; class Analog; class AnalogSnapshot; class Dso; @@ -58,8 +65,14 @@ class Group; class GroupSnapshot; } +namespace device { +class DevInst; +} + namespace view { class Signal; +class GroupSignal; +class DecodeTrace; } namespace decoder { @@ -86,69 +99,61 @@ public: ~SigSession(); - struct sr_dev_inst* get_device() const; + boost::shared_ptr get_device() const; /** * Sets device instance that will be used in the next capture session. */ - int set_device(struct sr_dev_inst *sdi); + void set_device(boost::shared_ptr dev_inst) + throw(QString); - void release_device(struct sr_dev_inst *sdi); - - void load_file(const std::string &name, - boost::function error_handler); + void set_file(const std::string &name) + throw(QString); void save_file(const std::string &name); + void set_default_device(); + + void release_device(device::DevInst *dev_inst); + capture_state get_capture_state() const; - void start_capture(uint64_t record_length, + void start_capture(bool instant, boost::function error_handler); void stop_capture(); + std::set< boost::shared_ptr > get_data() const; + std::vector< boost::shared_ptr > get_signals(); - std::vector< boost::shared_ptr > - get_pro_signals(); - - int get_logic_probe_cnt(const struct sr_dev_inst *sdi); - int get_dso_probe_cnt(const struct sr_dev_inst *sdi); - int get_analog_probe_cnt(const struct sr_dev_inst *sdi); - - void init_signals(const struct sr_dev_inst *sdi); - - void update_signals(const struct sr_dev_inst *sdi); - void add_group(); + std::vector< boost::shared_ptr > + get_group_signals(); - void del_group(); +#ifdef ENABLE_DECODE + bool add_decoder(srd_decoder *const dec); - void add_protocol(std::list probe_index_list, decoder::Decoder *decoder); + std::vector< boost::shared_ptr > + get_decode_signals() const; - void del_protocol(int protocol_index); + void remove_decode_signal(view::DecodeTrace *signal); - void del_signal(std::vector< boost::shared_ptr >::iterator i); + void remove_decode_signal(int index); - boost::shared_ptr get_data(); + void rst_decoder(int index); - void* get_buf(int& unit_size, uint64_t& length); + void rst_decoder(view::DecodeTrace *signal); - quint64 get_last_sample_rate() const; +#endif - quint64 get_total_sample_len() const; - void set_total_sample_len(quint64 length); + void init_signals(); - QVector > > get_decoders() const; + void add_group(); - void add_protocol_analyzer(int decoder_index, std::list _sel_probes, - QMap &_options, QMap _options_index); - void rst_protocol_analyzer(int rst_index, std::list _sel_probes, - QMap &_options, QMap _options_index); - void del_protocol_analyzer(int protocol_index); + void del_group(); - std::list get_decode_probes(int decode_index); - QMap get_decode_options_index(int decode_index); + void* get_buf(int& unit_size, uint64_t& length); void start_hotplug_proc(boost::function error_handler); void stop_hotplug_proc(); @@ -157,21 +162,33 @@ public: void set_adv_trigger(bool adv_trigger); - void start_dso_ctrl_proc(boost::function error_handler); - void stop_dso_ctrl_proc(); - int set_dso_ctrl(int key); uint16_t get_dso_ch_num(); + + void set_sample_rate(uint64_t sample_rate); private: void set_capture_state(capture_state state); + void read_sample_rate(const sr_dev_inst *const sdi); + private: - // thread for sample/load - void load_thread_proc(const std::string name, - boost::function error_handler); - void sample_thread_proc(struct sr_dev_inst *sdi, - uint64_t record_length, - boost::function error_handler); + /** + * Attempts to autodetect the format. Failing that + * @param filename The filename of the input file. + * @return A pointer to the 'struct sr_input_format' that should be + * used, or NULL if no input format was selected or + * auto-detected. + */ + static sr_input_format* determine_input_file_format( + const std::string &filename); + + static sr_input* load_input_file_format( + const std::string &filename, + boost::function error_handler, + sr_input_format *format = NULL); + + void sample_thread_proc(boost::shared_ptr dev_inst, + boost::function error_handler); // data feed void feed_in_header(const sr_dev_inst *sdi); @@ -186,11 +203,9 @@ private: static void data_feed_in_proc(const struct sr_dev_inst *sdi, const struct sr_datafeed_packet *packet, void *cb_data); - void hotplug_proc(boost::function error_handler); - static int hotplug_callback(struct libusb_context *ctx, struct libusb_device *dev, - libusb_hotplug_event event, void *user_data); - - void dso_ctrl_proc(boost::function error_handler); + void hotplug_proc(boost::function error_handler); + static int hotplug_callback(struct libusb_context *ctx, struct libusb_device *dev, + libusb_hotplug_event event, void *user_data); private: DeviceManager &_device_manager; @@ -198,19 +213,18 @@ private: /** * The device instance that will be used in the next capture session. */ - struct sr_dev_inst *_sdi; + boost::shared_ptr _dev_inst; mutable boost::mutex _sampling_mutex; capture_state _capture_state; + bool _instant; mutable boost::mutex _signals_mutex; std::vector< boost::shared_ptr > _signals; + std::vector< boost::shared_ptr > _group_traces; + std::vector< boost::shared_ptr > _decode_traces; - decoder::DecoderFactory *_decoderFactory; - QVector< std::pair > > _decoders; - std::vector< boost::shared_ptr > _protocol_signals; - - mutable boost::mutex _data_mutex; + mutable boost::mutex _data_mutex; boost::shared_ptr _logic_data; boost::shared_ptr _cur_logic_snapshot; boost::shared_ptr _dso_data; @@ -220,14 +234,9 @@ private: boost::shared_ptr _group_data; boost::shared_ptr _cur_group_snapshot; int _group_cnt; - int _protocol_cnt; std::auto_ptr _sampling_thread; - quint64 _last_sample_rate; - - quint64 _total_sample_len; - libusb_hotplug_callback_handle _hotplug_handle; std::auto_ptr _hotplug; bool _hot_attach; @@ -235,11 +244,6 @@ private: bool _adv_trigger; - bool _vDial_changed; - bool _hDial_changed; - uint16_t _dso_ctrl_channel; - std::auto_ptr _dso_ctrl_thread; - signals: void capture_state_changed(int state); @@ -247,7 +251,7 @@ signals: void data_updated(); - void sample_rate_changed(quint64 sample_rate); + void sample_rate_changed(uint64_t sample_rate); void receive_data(quint64 length); @@ -260,8 +264,16 @@ signals: void dso_ch_changed(uint16_t num); -public slots: + void frame_began(); + + void data_received(); + void frame_ended(); + + void device_setted(); + +public slots: + void reload(); private: // TODO: This should not be necessary. Multiple concurrent diff --git a/DSLogic-gui/pv/storesession.cpp b/DSLogic-gui/pv/storesession.cpp new file mode 100644 index 0000000..2270ae5 --- /dev/null +++ b/DSLogic-gui/pv/storesession.cpp @@ -0,0 +1,195 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2014 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "storesession.h" + +#include +#include +#include +#include + +using boost::dynamic_pointer_cast; +using boost::mutex; +using boost::shared_ptr; +using boost::thread; +using boost::lock_guard; +using std::deque; +using std::make_pair; +using std::min; +using std::pair; +using std::set; +using std::string; +using std::vector; + +namespace pv { + +const size_t StoreSession::BlockSize = 1024 * 1024; + +StoreSession::StoreSession(const std::string &file_name, + SigSession &session) : + _file_name(file_name), + _session(session), + _units_stored(0), + _unit_count(0) +{ +} + +StoreSession::~StoreSession() +{ + wait(); +} + +pair StoreSession::progress() const +{ + lock_guard lock(_mutex); + return make_pair(_units_stored, _unit_count); +} + +const QString& StoreSession::error() const +{ + lock_guard lock(_mutex); + return _error; +} + +bool StoreSession::start() +{ + set< shared_ptr > data_set = + _session.get_data(); + const vector< shared_ptr > sigs(_session.get_signals()); + + // Check we have logic data + if (data_set.empty() || sigs.empty()) { + _error = tr("No data to save."); + return false; + } + + if (data_set.size() > 1) { + _error = tr("DSLogic currently only has support for " + "storing a single data stream."); + return false; + } + + // Get the logic data + //shared_ptr data; + if (!(data = dynamic_pointer_cast(*data_set.begin()))) { + _error = tr("DSLogic currently only has support for " + "storing a logic data."); + return false; + } + + // Get the snapshot + const deque< shared_ptr > &snapshots = + data->get_snapshots(); + + if (snapshots.empty()) { + _error = tr("No snapshots to save."); + return false; + } + + const shared_ptr snapshot(snapshots.front()); + assert(snapshot); + + // Make a list of probes + char **const probes = new char*[sigs.size() + 1]; + for (size_t i = 0; i < sigs.size(); i++) { + shared_ptr sig(sigs[i]); + assert(sig); + probes[i] = strdup(sig->get_name().toUtf8().constData()); + } + probes[sigs.size()] = NULL; + + // Begin storing + if (sr_session_save_init(_file_name.c_str(), + data->samplerate(), probes) != SR_OK) { + _error = tr("Error while saving."); + return false; + } + + // Delete the probes array + for (size_t i = 0; i <= sigs.size(); i++) + free(probes[i]); + delete[] probes; + + _thread = boost::thread(&StoreSession::store_proc, this, snapshot); + return true; +} + +void StoreSession::wait() +{ + if (_thread.joinable()) + _thread.join(); +} + +void StoreSession::cancel() +{ + _thread.interrupt(); +} + +void StoreSession::store_proc(shared_ptr snapshot) +{ + assert(snapshot); + + uint64_t start_sample = 0; + + /// TODO: Wrap this in a std::unique_ptr when we transition to C++11 + uint8_t *const data = new uint8_t[BlockSize]; + assert(data); + + const int unit_size = snapshot->unit_size(); + assert(unit_size != 0); + + { + lock_guard lock(_mutex); + _unit_count = snapshot->get_sample_count(); + } + + const unsigned int samples_per_block = BlockSize / unit_size; + + while (!boost::this_thread::interruption_requested() && + start_sample < _unit_count) + { + progress_updated(); + + const uint64_t end_sample = min( + start_sample + samples_per_block, _unit_count); + snapshot->get_samples(data, start_sample, end_sample); + + if(sr_session_append(_file_name.c_str(), data, unit_size, + end_sample - start_sample) != SR_OK) + { + _error = tr("Error while saving."); + break; + } + + start_sample = end_sample; + + { + lock_guard lock(_mutex); + _units_stored = start_sample; + } + } + + progress_updated(); + + delete[] data; +} + +} // pv diff --git a/DSLogic-gui/pv/storesession.h b/DSLogic-gui/pv/storesession.h new file mode 100644 index 0000000..c893760 --- /dev/null +++ b/DSLogic-gui/pv/storesession.h @@ -0,0 +1,83 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2014 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DSLOGIC_PV_STORESESSION_H +#define DSLOGIC_PV_STORESESSION_H + +#include + +#include + +#include + +#include + +namespace pv { + +class SigSession; + +namespace data { +class LogicSnapshot; +} + +class StoreSession : public QObject +{ + Q_OBJECT + +private: + static const size_t BlockSize; + +public: + StoreSession(const std::string &file_name, + SigSession &session); + + ~StoreSession(); + + std::pair progress() const; + + const QString& error() const; + + bool start(); + + void wait(); + + void cancel(); + +private: + void store_proc(boost::shared_ptr snapshot); + +signals: + void progress_updated(); + +private: + const std::string _file_name; + SigSession &_session; + + boost::thread _thread; + + mutable boost::mutex _mutex; + uint64_t _units_stored; + uint64_t _unit_count; + QString _error; +}; + +} // pv + +#endif // DSLOGIC_PV_STORESESSION_H diff --git a/DSLogic-gui/pv/toolbars/filebar.cpp b/DSLogic-gui/pv/toolbars/filebar.cpp index 1124223..80ff5b7 100644 --- a/DSLogic-gui/pv/toolbars/filebar.cpp +++ b/DSLogic-gui/pv/toolbars/filebar.cpp @@ -31,6 +31,7 @@ #include #include "filebar.h" +#include "../device/devinst.h" #include @@ -80,23 +81,14 @@ FileBar::FileBar(SigSession &session, QWidget *parent) : void FileBar::on_actionOpen_triggered() { + // Show the dialog const QString file_name = QFileDialog::getOpenFileName( - this, tr("Open File"), "", - tr("DSLogic Sessions (*.dsl)")); + this, tr("Open File"), "", tr( + "DSLogic Sessions (*.dsl)")); if (!file_name.isEmpty()) load_file(file_name); } -void FileBar::load_file(QString file_name) -{ - const QString errorMessage( - QString("Failed to load file %1").arg(file_name)); - const QString infoMessage; - _session.load_file(file_name.toStdString(), - boost::bind(&FileBar::session_error, this, - errorMessage, infoMessage)); -} - void FileBar::session_error( const QString text, const QString info_text) { @@ -118,23 +110,31 @@ void FileBar::show_session_error( void FileBar::on_actionSave_triggered() { + //save(); int unit_size; uint64_t length; void* buf = _session.get_buf(unit_size, length); - if (buf != NULL) { - const QString file_name = QFileDialog::getSaveFileName( - this, tr("Save File"), "", - tr("DSLogic Session (*.dsl)")); - if (!file_name.isEmpty()) { - _session.save_file(file_name.toStdString()); - } - } else { + if (!buf) { QMessageBox msg(this); msg.setText("File Save"); msg.setInformativeText("No Data to Save!"); msg.setStandardButtons(QMessageBox::Ok); msg.setIcon(QMessageBox::Warning); msg.exec(); + } else if (_session.get_device()->dev_inst()->mode != LOGIC) { + QMessageBox msg(this); + msg.setText("File Save"); + msg.setInformativeText("DSLogic currently only support saving logic data to file!"); + msg.setStandardButtons(QMessageBox::Ok); + msg.setIcon(QMessageBox::Warning); + msg.exec(); + }else { + const QString file_name = QFileDialog::getSaveFileName( + this, tr("Save File"), "", + tr("DSLogic Session (*.dsl)")); + if (!file_name.isEmpty()) { + _session.save_file(file_name.toStdString()); + } } } @@ -146,6 +146,8 @@ void FileBar::on_actionCapture_triggered() void FileBar::enable_toggle(bool enable) { _file_button.setDisabled(!enable); + _file_button.setIcon(enable ? QIcon(":/icons/file.png") : + QIcon(":/icons/file_dis.png")); } } // namespace toolbars diff --git a/DSLogic-gui/pv/toolbars/filebar.h b/DSLogic-gui/pv/toolbars/filebar.h index e0760f8..67567b3 100644 --- a/DSLogic-gui/pv/toolbars/filebar.h +++ b/DSLogic-gui/pv/toolbars/filebar.h @@ -43,13 +43,15 @@ public: void enable_toggle(bool enable); private: - void load_file(QString file_name); + void session_error( const QString text, const QString info_text); void show_session_error( const QString text, const QString info_text); signals: + void load_file(QString); + void save(); void on_screenShot(); private slots: diff --git a/DSLogic-gui/pv/toolbars/logobar.cpp b/DSLogic-gui/pv/toolbars/logobar.cpp index 9aa17f8..9954e50 100644 --- a/DSLogic-gui/pv/toolbars/logobar.cpp +++ b/DSLogic-gui/pv/toolbars/logobar.cpp @@ -28,6 +28,8 @@ #include #include #include +#include +#include #include "logobar.h" #include "../dialogs/about.h" @@ -52,6 +54,15 @@ LogoBar::LogoBar(SigSession &session, QWidget *parent) : _logo_button.addAction(_about); connect(_about, SIGNAL(triggered()), this, SLOT(on_actionAbout_triggered())); + _wiki = new QAction(this); + _wiki->setText(QApplication::translate( + "File", "&Wiki", 0, QApplication::UnicodeUTF8)); + _wiki->setIcon(QIcon::fromTheme("file", + QIcon(":/icons/wiki.png"))); + _wiki->setObjectName(QString::fromUtf8("actionWiki")); + _logo_button.addAction(_wiki); + connect(_wiki, SIGNAL(triggered()), this, SLOT(on_actionWiki_triggered())); + _logo_button.setPopupMode(QToolButton::InstantPopup); _logo_button.setIcon(QIcon(":/icons/logo_noColor.png")); @@ -97,6 +108,12 @@ void LogoBar::on_actionAbout_triggered() dlg.exec(); } +void LogoBar::on_actionWiki_triggered() +{ + QDesktopServices::openUrl( + QUrl(QLatin1String("http://www.dreamsourcelab.com/wiki"))); +} + void LogoBar::enable_toggle(bool enable) { _logo_button.setDisabled(!enable); diff --git a/DSLogic-gui/pv/toolbars/logobar.h b/DSLogic-gui/pv/toolbars/logobar.h index df5508a..0613271 100644 --- a/DSLogic-gui/pv/toolbars/logobar.h +++ b/DSLogic-gui/pv/toolbars/logobar.h @@ -45,7 +45,6 @@ public: void dslogic_connected(bool conn); private: - void load_file(QString file_name); void session_error( const QString text, const QString info_text); void show_session_error( @@ -55,6 +54,7 @@ signals: private slots: void on_actionAbout_triggered(); + void on_actionWiki_triggered(); private: bool _enable; @@ -63,6 +63,7 @@ private: QToolButton _logo_button; QAction *_about; + QAction *_wiki; }; diff --git a/DSLogic-gui/pv/toolbars/samplingbar.cpp b/DSLogic-gui/pv/toolbars/samplingbar.cpp index 0283939..dd29b0d 100644 --- a/DSLogic-gui/pv/toolbars/samplingbar.cpp +++ b/DSLogic-gui/pv/toolbars/samplingbar.cpp @@ -35,7 +35,15 @@ #include "samplingbar.h" -using namespace std; +#include "../devicemanager.h" +#include "../device/devinst.h" +#include "../dialogs/deviceoptions.h" + +using boost::shared_ptr; +using std::map; +using std::max; +using std::min; +using std::string; namespace pv { namespace toolbars { @@ -84,93 +92,153 @@ const uint64_t SamplingBar::DSLogic_RecordLengths[15] = { const uint64_t SamplingBar::DSLogic_DefaultRecordLength = 16777216; -SamplingBar::SamplingBar(QWidget *parent) : +SamplingBar::SamplingBar(SigSession &session, QWidget *parent) : QToolBar("Sampling Bar", parent), - _record_length_selector(this), - _sample_rate_list(this), + _session(session), + _enable(true), + _device_selector(this), + _updating_device_selector(false), + _configure_button(this), + _sample_count(this), + _sample_rate(this), + _updating_sample_rate(false), + _updating_sample_count(false), _icon_stop(":/icons/stop.png"), _icon_start(":/icons/start.png"), - _run_stop_button(this) + _icon_instant(":/icons/instant.png"), + _run_stop_button(this), + _instant_button(this), + _instant(false) { setMovable(false); + connect(&_device_selector, SIGNAL(currentIndexChanged (int)), + this, SLOT(on_device_selected())); + connect(&_configure_button, SIGNAL(clicked()), + this, SLOT(on_configure())); connect(&_run_stop_button, SIGNAL(clicked()), this, SLOT(on_run_stop())); + connect(&_instant_button, SIGNAL(clicked()), + this, SLOT(on_instant_stop())); + + _configure_button.setIcon(QIcon::fromTheme("configure", + QIcon(":/icons/params.png"))); + _run_stop_button.setIcon(_icon_start); + _instant_button.setIcon(_icon_instant); // for (size_t i = 0; i < countof(RecordLengths); i++) // { // const uint64_t &l = RecordLengths[i]; // char *const text = ds_si_string_u64(l, " samples"); -// _record_length_selector.addItem(QString(text), +// _sample_count.addItem(QString(text), // qVariantFromValue(l)); // g_free(text); // if (l == DefaultRecordLength) -// _record_length_selector.setCurrentIndex(i); +// _sample_count.setCurrentIndex(i); // } - _record_length_selector.setSizeAdjustPolicy(QComboBox::AdjustToContents); + _sample_count.setSizeAdjustPolicy(QComboBox::AdjustToContents); set_sampling(false); //_run_stop_button.setToolButtonStyle(Qt::ToolButtonTextBesideIcon); _run_stop_button.setObjectName(tr("run_stop_button")); + connect(&_sample_rate, SIGNAL(currentIndexChanged(int)), + this, SLOT(on_samplerate_sel(int))); + addWidget(new QLabel(tr(" "))); - addWidget(&_record_length_selector); + addWidget(&_device_selector); + addWidget(&_configure_button); + addWidget(&_sample_count); addWidget(new QLabel(tr(" @ "))); - _sample_rate_list_action = addWidget(&_sample_rate_list); + addWidget(&_sample_rate); addWidget(&_run_stop_button); - - connect(&_sample_rate_list, SIGNAL(currentIndexChanged(int)), - this, SLOT(on_sample_rate_changed())); + addWidget(&_instant_button); } -void SamplingBar::set_device(struct sr_dev_inst *sdi) +void SamplingBar::set_device_list( + const std::list< shared_ptr > &devices, + shared_ptr selected) { - assert(sdi); - _sdi = sdi; - if (strcmp(sdi->driver->name, "DSLogic") == 0) { - _record_length_selector.clear(); - for (size_t i = 0; i < countof(DSLogic_RecordLengths); i++) - { - const uint64_t &l = DSLogic_RecordLengths[i]; - char *const text = sr_iec_string_u64(l, " samples"); - _record_length_selector.addItem(QString(text), - qVariantFromValue(l)); - g_free(text); - - if (l == DSLogic_DefaultRecordLength) - _record_length_selector.setCurrentIndex(i); - } - } else { - for (size_t i = 0; i < countof(RecordLengths); i++) - { - const uint64_t &l = RecordLengths[i]; - char *const text = sr_si_string_u64(l, " samples"); - _record_length_selector.addItem(QString(text), - qVariantFromValue(l)); - g_free(text); - - if (l == DefaultRecordLength) - _record_length_selector.setCurrentIndex(i); - } + int selected_index = -1; + + assert(selected); + + _updating_device_selector = true; + + _device_selector.clear(); + _device_selector_map.clear(); + + BOOST_FOREACH (shared_ptr dev_inst, devices) { + assert(dev_inst); + const string title = dev_inst->format_device_title(); + const void *id = dev_inst->get_id(); + assert(id); + + if (selected == dev_inst) + selected_index = _device_selector.count(); + + _device_selector_map[id] = dev_inst; + _device_selector.addItem(title.c_str(), + qVariantFromValue((void*)id)); } + + // The selected device should have been in the list + assert(selected_index != -1); + _device_selector.setCurrentIndex(selected_index); + + update_sample_rate_selector(); + update_sample_count_selector(); + update_scale(); + + _updating_device_selector = false; +} + +shared_ptr SamplingBar::get_selected_device() const +{ + const int index = _device_selector.currentIndex(); + if (index < 0) + return shared_ptr(); + + const void *const id = + _device_selector.itemData(index).value(); + assert(id); + + map >:: + const_iterator iter = _device_selector_map.find(id); + if (iter == _device_selector_map.end()) + return shared_ptr(); + + return shared_ptr((*iter).second); +} + +void SamplingBar::on_configure() +{ + int ret; + shared_ptr dev_inst = get_selected_device(); + assert(dev_inst); + + pv::dialogs::DeviceOptions dlg(this, dev_inst->dev_inst()); + ret = dlg.exec(); + if (ret == QDialog::Accepted) + device_updated(); } uint64_t SamplingBar::get_record_length() const { - const int index = _record_length_selector.currentIndex(); + const int index = _sample_count.currentIndex(); if (index < 0) return 0; - return _record_length_selector.itemData(index).value(); + return _sample_count.itemData(index).value(); } void SamplingBar::set_record_length(uint64_t length) { - for (int i = 0; i < _record_length_selector.count(); i++) { - if (length == _record_length_selector.itemData( + for (int i = 0; i < _sample_count.count(); i++) { + if (length == _sample_count.itemData( i).value()) { - _record_length_selector.setCurrentIndex(i); + _sample_count.setCurrentIndex(i); break; } } @@ -178,26 +246,43 @@ void SamplingBar::set_record_length(uint64_t length) void SamplingBar::set_sampling(bool sampling) { - _run_stop_button.setIcon(sampling ? _icon_stop : _icon_start); - //_run_stop_button.setText(sampling ? " Stop" : "Start"); + if (_instant) + _instant_button.setIcon(sampling ? _icon_stop : _icon_instant); + else + _run_stop_button.setIcon(sampling ? _icon_stop : _icon_start); + + if (!sampling) { + _run_stop_button.setEnabled(true); + _instant_button.setEnabled(true); + } } void SamplingBar::set_sample_rate(uint64_t sample_rate) { - for (int i = 0; i < _sample_rate_list.count(); i++) { - if (sample_rate == _sample_rate_list.itemData( - i).value()) { - _sample_rate_list.setCurrentIndex(i); - // Set the samplerate - if (sr_config_set(_sdi, SR_CONF_SAMPLERATE, - g_variant_new_uint64(sample_rate)) != SR_OK) { - qDebug() << "Failed to configure samplerate."; - return; - } + for (int i = _sample_rate.count() - 1; i >= 0; i--) { + uint64_t cur_index_sample_rate = _sample_rate.itemData( + i).value(); + if (sample_rate >= cur_index_sample_rate) { + _sample_rate.setCurrentIndex(i); + // commit the samplerate + commit_sample_rate(); break; } } +} +void SamplingBar::set_sample_limit(uint64_t sample_limit) +{ + for (int i = 0; i < _sample_count.count(); i++) { + uint64_t cur_index_sample_limit = _sample_count.itemData( + i).value(); + if (sample_limit <= cur_index_sample_limit) { + _sample_count.setCurrentIndex(i); + // commit the samplecount + commit_sample_count(); + break; + } + } } void SamplingBar::update_sample_rate_selector() @@ -205,134 +290,266 @@ void SamplingBar::update_sample_rate_selector() GVariant *gvar_dict, *gvar_list; const uint64_t *elements = NULL; gsize num_elements; - //QAction *selector_action = NULL; - assert(_sample_rate_list_action); + if (_updating_sample_rate) + return; - if (!_sdi) - return; + const shared_ptr dev_inst = get_selected_device(); + if (!dev_inst) + return; - if (sr_config_list(_sdi->driver, SR_CONF_SAMPLERATE, - &gvar_dict, _sdi) != SR_OK) - return; + assert(!_updating_sample_rate); + _updating_sample_rate = true; - _sample_rate_list_action->setVisible(false); + if (!(gvar_dict = dev_inst->list_config(NULL, SR_CONF_SAMPLERATE))) + { + _sample_rate.clear(); + _sample_rate.show(); + _updating_sample_rate = false; + return; + } if ((gvar_list = g_variant_lookup_value(gvar_dict, "samplerates", G_VARIANT_TYPE("at")))) { elements = (const uint64_t *)g_variant_get_fixed_array( gvar_list, &num_elements, sizeof(uint64_t)); - _sample_rate_list.clear(); + _sample_rate.clear(); for (unsigned int i = 0; i < num_elements; i++) { char *const s = sr_samplerate_string(elements[i]); - _sample_rate_list.addItem(QString(s), + _sample_rate.addItem(QString(s), qVariantFromValue(elements[i])); g_free(s); } - _sample_rate_list.show(); + _sample_rate.show(); g_variant_unref(gvar_list); - - //selector_action = _sample_rate_list_action; } + _updating_sample_rate = false; g_variant_unref(gvar_dict); - _sample_rate_list_action->setVisible(true); update_sample_rate_selector_value(); } void SamplingBar::update_sample_rate_selector_value() { - GVariant *gvar; - uint64_t samplerate; + if (_updating_sample_rate) + return; - assert(_sdi); + const uint64_t samplerate = get_selected_device()->get_sample_rate(); - if (sr_config_get(_sdi->driver, SR_CONF_SAMPLERATE, - &gvar, _sdi) != SR_OK) { - qDebug() << - "WARNING: Failed to get value of sample rate"; - return; - } - samplerate = g_variant_get_uint64(gvar); - g_variant_unref(gvar); + assert(!_updating_sample_rate); + _updating_sample_rate = true; - assert(_sample_rate_list_action); + for (int i = 0; i < _sample_rate.count(); i++) + if (samplerate == _sample_rate.itemData( + i).value()) + _sample_rate.setCurrentIndex(i); - if (_sample_rate_list_action->isVisible()) - { - for (int i = 0; i < _sample_rate_list.count(); i++) - if (samplerate == _sample_rate_list.itemData( - i).value()) - _sample_rate_list.setCurrentIndex(i); - } + _updating_sample_rate = false; } void SamplingBar::commit_sample_rate() { - GVariant *gvar; uint64_t sample_rate = 0; uint64_t last_sample_rate = 0; - assert(_sdi); + if (_updating_sample_rate) + return; - assert(_sample_rate_list_action); + assert(!_updating_sample_rate); + _updating_sample_rate = true; - if (_sample_rate_list_action->isVisible()) - { - const int index = _sample_rate_list.currentIndex(); - if (index >= 0) - sample_rate = _sample_rate_list.itemData( - index).value(); - } + const int index = _sample_rate.currentIndex(); + if (index >= 0) + sample_rate = _sample_rate.itemData( + index).value(); if (sample_rate == 0) return; // Get last samplerate - if (sr_config_get(_sdi->driver, SR_CONF_SAMPLERATE, - &gvar, _sdi) != SR_OK) { - qDebug() << - "WARNING: Failed to get value of sample rate"; - return; - } - last_sample_rate = g_variant_get_uint64(gvar); - g_variant_unref(gvar); + last_sample_rate = get_selected_device()->get_sample_rate(); // Set the samplerate - if (sr_config_set(_sdi, SR_CONF_SAMPLERATE, - g_variant_new_uint64(sample_rate)) != SR_OK) { - qDebug() << "Failed to configure samplerate."; - return; - } + get_selected_device()->set_config(NULL, NULL, + SR_CONF_SAMPLERATE, + g_variant_new_uint64(sample_rate)); + + if (last_sample_rate != sample_rate) + update_scale(); + + _updating_sample_rate = false; +} + +void SamplingBar::on_samplerate_sel(int index) +{ + uint64_t sample_rate = 0; + uint64_t last_sample_rate = 0; + + if (index >= 0) + sample_rate = _sample_rate.itemData( + index).value(); + + const sr_dev_inst* _sdi = get_selected_device()->dev_inst(); + assert(_sdi); + + // Get last samplerate + last_sample_rate = get_selected_device()->get_sample_rate(); if (strcmp(_sdi->driver->name, "DSLogic") == 0 && _sdi->mode != DSO) { if ((last_sample_rate == SR_MHZ(200)&& sample_rate != SR_MHZ(200)) || (last_sample_rate != SR_MHZ(200) && sample_rate == SR_MHZ(200)) || (last_sample_rate == SR_MHZ(400)&& sample_rate != SR_MHZ(400)) || - (last_sample_rate != SR_MHZ(400) && sample_rate == SR_MHZ(400))) - device_reload(); + (last_sample_rate != SR_MHZ(400) && sample_rate == SR_MHZ(400))) { + + // Set the samplerate + get_selected_device()->set_config(NULL, NULL, + SR_CONF_SAMPLERATE, + g_variant_new_uint64(sample_rate)); + device_updated(); + update_scale(); + } } } -void SamplingBar::on_sample_rate_changed() +void SamplingBar::update_sample_count_selector() { - commit_sample_rate(); + GVariant *gvar_dict, *gvar_list; + const uint64_t *elements = NULL; + gsize num_elements; + + if (_updating_sample_count) + return; + + const shared_ptr dev_inst = get_selected_device(); + if (!dev_inst) + return; + + assert(!_updating_sample_count); + _updating_sample_count = true; + + if (!(gvar_dict = dev_inst->list_config(NULL, SR_CONF_LIMIT_SAMPLES))) + { + _sample_count.clear(); + _sample_count.show(); + _updating_sample_count = false; + return; + } + + if ((gvar_list = g_variant_lookup_value(gvar_dict, + "samplecounts", G_VARIANT_TYPE("at")))) + { + elements = (const uint64_t *)g_variant_get_fixed_array( + gvar_list, &num_elements, sizeof(uint64_t)); + _sample_count.clear(); + + for (unsigned int i = 0; i < num_elements; i++) + { + char *const s = sr_samplecount_string(elements[i]); + _sample_count.addItem(QString(s), + qVariantFromValue(elements[i])); + g_free(s); + } + + _sample_count.show(); + g_variant_unref(gvar_list); + } + + _updating_sample_count = false; + + g_variant_unref(gvar_dict); + update_sample_count_selector_value(); +} + +void SamplingBar::update_sample_count_selector_value() +{ + if (_updating_sample_count) + return; + + const uint64_t samplecount = get_selected_device()->get_sample_limit(); + + assert(!_updating_sample_count); + _updating_sample_count = true; + + for (int i = 0; i < _sample_count.count(); i++) + if (samplecount == _sample_count.itemData( + i).value()) + _sample_count.setCurrentIndex(i); + + _updating_sample_count = false; +} + +void SamplingBar::commit_sample_count() +{ + uint64_t sample_count = 0; + uint64_t last_sample_count = 0; + + if (_updating_sample_count) + return; + + assert(!_updating_sample_count); + _updating_sample_count = true; + + + const int index = _sample_count.currentIndex(); + if (index >= 0) + sample_count = _sample_count.itemData( + index).value(); + + if (sample_count == 0) + return; + + // Get last samplecount + last_sample_count = get_selected_device()->get_sample_limit(); + + // Set the samplecount + get_selected_device()->set_config(NULL, NULL, + SR_CONF_LIMIT_SAMPLES, + g_variant_new_uint64(sample_count)); + + _updating_sample_count = false; + + if (sample_count != last_sample_count) + update_scale(); } void SamplingBar::on_run_stop() { commit_sample_rate(); + commit_sample_count(); + _instant = false; run_stop(); } +void SamplingBar::on_instant_stop() +{ + commit_sample_rate(); + commit_sample_count(); + _instant = true; + instant_stop(); +} + +void SamplingBar::on_device_selected() +{ + if (_updating_device_selector) + return; + + const shared_ptr dev_inst = get_selected_device(); + if (!dev_inst) + return; + + _session.set_device(dev_inst); + + device_selected(); +} + void SamplingBar::enable_toggle(bool enable) { - _record_length_selector.setDisabled(!enable); - _sample_rate_list.setDisabled(!enable); + _sample_count.setDisabled(!enable); + _sample_rate.setDisabled(!enable); } void SamplingBar::enable_run_stop(bool enable) @@ -340,5 +557,10 @@ void SamplingBar::enable_run_stop(bool enable) _run_stop_button.setDisabled(!enable); } +void SamplingBar::enable_instant(bool enable) +{ + _instant_button.setDisabled(!enable); +} + } // namespace toolbars } // namespace pv diff --git a/DSLogic-gui/pv/toolbars/samplingbar.h b/DSLogic-gui/pv/toolbars/samplingbar.h index d02b8e2..8718d2d 100644 --- a/DSLogic-gui/pv/toolbars/samplingbar.h +++ b/DSLogic-gui/pv/toolbars/samplingbar.h @@ -27,6 +27,9 @@ #include #include +#include + +#include #include #include @@ -34,10 +37,23 @@ #include +#include "../sigsession.h" + struct st_dev_inst; class QAction; namespace pv { + +class SigSession; + +namespace device { +class DevInst; +} + +namespace dialogs { +class deviceoptions; +} + namespace toolbars { class SamplingBar : public QToolBar @@ -51,46 +67,76 @@ private: static const uint64_t DSLogic_DefaultRecordLength; public: - SamplingBar(QWidget *parent); + SamplingBar(SigSession &session, QWidget *parent); + + void set_device_list(const std::list< boost::shared_ptr > &devices, + boost::shared_ptr selected); + + boost::shared_ptr get_selected_device() const; uint64_t get_record_length() const; void set_record_length(uint64_t length); void set_sampling(bool sampling); - void update_sample_rate_selector(); - void set_sample_rate(uint64_t sample_rate); - - void set_device(struct sr_dev_inst *sdi); void enable_toggle(bool enable); void enable_run_stop(bool enable); + void enable_instant(bool enable); + +public slots: + void set_sample_rate(uint64_t sample_rate); + void set_sample_limit(uint64_t sample_limit); + signals: void run_stop(); - void device_reload(); + void instant_stop(); + void device_selected(); + void device_updated(); + void update_scale(); private: + void update_sample_rate_selector(); void update_sample_rate_selector_value(); + void update_sample_count_selector(); + void update_sample_count_selector_value(); void commit_sample_rate(); + void commit_sample_count(); private slots: - void on_sample_rate_changed(); void on_run_stop(); + void on_instant_stop(); + void on_device_selected(); + void on_samplerate_sel(int index); + +public slots: + void on_configure(); private: + SigSession &_session; + bool _enable; - struct sr_dev_inst *_sdi; + QComboBox _device_selector; + std::map > + _device_selector_map; + bool _updating_device_selector; - QComboBox _record_length_selector; + QToolButton _configure_button; - QComboBox _sample_rate_list; - QAction *_sample_rate_list_action; + QComboBox _sample_count; + QComboBox _sample_rate; + bool _updating_sample_rate; + bool _updating_sample_count; QIcon _icon_stop; QIcon _icon_start; + QIcon _icon_instant; QToolButton _run_stop_button; + QToolButton _instant_button; + + bool _instant; }; } // namespace toolbars diff --git a/DSLogic-gui/pv/toolbars/trigbar.cpp b/DSLogic-gui/pv/toolbars/trigbar.cpp index f60b752..b0078e8 100644 --- a/DSLogic-gui/pv/toolbars/trigbar.cpp +++ b/DSLogic-gui/pv/toolbars/trigbar.cpp @@ -50,7 +50,9 @@ TrigBar::TrigBar(QWidget *parent) : _trig_button.setCheckable(true); _protocol_button.setIcon(QIcon::fromTheme("trig", QIcon(":/icons/protocol.png"))); +#ifdef ENABLE_DECODE _protocol_button.setCheckable(true); +#endif _measure_button.setIcon(QIcon::fromTheme("trig", QIcon(":/icons/measure.png"))); _measure_button.setCheckable(true); @@ -90,6 +92,35 @@ void TrigBar::enable_toggle(bool enable) _protocol_button.setDisabled(!enable); _measure_button.setDisabled(!enable); _search_button.setDisabled(!enable); + + _trig_button.setIcon(enable ? QIcon::fromTheme("trig", QIcon(":/icons/trigger.png")) : + QIcon::fromTheme("trig", QIcon(":/icons/trigger_dis.png"))); + _protocol_button.setIcon(enable ? QIcon::fromTheme("trig", QIcon(":/icons/protocol.png")) : + QIcon::fromTheme("trig", QIcon(":/icons/protocol_dis.png"))); + _measure_button.setIcon(enable ? QIcon::fromTheme("trig", QIcon(":/icons/measure.png")) : + QIcon::fromTheme("trig", QIcon(":/icons/measure_dis.png"))); + _search_button.setIcon(enable ? QIcon::fromTheme("trig", QIcon(":/icons/search-bar.png")) : + QIcon::fromTheme("trig", QIcon(":/icons/search-bar_dis.png"))); +} + +void TrigBar::close_all() +{ + if (_trig_button.isChecked()) { + _trig_button.setChecked(false); + on_trigger(false); + } + if (_protocol_button.isChecked()) { + _protocol_button.setChecked(false); + on_protocol(false); + } + if (_measure_button.isChecked()) { + _measure_button.setChecked(false); + on_measure(false); + } + if(_search_button.isChecked()) { + _search_button.setChecked(false); + on_search(false); + } } } // namespace toolbars diff --git a/DSLogic-gui/pv/toolbars/trigbar.h b/DSLogic-gui/pv/toolbars/trigbar.h index bc9333c..ae3cac8 100644 --- a/DSLogic-gui/pv/toolbars/trigbar.h +++ b/DSLogic-gui/pv/toolbars/trigbar.h @@ -38,6 +38,8 @@ public: void enable_toggle(bool enable); + void close_all(); + signals: void on_protocol(bool visible); void on_trigger(bool visible); diff --git a/DSLogic-gui/pv/view/analogsignal.cpp b/DSLogic-gui/pv/view/analogsignal.cpp index 2e62bfa..ec251ac 100644 --- a/DSLogic-gui/pv/view/analogsignal.cpp +++ b/DSLogic-gui/pv/view/analogsignal.cpp @@ -28,6 +28,7 @@ #include "analogsignal.h" #include "pv/data/analog.h" #include "pv/data/analogsnapshot.h" +#include "view.h" using namespace boost; using namespace std; @@ -50,12 +51,13 @@ const QColor AnalogSignal::SignalColours[4] = { const float AnalogSignal::EnvelopeThreshold = 256.0f; -AnalogSignal::AnalogSignal(QString name, boost::shared_ptr data, - int probe_index, int order) : - Signal(name, probe_index, DS_ANALOG, order), +AnalogSignal::AnalogSignal(boost::shared_ptr dev_inst, + boost::shared_ptr data, + const sr_channel * const probe) : + Signal(dev_inst, probe, DS_ANALOG), _data(data) { - _colour = SignalColours[probe_index % countof(SignalColours)]; + _colour = SignalColours[probe->index % countof(SignalColours)]; _scale = _signalHeight * 1.0f / 65536; } @@ -63,18 +65,9 @@ AnalogSignal::~AnalogSignal() { } -void AnalogSignal::set_data(boost::shared_ptr _logic_data, - boost::shared_ptr _dso_data, - boost::shared_ptr _analog_data, - boost::shared_ptr _group_data) +shared_ptr AnalogSignal::data() const { - (void)_dso_data; - (void)_logic_data; - (void)_group_data; - - assert(_analog_data); - - _data = _analog_data; + return _data; } void AnalogSignal::set_scale(float scale) @@ -82,16 +75,18 @@ void AnalogSignal::set_scale(float scale) _scale = scale; } -void AnalogSignal::paint(QPainter &p, int y, int left, int right, double scale, - double offset) +void AnalogSignal::paint_mid(QPainter &p, int left, int right) { - assert(scale > 0); - assert(_data); - assert(right >= left); + assert(_data); + assert(_view); + assert(right >= left); - //paint_axis(p, y, left, right); + const int y = get_y() + _signalHeight * 0.5; + const double scale = _view->scale(); + assert(scale > 0); + const double offset = _view->offset(); - const deque< boost::shared_ptr > &snapshots = + const deque< shared_ptr > &snapshots = _data->get_snapshots(); if (snapshots.empty()) return; @@ -104,7 +99,7 @@ void AnalogSignal::paint(QPainter &p, int y, int left, int right, double scale, return; const double pixels_offset = offset / scale; - const double samplerate = _data->get_samplerate(); + const double samplerate = _data->samplerate(); const double start_time = _data->get_start_time(); const int64_t last_sample = max((int64_t)(snapshot->get_sample_count() - 1), (int64_t)0); const double samples_per_pixel = samplerate * scale; @@ -210,19 +205,5 @@ const std::vector< std::pair > AnalogSignal::cur_edges() const } -void AnalogSignal::set_decoder(pv::decoder::Decoder *decoder) -{ - (void)decoder; -} - -decoder::Decoder *AnalogSignal::get_decoder() -{ - return NULL; -} - -void AnalogSignal::del_decoder() -{ -} - } // namespace view } // namespace pv diff --git a/DSLogic-gui/pv/view/analogsignal.h b/DSLogic-gui/pv/view/analogsignal.h index b954487..33d0bed 100644 --- a/DSLogic-gui/pv/view/analogsignal.h +++ b/DSLogic-gui/pv/view/analogsignal.h @@ -42,43 +42,31 @@ class AnalogSignal : public Signal { private: static const QColor SignalColours[4]; - static const float EnvelopeThreshold; - + static const int NumSpanY = 5; + static const int NumMiniSpanY = 5; + static const int NumSpanX = 10; public: - AnalogSignal(QString name, - boost::shared_ptr data, int probe_index, int order); + AnalogSignal(boost::shared_ptr dev_inst, + boost::shared_ptr data, + const sr_channel * const probe); virtual ~AnalogSignal(); + boost::shared_ptr data() const; + void set_scale(float scale); /** * Paints the signal with a QPainter * @param p the QPainter to paint into. - * @param y the y-coordinate to draw the signal at. * @param left the x-coordinate of the left edge of the signal. * @param right the x-coordinate of the right edge of the signal. - * @param scale the scale in seconds per pixel. - * @param offset the time to show at the left hand edge of - * the view in seconds. **/ - void paint(QPainter &p, int y, int left, int right, double scale, - double offset); + void paint_mid(QPainter &p, int left, int right); const std::vector< std::pair > cur_edges() const; - void set_decoder(pv::decoder::Decoder *decoder); - - pv::decoder::Decoder* get_decoder(); - - void del_decoder(); - - void set_data(boost::shared_ptr _logic_data, - boost::shared_ptr _dso_data, - boost::shared_ptr _analog_data, - boost::shared_ptr _group_data); - private: void paint_trace(QPainter &p, const boost::shared_ptr &snapshot, diff --git a/DSLogic-gui/pv/view/decodetrace.cpp b/DSLogic-gui/pv/view/decodetrace.cpp new file mode 100644 index 0000000..0be3777 --- /dev/null +++ b/DSLogic-gui/pv/view/decodetrace.cpp @@ -0,0 +1,833 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +extern "C" { +#include +} + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "decodetrace.h" + +#include "../sigsession.h" +#include "../data/decoderstack.h" +#include "../data/decode/decoder.h" +#include "../data/logic.h" +#include "../data/logicsnapshot.h" +#include "../data/decode/annotation.h" +#include "../view/logicsignal.h" +#include "../view/view.h" +#include "../widgets/decodergroupbox.h" +#include "../widgets/decodermenu.h" +#include "../device/devinst.h" + +using boost::dynamic_pointer_cast; +using boost::shared_ptr; +using std::list; +using std::max; +using std::map; +using std::min; +using std::vector; + +namespace pv { +namespace view { + +const QColor DecodeTrace::DecodeColours[4] = { + QColor(0xEF, 0x29, 0x29), // Red + QColor(0xFC, 0xE9, 0x4F), // Yellow + QColor(0x8A, 0xE2, 0x34), // Green + QColor(0x72, 0x9F, 0xCF) // Blue +}; + +const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29); +const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85); + +const int DecodeTrace::ArrowSize = 4; +const double DecodeTrace::EndCapWidth = 5; +const int DecodeTrace::DrawPadding = 100; + +const QColor DecodeTrace::Colours[16] = { + QColor(0xEF, 0x29, 0x29), + QColor(0xF6, 0x6A, 0x32), + QColor(0xFC, 0xAE, 0x3E), + QColor(0xFB, 0xCA, 0x47), + QColor(0xFC, 0xE9, 0x4F), + QColor(0xCD, 0xF0, 0x40), + QColor(0x8A, 0xE2, 0x34), + QColor(0x4E, 0xDC, 0x44), + QColor(0x55, 0xD7, 0x95), + QColor(0x64, 0xD1, 0xD2), + QColor(0x72, 0x9F, 0xCF), + QColor(0xD4, 0x76, 0xC4), + QColor(0x9D, 0x79, 0xB9), + QColor(0xAD, 0x7F, 0xA8), + QColor(0xC2, 0x62, 0x9B), + QColor(0xD7, 0x47, 0x6F) +}; + +const QColor DecodeTrace::OutlineColours[16] = { + QColor(0x77, 0x14, 0x14), + QColor(0x7B, 0x35, 0x19), + QColor(0x7E, 0x57, 0x1F), + QColor(0x7D, 0x65, 0x23), + QColor(0x7E, 0x74, 0x27), + QColor(0x66, 0x78, 0x20), + QColor(0x45, 0x71, 0x1A), + QColor(0x27, 0x6E, 0x22), + QColor(0x2A, 0x6B, 0x4A), + QColor(0x32, 0x68, 0x69), + QColor(0x39, 0x4F, 0x67), + QColor(0x6A, 0x3B, 0x62), + QColor(0x4E, 0x3C, 0x5C), + QColor(0x56, 0x3F, 0x54), + QColor(0x61, 0x31, 0x4D), + QColor(0x6B, 0x23, 0x37) +}; + +DecodeTrace::DecodeTrace(pv::SigSession &session, + boost::shared_ptr decoder_stack, int index) : + Trace(QString::fromUtf8( + decoder_stack->stack().front()->decoder()->name), Trace::DS_DECODER), + _session(session), + _decoder_stack(decoder_stack), + _show_hide_mapper(this) +{ + assert(_decoder_stack); + + _colour = DecodeColours[index % countof(DecodeColours)]; + + connect(_decoder_stack.get(), SIGNAL(new_decode_data()), + this, SLOT(on_new_decode_data())); + connect(_decoder_stack.get(), SIGNAL(decode_done()), + this, SLOT(on_decode_done())); + connect(&_show_hide_mapper, SIGNAL(mapped(int)), + this, SLOT(on_show_hide_decoder(int))); +} + +bool DecodeTrace::enabled() const +{ + return true; +} + +const boost::shared_ptr& DecodeTrace::decoder() const +{ + return _decoder_stack; +} + +void DecodeTrace::set_view(pv::view::View *view) +{ + assert(view); + Trace::set_view(view); +} + +void DecodeTrace::paint_back(QPainter &p, int left, int right) +{ + QPen pen(Signal::dsGray); + pen.setStyle(Qt::DotLine); + p.setPen(pen); + const double sigY = get_y() - (_signalHeight - _view->get_signalHeight())*0.5; + p.drawLine(left, sigY, right, sigY); +} + +void DecodeTrace::paint_mid(QPainter &p, int left, int right) +{ + using namespace pv::data::decode; + + const double scale = _view->scale(); + assert(scale > 0); + + double samplerate = _decoder_stack->samplerate(); + + _cur_row_headings.clear(); + + // Show sample rate as 1Hz when it is unknown + if (samplerate == 0.0) + samplerate = 1.0; + + const double pixels_offset = (_view->offset() - + _decoder_stack->get_start_time()) / scale; + const double samples_per_pixel = samplerate * scale; + + const uint64_t start_sample = (uint64_t)max((left + pixels_offset) * + samples_per_pixel, 0.0); + const uint64_t end_sample = (uint64_t)max((right + pixels_offset) * + samples_per_pixel, 0.0); + + const int annotation_height = _view->get_signalHeight(); + + assert(_decoder_stack); + const QString err = _decoder_stack->error_message(); + if (!err.isEmpty()) + { + //draw_unresolved_period(p, _view->get_signalHeight(), left, right, + // samples_per_pixel, pixels_offset); + draw_error(p, err, left, right); + return; + } + + // Draw the hatching + if (draw_unresolved_period(p, _view->get_signalHeight(), left, right)) + return; + + // Iterate through the rows + assert(_view); + int y = get_y() - (_signalHeight - _view->get_signalHeight())*0.5; + + assert(_decoder_stack); + + const std::vector< std::pair > rows(_decoder_stack->get_visible_rows()); + for (size_t i = 0; i < rows.size(); i++) + { + const Row &row = rows[i].first; + const bool shown = rows[i].second; + + if (!shown && _decoder_stack->has_annotations(row)) { + draw_unshown_row(p, y, _view->get_signalHeight(), left, right); + y += _view->get_signalHeight(); + _cur_row_headings.push_back(row.title()); + continue; + } + + size_t base_colour = 0x13579BDF; + boost::hash_combine(base_colour, this); + boost::hash_combine(base_colour, row.decoder()); + boost::hash_combine(base_colour, row.row()); + base_colour >>= 16; + + const uint64_t max_annotation = + _decoder_stack->get_max_annotation(row); + const double max_annWidth = max_annotation / samples_per_pixel; + if (max_annWidth > 5) { + vector annotations; + _decoder_stack->get_annotation_subset(annotations, row, + start_sample, end_sample); + if (!annotations.empty()) { + BOOST_FOREACH(const Annotation &a, annotations) + draw_annotation(a, p, get_text_colour(), + annotation_height, left, right, + samples_per_pixel, pixels_offset, y, + base_colour); + } + } else if (max_annWidth != 0){ + draw_nodetail(p, annotation_height, left, right, y, base_colour); + } + if (max_annWidth != 0) { + y += _view->get_signalHeight(); + _cur_row_headings.push_back(row.title()); + } + } +} + +void DecodeTrace::paint_fore(QPainter &p, int left, int right) +{ + using namespace pv::data::decode; + + (void)right; + + const int row_height = _view->get_signalHeight(); + + for (size_t i = 0; i < _cur_row_headings.size(); i++) + { + const int y = (i + 0.5) * row_height + get_y() - _signalHeight * 0.5; + + p.setPen(QPen(Qt::NoPen)); + p.setBrush(QApplication::palette().brush(QPalette::WindowText)); + + if (i != 0) + { + const QPointF points[] = { + QPointF(left, y - ArrowSize), + QPointF(left + ArrowSize, y), + QPointF(left, y + ArrowSize) + }; + p.drawPolygon(points, countof(points)); + } + + const QRect r(left + ArrowSize * 2, y - row_height / 2, + right - left, row_height); + const QString h(_cur_row_headings[i]); + const int f = Qt::AlignLeft | Qt::AlignVCenter | + Qt::TextDontClip; + + // Draw the outline + QFont font=p.font(); + font.setPointSize(DefaultFontSize); + p.setFont(font); + p.setPen(QApplication::palette().color(QPalette::Base)); + for (int dx = -1; dx <= 1; dx++) + for (int dy = -1; dy <= 1; dy++) + if (dx != 0 && dy != 0) + p.drawText(r.translated(dx, dy), f, h); + + // Draw the text + p.setPen(QApplication::palette().color(QPalette::WindowText)); + p.drawText(r, f, h); + } +} + +bool DecodeTrace::create_popup() +{ + // Clear the layout + + // Transfer the layout and the child widgets to a temporary widget + // which then goes out of scope destroying the layout and all the child + // widgets. + //if (_popup_form) + // QWidget().setLayout(_popup_form); + + int ret = false; + QDialog popup; + QFormLayout popup_form; + popup.setLayout(&popup_form); + populate_popup_form(&popup, &popup_form); + + if (QDialog::Accepted == popup.exec()) + { + BOOST_FOREACH(shared_ptr dec, + _decoder_stack->stack()) + { + dec->commit_show(); + if (dec->commit()) { + _decoder_stack->options_changed(true); + ret = true; + } + } + return ret; + } + else + return false; +} + + +void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form) +{ + using pv::data::decode::Decoder; + + assert(form); + assert(parent); + assert(_decoder_stack); + + // Add the decoder options + _bindings.clear(); + _probe_selectors.clear(); + _decoder_forms.clear(); + + const list< shared_ptr >& stack = _decoder_stack->stack(); + + if (stack.empty()) + { + QLabel *const l = new QLabel( + tr("

No decoders in the stack

")); + l->setAlignment(Qt::AlignCenter); + form->addRow(l); + } + else + { + list< shared_ptr >::const_iterator iter = + stack.begin(); + for (int i = 0; i < (int)stack.size(); i++, iter++) { + shared_ptr dec(*iter); + create_decoder_form(i, dec, parent, form); + } + + form->addRow(new QLabel( + tr("* Required channels"), parent)); + } + + // Add stacking button + pv::widgets::DecoderMenu *const decoder_menu = + new pv::widgets::DecoderMenu(parent); + connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)), + this, SLOT(on_stack_decoder(srd_decoder*))); + connect(decoder_menu, SIGNAL(selected()), + parent, SLOT(accept())); + + QPushButton *const stack_button = + new QPushButton(tr("Stack Decoder"), parent); + stack_button->setMenu(decoder_menu); + + QHBoxLayout *stack_button_box = new QHBoxLayout; + stack_button_box->addWidget(stack_button, 0, Qt::AlignLeft); + form->addRow(stack_button_box); + + // Add ButtonBox (OK/Cancel) + QDialogButtonBox *button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, + Qt::Horizontal, parent); + connect(button_box, SIGNAL(accepted()), parent, SLOT(accept())); + connect(button_box, SIGNAL(rejected()), parent, SLOT(reject())); + + QHBoxLayout *confirm_button_box = new QHBoxLayout; + confirm_button_box->addWidget(button_box, 0, Qt::AlignRight); + form->addRow(confirm_button_box); +} + +void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a, + QPainter &p, QColor text_color, int h, int left, int right, + double samples_per_pixel, double pixels_offset, int y, + size_t base_colour) const +{ + const double start = max(a.start_sample() / samples_per_pixel - + pixels_offset, (double)left); + const double end = min(a.end_sample() / samples_per_pixel - + pixels_offset, (double)right); + + const size_t colour = (base_colour + a.format()) % countof(Colours); + const QColor &fill = Colours[colour]; + const QColor &outline = OutlineColours[colour]; + + if (start > right + DrawPadding || end < left - DrawPadding) + return; + + if (a.start_sample() == a.end_sample()) + draw_instant(a, p, fill, outline, text_color, h, + start, y); + else + draw_range(a, p, fill, outline, text_color, h, + start, end, y); +} + +void DecodeTrace::draw_nodetail(QPainter &p, + int h, int left, int right, int y, + size_t base_colour) const +{ + const QRectF nodetail_rect(left, y - h/2 + 0.5, right - left, h); + const size_t colour = base_colour % countof(Colours); + const QColor &fill = Colours[colour]; + + p.setPen(Qt::white); + p.setBrush(fill); + p.drawRect(nodetail_rect); + p.drawText(nodetail_rect, Qt::AlignCenter | Qt::AlignVCenter, "Zoom in for more detials"); +} + +void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p, + QColor fill, QColor outline, QColor text_color, int h, double x, int y) const +{ + const QString text = a.annotations().empty() ? + QString() : a.annotations().back(); + const double w = min((double)p.boundingRect(QRectF(), 0, text).width(), + 0.0) + h; + const QRectF rect(x - w / 2, y - h / 2, w, h); + + p.setPen(outline); + p.setBrush(fill); + p.drawRoundedRect(rect, h / 2, h / 2); + + p.setPen(text_color); + QFont font=p.font(); + font.setPointSize(DefaultFontSize); + p.setFont(font); + p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text); +} + +void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p, + QColor fill, QColor outline, QColor text_color, int h, double start, + double end, int y) const +{ + const double top = y + .5 - h / 2; + const double bottom = y + .5 + h / 2; + const vector annotations = a.annotations(); + + p.setPen(outline); + p.setBrush(fill); + + // If the two ends are within 2 pixel, draw a vertical line + if (start + 2.0 > end) + { + p.drawLine(QPointF(start, top), QPointF(start, bottom)); + return; + } + + double cap_width = min((end - start) / 4, EndCapWidth); + + QPointF pts[] = { + QPointF(start, y + .5f), + QPointF(start + cap_width, top), + QPointF(end - cap_width, top), + QPointF(end, y + .5f), + QPointF(end - cap_width, bottom), + QPointF(start + cap_width, bottom) + }; + + p.setPen(Qt::white); + p.drawConvexPolygon(pts, countof(pts)); + + if (annotations.empty()) + return; + + QRectF rect(start + cap_width, y - h / 2, + end - start - cap_width * 2, h); + if (rect.width() <= 4) + return; + + p.setPen(text_color); + + // Try to find an annotation that will fit + QString best_annotation; + int best_width = 0; + + BOOST_FOREACH(const QString &a, annotations) { + const int w = p.boundingRect(QRectF(), 0, a).width(); + if (w <= rect.width() && w > best_width) + best_annotation = a, best_width = w; + } + + if (best_annotation.isEmpty()) + best_annotation = annotations.back(); + + // If not ellide the last in the list + QFont font=p.font(); + font.setPointSize(DefaultFontSize); + p.setFont(font); + p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText( + best_annotation, Qt::ElideRight, rect.width())); +} + +void DecodeTrace::draw_error(QPainter &p, const QString &message, + int left, int right) +{ + const int y = get_y(); + + p.setPen(ErrorBgColour.darker()); + p.setBrush(ErrorBgColour); + + const QRectF bounding_rect = + QRectF(left, INT_MIN / 2 + y, right - left, INT_MAX); + const QRectF text_rect = p.boundingRect(bounding_rect, + Qt::AlignCenter, message); + const float r = text_rect.height() / 4; + + p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r, + Qt::AbsoluteSize); + + p.setPen(get_text_colour()); + QFont font=p.font(); + font.setPointSize(DefaultFontSize); + p.setFont(font); + p.drawText(text_rect, message); +} + +bool DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left, + int right) +{ + using namespace pv::data; + using pv::data::decode::Decoder; + + assert(_decoder_stack); + + shared_ptr data; + shared_ptr logic_signal; + + const int64_t sample_count = _session.get_device()->get_sample_limit(); + if (sample_count == 0) + return true; + + const int64_t samples_decoded = _decoder_stack->samples_decoded(); + if (sample_count == samples_decoded) + return false; + + const int y = get_y(); +// const double start = max(samples_decoded / +// samples_per_pixel - pixels_offset, left - 1.0); +// const double end = min(sample_count / samples_per_pixel - +// pixels_offset, right + 1.0); + const QRectF no_decode_rect(left, y - h/2 + 0.5, right - left, h); + + p.setPen(QPen(Qt::NoPen)); + p.setBrush(Qt::white); + p.drawRect(no_decode_rect); + + p.setPen(NoDecodeColour); + p.setBrush(QBrush(NoDecodeColour, Qt::Dense7Pattern)); + p.drawRect(no_decode_rect); + + const int progress100 = ceil(samples_decoded * 100.0 / sample_count); + p.setPen(dsLightBlue); + QFont font=p.font(); + font.setPointSize(_view->get_signalHeight()*2/3); + font.setBold(true); + p.setFont(font); + p.drawText(no_decode_rect, Qt::AlignCenter | Qt::AlignVCenter, QString::number(progress100)+"%"); + + return true; +} + +void DecodeTrace::draw_unshown_row(QPainter &p, int y, int h, int left, + int right) +{ + const QRectF unshown_rect(left, y - h/2 + 0.5, right - left, h); + + p.setPen(QPen(Qt::NoPen)); + p.setBrush(QBrush(NoDecodeColour, Qt::Dense7Pattern)); + p.drawRect(unshown_rect); + + p.setPen(dsLightBlue); + QFont font=p.font(); + font.setPointSize(_view->get_signalHeight()*2/3); + font.setBold(true); + p.setFont(font); + p.drawText(unshown_rect, Qt::AlignCenter | Qt::AlignVCenter, "Unshown"); +} + +void DecodeTrace::create_decoder_form(int index, + shared_ptr &dec, QWidget *parent, + QFormLayout *form) +{ + const GSList *l; + + assert(dec); + const srd_decoder *const decoder = dec->decoder(); + assert(decoder); + + pv::widgets::DecoderGroupBox *const group = + new pv::widgets::DecoderGroupBox( + QString::fromUtf8(decoder->name)); + group->set_decoder_visible(dec->shown()); + + _show_hide_mapper.setMapping(group, index); + connect(group, SIGNAL(show_hide_decoder()), + &_show_hide_mapper, SLOT(map())); + + QFormLayout *const decoder_form = new QFormLayout; + group->add_layout(decoder_form); + + // Add the mandatory channels + for(l = decoder->channels; l; l = l->next) { + const struct srd_channel *const pdch = + (struct srd_channel *)l->data; + QComboBox *const combo = create_probe_selector(parent, dec, pdch); + connect(combo, SIGNAL(currentIndexChanged(int)), + this, SLOT(on_probe_selected(int))); + decoder_form->addRow(tr("%1 (%2) *") + .arg(QString::fromUtf8(pdch->name)) + .arg(QString::fromUtf8(pdch->desc)), combo); + + const ProbeSelector s = {combo, dec, pdch}; + _probe_selectors.push_back(s); + } + + // Add the optional channels + for(l = decoder->opt_channels; l; l = l->next) { + const struct srd_channel *const pdch = + (struct srd_channel *)l->data; + QComboBox *const combo = create_probe_selector(parent, dec, pdch); + connect(combo, SIGNAL(currentIndexChanged(int)), + this, SLOT(on_probe_selected(int))); + decoder_form->addRow(tr("%1 (%2)") + .arg(QString::fromUtf8(pdch->name)) + .arg(QString::fromUtf8(pdch->desc)), combo); + + const ProbeSelector s = {combo, dec, pdch}; + _probe_selectors.push_back(s); + } + + // Add the options + shared_ptr binding( + new prop::binding::DecoderOptions(_decoder_stack, dec)); + binding->add_properties_to_form(decoder_form, true); + + _bindings.push_back(binding); + + form->addRow(group); + _decoder_forms.push_back(group); +} + +QComboBox* DecodeTrace::create_probe_selector( + QWidget *parent, const shared_ptr &dec, + const srd_channel *const pdch) +{ + assert(dec); + + const vector< shared_ptr > sigs(_session.get_signals()); + + assert(_decoder_stack); + const map >::const_iterator probe_iter = + dec->channels().find(pdch); + + QComboBox *selector = new QComboBox(parent); + + selector->addItem("-", qVariantFromValue((void*)NULL)); + + if (probe_iter == dec->channels().end()) + selector->setCurrentIndex(0); + + for(size_t i = 0; i < sigs.size(); i++) { + const shared_ptr s(sigs[i]); + assert(s); + + if (dynamic_pointer_cast(s) && s->enabled()) + { + selector->addItem(s->get_name(), + qVariantFromValue((void*)s.get())); + if ((*probe_iter).second == s) + selector->setCurrentIndex(i + 1); + } + } + + return selector; +} + +void DecodeTrace::commit_decoder_probes(shared_ptr &dec) +{ + assert(dec); + + map > probe_map; + const vector< shared_ptr > sigs(_session.get_signals()); + + _index_list.clear(); + BOOST_FOREACH(const ProbeSelector &s, _probe_selectors) + { + if(s._decoder != dec) + break; + + const LogicSignal *const selection = + (LogicSignal*)s._combo->itemData( + s._combo->currentIndex()).value(); + + BOOST_FOREACH(shared_ptr sig, sigs) + if(sig.get() == selection) { + probe_map[s._pdch] = + dynamic_pointer_cast(sig); + _index_list.push_back(sig->get_index()); + break; + } + } + + dec->set_probes(probe_map); +} + +void DecodeTrace::commit_probes() +{ + assert(_decoder_stack); + BOOST_FOREACH(shared_ptr dec, + _decoder_stack->stack()) + commit_decoder_probes(dec); + + //_decoder_stack->begin_decode(); +} + +void DecodeTrace::on_new_decode_data() +{ + if (_view && _view->session().get_capture_state() == SigSession::Stopped) + _view->data_updated(); +} + +void DecodeTrace::on_decode_done() +{ + if (_view) { + _view->set_need_update(true); + _view->signals_changed(); + } +} + +void DecodeTrace::on_delete() +{ + _session.remove_decode_signal(this); +} + +void DecodeTrace::on_probe_selected(int) +{ + commit_probes(); +} + +void DecodeTrace::on_stack_decoder(srd_decoder *decoder) +{ + assert(decoder); + assert(_decoder_stack); + _decoder_stack->push(shared_ptr( + new data::decode::Decoder(decoder))); + //_decoder_stack->begin_decode(); + + create_popup(); +} + +void DecodeTrace::on_show_hide_decoder(int index) +{ + using pv::data::decode::Decoder; + + const list< shared_ptr > stack(_decoder_stack->stack()); + + // Find the decoder in the stack + list< shared_ptr >::const_iterator iter = stack.begin(); + for(int i = 0; i < index; i++, iter++) + assert(iter != stack.end()); + + shared_ptr dec = *iter; + assert(dec); + + const bool show = !dec->shown(); + dec->show(show); + + assert(index < (int)_decoder_forms.size()); + _decoder_forms[index]->set_decoder_visible(show); + + //_view->set_need_update(true); +} + + +int DecodeTrace::rows_size() +{ + return _decoder_stack->cur_rows_size(); +} + +void DecodeTrace::paint_type_options(QPainter &p, int right, bool hover, int action) +{ + (void)hover; + (void)action; + + int y = get_y(); + const QRectF group_index_rect = get_rect("groupIndex", y, right); + QString index_string; + int last_index; + p.setPen(Qt::transparent); + p.setBrush(dsBlue); + p.drawRect(group_index_rect); + std::list::iterator i = _index_list.begin(); + last_index = (*i); + index_string = QString::number(last_index); + while (++i != _index_list.end()) { + if ((*i) == last_index + 1 && index_string.indexOf("-") < 3 && index_string.indexOf("-") > 0) + index_string.replace(QString::number(last_index), QString::number((*i))); + else if ((*i) == last_index + 1) + index_string = QString::number((*i)) + "-" + index_string; + else + index_string = QString::number((*i)) + "," + index_string; + last_index = (*i); + } + p.setPen(Qt::white); + p.drawText(group_index_rect, Qt::AlignRight | Qt::AlignVCenter, index_string); +} + +} // namespace view +} // namespace pv diff --git a/DSLogic-gui/pv/view/decodetrace.h b/DSLogic-gui/pv/view/decodetrace.h new file mode 100644 index 0000000..fe0f5f7 --- /dev/null +++ b/DSLogic-gui/pv/view/decodetrace.h @@ -0,0 +1,204 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DSLOGIC_PV_VIEW_DECODETRACE_H +#define DSLOGIC_PV_VIEW_DECODETRACE_H + +#include "trace.h" + +#include +#include + +#include +#include +#include + +#include + +#include + +struct srd_channel; +struct srd_decoder; + +class QComboBox; + +namespace pv { + +class SigSession; + +namespace data { +class DecoderStack; + +namespace decode { +class Annotation; +class Decoder; +class Row; +} +} + +namespace widgets { +class DecoderGroupBox; +} + +namespace view { + +class DecodeTrace : public Trace +{ + Q_OBJECT + +private: + struct ProbeSelector + { + const QComboBox *_combo; + const boost::shared_ptr _decoder; + const srd_channel *_pdch; + }; + +private: + static const QColor DecodeColours[4]; + static const QColor ErrorBgColour; + static const QColor NoDecodeColour; + + static const int ArrowSize; + static const double EndCapWidth; + static const int DrawPadding; + + static const QColor Colours[16]; + static const QColor OutlineColours[16]; + + static const int DefaultFontSize = 8; + +public: + DecodeTrace(pv::SigSession &session, + boost::shared_ptr decoder_stack, + int index); + + bool enabled() const; + + const boost::shared_ptr& decoder() const; + + void set_view(pv::view::View *view); + + /** + * Paints the background layer of the trace with a QPainter + * @param p the QPainter to paint into. + * @param left the x-coordinate of the left edge of the signal. + * @param right the x-coordinate of the right edge of the signal. + **/ + void paint_back(QPainter &p, int left, int right); + + /** + * Paints the mid-layer of the trace with a QPainter + * @param p the QPainter to paint into. + * @param left the x-coordinate of the left edge of the signal + * @param right the x-coordinate of the right edge of the signal + **/ + void paint_mid(QPainter &p, int left, int right); + + /** + * Paints the foreground layer of the trace with a QPainter + * @param p the QPainter to paint into. + * @param left the x-coordinate of the left edge of the signal + * @param right the x-coordinate of the right edge of the signal + **/ + void paint_fore(QPainter &p, int left, int right); + + bool create_popup(); + + int rows_size(); + +protected: + void paint_type_options(QPainter &p, int right, bool hover, int action); + +private: + void populate_popup_form(QWidget *parent, QFormLayout *form); + + void draw_annotation(const pv::data::decode::Annotation &a, QPainter &p, + QColor text_colour, int text_height, int left, int right, + double samples_per_pixel, double pixels_offset, int y, + size_t base_colour) const; + void draw_nodetail(QPainter &p, + int text_height, int left, int right, int y, + size_t base_colour) const; + + void draw_instant(const pv::data::decode::Annotation &a, QPainter &p, + QColor fill, QColor outline, QColor text_color, int h, double x, + int y) const; + + void draw_range(const pv::data::decode::Annotation &a, QPainter &p, + QColor fill, QColor outline, QColor text_color, int h, double start, + double end, int y) const; + + void draw_error(QPainter &p, const QString &message, + int left, int right); + + bool draw_unresolved_period(QPainter &p, int h, int left, + int right); + + void draw_unshown_row(QPainter &p, int y, int h, int left, + int right); + + void create_decoder_form(int index, + boost::shared_ptr &dec, + QWidget *parent, QFormLayout *form); + + QComboBox* create_probe_selector(QWidget *parent, + const boost::shared_ptr &dec, + const srd_channel *const pdch); + + void commit_decoder_probes( + boost::shared_ptr &dec); + + void commit_probes(); + +private slots: + void on_new_decode_data(); + + void on_delete(); + + void on_probe_selected(int); + + void on_stack_decoder(srd_decoder *decoder); + + void on_show_hide_decoder(int index); + + void on_decode_done(); + +private: + pv::SigSession &_session; + boost::shared_ptr _decoder_stack; + + uint64_t _decode_start, _decode_end; + + std::list< boost::shared_ptr > + _bindings; + + std::list _probe_selectors; + std::vector _decoder_forms; + + std::vector _cur_row_headings; + + QSignalMapper _show_hide_mapper; +}; + +} // namespace view +} // namespace pv + +#endif // DSLOGIC_PV_VIEW_DECODETRACE_H diff --git a/DSLogic-gui/pv/view/devmode.cpp b/DSLogic-gui/pv/view/devmode.cpp new file mode 100644 index 0000000..fb97a0c --- /dev/null +++ b/DSLogic-gui/pv/view/devmode.cpp @@ -0,0 +1,194 @@ +/* + * This file is part of the DSLogic-gui project. + * DSLogic-gui is based on PulseView. + * + * Copyright (C) 2014 DreamSourceLab + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include "devmode.h" +#include "view.h" +#include "trace.h" +#include "../sigsession.h" +#include "../device/devinst.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +using boost::shared_ptr; +using namespace std; + +namespace pv { +namespace view { + +DevMode::DevMode(View &parent) : + QWidget(&parent), + _view(parent), + layout(new QGridLayout(this)) + +{ + setLayout(layout); +} + +void DevMode::set_device() +{ + int index = 0; + const boost::shared_ptr dev_inst = _view.session().get_device(); + + assert(dev_inst); + + _mode_button_list.clear(); + delete layout; + layout = new QGridLayout(this); + + for (GSList *l = dev_inst->get_dev_mode_list(); + l; l = l->next) { + sr_dev_mode *mode = (sr_dev_mode *)l->data; + + shared_ptr mode_button = shared_ptr(new QPushButton(NULL)); + mode_button->setFlat(true); + mode_button->setText(mode->name); + + _mode_button_list[mode_button] = mode; + + connect(mode_button.get(), SIGNAL(clicked()), this, SLOT(on_mode_change())); + + layout->addWidget(mode_button.get(), index / GRID_COLS, index % GRID_COLS); + layout->addWidget(new QWidget(), index / GRID_COLS, GRID_COLS); + layout->setColumnStretch(GRID_COLS, 1); + index++; + } + + setLayout(layout); + update(); +} + +void DevMode::paintEvent(QPaintEvent*) +{ + using pv::view::Trace; + + QStyleOption o; + o.initFrom(this); + QPainter painter(this); + style()->drawPrimitive(QStyle::PE_Widget, &o, &painter, this); + + painter.setRenderHint(QPainter::Antialiasing); + painter.setPen(Qt::NoPen); + for(std::map, sr_dev_mode *>::const_iterator i = _mode_button_list.begin(); + i != _mode_button_list.end(); i++) { + const boost::shared_ptr dev_inst = _view.session().get_device(); + assert(dev_inst); + if (dev_inst->dev_inst()->mode == (*i).second->mode) + painter.setBrush(Trace::dsBlue); + else + painter.setBrush(Trace::dsGray); + + painter.drawRoundedRect((*i).first->geometry(), 4, 4); + } + + painter.end(); +} + +void DevMode::on_mode_change() +{ + const boost::shared_ptr dev_inst = _view.session().get_device(); + assert(dev_inst); + QPushButton *button = qobject_cast(sender()); + + for(std::map, sr_dev_mode *>::const_iterator i = _mode_button_list.begin(); + i != _mode_button_list.end(); i++) { + if ((*i).first.get() == button) { + if (dev_inst->dev_inst()->mode != (*i).second->mode) { + dev_inst->set_config(NULL, NULL, + SR_CONF_DEVICE_MODE, + g_variant_new_int16((*i).second->mode)); + + mode_changed(); + + if (dev_inst->dev_inst()->mode == DSO && + strcmp(dev_inst->dev_inst()->driver->name, "DSLogic") == 0) { + bool zero_adjusted = false; + GVariant *gvar = dev_inst->get_config(NULL, NULL, SR_CONF_ZERO); + if (gvar != NULL) { + zero_adjusted = g_variant_get_boolean(gvar); + g_variant_unref(gvar); + } else { + qDebug() << "ERROR: config_get SR_CONF_ZERO failed."; + } + + if (!zero_adjusted) { + QMessageBox msg(this); + msg.setText("Zero Adjustment"); + msg.setInformativeText("Please left both of channels unconnect for zero adjustment!"); + msg.setStandardButtons(QMessageBox::Ok); + msg.setIcon(QMessageBox::Warning); + msg.exec(); + + int ret = dev_inst->set_config(NULL, NULL, + SR_CONF_ZERO, + g_variant_new_boolean(TRUE)); + if (!ret) { + QMessageBox msg(this); + msg.setText("Zero Adjustment Issue"); + msg.setInformativeText("Can't send out the command of zero adjustment!"); + msg.setStandardButtons(QMessageBox::Ok); + msg.setIcon(QMessageBox::Warning); + msg.exec(); + } + } + } + } + } + } +} + +void DevMode::mousePressEvent(QMouseEvent *event) +{ + assert(event); + (void)event; +} + +void DevMode::mouseReleaseEvent(QMouseEvent *event) +{ + assert(event); + (void)event; +} + +void DevMode::mouseMoveEvent(QMouseEvent *event) +{ + assert(event); + _mouse_point = event->pos(); + update(); +} + +void DevMode::leaveEvent(QEvent*) +{ + _mouse_point = QPoint(-1, -1); + update(); +} + +} // namespace view +} // namespace pv diff --git a/DSLogic-gui/pv/view/devmode.h b/DSLogic-gui/pv/view/devmode.h new file mode 100644 index 0000000..7e15a50 --- /dev/null +++ b/DSLogic-gui/pv/view/devmode.h @@ -0,0 +1,90 @@ +/* + * This file is part of the DSLogic-gui project. + * DSLogic-gui is based on PulseView. + * + * Copyright (C) 2014 DreamSourceLab + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#ifndef DSLOGIC_PV_VIEW_DEVMODE_H +#define DSLOGIC_PV_VIEW_DEVMODE_H + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace pv { + +namespace device{ +class DevInst; +} + +namespace view { + +class View; + +class DevMode : public QWidget +{ + Q_OBJECT + +private: + static const int GRID_COLS = 3; + +public: + DevMode(View &parent); + +private: + void paintEvent(QPaintEvent *event); + +private: + void mousePressEvent(QMouseEvent * event); + void mouseReleaseEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void leaveEvent(QEvent *event); + +public slots: + void set_device(); + void on_mode_change(); + +private slots: + +signals: + void mode_changed(); + +private: + View &_view; + + QGridLayout * layout; + std::map , sr_dev_mode *> _mode_button_list; + QPoint _mouse_point; +}; + +} // namespace view +} // namespace pv + +#endif // DSLOGIC_PV_VIEW_DEVMODE_H diff --git a/DSLogic-gui/pv/view/dsosignal.cpp b/DSLogic-gui/pv/view/dsosignal.cpp index d11108b..b2b34a0 100644 --- a/DSLogic-gui/pv/view/dsosignal.cpp +++ b/DSLogic-gui/pv/view/dsosignal.cpp @@ -27,6 +27,11 @@ #include "dsosignal.h" #include "pv/data/dso.h" #include "pv/data/dsosnapshot.h" +#include "view.h" +#include "../sigsession.h" +#include "../device/devinst.h" + +#include using namespace boost; using namespace std; @@ -34,6 +39,55 @@ using namespace std; namespace pv { namespace view { +const uint64_t DsoSignal::vDialValue[DsoSignal::vDialValueCount] = { + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, +}; +const QString DsoSignal::vDialUnit[DsoSignal::vDialUnitCount] = { + "mv", + "v", +}; + +const uint64_t DsoSignal::hDialValue[DsoSignal::hDialValueCount] = { + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 50000, + 100000, + 200000, + 500000, + 1000000, + 2000000, + 5000000, + 10000000, + 20000000, + 50000000, + 100000000, +}; + +const QString DsoSignal::hDialUnit[DsoSignal::hDialUnitCount] = { + "ns", + "us", + "ms", + "s", +}; + const QColor DsoSignal::SignalColours[4] = { QColor(238, 178, 17, 200), // dsYellow QColor(0, 153, 37, 200), // dsGreen @@ -44,35 +98,74 @@ const QColor DsoSignal::SignalColours[4] = { const float DsoSignal::EnvelopeThreshold = 256.0f; -DsoSignal::DsoSignal(QString name, boost::shared_ptr data, - int probe_index, int order, uint64_t vdiv, uint64_t timebase, bool coupling, bool active) : - Signal(name, probe_index, DS_DSO, order), - _data(data) +const int DsoSignal::UpMargin = 30; +const int DsoSignal::DownMargin = 30; +const int DsoSignal::RightMargin = 30; + +DsoSignal::DsoSignal(boost::shared_ptr dev_inst, + shared_ptr data, + const sr_channel * const probe): + Signal(dev_inst, probe, DS_DSO), + _data(data), + _scale(0), + _vDialActive(false), + _hDialActive(false), + _trig_vpos(probe->index * 0.5 + 0.25), + _zeroPos(probe->index * 0.5 + 0.25) { - _colour = SignalColours[probe_index % countof(SignalColours)]; - _scale = _windowHeight * 1.0f / 256; - _vDial->set_value(vdiv); - _hDial->set_value(timebase); - _acCoupling = coupling; - _active = active; + QVector vValue; + QVector vUnit; + QVector hValue; + QVector hUnit; + for(quint64 i = 0; i < vDialValueCount; i++) + vValue.append(vDialValue[i]); + for(quint64 i = 0; i < vDialUnitCount; i++) + vUnit.append(vDialUnit[i]); + + for(quint64 i = 0; i < hDialValueCount; i++) + hValue.append(hDialValue[i]); + for(quint64 i = 0; i < hDialUnitCount; i++) + hUnit.append(hDialUnit[i]); + + _vDial = new dslDial(vDialValueCount, vDialValueStep, vValue, vUnit); + _hDial = new dslDial(hDialValueCount, hDialValueStep, hValue, hUnit); + + _colour = SignalColours[probe->index % countof(SignalColours)]; + + GVariant* gvar; + + gvar = dev_inst->get_config(probe, NULL, SR_CONF_VDIV); + if (gvar != NULL) { + _vDial->set_value(g_variant_get_uint64(gvar)); + g_variant_unref(gvar); + } else { + qDebug() << "ERROR: config_get SR_CONF_VDIV failed."; + } + + gvar = dev_inst->get_config(NULL, NULL, SR_CONF_TIMEBASE); + if (gvar != NULL) { + _hDial->set_value(g_variant_get_uint64(gvar)); + g_variant_unref(gvar); + } else { + qDebug() << "ERROR: config_get SR_CONF_TIMEBASE failed."; + } + + gvar = dev_inst->get_config(probe, NULL, SR_CONF_COUPLING); + if (gvar != NULL) { + _acCoupling = g_variant_get_boolean(gvar); + g_variant_unref(gvar); + } else { + qDebug() << "ERROR: config_get SR_CONF_COUPLING failed."; + } } DsoSignal::~DsoSignal() { } -void DsoSignal::set_data(boost::shared_ptr _logic_data, - boost::shared_ptr _dso_data, - boost::shared_ptr _analog_data, - boost::shared_ptr _group_data) +shared_ptr DsoSignal::data() const { - (void)_analog_data; - (void)_logic_data; - (void)_group_data; - - assert(_dso_data); - - _data = _dso_data; + return _data; } void DsoSignal::set_scale(float scale) @@ -80,47 +173,333 @@ void DsoSignal::set_scale(float scale) _scale = scale; } -void DsoSignal::paint(QPainter &p, int y, int left, int right, double scale, - double offset) +void DsoSignal::set_enable(bool enable) { - assert(scale > 0); - assert(_data); - assert(right >= left); + _dev_inst->set_config(_probe, NULL, SR_CONF_EN_CH, + g_variant_new_boolean(enable)); + _view->set_need_update(true); + _view->update(); +} - const deque< boost::shared_ptr > &snapshots = - _data->get_snapshots(); - if (snapshots.empty()) - return; +bool DsoSignal::get_vDialActive() const +{ + return _vDialActive; +} - _scale = _windowHeight * 1.0f / 256; - const boost::shared_ptr &snapshot = - snapshots.front(); - - const uint16_t number_channels = snapshot->get_channel_num(); - if ((unsigned int)get_index() >= number_channels) - return; - - const double pixels_offset = offset / scale; - const double samplerate = _data->get_samplerate(); - const double start_time = _data->get_start_time(); - const int64_t last_sample = max((int64_t)(snapshot->get_sample_count() - 1), (int64_t)0); - const double samples_per_pixel = samplerate * scale; - const double start = samplerate * (offset - start_time); - const double end = start + samples_per_pixel * (right - left); - - const int64_t start_sample = min(max((int64_t)floor(start), - (int64_t)0), last_sample); - const int64_t end_sample = min(max((int64_t)ceil(end) + 1, - (int64_t)0), last_sample); - - if (samples_per_pixel < EnvelopeThreshold) - paint_trace(p, snapshot, y, left, - start_sample, end_sample, - pixels_offset, samples_per_pixel, number_channels); - else - paint_envelope(p, snapshot, y, left, - start_sample, end_sample, - pixels_offset, samples_per_pixel); +void DsoSignal::set_vDialActive(bool active) +{ + if (enabled()) + _vDialActive = active; +} + +bool DsoSignal::go_vDialPre() +{ + if (enabled() && !_vDial->isMin()) { + _vDial->set_sel(_vDial->get_sel() - 1); + _dev_inst->set_config(_probe, NULL, SR_CONF_VDIV, + g_variant_new_uint64(_vDial->get_value())); + return true; + } else { + return false; + } +} + +bool DsoSignal::go_vDialNext() +{ + if (enabled() && !_vDial->isMax()) { + _vDial->set_sel(_vDial->get_sel() + 1); + _dev_inst->set_config(_probe, NULL, SR_CONF_VDIV, + g_variant_new_uint64(_vDial->get_value())); + return true; + } else { + return false; + } +} + +bool DsoSignal::get_hDialActive() const +{ + return _hDialActive; +} + +void DsoSignal::set_hDialActive(bool active) +{ + if (enabled()) + _hDialActive = active; +} + +bool DsoSignal::go_hDialPre() +{ + if (!_hDial->isMin()) { + _hDial->set_sel(_hDial->get_sel() - 1); + int ch_num = _view->session().get_dso_ch_num(); + uint64_t sample_limit = _view->session().get_device()->get_sample_limit(); + uint64_t sample_rate = min((uint64_t)(sample_limit * pow(10, 9) / (_hDial->get_value() * DS_CONF_DSO_HDIVS)), + (uint64_t)(DS_MAX_DSO_SAMPLERATE / ch_num)); + _view->session().set_sample_rate(sample_rate); + const double scale = _hDial->get_value() * pow(10, -9) * DS_CONF_DSO_HDIVS / get_view_rect().width(); + _view->set_scale_offset(scale, _view->offset()); + _dev_inst->set_config(_probe, NULL, SR_CONF_TIMEBASE, + g_variant_new_uint64(_hDial->get_value())); + return true; + } else { + return false; + } +} + +bool DsoSignal::go_hDialNext() +{ + if (!_hDial->isMax()) { + _hDial->set_sel(_hDial->get_sel() + 1); + int ch_num = _view->session().get_dso_ch_num(); + uint64_t sample_limit = _view->session().get_device()->get_sample_limit(); + uint64_t sample_rate = min((uint64_t)(sample_limit * pow(10, 9) / (_hDial->get_value() * DS_CONF_DSO_HDIVS)), + (uint64_t)(DS_MAX_DSO_SAMPLERATE / ch_num)); + _view->session().set_sample_rate(sample_rate); + const double scale = _hDial->get_value() * pow(10, -9) * DS_CONF_DSO_HDIVS / get_view_rect().width(); + _view->set_scale_offset(scale, _view->offset()); + _dev_inst->set_config(_probe, NULL, SR_CONF_TIMEBASE, + g_variant_new_uint64(_hDial->get_value())); + return true; + } else { + return false; + } +} + +uint64_t DsoSignal::get_vDialValue() const +{ + return _vDial->get_value(); +} + +uint64_t DsoSignal::get_hDialValue() const +{ + return _hDial->get_value(); +} + +uint16_t DsoSignal::get_vDialSel() const +{ + return _vDial->get_sel(); +} + +uint16_t DsoSignal::get_hDialSel() const +{ + return _hDial->get_sel(); +} + +bool DsoSignal::get_acCoupling() const +{ + return _acCoupling; +} + +void DsoSignal::set_acCoupling(bool coupling) +{ + if (enabled()) { + _acCoupling = coupling; + _dev_inst->set_config(_probe, NULL, SR_CONF_COUPLING, + g_variant_new_boolean(_acCoupling)); + } +} + +int DsoSignal::get_trig_vpos() const +{ + return _trig_vpos * get_view_rect().height() + UpMargin; +} + +void DsoSignal::set_trig_vpos(int pos) +{ + assert(_view); + if (enabled()) { + double delta = min((double)max(pos - UpMargin, 0), get_view_rect().height()) * 1.0f / get_view_rect().height() - _zeroPos; + delta = min(delta, 0.5); + delta = max(delta, -0.5); + _trig_vpos = _zeroPos + delta; + + int trig_value = (-delta * 255.0f + 0x80); + + _dev_inst->set_config(_probe, NULL, SR_CONF_TRIGGER_VALUE, + g_variant_new_uint16(trig_value)); + } +} + +int DsoSignal::get_zeroPos() +{ + return _zeroPos * get_view_rect().height() + UpMargin; +} + +void DsoSignal::set_zeroPos(int pos) +{ + if (enabled()) { + double delta = _trig_vpos - _zeroPos; + _zeroPos = min((double)max(pos - UpMargin, 0), get_view_rect().height()) * 1.0f / get_view_rect().height(); + _trig_vpos = min(max(_zeroPos + delta, 0.0), 1.0); + } +} + +QRectF DsoSignal::get_view_rect() const +{ + assert(_view); + return QRectF(0, UpMargin, + _view->viewport()->width() - RightMargin, + _view->viewport()->height() - UpMargin - DownMargin); +} + +void DsoSignal::paint_back(QPainter &p, int left, int right) +{ + assert(_view); + + int i, j; + const int height = _view->viewport()->height() - UpMargin - DownMargin; + const int width = right - left - RightMargin; + + p.setPen(Qt::NoPen); + p.setBrush(Trace::dsBack); + p.drawRect(left, UpMargin, width, height); + + p.setPen(Trace::dsLightBlue); + p.drawLine(left, UpMargin/2, left + width, UpMargin/2); + const uint64_t sample_len = _dev_inst->get_sample_limit(); + const double samplerate = _dev_inst->get_sample_rate(); + const double samples_per_pixel = samplerate * _view->scale(); + const double shown_rate = min(samples_per_pixel * width * 1.0f / sample_len, 1.0); + const double start_time = _data->get_start_time(); + const double start = samplerate * (_view->offset() - start_time); + const double shown_offset = min(start / sample_len, 1.0) * width; + const double shown_len = shown_rate * width; + const QPointF left_edge[] = {QPoint(shown_offset + 3, UpMargin/2 - 6), + QPoint(shown_offset, UpMargin/2 - 6), + QPoint(shown_offset, UpMargin/2 + 6), + QPoint(shown_offset + 3, UpMargin/2 + 6)}; + const QPointF right_edge[] = {QPoint(shown_offset + shown_len - 3, UpMargin/2 - 6), + QPoint(shown_offset + shown_len , UpMargin/2 - 6), + QPoint(shown_offset + shown_len , UpMargin/2 + 6), + QPoint(shown_offset + shown_len - 3, UpMargin/2 + 6)}; + p.drawPolyline(left_edge, countof(left_edge)); + p.drawPolyline(right_edge, countof(right_edge)); + p.setBrush(Trace::dsBlue); + p.drawRect(shown_offset, UpMargin/2 - 3, shown_len, 6); + + QPen pen(Signal::dsFore); + pen.setStyle(Qt::DotLine); + p.setPen(pen); + const double spanY =height * 1.0f / 10; + for (i = 1; i <= DS_CONF_DSO_VDIVS; i++) { + const double posY = spanY * i + UpMargin; + p.drawLine(left, posY, right - RightMargin, posY); + const double miniSpanY = spanY / 5; + for (j = 1; j < 5; j++) { + p.drawLine(width / 2.0f - 10, posY - miniSpanY * j, + width / 2.0f + 10, posY - miniSpanY * j); + } + } + const double spanX = width * 1.0f / 10; + for (i = 1; i <= DS_CONF_DSO_HDIVS; i++) { + const double posX = spanX * i; + p.drawLine(posX, UpMargin, + posX, height + UpMargin); + const double miniSpanX = spanX / 5; + for (j = 1; j < 5; j++) { + p.drawLine(posX - miniSpanX * j, height / 2.0f + UpMargin - 10, + posX - miniSpanX * j, height / 2.0f + UpMargin + 10); + } + } +} + +void DsoSignal::paint_mid(QPainter &p, int left, int right) +{ + assert(_data); + assert(_view); + assert(right >= left); + + if (enabled()) { + const int height = _view->viewport()->height() - UpMargin - DownMargin; + const int width = right - left - RightMargin; + + const int y = get_zeroPos() + height * 0.5; + const double scale = _view->scale(); + assert(scale > 0); + const double offset = _view->offset(); + + const deque< boost::shared_ptr > &snapshots = + _data->get_snapshots(); + if (snapshots.empty()) + return; + + _scale = height * 1.0f / 256; + const shared_ptr &snapshot = + snapshots.front(); + + const uint16_t number_channels = snapshot->get_channel_num(); + if ((unsigned int)get_index() >= number_channels) + return; + + const double pixels_offset = offset / scale; + //const double samplerate = _data->samplerate(); + const double samplerate = _dev_inst->get_sample_rate(); + const double start_time = _data->get_start_time(); + const int64_t last_sample = max((int64_t)(snapshot->get_sample_count() - 1), (int64_t)0); + const double samples_per_pixel = samplerate * scale; + const double start = samplerate * (offset - start_time); + const double end = start + samples_per_pixel * width; + + const int64_t start_sample = min(max((int64_t)floor(start), + (int64_t)0), last_sample); + const int64_t end_sample = min(max((int64_t)ceil(end) + 1, + (int64_t)0), last_sample); + + if (samples_per_pixel < EnvelopeThreshold) + paint_trace(p, snapshot, y, left, + start_sample, end_sample, + pixels_offset, samples_per_pixel, number_channels); + else + paint_envelope(p, snapshot, y, left, + start_sample, end_sample, + pixels_offset, samples_per_pixel); + } +} + +void DsoSignal::paint_fore(QPainter &p, int left, int right) +{ + assert(_view); + + QPen pen(Signal::dsGray); + pen.setStyle(Qt::DotLine); + p.setPen(pen); + p.drawLine(left, get_zeroPos(), right - RightMargin, get_zeroPos()); + + if(enabled()) { + const QPointF mouse_point = _view->hover_point(); + const QRectF label_rect = get_trig_rect(left, right); + const bool hover = label_rect.contains(mouse_point); + + // Paint the trig line + const QPointF points[] = { + QPointF(right - label_rect.width()*1.5, get_trig_vpos()), + label_rect.topLeft(), + label_rect.topRight(), + label_rect.bottomRight(), + label_rect.bottomLeft() + }; + + p.setPen(Qt::transparent); + p.setBrush(hover ? _colour.dark() : _colour); + p.drawPolygon(points, countof(points)); + + // paint the _trig_vpos line + p.setPen(QPen(_colour, 1, Qt::DashLine)); + p.drawLine(left, get_trig_vpos(), right - label_rect.width()*1.5, get_trig_vpos()); + + // Paint the text + p.setPen(Qt::white); + p.drawText(label_rect, Qt::AlignCenter | Qt::AlignVCenter, "T"); + } +} + +QRectF DsoSignal::get_trig_rect(int left, int right) const +{ + (void)left; + + return QRectF(right - SquareWidth, + get_trig_vpos() - SquareWidth / 2, + SquareWidth, SquareWidth); } void DsoSignal::paint_trace(QPainter &p, @@ -140,11 +519,15 @@ void DsoSignal::paint_trace(QPainter &p, QPointF *points = new QPointF[sample_count]; QPointF *point = points; + float top = get_view_rect().top(); + float bottom = get_view_rect().bottom(); for (int64_t sample = start; sample < end; sample++) { const float x = (sample / samples_per_pixel - pixels_offset) + left; uint8_t offset = samples[(sample - start)*num_channels]; - *point++ = QPointF(x, - y - offset * _scale); + if (offset >0 && offset < 0xff) { + const float yp = min(bottom, max(top, y - offset * _scale)); + *point++ = QPointF(x, yp); + } } p.drawPolyline(points, point - points); @@ -206,18 +589,30 @@ const std::vector< std::pair > DsoSignal::cur_edges() const } -void DsoSignal::set_decoder(pv::decoder::Decoder *decoder) -{ - (void)decoder; -} - -decoder::Decoder *DsoSignal::get_decoder() -{ - return NULL; -} - -void DsoSignal::del_decoder() +void DsoSignal::paint_type_options(QPainter &p, int right, bool hover, int action) { + int y = get_y(); + const QRectF vDial_rect = get_rect("vDial", y, right); + const QRectF hDial_rect = get_rect("hDial", y, right); + const QRectF acdc_rect = get_rect("acdc", y, right); + const QRectF chEn_rect = get_rect("chEn", y, right); + + QColor vDial_color = _vDialActive ? dsActive : dsDisable; + QColor hDial_color = _hDialActive ? dsActive : dsDisable; + _vDial->paint(p, vDial_rect, vDial_color); + _hDial->paint(p, hDial_rect, hDial_color); + + p.setPen(Qt::transparent); + p.setBrush((hover && action == CHEN) ? _colour.darker() : _colour); + p.drawRect(chEn_rect); + p.setPen(Qt::white); + p.drawText(chEn_rect, Qt::AlignCenter | Qt::AlignVCenter, enabled() ? "EN" : "DIS"); + + p.setPen(Qt::transparent); + p.setBrush(enabled() ? ((hover && action == ACDC) ? _colour.darker() : _colour) : dsDisable); + p.drawRect(acdc_rect); + p.setPen(Qt::white); + p.drawText(acdc_rect, Qt::AlignCenter | Qt::AlignVCenter, _acCoupling ? "AC" : "DC"); } } // namespace view diff --git a/DSLogic-gui/pv/view/dsosignal.h b/DSLogic-gui/pv/view/dsosignal.h index e22680a..c6b6626 100644 --- a/DSLogic-gui/pv/view/dsosignal.h +++ b/DSLogic-gui/pv/view/dsosignal.h @@ -42,43 +42,99 @@ class DsoSignal : public Signal { private: static const QColor SignalColours[4]; - static const float EnvelopeThreshold; + static const int HitCursorMargin = 3; + static const uint64_t vDialValueCount = 10; + static const uint64_t vDialValueStep = 1000; + static const uint64_t vDialUnitCount = 2; + static const uint64_t hDialValueCount = 22; + static const uint64_t hDialValueStep = 1000; + static const uint64_t hDialUnitCount = 4; + static const uint64_t vDialValue[vDialValueCount]; + static const QString vDialUnit[vDialUnitCount]; + + static const uint64_t hDialValue[hDialValueCount]; + static const QString hDialUnit[hDialUnitCount]; + + static const int UpMargin; + static const int DownMargin; + static const int RightMargin; + public: - DsoSignal(QString name, - boost::shared_ptr data, int probe_index, int order, - uint64_t vdiv, uint64_t timebase, bool coupling, bool active); + DsoSignal(boost::shared_ptr dev_inst, + boost::shared_ptr data, + const sr_channel * const probe); virtual ~DsoSignal(); + boost::shared_ptr data() const; + void set_scale(float scale); + /** + * + */ + void set_enable(bool enable); + bool get_vDialActive() const; + void set_vDialActive(bool active); + bool get_hDialActive() const; + void set_hDialActive(bool active); + bool go_vDialPre(); + bool go_vDialNext(); + bool go_hDialPre(); + bool go_hDialNext(); + uint64_t get_vDialValue() const; + uint64_t get_hDialValue() const; + uint16_t get_vDialSel() const; + uint16_t get_hDialSel() const; + bool get_acCoupling() const; + void set_acCoupling(bool coupling); + void set_trig_vpos(int pos); + int get_trig_vpos() const; + + /** + * Gets the mid-Y position of this signal. + */ + int get_zeroPos(); + + /** + * Sets the mid-Y position of this signal. + */ + void set_zeroPos(int pos); + + /** + * Paints the background layer of the trace with a QPainter + * @param p the QPainter to paint into. + * @param left the x-coordinate of the left edge of the signal + * @param right the x-coordinate of the right edge of the signal + **/ + void paint_back(QPainter &p, int left, int right); + /** * Paints the signal with a QPainter * @param p the QPainter to paint into. - * @param y the y-coordinate to draw the signal at. * @param left the x-coordinate of the left edge of the signal. * @param right the x-coordinate of the right edge of the signal. - * @param scale the scale in seconds per pixel. - * @param offset the time to show at the left hand edge of - * the view in seconds. **/ - void paint(QPainter &p, int y, int left, int right, double scale, - double offset); + void paint_mid(QPainter &p, int left, int right); - const std::vector< std::pair > cur_edges() const; + /** + * Paints the signal with a QPainter + * @param p the QPainter to paint into. + * @param left the x-coordinate of the left edge of the signal. + * @param right the x-coordinate of the right edge of the signal. + **/ + void paint_fore(QPainter &p, int left, int right); - void set_decoder(pv::decoder::Decoder *decoder); + const std::vector< std::pair > cur_edges() const; - pv::decoder::Decoder* get_decoder(); + QRectF get_view_rect() const; - void del_decoder(); + QRectF get_trig_rect(int left, int right) const; - void set_data(boost::shared_ptr _logic_data, - boost::shared_ptr _dso_data, - boost::shared_ptr _analog_data, - boost::shared_ptr _group_data); +protected: + void paint_type_options(QPainter &p, int right, bool hover, int action); private: void paint_trace(QPainter &p, @@ -95,6 +151,15 @@ private: private: boost::shared_ptr _data; float _scale; + + dslDial *_vDial; + dslDial *_hDial; + bool _vDialActive; + bool _hDialActive; + bool _acCoupling; + + double _trig_vpos; + double _zeroPos; }; } // namespace view diff --git a/DSLogic-gui/pv/view/groupsignal.cpp b/DSLogic-gui/pv/view/groupsignal.cpp index 5330c06..1ba1bfe 100644 --- a/DSLogic-gui/pv/view/groupsignal.cpp +++ b/DSLogic-gui/pv/view/groupsignal.cpp @@ -28,6 +28,7 @@ #include "groupsignal.h" #include "pv/data/group.h" #include "pv/data/groupsnapshot.h" +#include "view.h" using namespace boost; using namespace std; @@ -45,8 +46,8 @@ const QColor GroupSignal::SignalColours[4] = { const float GroupSignal::EnvelopeThreshold = 256.0f; GroupSignal::GroupSignal(QString name, boost::shared_ptr data, - std::list probe_index_list, int order, int group_index) : - Signal(name, probe_index_list, DS_GROUP, order, group_index), + std::list probe_index_list, int group_index) : + Trace(name, probe_index_list, DS_GROUP, group_index), _data(data) { _colour = SignalColours[probe_index_list.front() % countof(SignalColours)]; @@ -57,18 +58,14 @@ GroupSignal::~GroupSignal() { } -void GroupSignal::set_data(boost::shared_ptr _logic_data, - boost::shared_ptr _dso_data, - boost::shared_ptr _analog_data, - boost::shared_ptr _group_data) +bool GroupSignal::enabled() const { - (void)_logic_data; - (void)_dso_data; - (void)_analog_data; - - assert(_group_data); + return true; +} - _data = _group_data; +shared_ptr GroupSignal::data() const +{ + return _data; } void GroupSignal::set_scale(float scale) @@ -76,15 +73,18 @@ void GroupSignal::set_scale(float scale) _scale = scale; } -void GroupSignal::paint(QPainter &p, int y, int left, int right, double scale, - double offset) +void GroupSignal::paint_mid(QPainter &p, int left, int right) { - assert(scale > 0); - assert(_data); - assert(right >= left); + assert(_data); + assert(_view); + assert(right >= left); + + const int y = get_y() + _signalHeight * 0.5; + const double scale = _view->scale(); + assert(scale > 0); + const double offset = _view->offset(); _scale = _signalHeight * 1.0f / pow(2, _index_list.size()); - paint_axis(p, y, left, right); const deque< boost::shared_ptr > &snapshots = _data->get_snapshots(); @@ -95,7 +95,7 @@ void GroupSignal::paint(QPainter &p, int y, int left, int right, double scale, snapshots.at(_sec_index); const double pixels_offset = offset / scale; - const double samplerate = _data->get_samplerate(); + const double samplerate = _data->samplerate(); const double start_time = _data->get_start_time(); const int64_t last_sample = snapshot->get_sample_count() - 1; const double samples_per_pixel = samplerate * scale; @@ -196,18 +196,32 @@ const std::vector< std::pair > GroupSignal::cur_edges() const } -void GroupSignal::set_decoder(pv::decoder::Decoder *decoder) -{ - (void)decoder; -} - -decoder::Decoder *GroupSignal::get_decoder() -{ - return NULL; -} - -void GroupSignal::del_decoder() +void GroupSignal::paint_type_options(QPainter &p, int right, bool hover, int action) { + (void)hover; + (void)action; + + int y = get_y(); + const QRectF group_index_rect = get_rect("groupIndex", y, right); + QString index_string; + int last_index; + p.setPen(Qt::transparent); + p.setBrush(dsBlue); + p.drawRect(group_index_rect); + std::list::iterator i = _index_list.begin(); + last_index = (*i); + index_string = QString::number(last_index); + while (++i != _index_list.end()) { + if ((*i) == last_index + 1 && index_string.indexOf("-") < 3 && index_string.indexOf("-") > 0) + index_string.replace(QString::number(last_index), QString::number((*i))); + else if ((*i) == last_index + 1) + index_string = QString::number((*i)) + "-" + index_string; + else + index_string = QString::number((*i)) + "," + index_string; + last_index = (*i); + } + p.setPen(Qt::white); + p.drawText(group_index_rect, Qt::AlignRight | Qt::AlignVCenter, index_string); } } // namespace view diff --git a/DSLogic-gui/pv/view/groupsignal.h b/DSLogic-gui/pv/view/groupsignal.h index d38d1be..04c1bba 100644 --- a/DSLogic-gui/pv/view/groupsignal.h +++ b/DSLogic-gui/pv/view/groupsignal.h @@ -41,7 +41,7 @@ class GroupSnapshot; namespace view { -class GroupSignal : public Signal +class GroupSignal : public Trace { private: static const QColor SignalColours[4]; @@ -51,37 +51,31 @@ private: public: GroupSignal(QString name, boost::shared_ptr data, - std::list probe_index_list, int order, int group_index); + std::list probe_index_list, int group_index); virtual ~GroupSignal(); + /** + * Returns true if the trace is visible and enabled. + */ + bool enabled() const; + + boost::shared_ptr data() const; + void set_scale(float scale); /** * Paints the signal with a QPainter * @param p the QPainter to paint into. - * @param y the y-coordinate to draw the signal at. * @param left the x-coordinate of the left edge of the signal. * @param right the x-coordinate of the right edge of the signal. - * @param scale the scale in seconds per pixel. - * @param offset the time to show at the left hand edge of - * the view in seconds. **/ - void paint(QPainter &p, int y, int left, int right, double scale, - double offset); + void paint_mid(QPainter &p, int left, int right); const std::vector< std::pair > cur_edges() const; - void set_decoder(pv::decoder::Decoder *decoder); - - pv::decoder::Decoder* get_decoder(); - - void del_decoder(); - - void set_data(boost::shared_ptr _logic_data, - boost::shared_ptr _dso_data, - boost::shared_ptr _analog_data, - boost::shared_ptr _group_data); +protected: + void paint_type_options(QPainter &p, int right, bool hover, int action); private: void paint_trace(QPainter &p, diff --git a/DSLogic-gui/pv/view/header.cpp b/DSLogic-gui/pv/view/header.cpp index 6803e3a..8301fb3 100644 --- a/DSLogic-gui/pv/view/header.cpp +++ b/DSLogic-gui/pv/view/header.cpp @@ -24,8 +24,14 @@ #include "header.h" #include "view.h" -#include "signal.h" +#include "trace.h" +#include "dsosignal.h" +#include "logicsignal.h" +#include "analogsignal.h" +#include "groupsignal.h" +#include "decodetrace.h" #include "../sigsession.h" +#include "../device/devinst.h" #include @@ -70,8 +76,8 @@ Header::Header(View &parent) : connect(nameEdit, SIGNAL(editingFinished()), this, SLOT(on_action_set_name_triggered())); - connect(&_view, SIGNAL(signals_moved()), - this, SLOT(on_signals_moved())); + connect(&_view, SIGNAL(traces_moved()), + this, SLOT(on_traces_moved())); } @@ -83,30 +89,28 @@ int Header::get_nameEditWidth() return 0; } -boost::shared_ptr Header::get_mSig( +boost::shared_ptr Header::get_mTrace( int &action, const QPoint &pt) { const int w = width(); - const vector< boost::shared_ptr > sigs( - _view.session().get_signals()); + const vector< boost::shared_ptr > traces( + _view.get_traces()); - const int v_offset = _view.v_offset(); - BOOST_FOREACH(const boost::shared_ptr s, sigs) + BOOST_FOREACH(const boost::shared_ptr t, traces) { - assert(s); + assert(t); - if ((action = s->pt_in_rect(s->get_v_offset() - v_offset - _view.get_signalHeight() / 2, - w, pt))) - return s; + if ((action = t->pt_in_rect(t->get_y(), w, pt))) + return t; } - return boost::shared_ptr(); + return boost::shared_ptr(); } void Header::paintEvent(QPaintEvent*) { - using pv::view::Signal; + using pv::view::Trace; QStyleOption o; o.initFrom(this); @@ -114,47 +118,22 @@ void Header::paintEvent(QPaintEvent*) style()->drawPrimitive(QStyle::PE_Widget, &o, &painter, this); const int w = width(); - int action; - const vector< boost::shared_ptr > sigs( - _view.session().get_signals()); + int action = 0; + const vector< boost::shared_ptr > traces( + _view.get_traces()); //QPainter painter(this); //painter.setRenderHint(QPainter::Antialiasing); - const int v_offset = _view.v_offset(); - const bool dragging = !_drag_sigs.empty(); - BOOST_FOREACH(const boost::shared_ptr s, sigs) + const bool dragging = !_drag_traces.empty(); + BOOST_FOREACH(const boost::shared_ptr t, traces) { - assert(s); + assert(t); - const int y = s->get_v_offset() - v_offset - _view.get_signalHeight() / 2; + const int y = t->get_y(); const bool highlight = !dragging && - (action = s->pt_in_rect(y, w, _mouse_point)); - s->paint_label(painter, y, w, highlight, action); - // Paint the Backgroud - painter.setRenderHint(QPainter::Antialiasing, false); - painter.setPen(Signal::dsGray); - if (s->selected() && _moveFlag) { - if (s->get_type() == Signal::DS_ANALOG) { - painter.drawLine(0, s->get_old_v_offset() - v_offset - s->get_signalHeight(), - w, s->get_old_v_offset() - v_offset - s->get_signalHeight()); - painter.drawLine(0, s->get_old_v_offset() - v_offset, - w, s->get_old_v_offset() - v_offset); - } else if (s->get_type() == Signal::DS_LOGIC){ - painter.drawLine(0, s->get_old_v_offset() - v_offset + 10, - w, s->get_old_v_offset() - v_offset + 10); - } - } else { - if (s->get_type() == Signal::DS_ANALOG) { - painter.drawLine(0, s->get_v_offset() - v_offset, - w, s->get_v_offset() - v_offset); - painter.drawLine(0, s->get_v_offset() - v_offset - s->get_signalHeight(), - w, s->get_v_offset() - v_offset - s->get_signalHeight()); - } else if (s->get_type() == Signal::DS_LOGIC) { - painter.drawLine(0, s->get_v_offset() - v_offset + 10, - w, s->get_v_offset() - v_offset + 10); - } - } + (action = t->pt_in_rect(y, w, _mouse_point)); + t->paint_label(painter, w, highlight, action); } painter.end(); @@ -164,126 +143,116 @@ void Header::mousePressEvent(QMouseEvent *event) { assert(event); - const vector< boost::shared_ptr > sigs( - _view.session().get_signals()); + const vector< boost::shared_ptr > traces( + _view.get_traces()); int action; if (event->button() & Qt::LeftButton) { _mouse_down_point = event->pos(); - // Save the offsets of any signals which will be dragged - BOOST_FOREACH(const boost::shared_ptr s, sigs) - if (s->selected()) - _drag_sigs.push_back( - make_pair(s, s->get_v_offset())); + // Save the offsets of any Traces which will be dragged + BOOST_FOREACH(const boost::shared_ptr t, traces) + if (t->selected()) + _drag_traces.push_back( + make_pair(t, t->get_v_offset())); - // Select the signal if it has been clicked - const boost::shared_ptr mSig = - get_mSig(action, event->pos()); - if (action == Signal::COLOR && mSig) { + // Select the Trace if it has been clicked + const boost::shared_ptr mTrace = + get_mTrace(action, event->pos()); + if (action == Trace::COLOR && mTrace) { _colorFlag = true; - } else if (action == Signal::NAME && mSig) { + } else if (action == Trace::NAME && mTrace) { _nameFlag = true; - } else if (action == Signal::LABEL && mSig) { - if (mSig->selected()) - mSig->select(false); + } else if (action == Trace::LABEL && mTrace) { + if (mTrace->selected()) + mTrace->select(false); else { - if (mSig->get_type() != Signal::DS_DSO) - mSig->select(true); + if (mTrace->get_type() != Trace::DS_DSO) + mTrace->select(true); if (~QApplication::keyboardModifiers() & Qt::ControlModifier) - _drag_sigs.clear(); + _drag_traces.clear(); - // Add the signal to the drag list - if (event->button() & Qt::LeftButton) - _drag_sigs.push_back( - make_pair(mSig, - (mSig->get_type() == Signal::DS_DSO) ? mSig->get_zeroPos() : mSig->get_v_offset())); + // Add the Trace to the drag list + if (event->button() & Qt::LeftButton) { + _drag_traces.push_back(make_pair(mTrace, mTrace->get_zeroPos())); + } } - mSig->set_old_v_offset(mSig->get_v_offset()); - } else if (action == Signal::POSTRIG && mSig) { - if (mSig->get_trig() == Signal::POSTRIG) - mSig->set_trig(0); + mTrace->set_old_v_offset(mTrace->get_v_offset()); + } else if (action == Trace::POSTRIG && mTrace) { + if (mTrace->get_trig() == Trace::POSTRIG) + mTrace->set_trig(0); else - mSig->set_trig(Signal::POSTRIG); - } else if (action == Signal::HIGTRIG && mSig) { - if (mSig->get_trig() == Signal::HIGTRIG) - mSig->set_trig(0); + mTrace->set_trig(Trace::POSTRIG); + } else if (action == Trace::HIGTRIG && mTrace) { + if (mTrace->get_trig() == Trace::HIGTRIG) + mTrace->set_trig(0); else - mSig->set_trig(Signal::HIGTRIG); - } else if (action == Signal::NEGTRIG && mSig) { - if (mSig->get_trig() == Signal::NEGTRIG) - mSig->set_trig(0); + mTrace->set_trig(Trace::HIGTRIG); + } else if (action == Trace::NEGTRIG && mTrace) { + if (mTrace->get_trig() == Trace::NEGTRIG) + mTrace->set_trig(0); else - mSig->set_trig(Signal::NEGTRIG); - } else if (action == Signal::LOWTRIG && mSig) { - if (mSig->get_trig() == Signal::LOWTRIG) - mSig->set_trig(0); + mTrace->set_trig(Trace::NEGTRIG); + } else if (action == Trace::LOWTRIG && mTrace) { + if (mTrace->get_trig() == Trace::LOWTRIG) + mTrace->set_trig(0); else - mSig->set_trig(Signal::LOWTRIG); - } else if (action == Signal::EDGETRIG && mSig) { - if (mSig->get_trig() == Signal::EDGETRIG) - mSig->set_trig(0); + mTrace->set_trig(Trace::LOWTRIG); + } else if (action == Trace::EDGETRIG && mTrace) { + if (mTrace->get_trig() == Trace::EDGETRIG) + mTrace->set_trig(0); else - mSig->set_trig(Signal::EDGETRIG); - } else if (action == Signal::VDIAL && mSig) { - BOOST_FOREACH(const shared_ptr s, sigs) { - s->set_hDialActive(false); - if (s != mSig) { - s->set_vDialActive(false); - } - } - mSig->set_vDialActive(!mSig->get_vDialActive()); - } else if (action == Signal::HDIAL && mSig) { - if (mSig->get_hDialActive()) { - BOOST_FOREACH(const shared_ptr s, sigs) { - s->set_vDialActive(false); - s->set_hDialActive(false); - } - } else { - BOOST_FOREACH(const shared_ptr s, sigs) { - s->set_vDialActive(false); - s->set_hDialActive(true); + mTrace->set_trig(Trace::EDGETRIG); + } else if (action == Trace::VDIAL && mTrace) { + shared_ptr dsoSig; + BOOST_FOREACH(const shared_ptr t, traces) { + if (dsoSig = dynamic_pointer_cast(t)) { + dsoSig->set_hDialActive(false); + if (t != mTrace) { + dsoSig->set_vDialActive(false); + } } } - } else if (action == Signal::CHEN && mSig) { - int channel; - if (mSig->get_index() == 0) { - bool last = 1; - channel = 0; - BOOST_FOREACH(const shared_ptr s, sigs) { - if (s->get_index() != 0 && s->get_active()) { - QMessageBox msg(this); - msg.setText("Tips"); - msg.setInformativeText("If only one channel want, Channel0 has a higher maximum sample rate!"); - msg.setStandardButtons(QMessageBox::Ok); - msg.setIcon(QMessageBox::Information); - msg.exec(); - s->set_active(!s->get_active()); - last = 0; - channel = s->get_index(); - break; + if (dsoSig = dynamic_pointer_cast(mTrace)) + dsoSig->set_vDialActive(!dsoSig->get_vDialActive()); + } else if (action == Trace::HDIAL && mTrace) { + shared_ptr dsoSig; + if (dsoSig = dynamic_pointer_cast(mTrace)) { + if (dsoSig->get_hDialActive()) { + BOOST_FOREACH(const shared_ptr t, traces) { + if(dsoSig = dynamic_pointer_cast(t)) { + dsoSig->set_vDialActive(false); + dsoSig->set_hDialActive(false); + } + } + } else { + BOOST_FOREACH(const shared_ptr t, traces) { + if(dsoSig = dynamic_pointer_cast(t)) { + dsoSig->set_vDialActive(false); + dsoSig->set_hDialActive(true); + } } } - if (last) - mSig->set_active(!mSig->get_active()); - } else { - mSig->set_active(!mSig->get_active()); - channel = mSig->get_index(); } - ch_changed(channel); - } else if (action == Signal::ACDC && mSig) { - mSig->set_acCoupling(!mSig->get_acCoupling()); - acdc_changed(mSig->get_index()); + } else if (action == Trace::CHEN && mTrace) { + shared_ptr dsoSig; + if (dsoSig = dynamic_pointer_cast(mTrace)) { + dsoSig->set_enable(!dsoSig->enabled()); + } + } else if (action == Trace::ACDC && mTrace) { + shared_ptr dsoSig; + if (dsoSig = dynamic_pointer_cast(mTrace)) + dsoSig->set_acCoupling(!dsoSig->get_acCoupling()); } if (~QApplication::keyboardModifiers() & Qt::ControlModifier) { - // Unselect all other signals because the Ctrl is not + // Unselect all other Traces because the Ctrl is not // pressed - BOOST_FOREACH(const boost::shared_ptr s, sigs) - if (s != mSig) - s->select(false); + BOOST_FOREACH(const shared_ptr t, traces) + if (t != mTrace) + t->select(false); } update(); } @@ -295,26 +264,27 @@ void Header::mouseReleaseEvent(QMouseEvent *event) // judge for color / name / trigger / move int action; - const boost::shared_ptr mSig = - get_mSig(action, event->pos()); - if (mSig){ - if (action == Signal::COLOR && _colorFlag) { - _context_signal = mSig; + const boost::shared_ptr mTrace = + get_mTrace(action, event->pos()); + if (mTrace){ + if (action == Trace::COLOR && _colorFlag) { + _context_trace = mTrace; changeColor(event); _view.set_need_update(true); - } else if (action == Signal::NAME && _nameFlag) { - _context_signal = mSig; + } else if (action == Trace::NAME && _nameFlag) { + _context_trace = mTrace; changeName(event); } } if (_moveFlag) { - move(event); + //move(event); + _view.signals_changed(); _view.set_need_update(true); } _colorFlag = false; _nameFlag = false; _moveFlag = false; - _drag_sigs.clear(); + _drag_traces.clear(); _view.normalize_layout(); } @@ -323,30 +293,24 @@ void Header::wheelEvent(QWheelEvent *event) assert(event); if (event->orientation() == Qt::Vertical) { - const vector< shared_ptr > sigs( - _view.session().get_signals()); + const vector< shared_ptr > traces( + _view.get_traces()); // Vertical scrolling double shift = event->delta() / 20.0; - if (shift > 1.0) { - BOOST_FOREACH(const shared_ptr s, sigs) { - if (s->get_vDialActive()) { - if(s->go_vDialNext()) - vDial_changed(s->get_index()); - break; - } else if (s->get_hDialActive()) { - if(s->go_hDialNext()) - hDial_changed(0); - } - } - } else if (shift < -1.0) { - BOOST_FOREACH(const shared_ptr s, sigs) { - if (s->get_vDialActive()) { - if(s->go_vDialPre()) - vDial_changed(s->get_index()); + BOOST_FOREACH(const shared_ptr t, traces) { + shared_ptr dsoSig; + if (dsoSig = dynamic_pointer_cast(t)) { + if (dsoSig->get_vDialActive()) { + if (shift > 1.0) + dsoSig->go_vDialNext(); + else if (shift < -1.0) + dsoSig->go_vDialPre(); break; - } else if (s->get_hDialActive()) { - if(s->go_hDialPre()) - hDial_changed(0); + } else if (dsoSig->get_hDialActive()) { + if (shift > 1.0) + dsoSig->go_hDialNext(); + else if (shift < -1.0) + dsoSig->go_hDialPre(); } } } @@ -354,145 +318,11 @@ void Header::wheelEvent(QWheelEvent *event) } } -void Header::move(QMouseEvent *event) -{ - bool _moveValid = false; - bool _moveUp = false; - bool firstCheck = true; - const vector< boost::shared_ptr > sigs( - _view.session().get_signals()); - boost::shared_ptr minDragSig; - boost::shared_ptr maxDragSig; - int minOffset; - int minOldOffset; - int maxOffset; - int maxOldOffset; - int targetOffset; - std::list, - int> >::iterator minJ; - std::list, - int> >::iterator maxJ; - int targetOrder; - - // reCalculate _v_offset of all signals after dragging release - if ((event->button() == Qt::LeftButton)) { - while (!_drag_sigs.empty()) { - minOffset = INT_MAX; - maxOffset = 0; - for (std::list, - int> >::iterator i = _drag_sigs.begin(); - i != _drag_sigs.end(); i++) { - const boost::shared_ptr sig((*i).first); - if (sig) { - if (sig->get_v_offset() < minOffset) { - minDragSig = sig; - minOldOffset = (*i).second; - minOffset = sig->get_v_offset(); - minJ = i; - } - if (sig->get_v_offset() > maxOffset) { - maxDragSig = sig; - maxOldOffset = (*i).second; - maxOffset = sig->get_v_offset(); - maxJ = i; - } - } - } - if (minOffset > minOldOffset) { - _moveUp = false; - _drag_sigs.erase(maxJ); - } else { - _moveUp = true; - _drag_sigs.erase(minJ); - } - if (!_moveValid && firstCheck){ - firstCheck = false; - BOOST_FOREACH(const boost::shared_ptr s, sigs) { - if (_moveUp) { - if (s->selected()) { - if ((minOffset <= s->get_old_v_offset()) && (minOffset > (s->get_old_v_offset() - _view.get_spanY()))) { - _moveValid = true; - targetOffset = s->get_old_v_offset(); - targetOrder = s->get_order(); - break; - } - } else { - if ((minOffset <= s->get_v_offset()) && (minOffset > (s->get_v_offset() - _view.get_spanY()))) { - _moveValid = true; - targetOffset = s->get_v_offset(); - targetOrder = s->get_order(); - break; - } - } - } else { - if (s->selected()) { - if ((maxOffset >= s->get_old_v_offset()) && (maxOffset < (s->get_old_v_offset() + _view.get_spanY()))) { - _moveValid = true; - targetOffset = s->get_old_v_offset(); - targetOrder = s->get_order(); - break; - } - } else { - if ((maxOffset >= s->get_v_offset()) && (maxOffset < (s->get_v_offset() + _view.get_spanY()))) { - _moveValid = true; - targetOffset = s->get_v_offset(); - targetOrder = s->get_order(); - break; - } - } - } - } - } - if (_moveValid) { - BOOST_FOREACH(const boost::shared_ptr s, sigs) { - if (_moveUp) { - if (s->selected() && s == minDragSig) { - s->set_v_offset(targetOffset); - s->set_order(targetOrder); - s->select(false); - } else if (!s->selected() && s->get_v_offset() >= targetOffset && s->get_v_offset() < minOldOffset) { - s->set_v_offset(s->get_v_offset() + _view.get_spanY()); - s->set_order(s->get_order() + 1); - } - } else { - if (s->selected() && s == maxDragSig) { - s->set_v_offset(targetOffset); - s->set_order(targetOrder); - s->select(false); - } else if (!s->selected() && s->get_v_offset() <= targetOffset && s->get_v_offset() > maxOldOffset) { - s->set_v_offset(s->get_v_offset() - _view.get_spanY()); - s->set_order(s->get_order() - 1); - } - } - } - if (_moveUp) { - targetOffset += _view.get_spanY(); - targetOrder++; - } else { - targetOffset -= _view.get_spanY(); - targetOrder--; - } - } - } - if (_moveValid) { - signals_moved(); - } else { - BOOST_FOREACH(const boost::shared_ptr s, sigs) { - if (s->selected()) { - s->set_v_offset(s->get_old_v_offset()); - s->select(false); - } - } - } - } - _moveValid = false; -} - void Header::changeName(QMouseEvent *event) { if ((event->button() == Qt::LeftButton)) { header_resize(); - nameEdit->setText(_context_signal->get_name()); + nameEdit->setText(_context_trace->get_name()); nameEdit->selectAll(); nameEdit->setFocus(); nameEdit->show(); @@ -503,9 +333,9 @@ void Header::changeName(QMouseEvent *event) void Header::changeColor(QMouseEvent *event) { if ((event->button() == Qt::LeftButton)) { - const QColor new_color = QColorDialog::getColor(_context_signal->get_colour(), this, tr("Set Channel Colour")); + const QColor new_color = QColorDialog::getColor(_context_trace->get_colour(), this, tr("Set Channel Colour")); if (new_color.isValid()) - _context_signal->set_colour(new_color); + _context_trace->set_colour(new_color); } } @@ -514,17 +344,17 @@ void Header::mouseMoveEvent(QMouseEvent *event) assert(event); _mouse_point = event->pos(); - // Move the signals if we are dragging - if (!_drag_sigs.empty()) { + // Move the Traces if we are dragging + if (!_drag_traces.empty()) { const int delta = event->pos().y() - _mouse_down_point.y(); - for (std::list, - int> >::iterator i = _drag_sigs.begin(); - i != _drag_sigs.end(); i++) { - const boost::shared_ptr sig((*i).first); + for (std::list, + int> >::iterator i = _drag_traces.begin(); + i != _drag_traces.end(); i++) { + const boost::shared_ptr sig((*i).first); if (sig) { int y = (*i).second + delta; - if (sig->get_type() != Signal::DS_DSO) { + if (sig->get_type() != Trace::DS_DSO) { const int y_snap = ((y + View::SignalSnapGridSize / 2) / View::SignalSnapGridSize) * @@ -533,20 +363,19 @@ void Header::mouseMoveEvent(QMouseEvent *event) _moveFlag = true; sig->set_v_offset(y_snap); } - // Ensure the signal is selected + // Ensure the Trace is selected sig->select(true); } else { - if (y < 0) - y = 0; - else if (y > height()) - y = height(); - sig->set_zeroPos(y); - sig->select(false); - signals_moved(); + shared_ptr dsoSig; + if (dsoSig = dynamic_pointer_cast(sig)) { + dsoSig->set_zeroPos(y); + dsoSig->select(false); + traces_moved(); + } } } } - //signals_moved(); + //traces_moved(); } update(); } @@ -561,33 +390,33 @@ void Header::contextMenuEvent(QContextMenuEvent *event) { int action; - const boost::shared_ptr s = get_mSig(action, _mouse_point); + const boost::shared_ptr t = get_mTrace(action, _mouse_point); - if (!s || !s->selected() || action != Signal::LABEL) + if (!t || !t->selected() || action != Trace::LABEL) return; QMenu menu(this); - if (s->get_type() == Signal::DS_LOGIC) + if (t->get_type() == Trace::DS_LOGIC) menu.addAction(_action_add_group); - else if (s->get_type() == Signal::DS_GROUP) + else if (t->get_type() == Trace::DS_GROUP) menu.addAction(_action_del_group); - _context_signal = s; + _context_trace = t; menu.exec(event->globalPos()); - _context_signal.reset(); + _context_trace.reset(); } void Header::on_action_set_name_triggered() { - boost::shared_ptr context_signal = _context_signal; - if (!context_signal) + boost::shared_ptr context_Trace = _context_trace; + if (!context_Trace) return; if (nameEdit->isModified()) { - context_signal->set_name(nameEdit->text()); - if (context_signal->get_type() == Signal::DS_LOGIC || - context_signal->get_type() == Signal::DS_ANALOG) - sr_dev_probe_name_set(_view.session().get_device(), context_signal->get_index(), nameEdit->text().toUtf8().constData()); + context_Trace->set_name(nameEdit->text()); + if (context_Trace->get_type() == Trace::DS_LOGIC || + context_Trace->get_type() == Trace::DS_ANALOG) + sr_dev_probe_name_set(_view.session().get_device()->dev_inst(), context_Trace->get_index(), nameEdit->text().toUtf8().constData()); } nameEdit->hide(); @@ -604,7 +433,7 @@ void Header::on_action_del_group_triggered() _view.session().del_group(); } -void Header::on_signals_moved() +void Header::on_traces_moved() { update(); } @@ -612,9 +441,9 @@ void Header::on_signals_moved() void Header::header_resize() { //if (nameEdit->isVisible()) { - if (_context_signal) { - const int y = _context_signal->get_v_offset() - _view.v_offset() - _view.get_signalHeight() / 2; - nameEdit->move(QPoint(_context_signal->get_leftWidth(), y - nameEdit->height() / 2)); + if (_context_trace) { + const int y = _context_trace->get_y(); + nameEdit->move(QPoint(_context_trace->get_leftWidth(), y - nameEdit->height() / 2)); } } diff --git a/DSLogic-gui/pv/view/header.h b/DSLogic-gui/pv/view/header.h index 3e16f27..4c76f60 100644 --- a/DSLogic-gui/pv/view/header.h +++ b/DSLogic-gui/pv/view/header.h @@ -36,7 +36,7 @@ namespace pv { namespace view { -class Signal; +class Trace; class View; class Header : public QWidget @@ -47,7 +47,7 @@ public: Header(View &parent); private: - boost::shared_ptr get_mSig( + boost::shared_ptr get_mTrace( int &action, const QPoint &pt); @@ -62,7 +62,6 @@ private: void wheelEvent(QWheelEvent *event); void contextMenuEvent(QContextMenuEvent *event); - void move(QMouseEvent *event); void changeName(QMouseEvent *event); void changeColor(QMouseEvent *event); @@ -77,13 +76,12 @@ private slots: void on_action_del_group_triggered(); - void on_signals_moved(); + void on_traces_moved(); signals: - void signals_moved(); + void traces_moved(); void header_updated(); void vDial_changed(quint16); - void hDial_changed(quint16); void acdc_changed(quint16); void ch_changed(quint16); @@ -99,10 +97,10 @@ private: QLineEdit *nameEdit; - std::list, int> > - _drag_sigs; + std::list, int> > + _drag_traces; - boost::shared_ptr _context_signal; + boost::shared_ptr _context_trace; QAction *_action_add_group; QAction *_action_del_group; diff --git a/DSLogic-gui/pv/view/logicsignal.cpp b/DSLogic-gui/pv/view/logicsignal.cpp index f9fae10..52a454e 100644 --- a/DSLogic-gui/pv/view/logicsignal.cpp +++ b/DSLogic-gui/pv/view/logicsignal.cpp @@ -29,6 +29,7 @@ #include "view.h" #include "pv/data/logic.h" #include "pv/data/logicsnapshot.h" +#include "view.h" using namespace boost; using namespace std; @@ -65,50 +66,50 @@ const QColor LogicSignal::SignalColours[8] = { const int LogicSignal::StateHeight = 12; const int LogicSignal::StateRound = 5; -LogicSignal::LogicSignal(QString name, boost::shared_ptr data, - int probe_index, int order) : - Signal(name, probe_index, DS_LOGIC, order), - _probe_index(probe_index), - _data(data), - _need_decode(false), - _decoder(NULL) +LogicSignal::LogicSignal(boost::shared_ptr dev_inst, + boost::shared_ptr data, + const sr_channel * const probe) : + Signal(dev_inst, probe, DS_LOGIC), + _data(data) { - assert(_probe_index >= 0); - _colour = SignalColours[_probe_index % countof(SignalColours)]; + assert(probe->index >= 0); + _colour = SignalColours[probe->index % countof(SignalColours)]; } LogicSignal::~LogicSignal() { } -void LogicSignal::set_data(boost::shared_ptr _logic_data, - boost::shared_ptr _dso_data, - boost::shared_ptr _analog_data, - boost::shared_ptr _group_data) +const sr_channel* LogicSignal::probe() const { - (void)_dso_data; - (void)_analog_data; - (void)_group_data; - - assert(_logic_data); + return _probe; +} - if (!_cur_edges.empty()) - _cur_edges.clear(); +shared_ptr LogicSignal::data() const +{ + return _data; +} - _data = _logic_data; +shared_ptr LogicSignal::logic_data() const +{ + return _data; } -void LogicSignal::paint(QPainter &p, int y, int left, int right, - double scale, double offset) +void LogicSignal::paint_mid(QPainter &p, int left, int right) { using pv::view::View; QLineF *line; - assert(scale > 0); assert(_data); + assert(_view); assert(right >= left); + const int y = get_y() + _signalHeight * 0.5; + const double scale = _view->scale(); + assert(scale > 0); + const double offset = _view->offset(); + const float high_offset = y - _signalHeight + 0.5f; const float low_offset = y + 0.5f; @@ -119,8 +120,10 @@ void LogicSignal::paint(QPainter &p, int y, int left, int right, const boost::shared_ptr &snapshot = snapshots.front(); + if (snapshot->buf_null()) + return; - double samplerate = _data->get_samplerate(); + double samplerate = _data->samplerate(); // Show sample rate as 1Hz when it is unknown if (samplerate == 0.0) @@ -136,8 +139,9 @@ void LogicSignal::paint(QPainter &p, int y, int left, int right, snapshot->get_subsampled_edges(_cur_edges, min(max((int64_t)floor(start), (int64_t)0), last_sample), min(max((int64_t)ceil(end), (int64_t)0), last_sample), - samples_per_pixel / Oversampling, _probe_index); - assert(_cur_edges.size() >= 2); + samples_per_pixel / Oversampling, _probe->index); + if (_cur_edges.size() < 2) + return; // Paint the edges const unsigned int edge_count = 2 * _cur_edges.size() - 3; @@ -163,61 +167,6 @@ void LogicSignal::paint(QPainter &p, int y, int left, int right, p.setPen(_colour); p.drawLines(edge_lines, edge_count); delete[] edge_lines; - - if (_need_decode) { - assert(_decoder); - _decoder->get_subsampled_states(_cur_states, - min(max((int64_t)floor(start), (int64_t)0), last_sample), - min(max((int64_t)ceil(end), (int64_t)0), last_sample), - samples_per_pixel); - - const float top_offset = y - (_signalHeight + StateHeight) / 2.0f; - const uint64_t sig_mask = 1ULL << _probe_index; - const uint8_t *const init_ptr = (uint8_t*)snapshot->get_data(); - const uint8_t *src_ptr; - const int unit_size = snapshot->get_unit_size(); - uint64_t value; - uint64_t finalValue = 0; - - if (!_cur_states.empty()) { - _decoder->fill_color_table(_color_table); - _decoder->fill_state_table(_state_table); - - vector::const_iterator i; - for ( i = _cur_states.begin(); i != _cur_states.end(); i++) { - finalValue = 0; - const uint64_t index = (*i).index; - const uint64_t samples = (*i).samples; - const int64_t x = (index / samples_per_pixel - - pixels_offset) + left; - const int64_t width = samples / samples_per_pixel; - - if ((*i).type == decoder::DEC_DATA) { - src_ptr = init_ptr + index * unit_size; - for (uint64_t j = 0; j < samples; j++) { - value = (*(uint64_t*)src_ptr & sig_mask); - if (_probe_index - j > 0) - value = value >> (_probe_index - j); - else - value = value << (j - _probe_index); - finalValue |= value; - src_ptr += unit_size; - } - } - - p.setBrush(_color_table.at((*i).state)); - const QRectF state_rect = QRectF(x, top_offset, width, StateHeight); - p.drawRoundedRect(state_rect, StateRound, StateRound); - p.setPen(Qt::black); - if ((*i).type == decoder::DEC_CMD) - p.drawText(state_rect, Qt::AlignCenter | Qt::AlignVCenter, - _state_table.at((*i).state)); - else if ((*i).type == decoder::DEC_DATA) - p.drawText(state_rect, Qt::AlignCenter | Qt::AlignVCenter, - _state_table.at((*i).state) + "0x" + QString::number(finalValue, 16).toUpper()); - } - } - } } void LogicSignal::paint_caps(QPainter &p, QLineF *const lines, @@ -248,22 +197,80 @@ const std::vector< std::pair > LogicSignal::cur_edges() const return _cur_edges; } -void LogicSignal::set_decoder(pv::decoder::Decoder *decoder) -{ - assert(decoder); - _need_decode = true; - _decoder = decoder; -} - -decoder::Decoder *LogicSignal::get_decoder() -{ - return _decoder; -} - -void LogicSignal::del_decoder() +void LogicSignal::paint_type_options(QPainter &p, int right, bool hover, int action) { - _need_decode = false; - _decoder = NULL; + int y = get_y(); + const QRectF posTrig_rect = get_rect("posTrig", y, right); + const QRectF higTrig_rect = get_rect("higTrig", y, right); + const QRectF negTrig_rect = get_rect("negTrig", y, right); + const QRectF lowTrig_rect = get_rect("lowTrig", y, right); + const QRectF edgeTrig_rect = get_rect("edgeTrig", y, right); + + p.setPen(Qt::transparent); + p.setBrush(((hover && action == POSTRIG) || (_trig == POSTRIG)) ? + dsYellow : + dsBlue); + p.drawRect(posTrig_rect); + p.setBrush(((hover && action == HIGTRIG) || (_trig == HIGTRIG)) ? + dsYellow : + dsBlue); + p.drawRect(higTrig_rect); + p.setBrush(((hover && action == NEGTRIG) || (_trig == NEGTRIG)) ? + dsYellow : + dsBlue); + p.drawRect(negTrig_rect); + p.setBrush(((hover && action == LOWTRIG) || (_trig == LOWTRIG)) ? + dsYellow : + dsBlue); + p.drawRect(lowTrig_rect); + p.setBrush(((hover && action == EDGETRIG) || (_trig == EDGETRIG)) ? + dsYellow : + dsBlue); + p.drawRect(edgeTrig_rect); + + p.setPen(QPen(Qt::blue, 1, Qt::DotLine)); + p.setBrush(Qt::transparent); + p.drawLine(posTrig_rect.right(), posTrig_rect.top() + 3, + posTrig_rect.right(), posTrig_rect.bottom() - 3); + p.drawLine(higTrig_rect.right(), higTrig_rect.top() + 3, + higTrig_rect.right(), higTrig_rect.bottom() - 3); + p.drawLine(negTrig_rect.right(), negTrig_rect.top() + 3, + negTrig_rect.right(), negTrig_rect.bottom() - 3); + p.drawLine(lowTrig_rect.right(), lowTrig_rect.top() + 3, + lowTrig_rect.right(), lowTrig_rect.bottom() - 3); + + p.setPen(QPen(Qt::white, 2, Qt::SolidLine)); + p.setBrush(Qt::transparent); + p.drawLine(posTrig_rect.left() + 5, posTrig_rect.bottom() - 5, + posTrig_rect.center().x(), posTrig_rect.bottom() - 5); + p.drawLine(posTrig_rect.center().x(), posTrig_rect.bottom() - 5, + posTrig_rect.center().x(), posTrig_rect.top() + 5); + p.drawLine(posTrig_rect.center().x(), posTrig_rect.top() + 5, + posTrig_rect.right() - 5, posTrig_rect.top() + 5); + + p.drawLine(higTrig_rect.left() + 5, higTrig_rect.top() + 5, + higTrig_rect.right() - 5, higTrig_rect.top() + 5); + + p.drawLine(negTrig_rect.left() + 5, negTrig_rect.top() + 5, + negTrig_rect.center().x(), negTrig_rect.top() + 5); + p.drawLine(negTrig_rect.center().x(), negTrig_rect.top() + 5, + negTrig_rect.center().x(), negTrig_rect.bottom() - 5); + p.drawLine(negTrig_rect.center().x(), negTrig_rect.bottom() - 5, + negTrig_rect.right() - 5, negTrig_rect.bottom() - 5); + + p.drawLine(lowTrig_rect.left() + 5, lowTrig_rect.bottom() - 5, + lowTrig_rect.right() - 5, lowTrig_rect.bottom() - 5); + + p.drawLine(edgeTrig_rect.left() + 5, edgeTrig_rect.top() + 5, + edgeTrig_rect.center().x() - 2, edgeTrig_rect.top() + 5); + p.drawLine(edgeTrig_rect.center().x() + 2 , edgeTrig_rect.top() + 5, + edgeTrig_rect.right() - 5, edgeTrig_rect.top() + 5); + p.drawLine(edgeTrig_rect.center().x(), edgeTrig_rect.top() + 7, + edgeTrig_rect.center().x(), edgeTrig_rect.bottom() - 7); + p.drawLine(edgeTrig_rect.left() + 5, edgeTrig_rect.bottom() - 5, + edgeTrig_rect.center().x() - 2, edgeTrig_rect.bottom() - 5); + p.drawLine(edgeTrig_rect.center().x() + 2, edgeTrig_rect.bottom() - 5, + edgeTrig_rect.right() - 5, edgeTrig_rect.bottom() - 5); } } // namespace view diff --git a/DSLogic-gui/pv/view/logicsignal.h b/DSLogic-gui/pv/view/logicsignal.h index fa407f2..b1e8721 100644 --- a/DSLogic-gui/pv/view/logicsignal.h +++ b/DSLogic-gui/pv/view/logicsignal.h @@ -25,7 +25,6 @@ #define DSLOGIC_PV_LOGICSIGNAL_H #include "signal.h" -#include "../decoder/decoder.h" #include @@ -55,36 +54,30 @@ private: static const int StateRound; public: - LogicSignal(QString name, - boost::shared_ptr data, - int probe_index, int order); + LogicSignal(boost::shared_ptr dev_inst, + boost::shared_ptr data, + const sr_channel * const probe); virtual ~LogicSignal(); - void set_data(boost::shared_ptr _logic_data, - boost::shared_ptr _dso_data, - boost::shared_ptr _analog_data, - boost::shared_ptr _group_data); + const sr_channel* probe() const; + + boost::shared_ptr data() const; + + boost::shared_ptr logic_data() const; + /** * Paints the signal with a QPainter * @param p the QPainter to paint into. - * @param y the y-coordinate to draw the signal at. * @param left the x-coordinate of the left edge of the signal. * @param right the x-coordinate of the right edge of the signal. - * @param scale the scale in seconds per pixel. - * @param offset the time to show at the left hand edge of - * the view in seconds. **/ - void paint(QPainter &p, int y, int left, int right, double scale, - double offset); + void paint_mid(QPainter &p, int left, int right); const std::vector< std::pair > cur_edges() const; - void set_decoder(pv::decoder::Decoder *decoder); - - pv::decoder::Decoder* get_decoder(); - - void del_decoder(); +protected: + void paint_type_options(QPainter &p, int right, bool hover, int action); private: @@ -94,15 +87,8 @@ private: float x_offset, float y_offset); private: - int _probe_index; boost::shared_ptr _data; std::vector< std::pair > _cur_edges; - - bool _need_decode; - pv::decoder::Decoder * _decoder; - std::vector _cur_states; - std::vector< QColor > _color_table; - std::vector< QString > _state_table; }; } // namespace view diff --git a/DSLogic-gui/pv/view/ruler.cpp b/DSLogic-gui/pv/view/ruler.cpp index f049beb..43f4013 100644 --- a/DSLogic-gui/pv/view/ruler.cpp +++ b/DSLogic-gui/pv/view/ruler.cpp @@ -27,6 +27,8 @@ #include "view.h" #include "viewport.h" #include "../sigsession.h" +#include "../device/devinst.h" +#include "dsosignal.h" #include @@ -39,6 +41,9 @@ #include #include +#include + +using namespace boost; using namespace std; namespace pv { @@ -63,7 +68,7 @@ const QColor Ruler::CursorColor[8] = QColor(46, 205, 113, 200), QColor(53, 152, 220, 200), QColor(154, 89, 181, 200), - QColor(52, 73, 94 , 200), + QColor(52, 73, 94, 200), QColor(242, 196, 15, 200), QColor(231, 126, 34, 200), QColor(232, 76, 61, 200)}; @@ -405,7 +410,7 @@ void Ruler::draw_logic_tick_mark(QPainter &p) const double MinValueSpacing = 16.0f; const int ValueMargin = 5; - const double abs_min_period = 10.0f / _view.session().get_last_sample_rate(); + const double abs_min_period = 10.0f / _view.session().get_device()->get_sample_rate(); double min_width = SpacingIncrement; double typical_width; @@ -415,7 +420,11 @@ void Ruler::draw_logic_tick_mark(QPainter &p) // Find tick spacing, and number formatting that does not cause // value to collide. - _min_period = cur_period_scale * abs_min_period; + if (_view.session().get_device()->dev_inst()->mode == DSO) { + _min_period = _view.session().get_device()->get_time_base() * pow(10, -9); + } else { + _min_period = cur_period_scale * abs_min_period; + } const int order = (int)floorf(log10f(_min_period)); //const double order_decimal = pow(10, order); const unsigned int prefix = (order - FirstSIPrefixPower) / 3; @@ -459,7 +468,7 @@ void Ruler::draw_logic_tick_mark(QPainter &p) const double inc_text_width = p.boundingRect(0, 0, INT_MAX, INT_MAX, AlignLeft | AlignTop, - format_time(tick_period - minor_tick_period, + format_time(minor_tick_period, minor_prefix)).width() + MinValueSpacing; do { const double t = t0 + division * minor_tick_period; @@ -485,7 +494,7 @@ void Ruler::draw_logic_tick_mark(QPainter &p) AlignCenter | AlignTop | TextDontClip, format_time(t, prefix)); //else if ((tick_period / _view.scale() > width() / 4) && (minor_tick_period / _view.scale() > inc_text_width)) - else if (minor_tick_period / _view.scale() > 1.2 * inc_text_width) + else if (minor_tick_period / _view.scale() > 1.1 * inc_text_width) p.drawText(x, 2 * ValueMargin, 0, minor_tick_y1 + ValueMargin, AlignCenter | AlignTop | TextDontClip, format_time(t - major_t, minor_prefix)); @@ -495,7 +504,7 @@ void Ruler::draw_logic_tick_mark(QPainter &p) division++; - } while (x < width()); + } while (x < _view.get_max_width()); // Draw the cursors if (!_view.get_cursorList().empty()) { @@ -509,10 +518,10 @@ void Ruler::draw_logic_tick_mark(QPainter &p) _view.on_cursor_moved(); } if (_view.trig_cursor_shown()) { - _view.get_trig_cursor()->paint_fix_label(p, rect(), prefix, 'T', Signal::dsLightRed); + _view.get_trig_cursor()->paint_fix_label(p, rect(), prefix, 'T', Trace::dsLightRed); } if (_view.search_cursor_shown()) { - _view.get_search_cursor()->paint_fix_label(p, rect(), prefix, 'S', Signal::dsLightBlue); + _view.get_search_cursor()->paint_fix_label(p, rect(), prefix, 'S', Trace::dsLightBlue); } } diff --git a/DSLogic-gui/pv/view/selectableitem.cpp b/DSLogic-gui/pv/view/selectableitem.cpp new file mode 100644 index 0000000..e849dba --- /dev/null +++ b/DSLogic-gui/pv/view/selectableitem.cpp @@ -0,0 +1,57 @@ +/* + * This file is part of the DSLogic-gui project. + * DSLogic-gui is based on PulseView. + * + * Copyright (C) 2013 Joel Holdsworth + * Copyright (C) 2014 DreamSourceLab + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "selectableitem.h" + +#include +#include +#include + +namespace pv { +namespace view { + +const int SelectableItem::HighlightRadius = 6; + +SelectableItem::SelectableItem() : + _selected(false) +{ +} + +bool SelectableItem::selected() const +{ + return _selected; +} + +void SelectableItem::select(bool select) +{ + _selected = select; +} + +QPen SelectableItem::highlight_pen() +{ + return QPen(QApplication::palette().brush( + QPalette::Highlight), HighlightRadius, + Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); +} + +} // namespace view +} // namespace pv diff --git a/DSLogic-gui/pv/view/selectableitem.h b/DSLogic-gui/pv/view/selectableitem.h new file mode 100644 index 0000000..f952f82 --- /dev/null +++ b/DSLogic-gui/pv/view/selectableitem.h @@ -0,0 +1,69 @@ +/* + * This file is part of the DSLogic-gui project. + * DSLogic-gui is based on PulseView. + * + * Copyright (C) 2013 Joel Holdsworth + * Copyright (C) 2014 DreamSourceLab + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DSLOGIC_PV_SELECTABLEITEM_H +#define DSLOGIC_PV_SELECTABLEITEM_H + +#include + +#include + +class QAction; +class QMenu; +class QWidget; + +namespace pv { + +namespace view { + +class SelectableItem : public QObject +{ + Q_OBJECT + +private: + static const int HighlightRadius; + +public: + SelectableItem(); + +public: + /** + * Returns true if the signal has been selected by the user. + */ + bool selected() const; + + /** + * Selects or deselects the signal. + */ + void select(bool select = true); + +protected: + static QPen highlight_pen(); + +private: + bool _selected; +}; + +} // namespace view +} // namespace pv + +#endif // DSLOGIC_PV_SELECTABLEITEM_H diff --git a/DSLogic-gui/pv/view/signal.cpp b/DSLogic-gui/pv/view/signal.cpp index 11ef319..828e7fc 100644 --- a/DSLogic-gui/pv/view/signal.cpp +++ b/DSLogic-gui/pv/view/signal.cpp @@ -29,673 +29,22 @@ #include "signal.h" #include "view.h" +#include "../device/devinst.h" namespace pv { namespace view { -const QColor Signal::dsBlue = QColor(17, 133, 209, 255); -const QColor Signal::dsYellow = QColor(238, 178, 17, 255); -const QColor Signal::dsRed = QColor(213, 15, 37, 255); -const QColor Signal::dsGreen = QColor(0, 153, 37, 255); -const QColor Signal::dsGray = QColor(0x88, 0x8A, 0x85, 100); -const QColor Signal::dsDisable = QColor(0x88, 0x8A, 0x85, 200); -const QColor Signal::dsActive = QColor(17, 133, 209, 255); -const QColor Signal::dsLightBlue = QColor(17, 133, 209, 150); -const QColor Signal::dsLightRed = QColor(213, 15, 37, 150); -const QPen Signal::SignalAxisPen = QColor(128, 128, 128, 64); - -const quint64 Signal::vDialValue[Signal::vDialValueCount] = { - 5, - 10, - 20, - 50, - 100, - 200, - 500, - 1000, - 2000, - 5000, -}; -const QString Signal::vDialUnit[Signal::vDialUnitCount] = { - "mv", - "v", -}; - -const quint64 Signal::hDialValue[Signal::hDialValueCount] = { - 10, - 20, - 50, - 100, - 200, - 500, - 1000, - 2000, - 5000, - 10000, - 20000, - 50000, - 100000, - 200000, - 500000, - 1000000, - 2000000, - 5000000, - 10000000, - 20000000, - 50000000, - 100000000, - 200000000, - 500000000, - 1000000000, -}; -const QString Signal::hDialUnit[Signal::hDialUnitCount] = { - "ns", - "us", - "ms", - "s", -}; - -Signal::Signal(QString name, int index, int type, int order) : - _type(type), - _order(order), - _sec_index(0), - _name(name), - _v_offset(0), - _signalHeight(30), - _selected(false), - _trig(0), - _vDialActive(false), - _hDialActive(false), - _acCoupling(false), - _active(true), - _windowHeight(0) -{ - _index_list.push_back(index); - if (_type == DS_DSO) { - QVector vValue; - QVector vUnit; - QVector hValue; - QVector hUnit; - for(quint64 i = 0; i < Signal::vDialValueCount; i++) - vValue.append(Signal::vDialValue[i]); - for(quint64 i = 0; i < Signal::vDialUnitCount; i++) - vUnit.append(Signal::vDialUnit[i]); - - for(quint64 i = 0; i < Signal::hDialValueCount; i++) - hValue.append(Signal::hDialValue[i]); - for(quint64 i = 0; i < Signal::hDialUnitCount; i++) - hUnit.append(Signal::hDialUnit[i]); - - _vDial = new dslDial(vDialValueCount, vDialValueStep, vValue, vUnit); - _hDial = new dslDial(hDialValueCount, hDialValueStep, hValue, hUnit); - _vDial->set_sel(0); - _hDial->set_sel(0); - - _trig_vpos = 0; - _trig_en = true; - } -} - -Signal::Signal(QString name, std::list index_list, int type, int order, int sec_index) : - _type(type), - _index_list(index_list), - _order(order), - _sec_index(sec_index), - _name(name), - _v_offset(0), - _signalHeight(30), - _selected(false), - _trig(0), - _vDialActive(false), - _hDialActive(false), - _acCoupling(false), - _active(true) -{ - if (_type == DS_DSO) { - QVector vValue; - QVector vUnit; - QVector hValue; - QVector hUnit; - for(quint64 i = 0; i < Signal::vDialValueCount; i++) - vValue.append(Signal::vDialValue[i]); - for(quint64 i = 0; i < Signal::vDialUnitCount; i++) - vUnit.append(Signal::vDialUnit[i]); - - for(quint64 i = 0; i < Signal::hDialValueCount; i++) - hValue.append(Signal::hDialValue[i]); - for(quint64 i = 0; i < Signal::hDialUnitCount; i++) - hUnit.append(Signal::hDialUnit[i]); - - _vDial = new dslDial(Signal::vDialValueCount, Signal::vDialValueStep, vValue, vUnit); - _hDial = new dslDial(Signal::hDialValueCount, Signal::hDialValueStep, hValue, hUnit); - _vDial->set_sel(0); - _hDial->set_sel(0); - } -} - -int Signal::get_type() const -{ - return _type; -} - -int Signal::get_order() const -{ - return _order; -} - -void Signal::set_order(int order) -{ - assert(order >= 0); - - _order = order; -} - -int Signal::get_leftWidth() const -{ - return SquareWidth + Margin; -} - -int Signal::get_rightWidth() const -{ - return 2 * Margin + SquareNum * SquareWidth + 1.5 * SquareWidth; -} - -int Signal::get_headerHeight() const -{ - return SquareWidth; -} - -int Signal::get_index() const -{ - return _index_list.front(); -} - -std::list &Signal::get_index_list() -{ - return _index_list; -} - -void Signal::set_index_list(std::list index_list) -{ - assert(index_list.size() != 0); - - _index_list = index_list; -} - -int Signal::get_sec_index() const -{ - return _sec_index; -} - -void Signal::set_sec_index(int sec_index) -{ - _sec_index = sec_index; -} - -QString Signal::get_name() const +Signal::Signal(boost::shared_ptr dev_inst, + const sr_channel *const probe, int type) : + Trace(probe->name, probe->index, type), + _dev_inst(dev_inst), + _probe(probe) { - return _name; } -void Signal::set_name(QString name) +bool Signal::enabled() const { - _name = name; -} - -QColor Signal::get_colour() const -{ - return _colour; -} - -void Signal::set_colour(QColor colour) -{ - _colour = colour; -} - -int Signal::get_v_offset() const -{ - return _v_offset; -} - -void Signal::set_v_offset(int v_offset) -{ - _v_offset = v_offset; -} - -int Signal::get_old_v_offset() const -{ - return _old_v_offset; -} - -void Signal::set_old_v_offset(int v_offset) -{ - _old_v_offset = v_offset; -} - -int Signal::get_signalHeight() const -{ - return _signalHeight; -} - -void Signal::set_signalHeight(int height) -{ - _signalHeight = height; -} - -bool Signal::selected() const -{ - return _selected; -} - -void Signal::select(bool select) -{ - _selected = select; -} - -int Signal::get_trig() const -{ - return _trig; -} - -void Signal::set_trig(int trig) -{ - _trig = trig; - if (trig == 0) - ds_trigger_probe_set(_index_list.front(), 'X', 'X'); - else if (trig == POSTRIG) - ds_trigger_probe_set(_index_list.front(), 'R', 'X'); - else if (trig == HIGTRIG) - ds_trigger_probe_set(_index_list.front(), '1', 'X'); - else if (trig == NEGTRIG) - ds_trigger_probe_set(_index_list.front(), 'F', 'X'); - else if (trig == LOWTRIG) - ds_trigger_probe_set(_index_list.front(), '0', 'X'); - else if (trig == EDGETRIG) - ds_trigger_probe_set(_index_list.front(), 'C', 'X'); -} - -bool Signal::get_vDialActive() const -{ - return _vDialActive; -} - -void Signal::set_vDialActive(bool active) -{ - _vDialActive = active; -} - -bool Signal::go_vDialPre() -{ - assert(_type == DS_DSO); - - if (!_vDial->isMin()) { - _vDial->set_sel(_vDial->get_sel() - 1); - return true; - } else { - return false; - } -} - -bool Signal::go_vDialNext() -{ - assert(_type == DS_DSO); - - if (!_vDial->isMax()) { - _vDial->set_sel(_vDial->get_sel() + 1); - return true; - } else { - return false; - } -} - -bool Signal::get_hDialActive() const -{ - return _hDialActive; -} - -void Signal::set_hDialActive(bool active) -{ - _hDialActive = active; -} - -bool Signal::go_hDialPre() -{ - assert(_type == DS_DSO); - - if (!_hDial->isMin()) { - _hDial->set_sel(_hDial->get_sel() - 1); - return true; - } else { - return false; - } -} - -bool Signal::go_hDialNext() -{ - assert(_type == DS_DSO); - - if (!_hDial->isMax()) { - _hDial->set_sel(_hDial->get_sel() + 1); - return true; - } else { - return false; - } -} - -quint64 Signal::get_vDialValue() const -{ - return _vDial->get_value(); -} - -quint64 Signal::get_hDialValue() const -{ - return _hDial->get_value(); -} - -uint16_t Signal::get_vDialSel() const -{ - return _vDial->get_sel(); -} - -uint16_t Signal::get_hDialSel() const -{ - return _hDial->get_sel(); -} - -bool Signal::get_acCoupling() const -{ - return _acCoupling; -} - -void Signal::set_acCoupling(bool coupling) -{ - _acCoupling = coupling; -} - -bool Signal::get_active() const -{ - return _active; -} - -void Signal::set_active(bool active) -{ - _active = active; -} - -int Signal::get_zeroPos() const -{ - return _zeroPos; -} - -void Signal::set_zeroPos(int pos) -{ - _zeroPos = pos; -} - -int Signal::get_windowHeight() const -{ - return _windowHeight; -} - -void Signal::set_windowHeight(int height) -{ - _windowHeight = height; -} - -int Signal::get_trig_vpos() const -{ - return _trig_vpos; -} - -void Signal::set_trig_vpos(int value) -{ - _trig_vpos = value; -} - -void Signal::paint_label(QPainter &p, int y, int right, bool hover, int action) -{ - compute_text_size(p); - - const QRectF color_rect = get_rect("color", y, right); - const QRectF name_rect = get_rect("name", y, right); - const QRectF label_rect = get_rect("label", (_type == DS_DSO) ? _zeroPos : y, right); - - p.setRenderHint(QPainter::Antialiasing); - // Paint the ColorButton - p.setPen(Qt::transparent); - p.setBrush(_active ? _colour : dsDisable); - p.drawRect(color_rect); - - // Paint the signal name - p.setPen(_active ? Qt::black : dsDisable); - p.drawText(name_rect, Qt::AlignLeft | Qt::AlignVCenter, _name); - - // Paint the trigButton - if (_type == DS_LOGIC) { - const QRectF posTrig_rect = get_rect("posTrig", y, right); - const QRectF higTrig_rect = get_rect("higTrig", y, right); - const QRectF negTrig_rect = get_rect("negTrig", y, right); - const QRectF lowTrig_rect = get_rect("lowTrig", y, right); - const QRectF edgeTrig_rect = get_rect("edgeTrig", y, right); - - p.setPen(Qt::transparent); - p.setBrush(((hover && action == POSTRIG) || (_trig == POSTRIG)) ? - dsYellow : - dsBlue); - p.drawRect(posTrig_rect); - p.setBrush(((hover && action == HIGTRIG) || (_trig == HIGTRIG)) ? - dsYellow : - dsBlue); - p.drawRect(higTrig_rect); - p.setBrush(((hover && action == NEGTRIG) || (_trig == NEGTRIG)) ? - dsYellow : - dsBlue); - p.drawRect(negTrig_rect); - p.setBrush(((hover && action == LOWTRIG) || (_trig == LOWTRIG)) ? - dsYellow : - dsBlue); - p.drawRect(lowTrig_rect); - p.setBrush(((hover && action == EDGETRIG) || (_trig == EDGETRIG)) ? - dsYellow : - dsBlue); - p.drawRect(edgeTrig_rect); - - p.setPen(QPen(Qt::blue, 1, Qt::DotLine)); - p.setBrush(Qt::transparent); - p.drawLine(posTrig_rect.right(), posTrig_rect.top() + 3, - posTrig_rect.right(), posTrig_rect.bottom() - 3); - p.drawLine(higTrig_rect.right(), higTrig_rect.top() + 3, - higTrig_rect.right(), higTrig_rect.bottom() - 3); - p.drawLine(negTrig_rect.right(), negTrig_rect.top() + 3, - negTrig_rect.right(), negTrig_rect.bottom() - 3); - p.drawLine(lowTrig_rect.right(), lowTrig_rect.top() + 3, - lowTrig_rect.right(), lowTrig_rect.bottom() - 3); - - p.setPen(QPen(Qt::white, 2, Qt::SolidLine)); - p.setBrush(Qt::transparent); - p.drawLine(posTrig_rect.left() + 5, posTrig_rect.bottom() - 5, - posTrig_rect.center().x(), posTrig_rect.bottom() - 5); - p.drawLine(posTrig_rect.center().x(), posTrig_rect.bottom() - 5, - posTrig_rect.center().x(), posTrig_rect.top() + 5); - p.drawLine(posTrig_rect.center().x(), posTrig_rect.top() + 5, - posTrig_rect.right() - 5, posTrig_rect.top() + 5); - - p.drawLine(higTrig_rect.left() + 5, higTrig_rect.top() + 5, - higTrig_rect.right() - 5, higTrig_rect.top() + 5); - - p.drawLine(negTrig_rect.left() + 5, negTrig_rect.top() + 5, - negTrig_rect.center().x(), negTrig_rect.top() + 5); - p.drawLine(negTrig_rect.center().x(), negTrig_rect.top() + 5, - negTrig_rect.center().x(), negTrig_rect.bottom() - 5); - p.drawLine(negTrig_rect.center().x(), negTrig_rect.bottom() - 5, - negTrig_rect.right() - 5, negTrig_rect.bottom() - 5); - - p.drawLine(lowTrig_rect.left() + 5, lowTrig_rect.bottom() - 5, - lowTrig_rect.right() - 5, lowTrig_rect.bottom() - 5); - - p.drawLine(edgeTrig_rect.left() + 5, edgeTrig_rect.top() + 5, - edgeTrig_rect.center().x() - 2, edgeTrig_rect.top() + 5); - p.drawLine(edgeTrig_rect.center().x() + 2 , edgeTrig_rect.top() + 5, - edgeTrig_rect.right() - 5, edgeTrig_rect.top() + 5); - p.drawLine(edgeTrig_rect.center().x(), edgeTrig_rect.top() + 7, - edgeTrig_rect.center().x(), edgeTrig_rect.bottom() - 7); - p.drawLine(edgeTrig_rect.left() + 5, edgeTrig_rect.bottom() - 5, - edgeTrig_rect.center().x() - 2, edgeTrig_rect.bottom() - 5); - p.drawLine(edgeTrig_rect.center().x() + 2, edgeTrig_rect.bottom() - 5, - edgeTrig_rect.right() - 5, edgeTrig_rect.bottom() - 5); - } else if (_type == DS_GROUP || _type == DS_PROTOCOL) { - const QRectF group_index_rect = get_rect("groupIndex", y, right); - QString index_string; - int last_index; - p.setPen(Qt::transparent); - p.setBrush(dsBlue); - p.drawRect(group_index_rect); - std::list::iterator i = _index_list.begin(); - last_index = (*i); - index_string = QString::number(last_index); - while (++i != _index_list.end()) { - if ((*i) == last_index + 1 && index_string.indexOf("-") < 3 && index_string.indexOf("-") > 0) - index_string.replace(QString::number(last_index), QString::number((*i))); - else if ((*i) == last_index + 1) - index_string = QString::number((*i)) + "-" + index_string; - else - index_string = QString::number((*i)) + "," + index_string; - last_index = (*i); - } - p.setPen(Qt::white); - p.drawText(group_index_rect, Qt::AlignRight | Qt::AlignVCenter, index_string); - } else if (_type == DS_DSO) { - const QRectF vDial_rect = get_rect("vDial", y, right); - const QRectF hDial_rect = get_rect("hDial", y, right); - const QRectF acdc_rect = get_rect("acdc", y, right); - const QRectF chEn_rect = get_rect("chEn", y, right); - - QColor vDial_color = _vDialActive ? dsActive : dsDisable; - QColor hDial_color = _hDialActive ? dsActive : dsDisable; - _vDial->paint(p, vDial_rect, vDial_color); - _hDial->paint(p, hDial_rect, hDial_color); - - p.setPen(Qt::transparent); - p.setBrush((hover && action == CHEN) ? _colour.darker() : _colour); - p.drawRect(chEn_rect); - p.setPen(Qt::white); - p.drawText(chEn_rect, Qt::AlignCenter | Qt::AlignVCenter, _active ? "EN" : "DIS"); - - p.setPen(Qt::transparent); - p.setBrush(_active ? ((hover && action == ACDC) ? _colour.darker() : _colour) : dsDisable); - p.drawRect(acdc_rect); - p.setPen(Qt::white); - p.drawText(acdc_rect, Qt::AlignCenter | Qt::AlignVCenter, _acCoupling ? "AC" : "DC"); - } - - // Paint the label - if (_active) { - const QPointF points[] = { - label_rect.topLeft(), - label_rect.topRight(), - QPointF(right, (_type == DS_DSO) ? _zeroPos : y), - label_rect.bottomRight(), - label_rect.bottomLeft() - }; - - p.setPen(Qt::transparent); - if (_type == DS_DSO) - p.setBrush(((hover && action == LABEL) || _selected) ? _colour.darker() : _colour); - else - p.setBrush(((hover && action == LABEL) || _selected) ? dsYellow : dsBlue); - p.drawPolygon(points, countof(points)); - - p.setPen(QPen(Qt::blue, 1, Qt::DotLine)); - p.setBrush(Qt::transparent); - p.drawLine(label_rect.right(), label_rect.top() + 3, - label_rect.right(), label_rect.bottom() - 3); - - // Paint the text - p.setPen(Qt::white); - if (_type == DS_GROUP) - p.drawText(label_rect, Qt::AlignCenter | Qt::AlignVCenter, "G"); - else if (_type == DS_ANALOG) - p.drawText(label_rect, Qt::AlignCenter | Qt::AlignVCenter, "A"); - else if (_type == DS_PROTOCOL) - p.drawText(label_rect, Qt::AlignCenter | Qt::AlignVCenter, "D"); - else - p.drawText(label_rect, Qt::AlignCenter | Qt::AlignVCenter, QString::number(_index_list.front())); - } -} - -void Signal::paint_trig(QPainter &p, int left, int right, bool hover) -{ - if (_type == DS_DSO) { - const QRectF label_rect = get_rect("dsoTrig", -1, right); - // Paint the trig line - if (_trig_en) { - const QPointF points[] = { - QPointF(right - label_rect.width()*1.5, _trig_vpos), - label_rect.topLeft(), - label_rect.topRight(), - label_rect.bottomRight(), - label_rect.bottomLeft() - }; - - p.setPen(Qt::transparent); - p.setBrush(_colour); - p.drawPolygon(points, countof(points)); - - // paint the _trig_vpos line - p.setPen(QPen(_colour, hover ? 2 : 1, Qt::DashLine)); - p.drawLine(left, _trig_vpos, right - label_rect.width()*1.5, _trig_vpos); - - // Paint the text - p.setPen(Qt::white); - p.drawText(label_rect, Qt::AlignCenter | Qt::AlignVCenter, "T"); - } - } -} - -int Signal::pt_in_rect(int y, int right, const QPoint &point) -{ - const QRectF color = get_rect("color", y, right); - const QRectF name = get_rect("name", y, right); - const QRectF posTrig = get_rect("posTrig", y, right); - const QRectF higTrig = get_rect("higTrig", y, right); - const QRectF negTrig = get_rect("negTrig", y, right); - const QRectF lowTrig = get_rect("lowTrig", y, right); - const QRectF edgeTrig = get_rect("edgeTrig", y, right); - const QRectF label = get_rect("label", (_type == DS_DSO) ? _zeroPos : y, right); - const QRectF vDial = get_rect("vDial", y, right); - const QRectF hDial = get_rect("hDial", y, right); - const QRectF chEn = get_rect("chEn", y, right); - const QRectF acdc = get_rect("acdc", y, right); - const QRectF dsoTrig = get_rect("dsoTrig", 0, right); - - if (color.contains(point) && _active) - return COLOR; - else if (name.contains(point) && _active) - return NAME; - else if (posTrig.contains(point) && _type == DS_LOGIC) - return POSTRIG; - else if (higTrig.contains(point) && _type == DS_LOGIC) - return HIGTRIG; - else if (negTrig.contains(point) && _type == DS_LOGIC) - return NEGTRIG; - else if (lowTrig.contains(point) && _type == DS_LOGIC) - return LOWTRIG; - else if (edgeTrig.contains(point) && _type == DS_LOGIC) - return EDGETRIG; - else if (label.contains(point) && _active) - return LABEL; - else if (vDial.contains(point) && _type == DS_DSO && _active) - return VDIAL; - else if (hDial.contains(point) && _type == DS_DSO && _active) - return HDIAL; - else if (chEn.contains(point) && _type == DS_DSO) - return CHEN; - else if (acdc.contains(point) && _type == DS_DSO && _active) - return ACDC; - else if (dsoTrig.contains(point) && _type == DS_DSO && _active) - return DSOTRIG; - else - return 0; + return _probe->enabled; } void Signal::paint_axis(QPainter &p, int y, int left, int right) @@ -704,91 +53,5 @@ void Signal::paint_axis(QPainter &p, int y, int left, int right) p.drawLine(QPointF(left, y + 0.5f), QPointF(right, y + 0.5f)); } -void Signal::compute_text_size(QPainter &p) -{ - _text_size = QSize( - p.boundingRect(QRectF(), 0, "99").width(), - p.boundingRect(QRectF(), 0, "99").height()); -} - -QRectF Signal::get_rect(const char *s, int y, int right) -{ - const QSizeF color_size(SquareWidth, SquareWidth); - const QSizeF name_size(right - get_leftWidth() - get_rightWidth(), SquareWidth); - //const QSizeF label_size(_text_size.width() + Margin, SquareWidth); - const QSizeF label_size(SquareWidth, SquareWidth); - - if (!strcmp(s, "name")) - return QRectF( - get_leftWidth(), - y - name_size.height() / 2, - name_size.width(), name_size.height()); - else if (!strcmp(s, "label")) - return QRectF( - right - 1.5f * label_size.width(), - y - SquareWidth / 2, - label_size.width(), label_size.height()); - else if (!strcmp(s, "posTrig")) - return QRectF( - get_leftWidth() + name_size.width() + Margin, - y - SquareWidth / 2, - SquareWidth, SquareWidth); - else if (!strcmp(s, "higTrig")) - return QRectF( - get_leftWidth() + name_size.width() + SquareWidth + Margin, - y - SquareWidth / 2, - SquareWidth, SquareWidth); - else if (!strcmp(s, "negTrig")) - return QRectF( - get_leftWidth() + name_size.width() + 2 * SquareWidth + Margin, - y - SquareWidth / 2, - SquareWidth, SquareWidth); - else if (!strcmp(s, "lowTrig")) - return QRectF( - get_leftWidth() + name_size.width() + 3 * SquareWidth + Margin, - y - SquareWidth / 2, - SquareWidth, SquareWidth); - else if (!strcmp(s, "edgeTrig")) - return QRectF( - get_leftWidth() + name_size.width() + 4 * SquareWidth + Margin, - y - SquareWidth / 2, - SquareWidth, SquareWidth); - else if (!strcmp(s, "groupIndex")) - return QRectF( - get_leftWidth() + name_size.width() + Margin, - y - SquareWidth / 2, - SquareWidth * SquareNum, SquareWidth); - else if (!strcmp(s, "vDial")) - return QRectF( - get_leftWidth() + name_size.width() + SquareWidth*0.5 + Margin, - y - SquareWidth * SquareNum, - SquareWidth * (SquareNum-1), SquareWidth * (SquareNum-1)); - else if (!strcmp(s, "hDial")) - return QRectF( - get_leftWidth() + name_size.width() + SquareWidth*0.5 + Margin, - y + SquareWidth * 1.5, - SquareWidth * (SquareNum-1), SquareWidth * (SquareNum-1)); - else if (!strcmp(s, "chEn")) - return QRectF( - get_leftWidth() + name_size.width() + SquareWidth*0.75 + Margin, - y - SquareWidth / 2, - SquareWidth * 1.5, SquareWidth); - else if (!strcmp(s, "acdc")) - return QRectF( - get_leftWidth() + name_size.width() + SquareWidth*2.75 + Margin, - y - SquareWidth / 2, - SquareWidth * 1.5, SquareWidth); - else if (!strcmp(s, "dsoTrig")) - return QRectF( - right - label_size.width(), - _trig_vpos - SquareWidth / 2, - label_size.width(), label_size.height()); - else - return QRectF( - 2, - y - SquareWidth / 2, - SquareWidth, SquareWidth); -} - } // namespace view } // namespace pv diff --git a/DSLogic-gui/pv/view/signal.h b/DSLogic-gui/pv/view/signal.h index 8420314..9f7cd98 100644 --- a/DSLogic-gui/pv/view/signal.h +++ b/DSLogic-gui/pv/view/signal.h @@ -36,251 +36,48 @@ #include #include "libsigrok4DSLogic/libsigrok.h" -#include "dsldial.h" +#include "trace.h" namespace pv { namespace data { class SignalData; -class Logic; -class Dso; -class Analog; -class Group; } -namespace decoder { -class Decoder; +namespace device { +class DevInst; } namespace view { -class Signal +class Signal : public Trace { private: - static const int SquareWidth = 20; - static const int Margin = 3; - static const int SquareNum = 5; - static const quint64 vDialValueCount = 10; - static const quint64 vDialValueStep = 1000; - static const quint64 vDialUnitCount = 2; - static const quint64 hDialValueCount = 25; - static const quint64 hDialValueStep = 1000; - static const quint64 hDialUnitCount = 4; - static const quint64 vDialValue[vDialValueCount]; - static const QString vDialUnit[vDialUnitCount]; - - static const quint64 hDialValue[hDialValueCount]; - static const QString hDialUnit[hDialUnitCount]; - -public: - static const int COLOR = 1; - static const int NAME = 2; - static const int POSTRIG = 3; - static const int HIGTRIG = 4; - static const int NEGTRIG = 5; - static const int LOWTRIG = 6; - static const int EDGETRIG = 7; - static const int LABEL = 8; - static const int VDIAL = 9; - static const int HDIAL = 10; - static const int CHEN = 11; - static const int ACDC = 12; - static const int DSOTRIG = 13; - - static const QColor dsBlue; - static const QColor dsYellow; - static const QColor dsRed; - static const QColor dsGreen; - static const QColor dsGray; - static const QColor dsDisable; - static const QColor dsActive; - static const QColor dsLightBlue; - static const QColor dsLightRed; - static const QPen SignalAxisPen; - - enum {DS_LOGIC = 0, DS_ANALOG, DS_GROUP, DS_PROTOCOL, DS_DSO}; protected: - Signal(QString name, int index, int type, int order); - Signal(QString name, std::list index_list, int type, int order, int sec_index); + Signal(boost::shared_ptr dev_inst, + const sr_channel * const probe, int type); public: - int get_type() const; - int get_order() const; - void set_order(int order); - - /** - * Gets the header area size - */ - int get_leftWidth() const; - int get_rightWidth() const; - int get_headerHeight() const; - int get_index() const; - std::list &get_index_list(); - void set_index_list(std::list index_list); - int get_sec_index() const; - void set_sec_index(int sec_index); - - /** - * Gets the name of this signal. - */ - QString get_name() const; - - /** - * Sets the name of the signal. - */ - void set_name(QString name); - - /** - * Get the colour of the signal. - */ - QColor get_colour() const; - - /** - * Set the colour of the signal. - */ - void set_colour(QColor colour); - - /** - * Gets the vertical layout offset of this signal. - */ - int get_v_offset() const; - - /** - * Sets the vertical layout offset of this signal. - */ - void set_v_offset(int v_offset); - - /** - * Gets the height of this signal. - */ - int get_signalHeight() const; - - /** - * Sets the height of this signal. - */ - void set_signalHeight(int height); + virtual boost::shared_ptr data() const = 0; - /** - * Gets the old vertical layout offset of this signal. - */ - int get_old_v_offset() const; + virtual const std::vector< std::pair > cur_edges() const = 0; /** - * Sets the old vertical layout offset of this signal. + * Returns true if the trace is visible and enabled. */ - void set_old_v_offset(int v_offset); - - /** - * Returns true if the signal has been selected by the user. - */ - bool selected() const; - - /** - * Selects or deselects the signal. - */ - void select(bool select = true); - - /* - * - */ - int get_trig() const; - void set_trig(int trig); - - /* - * - */ - bool get_vDialActive() const; - void set_vDialActive(bool active); - bool get_hDialActive() const; - void set_hDialActive(bool active); - bool go_vDialPre(); - bool go_vDialNext(); - bool go_hDialPre(); - bool go_hDialNext(); - quint64 get_vDialValue() const; - quint64 get_hDialValue() const; - uint16_t get_vDialSel() const; - uint16_t get_hDialSel() const; - bool get_acCoupling() const; - void set_acCoupling(bool coupling); - bool get_active() const; - void set_active(bool active); - int get_zeroPos() const; - void set_zeroPos(int pos); - int get_windowHeight() const; - void set_windowHeight(int height); - void set_trig_vpos(int value); - int get_trig_vpos() const; - - /** - * Paints the signal with a QPainter - * @param p the QPainter to paint into. - * @param y the y-coordinate to draw the signal at - * @param left the x-coordinate of the left edge of the signal - * @param right the x-coordinate of the right edge of the signal - * @param scale the scale in seconds per pixel. - * @param offset the time to show at the left hand edge of - * the view in seconds. - **/ - virtual void paint(QPainter &p, int y, int left, int right, - double scale, double offset) = 0; - - virtual const std::vector< std::pair > cur_edges() const = 0; - - virtual void set_decoder(pv::decoder::Decoder *decoder) = 0; - - virtual pv::decoder::Decoder* get_decoder() = 0; - - virtual void del_decoder() = 0; - - virtual void set_data(boost::shared_ptr _logic_data, - boost::shared_ptr _dso_data, - boost::shared_ptr _analog_data, - boost::shared_ptr _group_data) = 0; + bool enabled() const; /** * Paints the signal label into a QGLWidget. * @param p the QPainter to paint into. - * @param y the y-coordinate of the signal. * @param right the x-coordinate of the right edge of the header * area. * @param hover true if the label is being hovered over by the mouse. + * @param action mouse position for hover */ - virtual void paint_label(QPainter &p, int y, int right, - bool hover, int action); - - virtual void paint_trig(QPainter &p, int left, int right, - bool hover); - - /** - * Determines if a point is in the header rect. - * 1 - in color rect - * 2 - in name rect - * 3 - in posTrig rect - * 4 - in higTrig rect - * 5 - in negTrig rect - * 6 - in lowTrig rect - * 7 - in label rect - * 0 - not - * @param y the y-coordinate of the signal. - * @param right the x-coordinate of the right edge of the header - * area. - * @param point the point to test. - */ - int pt_in_rect(int y, int right, - const QPoint &point); - - /** - * Computes the outline rectangle of a label. - * @param p the QPainter to lay out text with. - * @param y the y-coordinate of the signal. - * @param right the x-coordinate of the right edge of the header - * area. - * @return Returns the rectangle of the signal label. - */ - QRectF get_rect(const char *s, int y, int right); + //virtual void paint_label(QPainter &p, int right, bool hover, int action); protected: @@ -293,41 +90,9 @@ protected: */ void paint_axis(QPainter &p, int y, int left, int right); -private: - - /** - * Computes an caches the size of the label text. - */ - void compute_text_size(QPainter &p); - protected: - int _type; - std::list _index_list; - int _order; - int _sec_index; - QString _name; - QColor _colour; - int _v_offset; - int _old_v_offset; - - int _signalHeight; - - bool _selected; - - int _trig; - - QSizeF _text_size; - dslDial *_vDial; - dslDial *_hDial; - bool _vDialActive; - bool _hDialActive; - bool _acCoupling; - bool _active; - int _zeroPos; - int _windowHeight; - - int _trig_vpos; - bool _trig_en; + boost::shared_ptr _dev_inst; + const sr_channel *const _probe; }; } // namespace view diff --git a/DSLogic-gui/pv/view/trace.cpp b/DSLogic-gui/pv/view/trace.cpp new file mode 100644 index 0000000..5175f04 --- /dev/null +++ b/DSLogic-gui/pv/view/trace.cpp @@ -0,0 +1,474 @@ +/* + * This file is part of the DSLogic-gui project. + * DSLogic-gui is based on PulseView. + * + * Copyright (C) 2013 Joel Holdsworth + * Copyright (C) 2014 DreamSourceLab + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include +#include + +#include +#include +#include + +#include "trace.h" +#include "view.h" +#include "../device/devinst.h" +#include "../sigsession.h" + +namespace pv { +namespace view { + +const QColor Trace::dsBlue = QColor(17, 133, 209, 255); +const QColor Trace::dsYellow = QColor(238, 178, 17, 255); +const QColor Trace::dsRed = QColor(213, 15, 37, 255); +const QColor Trace::dsGreen = QColor(0, 153, 37, 200); +const QColor Trace::dsGray = QColor(0x88, 0x8A, 0x85, 60); +const QColor Trace::dsFore = QColor(0xff, 0xff, 0xff, 255); +const QColor Trace::dsBack = QColor(0x16, 0x18, 0x23, 255); +const QColor Trace::dsDisable = QColor(0x88, 0x8A, 0x85, 200); +const QColor Trace::dsActive = QColor(17, 133, 209, 255); +const QColor Trace::dsLightBlue = QColor(17, 133, 209, 150); +const QColor Trace::dsLightRed = QColor(213, 15, 37, 150); +const QPen Trace::SignalAxisPen = QColor(128, 128, 128, 64); + +const QPen Trace::AxisPen(QColor(128, 128, 128, 64)); +const int Trace::LabelHitPadding = 2; + +Trace::Trace(QString name, int type) : + _name(name), + _v_offset(0), + _type(type), + _sec_index(0), + _signalHeight(30), + _trig(0) +{ +} + +Trace::Trace(QString name, int index, int type) : + _name(name), + _v_offset(0), + _type(type), + _sec_index(0), + _signalHeight(30), + _trig(0) +{ + _index_list.push_back(index); +} + +Trace::Trace(QString name, std::list index_list, int type, int sec_index) : + _name(name), + _v_offset(0), + _type(type), + _index_list(index_list), + _sec_index(sec_index), + _signalHeight(30), + _trig(0) +{ +} + +QString Trace::get_name() const +{ + return _name; +} + +void Trace::set_name(QString name) +{ + _name = name; +} + +QColor Trace::get_colour() const +{ + return _colour; +} + +void Trace::set_colour(QColor colour) +{ + _colour = colour; +} + +int Trace::get_v_offset() const +{ + return _v_offset; +} + +void Trace::set_v_offset(int v_offset) +{ + _v_offset = v_offset; +} + + +int Trace::get_type() const +{ + return _type; +} + +int Trace::get_index() const +{ + return _index_list.front(); +} + +std::list &Trace::get_index_list() +{ + return _index_list; +} + +void Trace::set_index_list(std::list index_list) +{ + assert(index_list.size() != 0); + + _index_list = index_list; +} + +int Trace::get_sec_index() const +{ + return _sec_index; +} + +void Trace::set_sec_index(int sec_index) +{ + _sec_index = sec_index; +} + +int Trace::get_old_v_offset() const +{ + return _old_v_offset; +} + +void Trace::set_old_v_offset(int v_offset) +{ + _old_v_offset = v_offset; +} + +int Trace::get_zeroPos() +{ + return _v_offset - _view->v_offset(); +} + +int Trace::get_signalHeight() const +{ + return _signalHeight; +} + +void Trace::set_signalHeight(int height) +{ + _signalHeight = height; +} + +int Trace::get_trig() const +{ + return _trig; +} + +void Trace::set_trig(int trig) +{ + _trig = trig; + if (trig == 0) + ds_trigger_probe_set(_index_list.front(), 'X', 'X'); + else if (trig == POSTRIG) + ds_trigger_probe_set(_index_list.front(), 'R', 'X'); + else if (trig == HIGTRIG) + ds_trigger_probe_set(_index_list.front(), '1', 'X'); + else if (trig == NEGTRIG) + ds_trigger_probe_set(_index_list.front(), 'F', 'X'); + else if (trig == LOWTRIG) + ds_trigger_probe_set(_index_list.front(), '0', 'X'); + else if (trig == EDGETRIG) + ds_trigger_probe_set(_index_list.front(), 'C', 'X'); +} + +void Trace::set_view(pv::view::View *view) +{ + assert(view); + _view = view; +} + +void Trace::paint_back(QPainter &p, int left, int right) +{ + QPen pen(Signal::dsGray); + pen.setStyle(Qt::DotLine); + p.setPen(pen); + const double sigY = get_y(); + p.drawLine(left, sigY, right, sigY); +} + +void Trace::paint_mid(QPainter &p, int left, int right) +{ + (void)p; + (void)left; + (void)right; +} + +void Trace::paint_fore(QPainter &p, int left, int right) +{ + (void)p; + (void)left; + (void)right; +} + +void Trace::paint_label(QPainter &p, int right, bool hover, int action) +{ + compute_text_size(p); + const int y = get_y(); + + const QRectF color_rect = get_rect("color", y, right); + const QRectF name_rect = get_rect("name", y, right); + const QRectF label_rect = get_rect("label", get_zeroPos(), right); + + p.setRenderHint(QPainter::Antialiasing); + // Paint the ColorButton + p.setPen(Qt::transparent); + p.setBrush(enabled() ? _colour : dsDisable); + p.drawRect(color_rect); + + // Paint the signal name + p.setPen(enabled() ? Qt::black : dsDisable); + p.drawText(name_rect, Qt::AlignLeft | Qt::AlignVCenter, _name); + + // Paint the trigButton + paint_type_options(p, right, hover, action); + + // Paint the label + if (enabled()) { + const QPointF points[] = { + label_rect.topLeft(), + label_rect.topRight(), + QPointF(right, get_zeroPos()), + label_rect.bottomRight(), + label_rect.bottomLeft() + }; + + p.setPen(Qt::transparent); + if (_type == DS_DSO) + p.setBrush(((hover && action == LABEL) || selected()) ? _colour.darker() : _colour); + else + p.setBrush(((hover && action == LABEL) || selected()) ? dsYellow : dsBlue); + p.drawPolygon(points, countof(points)); + + p.setPen(QPen(Qt::blue, 1, Qt::DotLine)); + p.setBrush(Qt::transparent); + p.drawLine(label_rect.right(), label_rect.top() + 3, + label_rect.right(), label_rect.bottom() - 3); + + // Paint the text + p.setPen(Qt::white); + if (_type == DS_GROUP) + p.drawText(label_rect, Qt::AlignCenter | Qt::AlignVCenter, "G"); + else if (_type == DS_ANALOG) + p.drawText(label_rect, Qt::AlignCenter | Qt::AlignVCenter, "A"); + else if (_type == DS_DECODER) + p.drawText(label_rect, Qt::AlignCenter | Qt::AlignVCenter, "D"); + else + p.drawText(label_rect, Qt::AlignCenter | Qt::AlignVCenter, QString::number(_index_list.front())); + } +} + +void Trace::paint_type_options(QPainter &p, int right, bool hover, int action) +{ + (void)p; + (void)right; + (void)hover; + (void)action; +} + +int Trace::pt_in_rect(int y, int right, const QPoint &point) +{ + const QRectF color = get_rect("color", y, right); + const QRectF name = get_rect("name", y, right); + const QRectF posTrig = get_rect("posTrig", y, right); + const QRectF higTrig = get_rect("higTrig", y, right); + const QRectF negTrig = get_rect("negTrig", y, right); + const QRectF lowTrig = get_rect("lowTrig", y, right); + const QRectF edgeTrig = get_rect("edgeTrig", y, right); + const QRectF label = get_rect("label", get_zeroPos(), right); + const QRectF vDial = get_rect("vDial", y, right); + const QRectF hDial = get_rect("hDial", y, right); + const QRectF chEn = get_rect("chEn", y, right); + const QRectF acdc = get_rect("acdc", y, right); + const QRectF dsoTrig = get_rect("dsoTrig", 0, right); + + if (color.contains(point) && enabled()) + return COLOR; + else if (name.contains(point) && enabled()) + return NAME; + else if (posTrig.contains(point) && _type == DS_LOGIC) + return POSTRIG; + else if (higTrig.contains(point) && _type == DS_LOGIC) + return HIGTRIG; + else if (negTrig.contains(point) && _type == DS_LOGIC) + return NEGTRIG; + else if (lowTrig.contains(point) && _type == DS_LOGIC) + return LOWTRIG; + else if (edgeTrig.contains(point) && _type == DS_LOGIC) + return EDGETRIG; + else if (label.contains(point) && enabled()) + return LABEL; + else if (vDial.contains(point) && _type == DS_DSO && enabled()) + return VDIAL; + else if (hDial.contains(point) && _type == DS_DSO && enabled()) + return HDIAL; + else if (chEn.contains(point) && _type == DS_DSO) + return CHEN; + else if (acdc.contains(point) && _type == DS_DSO && enabled()) + return ACDC; + else if (dsoTrig.contains(point) && _type == DS_DSO && enabled()) + return DSOTRIG; + else + return 0; +} + +void Trace::paint_axis(QPainter &p, int y, int left, int right) +{ + p.setPen(SignalAxisPen); + p.drawLine(QPointF(left, y + 0.5f), QPointF(right, y + 0.5f)); +} + +void Trace::compute_text_size(QPainter &p) +{ + _text_size = QSize( + p.boundingRect(QRectF(), 0, "99").width(), + p.boundingRect(QRectF(), 0, "99").height()); +} + +QRectF Trace::get_rect(const char *s, int y, int right) +{ + const QSizeF color_size(SquareWidth, SquareWidth); + const QSizeF name_size(right - get_leftWidth() - get_rightWidth(), SquareWidth); + //const QSizeF label_size(_text_size.width() + Margin, SquareWidth); + const QSizeF label_size(SquareWidth, SquareWidth); + + if (!strcmp(s, "name")) + return QRectF( + get_leftWidth(), + y - name_size.height() / 2, + name_size.width(), name_size.height()); + else if (!strcmp(s, "label")) + return QRectF( + right - 1.5f * label_size.width(), + y - SquareWidth / 2, + label_size.width(), label_size.height()); + else if (!strcmp(s, "posTrig")) + return QRectF( + get_leftWidth() + name_size.width() + Margin, + y - SquareWidth / 2, + SquareWidth, SquareWidth); + else if (!strcmp(s, "higTrig")) + return QRectF( + get_leftWidth() + name_size.width() + SquareWidth + Margin, + y - SquareWidth / 2, + SquareWidth, SquareWidth); + else if (!strcmp(s, "negTrig")) + return QRectF( + get_leftWidth() + name_size.width() + 2 * SquareWidth + Margin, + y - SquareWidth / 2, + SquareWidth, SquareWidth); + else if (!strcmp(s, "lowTrig")) + return QRectF( + get_leftWidth() + name_size.width() + 3 * SquareWidth + Margin, + y - SquareWidth / 2, + SquareWidth, SquareWidth); + else if (!strcmp(s, "edgeTrig")) + return QRectF( + get_leftWidth() + name_size.width() + 4 * SquareWidth + Margin, + y - SquareWidth / 2, + SquareWidth, SquareWidth); + else if (!strcmp(s, "groupIndex")) + return QRectF( + get_leftWidth() + name_size.width() + Margin, + y - SquareWidth / 2, + SquareWidth * SquareNum, SquareWidth); + else if (!strcmp(s, "vDial")) + return QRectF( + get_leftWidth() + name_size.width() + SquareWidth*0.5 + Margin, + y - SquareWidth * SquareNum, + SquareWidth * (SquareNum-1), SquareWidth * (SquareNum-1)); + else if (!strcmp(s, "hDial")) + return QRectF( + get_leftWidth() + name_size.width() + SquareWidth*0.5 + Margin, + y + SquareWidth * 1.5, + SquareWidth * (SquareNum-1), SquareWidth * (SquareNum-1)); + else if (!strcmp(s, "chEn")) + return QRectF( + get_leftWidth() + name_size.width() + SquareWidth*0.75 + Margin, + y - SquareWidth / 2, + SquareWidth * 1.5, SquareWidth); + else if (!strcmp(s, "acdc")) + return QRectF( + get_leftWidth() + name_size.width() + SquareWidth*2.75 + Margin, + y - SquareWidth / 2, + SquareWidth * 1.5, SquareWidth); + else + return QRectF( + 2, + y - SquareWidth / 2, + SquareWidth, SquareWidth); +} + +QRectF Trace::get_view_rect() const +{ + assert(_view); + return QRectF(0, 0, _view->viewport()->width(), _view->viewport()->height()); +} + +int Trace::get_y() const +{ + return _v_offset - _view->v_offset(); +} + +QColor Trace::get_text_colour() const +{ + return (_colour.lightness() > 64) ? Qt::black : Qt::white; +} + +void Trace::on_text_changed(const QString &text) +{ + set_name(text); + text_changed(); +} + +void Trace::on_colour_changed(const QColor &colour) +{ + set_colour(colour); + colour_changed(); +} + +int Trace::rows_size() +{ + return 1; +} + +int Trace::get_leftWidth() const +{ + return SquareWidth + Margin; +} + +int Trace::get_rightWidth() const +{ + return 2 * Margin + SquareNum * SquareWidth + 1.5 * SquareWidth; +} + +int Trace::get_headerHeight() const +{ + return SquareWidth; +} + +} // namespace view +} // namespace pv diff --git a/DSLogic-gui/pv/view/trace.h b/DSLogic-gui/pv/view/trace.h new file mode 100644 index 0000000..b31ca18 --- /dev/null +++ b/DSLogic-gui/pv/view/trace.h @@ -0,0 +1,314 @@ +/* + * This file is part of the DSLogic-gui project. + * DSLogic-gui is based on PulseView. + * + * Copyright (C) 2013 Joel Holdsworth + * Copyright (C) 2014 DreamSourceLab + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DSLOGIC_PV_VIEW_TRACE_H +#define DSLOGIC_PV_VIEW_TRACE_H + +#include +#include +#include +#include +#include + +#include + +#include "selectableitem.h" +#include "dsldial.h" + +class QFormLayout; + +namespace pv { +namespace view { + +class View; + +class Trace : public SelectableItem +{ + Q_OBJECT + +private: + static const int Margin = 3; + static const int SquareNum = 5; + static const QPen AxisPen; + static const int LabelHitPadding; + +public: + static const int SquareWidth = 20; + static const int COLOR = 1; + static const int NAME = 2; + static const int POSTRIG = 3; + static const int HIGTRIG = 4; + static const int NEGTRIG = 5; + static const int LOWTRIG = 6; + static const int EDGETRIG = 7; + static const int LABEL = 8; + static const int VDIAL = 9; + static const int HDIAL = 10; + static const int CHEN = 11; + static const int ACDC = 12; + static const int DSOTRIG = 13; + + static const QColor dsBlue; + static const QColor dsYellow; + static const QColor dsRed; + static const QColor dsGreen; + static const QColor dsGray; + static const QColor dsFore; + static const QColor dsBack; + static const QColor dsDisable; + static const QColor dsActive; + static const QColor dsLightBlue; + static const QColor dsLightRed; + static const QPen SignalAxisPen; + +protected: + Trace(QString name, int type); + Trace(QString name, int index, int type); + Trace(QString name, std::list index_list, int type, int sec_index); + +public: + enum {DS_LOGIC = 0, DS_ANALOG, DS_GROUP, DS_DSO, DS_DECODER}; + +public: + /** + * Gets the name of this signal. + */ + QString get_name() const; + + /** + * Sets the name of the signal. + */ + virtual void set_name(QString name); + + /** + * Get the colour of the signal. + */ + QColor get_colour() const; + + /** + * Set the colour of the signal. + */ + void set_colour(QColor colour); + + /** + * Gets the vertical layout offset of this signal. + */ + int get_v_offset() const; + + /** + * Sets the vertical layout offset of this signal. + */ + void set_v_offset(int v_offset); + + /** + * Gets trace type + */ + int get_type() const; + + /** + * Index process + */ + int get_index() const; + std::list &get_index_list(); + void set_index_list(std::list index_list); + int get_sec_index() const; + void set_sec_index(int sec_index); + + /** + * Gets the height of this signal. + */ + int get_signalHeight() const; + + /** + * Sets the height of this signal. + */ + void set_signalHeight(int height); + + /** + * Geom + */ + int get_leftWidth() const; + int get_rightWidth() const; + int get_headerHeight() const; + + /** + * Gets the old vertical layout offset of this signal. + */ + int get_old_v_offset() const; + + /** + * Sets the old vertical layout offset of this signal. + */ + void set_old_v_offset(int v_offset); + + virtual int get_zeroPos(); + + /** + * + */ + int get_trig() const; + void set_trig(int trig); + + /** + * Returns true if the trace is visible and enabled. + */ + virtual bool enabled() const = 0; + + virtual void set_view(pv::view::View *view); + + /** + * Paints the background layer of the trace with a QPainter + * @param p the QPainter to paint into. + * @param left the x-coordinate of the left edge of the signal + * @param right the x-coordinate of the right edge of the signal + **/ + virtual void paint_back(QPainter &p, int left, int right); + + /** + * Paints the mid-layer of the trace with a QPainter + * @param p the QPainter to paint into. + * @param left the x-coordinate of the left edge of the signal + * @param right the x-coordinate of the right edge of the signal + **/ + virtual void paint_mid(QPainter &p, int left, int right); + + /** + * Paints the foreground layer of the trace with a QPainter + * @param p the QPainter to paint into. + * @param left the x-coordinate of the left edge of the signal + * @param right the x-coordinate of the right edge of the signal + **/ + virtual void paint_fore(QPainter &p, int left, int right); + + /** + * Paints the trace label. + * @param p the QPainter to paint into. + * @param right the x-coordinate of the right edge of the header + * area. + * @param hover true if the label is being hovered over by the mouse. + * @param action mouse position for hover + */ + virtual void paint_label(QPainter &p, int right, bool hover, int action); + + /** + * Gets the y-offset of the axis. + */ + int get_y() const; + + /** + * Determines if a point is in the header rect. + * 1 - in color rect + * 2 - in name rect + * 3 - in posTrig rect + * 4 - in higTrig rect + * 5 - in negTrig rect + * 6 - in lowTrig rect + * 7 - in label rect + * 0 - not + * @param y the y-coordinate of the signal. + * @param right the x-coordinate of the right edge of the header + * area. + * @param point the point to test. + */ + int pt_in_rect(int y, int right, + const QPoint &point); + + /** + * Computes the outline rectangle of a label. + * @param p the QPainter to lay out text with. + * @param y the y-coordinate of the signal. + * @param right the x-coordinate of the right edge of the header + * area. + * @return Returns the rectangle of the signal label. + */ + QRectF get_rect(const char *s, int y, int right); + + virtual int rows_size(); + + virtual QRectF get_view_rect() const; + +protected: + + /** + * Gets the text colour. + * @remarks This colour is computed by comparing the lightness + * of the trace colour against a threshold to determine whether + * white or black would be more visible. + */ + QColor get_text_colour() const; + + /** + * Paints a zero axis across the viewport. + * @param p the QPainter to paint into. + * @param y the y-offset of the axis. + * @param left the x-coordinate of the left edge of the view. + * @param right the x-coordinate of the right edge of the view. + */ + void paint_axis(QPainter &p, int y, int left, int right); + + /** + * Paints optoins for different trace type. + * @param p the QPainter to paint into. + * @param right the x-coordinate of the right edge of the header + * area. + * @param hover true if the label is being hovered over by the mouse. + * @param action mouse position for hover + */ + virtual void paint_type_options(QPainter &p, int right, bool hover, int action); + +private: + + /** + * Computes an caches the size of the label text. + */ + void compute_text_size(QPainter &p); + +private slots: + void on_text_changed(const QString &text); + + void on_colour_changed(const QColor &colour); + +signals: + void visibility_changed(); + void text_changed(); + void colour_changed(); + +protected: + pv::view::View *_view; + + QString _name; + QColor _colour; + int _v_offset; + int _type; + std::list _index_list; + int _sec_index; + int _old_v_offset; + int _signalHeight; + int _trig; + + QSizeF _text_size; +}; + +} // namespace view +} // namespace pv + +#endif // DSLOGIC_PV_VIEW_TRACE_H diff --git a/DSLogic-gui/pv/view/view.cpp b/DSLogic-gui/pv/view/view.cpp index fdd8624..30c3f3d 100644 --- a/DSLogic-gui/pv/view/view.cpp +++ b/DSLogic-gui/pv/view/view.cpp @@ -32,12 +32,16 @@ #include #include +#include "decodetrace.h" #include "header.h" +#include "devmode.h" #include "ruler.h" #include "signal.h" +#include "dsosignal.h" #include "view.h" #include "viewport.h" +#include "../device/devinst.h" #include "pv/sigsession.h" #include "pv/data/logic.h" #include "pv/data/logicsnapshot.h" @@ -61,16 +65,13 @@ const QColor View::CursorAreaColour(220, 231, 243); const QSizeF View::LabelPadding(4, 4); -const int View::WellPixelsPerSample = 1.0f; -const double View::MaxViewRate = 1.0f; - View::View(SigSession &session, QWidget *parent) : QAbstractScrollArea(parent), _session(session), _viewport(new Viewport(*this)), _ruler(new Ruler(*this)), _header(new Header(*this)), - _data_length(0), + _devmode(new DevMode(*this)), _scale(1e-8), _preScale(1e-6), _maxscale(1e9), @@ -81,6 +82,7 @@ View::View(SigSession &session, QWidget *parent) : _updating_scroll(false), _need_update(false), _show_cursors(false), + _trig_pos(0), _hover_point(-1, -1) { setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); @@ -90,46 +92,42 @@ View::View(SigSession &session, QWidget *parent) : connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(v_scroll_value_changed(int))); + setViewportMargins(headerWidth(), RulerHeight, 0, 0); + setViewport(_viewport); + connect(&_session, SIGNAL(signals_changed()), this, SLOT(signals_changed())); connect(&_session, SIGNAL(data_updated()), this, SLOT(data_updated())); - connect(&_session, SIGNAL(sample_rate_changed(quint64)), - this, SLOT(sample_rate_changed(quint64))); connect(&_session, SIGNAL(receive_data(quint64)), this, SLOT(receive_data(quint64))); connect(&_session, SIGNAL(receive_trigger(quint64)), this, SLOT(set_trig_pos(quint64))); - connect(_header, SIGNAL(signals_moved()), - this, SLOT(on_signals_moved())); + connect(&_session, SIGNAL(device_setted()), + _devmode, SLOT(set_device())); + connect(_devmode, SIGNAL(mode_changed()), + this, SIGNAL(mode_changed())); + + connect(_header, SIGNAL(traces_moved()), + this, SLOT(on_traces_moved())); connect(_header, SIGNAL(header_updated()), this, SLOT(header_updated())); - connect(_header, SIGNAL(vDial_changed(quint16)), - this, SLOT(vDial_changed(quint16))); - connect(_header, SIGNAL(hDial_changed(quint16)), - this, SLOT(hDial_changed(quint16))); - connect(_header, SIGNAL(acdc_changed(quint16)), - this, SLOT(acdc_changed(quint16))); - connect(_header, SIGNAL(ch_changed(quint16)), - this, SLOT(ch_changed(quint16))); - - setViewportMargins(headerWidth(), RulerHeight, 0, 0); - setViewport(_viewport); _viewport->installEventFilter(this); _ruler->installEventFilter(this); _header->installEventFilter(this); + _devmode->installEventFilter(this); _viewport->setObjectName(tr("ViewArea_viewport")); _ruler->setObjectName(tr("ViewArea_ruler")); _header->setObjectName(tr("ViewArea_header")); _show_trig_cursor = false; - _trig_cursor = new Cursor(*this, Signal::dsLightRed, 0); + _trig_cursor = new Cursor(*this, Trace::dsLightRed, 0); _show_search_cursor = false; _search_pos = 0; - _search_cursor = new Cursor(*this, Signal::dsLightBlue, _search_pos); + _search_cursor = new Cursor(*this, Trace::dsLightBlue, _search_pos); } SigSession& View::session() @@ -184,23 +182,24 @@ void View::zoom(double steps, int offset) _preOffset = _offset; const double cursor_offset = _offset + _scale * offset; - if (_session.get_device()->mode != DSO) { + if (_session.get_device()->dev_inst()->mode != DSO) { _scale *= pow(3.0/2.0, -steps); _scale = max(min(_scale, _maxscale), _minscale); - } else { + }else { const vector< shared_ptr > sigs(_session.get_signals()); - if (steps > 0.5) { - BOOST_FOREACH(const shared_ptr s, sigs) - s->go_hDialNext(); - } else if(steps < -0.5) { - BOOST_FOREACH(const shared_ptr s, sigs) - s->go_hDialPre(); + BOOST_FOREACH(const shared_ptr s, sigs) { + shared_ptr dsoSig; + if (dsoSig = dynamic_pointer_cast(s)) { + if(steps > 0.5) + dsoSig->go_hDialPre(); + else if (steps < -0.5) + dsoSig->go_hDialNext(); + } } - _scale = sigs.at(0)->get_hDialValue() * pow(10, -9) * Viewport::NumSpanX / _viewport->width(); } _offset = cursor_offset - _scale * offset; const double MinOffset = -(_scale * (_viewport->width() * (1 - MaxViewRate))); - const double MaxOffset = _data_length * 1.0f / _session.get_last_sample_rate() - + const double MaxOffset = _session.get_device()->get_sample_time() - _scale * (_viewport->width() * MaxViewRate); _offset = max(min(_offset, MaxOffset), MinOffset); @@ -220,16 +219,16 @@ void View::set_scale_offset(double scale, double offset) _preScale = _scale; _preOffset = _offset; - if (_session.get_device()->mode != DSO) - _scale = max(min(scale, _maxscale), _minscale); + _scale = max(min(scale, _maxscale), _minscale); const double MinOffset = -(_scale * (_viewport->width() * (1 - MaxViewRate))); - const double MaxOffset = _data_length * 1.0f / _session.get_last_sample_rate() + const double MaxOffset = _session.get_device()->get_sample_time() - _scale * (_viewport->width() * MaxViewRate); _offset = max(min(offset, MaxOffset), MinOffset); if (_scale != _preScale || _offset != _preOffset) { update_scroll(); + _header->update(); _ruler->update(); _viewport->update(); } @@ -245,6 +244,36 @@ void View::set_preScale_preOffset() set_scale_offset(_preScale, _preOffset); } +vector< shared_ptr > View::get_traces() const +{ + const vector< shared_ptr > sigs(_session.get_signals()); +#ifdef ENABLE_DECODE + const vector< shared_ptr > decode_sigs( + _session.get_decode_signals()); + vector< shared_ptr > traces( + sigs.size() + decode_sigs.size()); +#else + vector< shared_ptr > traces(sigs.size()); +#endif + + vector< shared_ptr >::iterator i = traces.begin(); + i = copy(sigs.begin(), sigs.end(), i); +#ifdef ENABLE_DECODE + i = copy(decode_sigs.begin(), decode_sigs.end(), i); +#endif + + stable_sort(traces.begin(), traces.end(), compare_trace_v_offsets); + return traces; +} + +bool View::compare_trace_v_offsets(const shared_ptr &a, + const shared_ptr &b) +{ + assert(a); + assert(b); + return a->get_v_offset() < b->get_v_offset(); +} + bool View::cursors_shown() const { return _show_cursors; @@ -283,7 +312,7 @@ void View::show_search_cursor(bool show) void View::set_trig_pos(quint64 trig_pos) { - const double time = trig_pos * 1.0f / _session.get_last_sample_rate(); + const double time = trig_pos * 1.0f / _session.get_device()->get_sample_rate(); _trig_pos = trig_pos; _trig_cursor->set_time(time); _show_trig_cursor = true; @@ -296,7 +325,7 @@ void View::set_search_pos(uint64_t search_pos) { //assert(search_pos >= 0); - const double time = search_pos * 1.0f / _session.get_last_sample_rate(); + const double time = search_pos * 1.0f / _session.get_device()->get_sample_rate(); _search_pos = search_pos; _search_cursor->set_time(time); set_scale_offset(_scale, time - _scale * _viewport->width() / 2); @@ -321,15 +350,15 @@ const QPointF& View::hover_point() const void View::normalize_layout() { - const vector< boost::shared_ptr > sigs(_session.get_signals()); + const vector< shared_ptr > traces(get_traces()); int v_min = INT_MAX; - BOOST_FOREACH(const boost::shared_ptr s, sigs) - v_min = min(s->get_v_offset(), v_min); + BOOST_FOREACH(const shared_ptr t, traces) + v_min = min(t->get_v_offset(), v_min); const int delta = -min(v_min, 0); - BOOST_FOREACH(boost::shared_ptr s, sigs) - s->set_v_offset(s->get_v_offset() + delta); + BOOST_FOREACH(shared_ptr t, traces) + t->set_v_offset(t->get_v_offset() + delta); verticalScrollBar()->setSliderPosition(_v_offset + delta); v_scroll_value_changed(verticalScrollBar()->sliderPosition()); @@ -343,16 +372,16 @@ int View::get_spanY() int View::get_signalHeight() { - return SignalHeight; + return _signalHeight; } void View::get_scroll_layout(double &length, double &offset) const { - const boost::shared_ptr sig_data = _session.get_data(); - if (!sig_data) + const set< shared_ptr > data_set = _session.get_data(); + if (data_set.empty()) return; - length = _data_length / (sig_data->get_samplerate() * _scale); + length = _session.get_device()->get_sample_time() / _scale; offset = _offset / _scale; } @@ -385,31 +414,63 @@ void View::update_scroll() // Set the vertical scrollbar verticalScrollBar()->setPageStep(areaSize.height()); verticalScrollBar()->setRange(0, - _viewport->get_total_height() + SignalMargin - - areaSize.height()); + _viewport->get_total_height() - areaSize.height()); } -void View::reset_signal_layout() +void View::update_scale() { - if (_session.get_signals().size()) - SignalHeight = - ((_viewport->height() - horizontalScrollBar()->height()) / - _session.get_signals().size()) - - 2 * SignalMargin; + const uint64_t sample_rate = _session.get_device()->get_sample_rate(); + assert(sample_rate > 0); + + if (_session.get_device()->dev_inst()->mode != DSO) { + _scale = (1.0f / sample_rate) / WellPixelsPerSample; + _maxscale = _session.get_device()->get_sample_time() / (get_max_width() * MaxViewRate); + } else { + _scale = _session.get_device()->get_time_base() * 10.0f / get_max_width() * pow(10, -9); + _maxscale = 1e9; + } - int offset = SignalMargin + SignalHeight; - _spanY = SignalHeight + 2 * SignalMargin; + _minscale = (1.0f / sample_rate) / MaxPixelsPerSample; + _offset = 0; + _preScale = _scale; + _preOffset = _offset; + + const double time = _trig_pos * 1.0f / sample_rate; + _trig_cursor->set_time(time); + + _ruler->update(); + _viewport->update(); +} + +void View::signals_changed() +{ + int total_rows = 0; + const vector< shared_ptr > traces(get_traces()); + BOOST_FOREACH(const shared_ptr t, traces) + { + assert(t); + if (dynamic_pointer_cast(t) || + t->enabled()) + total_rows += t->rows_size(); + } - const vector< boost::shared_ptr > sigs(_session.get_signals()); - BOOST_FOREACH(boost::shared_ptr s, sigs) { - s->set_signalHeight(SignalHeight); - s->set_windowHeight(_viewport->height()); - //s->set_v_offset(offset); - //offset += SignalHeight + 2 * SignalMargin; - s->set_v_offset(offset + s->get_order() * _spanY); - s->set_zeroPos(_viewport->height()*0.5); + const double height = (_viewport->height() + - horizontalScrollBar()->height() + - 2 * SignalMargin * traces.size()) * 1.0 / total_rows; + + _signalHeight = (int)((height <= 0) ? 1 : height); + _spanY = _signalHeight + 2 * SignalMargin; + int next_v_offset = SignalMargin; + BOOST_FOREACH(shared_ptr t, traces) { + t->set_view(this); + const double traceHeight = _signalHeight*t->rows_size(); + t->set_signalHeight((int)traceHeight); + t->set_v_offset(next_v_offset + 0.5 * traceHeight + SignalMargin); + next_v_offset += traceHeight + 2 * SignalMargin; } - normalize_layout(); + + header_updated(); + normalize_layout(); } bool View::eventFilter(QObject *object, QEvent *event) @@ -467,14 +528,13 @@ int View::headerWidth() QFont font = QApplication::font(); QFontMetrics fm(font); - int fontWidth=fm.width("A"); - const vector< shared_ptr > sigs(_session.get_signals()); - if (!sigs.empty()){ - BOOST_FOREACH(const shared_ptr s, sigs) { - maxNameWidth = max(s->get_name().length() * fontWidth, maxNameWidth); - maxLeftWidth = max(s->get_leftWidth(), maxLeftWidth); - maxRightWidth = max(s->get_rightWidth(), maxRightWidth); + const vector< shared_ptr > traces(get_traces()); + if (!traces.empty()){ + BOOST_FOREACH(const shared_ptr t, traces) { + maxNameWidth = max(fm.boundingRect(t->get_name()).width(), maxNameWidth); + maxLeftWidth = max(t->get_leftWidth(), maxLeftWidth); + maxRightWidth = max(t->get_rightWidth(), maxRightWidth); } } maxNameWidth = max(_header->get_nameEditWidth(), maxNameWidth); @@ -489,11 +549,14 @@ void View::resizeEvent(QResizeEvent*) { update_margins(); update_scroll(); - if (_session.get_capture_state() == SigSession::Stopped) { - _maxscale = (_data_length * 1.0f / _session.get_last_sample_rate()) / (_viewport->width() * MaxViewRate); - _scale = min(_scale, _maxscale); - } - reset_signal_layout(); + if (_session.get_device()->dev_inst()->mode == DSO) + _scale = _session.get_device()->get_time_base() * pow(10, -9) * DS_CONF_DSO_HDIVS / get_max_width(); + + _maxscale = _session.get_device()->get_sample_time() / (get_max_width() * MaxViewRate); + _scale = min(_scale, _maxscale); + + signals_changed(); + _ruler->update(); _header->header_resize(); _need_update = true; } @@ -506,7 +569,7 @@ void View::h_scroll_value_changed(int value) _preOffset = _offset; const double MinOffset = -(_scale * (_viewport->width() * (1 - MaxViewRate))); - const double MaxOffset = _data_length * 1.0f / _session.get_last_sample_rate() + const double MaxOffset = _session.get_device()->get_sample_time() - _scale * (_viewport->width() * MaxViewRate); const int range = horizontalScrollBar()->maximum(); @@ -533,19 +596,8 @@ void View::v_scroll_value_changed(int value) _viewport->update(); } -void View::signals_changed() -{ - reset_signal_layout(); -} - void View::data_updated() { - // Get the new data length - _data_length = max(_session.get_total_sample_len(), (quint64)1000); - _maxscale = (_data_length * 1.0f / _session.get_last_sample_rate()) / (_viewport->width() * MaxViewRate); - if(_session.get_device()->mode != DSO) - _scale = min(_scale, _maxscale); - setViewportMargins(headerWidth(), RulerHeight, 0, 0); update_margins(); @@ -563,6 +615,8 @@ void View::update_margins() _viewport->width(), _viewport->y()); _header->setGeometry(0, _viewport->y(), _viewport->x(), _viewport->height()); + _devmode->setGeometry(0, 0, + _viewport->x(), _viewport->y()); } void View::header_updated() @@ -577,33 +631,18 @@ void View::header_updated() _header->update(); } -void View::sample_rate_changed(quint64 sample_rate) -{ - assert(sample_rate > 0); - - if (_session.get_device()->mode != DSO) - _scale = (1.0f / sample_rate) / WellPixelsPerSample; - - _minscale = (1.0f / sample_rate) / (_viewport->width() * MaxViewRate); - _offset = 0; - _preScale = _scale; - _preOffset = _offset; - - _ruler->update(); - _viewport->update(); -} - void View::marker_time_changed() { _ruler->update(); _viewport->update(); } -void View::on_signals_moved() +void View::on_traces_moved() { update_scroll(); + _need_update = true; _viewport->update(); - //signals_moved(); + //traces_moved(); } /* @@ -689,16 +728,6 @@ QString View::get_cm_delta(int index1, int index2) get_cursor_time(index2))); } -QString View::get_cm_delta_cnt(int index1, int index2) -{ - if (index1 == index2) - return "0"; - - return QString::number( - floor(abs(get_cursor_time(index1) - - get_cursor_time(index2)) * _session.get_data()->get_samplerate())); -} - double View::get_cursor_time(int index) { assert(index < (int)_cursorList.size()); @@ -713,6 +742,15 @@ double View::get_cursor_time(int index) } } +uint64_t View::get_cursor_samples(int index) +{ + const double time = get_cursor_time(index); + const uint64_t sample_rate = _session.get_device()->get_sample_limit(); + assert(sample_rate !=0); + + return time*sample_rate; +} + void View::on_mouse_moved() { mouse_moved(); @@ -733,38 +771,15 @@ void View::on_state_changed(bool stop) _viewport->stop_trigger_timer(); } -void View::vDial_changed(uint16_t channel) -{ - if (channel == 0) - _session.set_dso_ctrl(SR_CONF_VDIV0); - else - _session.set_dso_ctrl(SR_CONF_VDIV1); -} - -void View::hDial_changed(uint16_t channel) +int View::get_max_width() { + int max_width = 0; const vector< shared_ptr > sigs(_session.get_signals()); - _session.set_dso_ctrl(SR_CONF_TIMEBASE); - _scale = sigs.at(channel)->get_hDialValue() * pow(10, -9) * Viewport::NumSpanX / _viewport->width(); - _ruler->update(); - _viewport->update(); - update_scroll(); -} - -void View::acdc_changed(uint16_t channel) -{ - if (channel == 0) - _session.set_dso_ctrl(SR_CONF_COUPLING0); - else - _session.set_dso_ctrl(SR_CONF_COUPLING1); -} + BOOST_FOREACH(const shared_ptr s, sigs) { + max_width = max((double)max_width, s->get_view_rect().width()); + } -void View::ch_changed(uint16_t channel) -{ - if (channel == 0) - _session.set_dso_ctrl(SR_CONF_EN_CH0); - else - _session.set_dso_ctrl(SR_CONF_EN_CH1); + return max_width; } } // namespace view diff --git a/DSLogic-gui/pv/view/view.h b/DSLogic-gui/pv/view/view.h index 61a3eea..b268792 100644 --- a/DSLogic-gui/pv/view/view.h +++ b/DSLogic-gui/pv/view/view.h @@ -26,9 +26,16 @@ #include +#include +#include + +#include +#include + #include #include +#include "../data/signaldata.h" #include "cursor.h" #include "signal.h" @@ -39,7 +46,9 @@ class SigSession; namespace view { class Header; +class DevMode; class Ruler; +class Trace; class Viewport; class View : public QAbstractScrollArea { @@ -60,9 +69,9 @@ public: static const QSizeF LabelPadding; - static const int WellPixelsPerSample; - - static const double MaxViewRate; + static const int WellPixelsPerSample = 10.0f; + static const double MaxViewRate = 1.0f; + static const int MaxPixelsPerSample = 100.0f; public: explicit View(SigSession &session, QWidget *parent = 0); @@ -92,6 +101,8 @@ public: void set_scale_offset(double scale, double offset); void set_preScale_preOffset(); + std::vector< boost::shared_ptr > get_traces() const; + /** * Returns true if cursors are displayed. false otherwise. */ @@ -146,39 +157,45 @@ public: void set_need_update(bool need_update); bool need_update() const; + uint64_t get_cursor_samples(int index); QString get_mm_width(); QString get_mm_period(); QString get_mm_freq(); QString get_cm_time(int index); QString get_cm_delta(int index1, int index2); - QString get_cm_delta_cnt(int index1, int index2); void on_mouse_moved(); void on_cursor_moved(); void on_state_changed(bool stop); + int get_max_width(); + signals: void hover_point_changed(); - void signals_moved(); + void traces_moved(); void cursor_update(); void mouse_moved(); void cursor_moved(); + void mode_changed(); + private: void get_scroll_layout(double &length, double &offset) const; void update_scroll(); - void reset_signal_layout(); - void update_margins(); double get_cursor_time(int index); + static bool compare_trace_v_offsets( + const boost::shared_ptr &a, + const boost::shared_ptr &b); + private: bool eventFilter(QObject *object, QEvent *event); @@ -188,32 +205,25 @@ private: public slots: void set_measure_en(int enable); - void hDial_changed(quint16 channel); + void signals_changed(); + void data_updated(); + void update_scale(); private slots: void h_scroll_value_changed(int value); void v_scroll_value_changed(int value); - void signals_changed(); - void data_updated(); - void marker_time_changed(); - void on_signals_moved(); + void on_traces_moved(); void header_updated(); - void sample_rate_changed(quint64 sample_rate); - void receive_data(quint64 length); void set_trig_pos(quint64 trig_pos); - void vDial_changed(quint16 channel); - void acdc_changed(quint16 channel); - void ch_changed(quint16 channel); - private: SigSession &_session; @@ -221,8 +231,7 @@ private: Viewport *_viewport; Ruler *_ruler; Header *_header; - - uint64_t _data_length; + DevMode *_devmode; /// The view time scale in seconds per pixel. double _scale; @@ -235,7 +244,7 @@ private: double _preOffset; int _spanY; - int SignalHeight; + int _signalHeight; int _v_offset; bool _updating_scroll; diff --git a/DSLogic-gui/pv/view/viewport.cpp b/DSLogic-gui/pv/view/viewport.cpp index 9a59aa8..c4eaed0 100644 --- a/DSLogic-gui/pv/view/viewport.cpp +++ b/DSLogic-gui/pv/view/viewport.cpp @@ -26,6 +26,8 @@ #include "ruler.h" #include "signal.h" +#include "dsosignal.h" +#include "../device/devinst.h" #include "../data/logic.h" #include "../data/logicsnapshot.h" #include "../sigsession.h" @@ -41,11 +43,6 @@ using namespace std; namespace pv { namespace view { -const int Viewport::HitCursorMargin = 3; -const int Viewport::NumSpanY = 5; -const int Viewport::NumMiniSpanY = 5; -const int Viewport::NumSpanX = 10; - Viewport::Viewport(View &parent) : QWidget(&parent), _view(parent), @@ -70,8 +67,8 @@ Viewport::Viewport(View &parent) : triggered = false; timer_cnt = 0; - connect(&_view, SIGNAL(signals_moved()), - this, SLOT(on_signals_moved())); + connect(&_view, SIGNAL(traces_moved()), + this, SLOT(on_traces_moved())); connect(&trigger_timer, SIGNAL(timeout()), this, SLOT(on_trigger_timer())); } @@ -79,32 +76,42 @@ Viewport::Viewport(View &parent) : int Viewport::get_total_height() const { int h = 0; - const vector< boost::shared_ptr > sigs( - _view.session().get_signals()); - BOOST_FOREACH(const boost::shared_ptr s, sigs) { - assert(s); - //h = max(s->get_v_offset() + _view.get_signalHeight(), h); - h = max(s->get_v_offset(), h); - } + + const vector< shared_ptr > traces(_view.get_traces()); + BOOST_FOREACH(const shared_ptr t, traces) { + assert(t); + h += (int)(t->get_signalHeight()); + } + h += 2 * View::SignalMargin; return h; } +QPoint Viewport::get_mouse_point() const +{ + return _mouse_point; +} + void Viewport::paintEvent(QPaintEvent *event) { (void)event; using pv::view::Signal; - int i, j; + QStyleOption o; o.initFrom(this); QPainter p(this); style()->drawPrimitive(QStyle::PE_Widget, &o, &p, this); - //QPainter p(this); - p.setRenderHint(QPainter::Antialiasing); + const vector< shared_ptr > traces(_view.get_traces()); + BOOST_FOREACH(const shared_ptr t, traces) + { + assert(t); + t->paint_back(p, 0, width()); + } - if (_view.session().get_device()->mode == LOGIC) { + p.setRenderHint(QPainter::Antialiasing); + if (_view.session().get_device()->dev_inst()->mode == LOGIC) { switch(_view.session().get_capture_state()) { case SigSession::Init: break; @@ -122,79 +129,23 @@ void Viewport::paintEvent(QPaintEvent *event) paintSignals(p); } + BOOST_FOREACH(const shared_ptr t, traces) + { + assert(t); + if (t->enabled()) + t->paint_fore(p, 0, width()); + } + p.setRenderHint(QPainter::Antialiasing, false); if (_view.get_signalHeight() != _curSignalHeight) _curSignalHeight = _view.get_signalHeight(); - const vector< boost::shared_ptr > sigs( - _view.session().get_signals()); - - BOOST_FOREACH(const boost::shared_ptr s, sigs) { - assert(s); - //paint_axis(p, y, left, right); - p.setPen(Signal::dsGray); - const double sigY = s->get_v_offset() - _view.v_offset(); - - if (s->get_type() == Signal::DS_ANALOG) { - p.drawLine(0, sigY, width(), sigY); - const double spanY = (s->get_signalHeight()) * 1.0f / NumSpanY; - for (i = 1; i <= NumSpanY; i++) { - const double posY = sigY - spanY * i; - p.drawLine(0, posY, width(), posY); - const double miniSpanY = spanY / NumMiniSpanY; - for (j = 1; j < NumMiniSpanY; j++) { - p.drawLine(width() / 2.0f - 10, posY + miniSpanY * j, - width() / 2.0f + 10, posY + miniSpanY * j); - } - } - const double spanX = width() * 1.0f / NumSpanX; - for (i = 1; i < NumSpanX; i++) { - p.drawLine(0 + spanX * i, sigY, - 0 + spanX * i, sigY - s->get_signalHeight()); - } - } else if (s->get_type() == Signal::DS_LOGIC) { - p.drawLine(0, sigY + 10, width(), sigY + 10); - } - } - - if (_view.session().get_device()->mode == DSO) { - - p.setPen(Signal::dsGray); - p.setPen(Qt::DotLine); - - const double spanY =height() * 1.0f / 10; - for (i = 1; i < 11; i++) { - const double posY = spanY * i; - p.drawLine(0, posY, width(), posY); - const double miniSpanY = spanY / 5; - for (j = 1; j < 5; j++) { - p.drawLine(width() / 2.0f - 10, posY - miniSpanY * j, - width() / 2.0f + 10, posY - miniSpanY * j); - } - } - const double spanX = width() * 1.0f / 10; - for (i = 1; i < 11; i++) { - const double posX = spanX * i; - p.drawLine(posX, 0, - posX, height()); - const double miniSpanX = spanX / 5; - for (j = 1; j < 5; j++) { - p.drawLine(posX - miniSpanX * j, height() / 2.0f - 10, - posX - miniSpanX * j, height() / 2.0f + 10); - } - } - } p.end(); } void Viewport::paintSignals(QPainter &p) { - const vector< boost::shared_ptr > sigs( - _view.session().get_signals()); -// const vector< boost::shared_ptr > pro_sigs( -// _view.session().get_pro_signals()); - // Plot the signal - const int v_offset = _view.v_offset(); + const vector< shared_ptr > traces(_view.get_traces()); if (_view.scale() != _curScale || _view.offset() != _curOffset || _view.get_signalHeight() != _curSignalHeight || @@ -208,29 +159,17 @@ void Viewport::paintSignals(QPainter &p) QPainter dbp(&pixmap); dbp.initFrom(this); p.setRenderHint(QPainter::Antialiasing, false); - BOOST_FOREACH(const boost::shared_ptr s, sigs) { - assert(s); - if (s->get_active()) - s->paint(dbp, ((s->get_type() == Signal::DS_DSO) ? s->get_zeroPos() + height()*0.5 : s->get_v_offset() - v_offset), 0, width(), - _view.scale(), _view.offset()); + BOOST_FOREACH(const shared_ptr t, traces) + { + assert(t); + if (t->enabled()) + t->paint_mid(dbp, 0, width()); } -// p.setRenderHint(QPainter::Antialiasing); -// BOOST_FOREACH(const boost::shared_ptr s, pro_sigs) { -// assert(s); -// s->paint(dbp, s->get_v_offset() - v_offset, 0, width(), -// _view.scale(), _view.offset()); -// } + _view.set_need_update(false); } p.drawPixmap(0, 0, pixmap); - // plot trig line in DSO mode - BOOST_FOREACH(const shared_ptr s, sigs) { - assert(s); - if (s->get_active() && s->get_type() == Signal::DS_DSO) - s->paint_trig(p, 0, width(), qAbs(_mouse_point.y() - s->get_trig_vpos()) <= HitCursorMargin ); - } - // plot cursors if (_view.cursors_shown()) { list::iterator i = _view.get_cursorList().begin(); @@ -242,21 +181,6 @@ void Viewport::paintSignals(QPainter &p) (*i)->paint(p, rect(), 1); else (*i)->paint(p, rect(), 0); - if (!_view.session().get_data()->get_snapshots().empty()) { - uint64_t _hit_sample = floor((*i)->time() * _view.session().get_last_sample_rate()); - if (_hit_sample > _total_receive_len) { - (*i)->set_time(0); - } else { - QRectF valueRect = QRectF(cursorX + 3, height()-20, 100, 20); - p.setPen(Qt::black); - p.drawLine(cursorX, height()-13, cursorX + 3, height()-10); - p.drawLine(cursorX, height()-7, cursorX + 3, height()-10); - p.drawText(valueRect, Qt::AlignLeft | Qt::AlignVCenter, - "Value: " + - QString::number((uint16_t)_view.session().get_data()->get_snapshots().front()->get_sample(_hit_sample))); - } - } - i++; } } @@ -270,7 +194,7 @@ void Viewport::paintSignals(QPainter &p) // plot zoom rect if (_zoom_rect_visible) { p.setPen(Qt::NoPen); - p.setBrush(Signal::dsLightBlue); + p.setBrush(Trace::dsLightBlue); p.drawRect(_zoom_rect); } @@ -284,14 +208,15 @@ void Viewport::paintProgress(QPainter &p) { using pv::view::Signal; - const quint64 _total_sample_len = _view.session().get_total_sample_len(); + const quint64 _total_sample_len = _view.session().get_device()->get_sample_limit(); double progress = -(_total_receive_len * 1.0f / _total_sample_len * 360 * 16); + int captured_progress = 0; p.setPen(Qt::gray); const QPoint cenPos = QPoint(width() / 2, height() / 2); const int radius = min(0.3 * width(), 0.3 * height()); p.drawEllipse(cenPos, radius - 2, radius - 2); - p.setPen(QPen(Signal::dsGreen, 4, Qt::SolidLine)); + p.setPen(QPen(Trace::dsGreen, 4, Qt::SolidLine)); p.drawArc(cenPos.x() - radius, cenPos.y() - radius, 2* radius, 2 * radius, 180 * 16, progress); p.setPen(Qt::gray); @@ -356,21 +281,53 @@ void Viewport::paintProgress(QPainter &p) const int trigger_radius = min(0.02 * width(), 0.02 * height()); p.setPen(Qt::NoPen); - p.setBrush((timer_cnt % 3) == 0 ? Signal::dsLightBlue : Signal::dsGray); + p.setBrush((timer_cnt % 3) == 0 ? Trace::dsLightBlue : Trace::dsGray); p.drawEllipse(cenLeftPos, trigger_radius, trigger_radius); - p.setBrush((timer_cnt % 3) == 1 ? Signal::dsLightBlue : Signal::dsGray); + p.setBrush((timer_cnt % 3) == 1 ? Trace::dsLightBlue : Trace::dsGray); p.drawEllipse(cenPos, trigger_radius, trigger_radius); - p.setBrush((timer_cnt % 3) == 2 ? Signal::dsLightBlue : Signal::dsGray); + p.setBrush((timer_cnt % 3) == 2 ? Trace::dsLightBlue : Trace::dsGray); p.drawEllipse(cenRightPos, trigger_radius, trigger_radius); + + sr_status status; + if (sr_status_get(_view.session().get_device()->dev_inst(), &status) == SR_OK){ + const bool triggred = status.trig_hit & 0x01; + const uint32_t captured_cnt = (status.captured_cnt0 + + (status.captured_cnt1 << 8) + + (status.captured_cnt2 << 16) + + (status.captured_cnt3 << 24)); + captured_progress = captured_cnt * 100.0 / _total_sample_len; + + + p.setPen(Trace::dsLightBlue); + QFont font=p.font(); + font.setPointSize(10); + font.setBold(true); + p.setFont(font); + QRect status_rect = QRect(cenPos.x() - radius, cenPos.y() + radius * 0.4, radius * 2, radius * 0.5); + if (triggred) + p.drawText(status_rect, + Qt::AlignCenter | Qt::AlignVCenter, + "Triggered! " + QString::number(captured_progress)+"% Captured"); + else + p.drawText(status_rect, + Qt::AlignCenter | Qt::AlignVCenter, + "Waiting for Trigger! " + QString::number(captured_progress)+"% Captured"); + } + } else { const int progress100 = ceil(progress / -3.6 / 16); - p.setPen(QColor(0, 0, 0, 50)); + p.setPen(Trace::dsGreen); QFont font=p.font(); font.setPointSize(50); font.setBold(true); p.setFont(font); p.drawText(rect(), Qt::AlignCenter | Qt::AlignVCenter, QString::number(progress100)+"%"); } + + p.setPen(QPen(Trace::dsLightBlue, 4, Qt::SolidLine)); + const int int_radius = max(radius - 4, 0); + p.drawArc(cenPos.x() - int_radius, cenPos.y() - int_radius, 2* int_radius, 2 * int_radius, 180 * 16, -captured_progress*3.6*16); + } void Viewport::mousePressEvent(QMouseEvent *event) @@ -403,16 +360,14 @@ void Viewport::mousePressEvent(QMouseEvent *event) const vector< shared_ptr > sigs(_view.session().get_signals()); BOOST_FOREACH(const shared_ptr s, sigs) { assert(s); - if (s->get_active() && - s->get_type() == Signal::DS_DSO && - qAbs(_mouse_point.y() - s->get_trig_vpos()) <= HitCursorMargin) { - if (_drag_sig) - _drag_sig.reset(); - else - _drag_sig = s; + shared_ptr dsoSig; + if ((dsoSig = dynamic_pointer_cast(s)) && + dsoSig->get_trig_rect(0, width()).contains(_mouse_point)) { + _drag_sig = s; break; } } + update(); } } @@ -427,38 +382,32 @@ void Viewport::mouseMoveEvent(QMouseEvent *event) } if (event->buttons() & Qt::LeftButton) { - _view.set_scale_offset(_view.scale(), - _mouse_down_offset + - (_mouse_down_point - event->pos()).x() * - _view.scale()); - measure(); - } - - if (!(event->buttons() || Qt::NoButton)) { if (_drag_sig) { - uint16_t trig_value = 0; - int vpos = _mouse_point.y(); - if (vpos < 0) - vpos = 0; - else if (vpos > height()) - vpos = height(); - _drag_sig->set_trig_vpos(vpos); - - const vector< shared_ptr > sigs(_view.session().get_signals()); - BOOST_FOREACH(const shared_ptr s, sigs) { - assert(s); - if (s->get_active() && - s->get_type() == Signal::DS_DSO) { - trig_value += (((uint16_t)(255 - s->get_trig_vpos()*1.0/height()*255)) << 8*s->get_index()); - } - } - sr_config_set(_view.session().get_device(), - SR_CONF_TRIGGER_VALUE, g_variant_new_uint16(trig_value)); + shared_ptr dsoSig; + if (dsoSig = dynamic_pointer_cast(_drag_sig)) + dsoSig->set_trig_vpos(_mouse_point.y()); + } else { + _view.set_scale_offset(_view.scale(), + _mouse_down_offset + + (_mouse_down_point - event->pos()).x() * + _view.scale()); + measure(); } + } + if (!(event->buttons() || Qt::NoButton)) { + uint64_t sample_rate = _view.session().get_device()->get_sample_rate(); TimeMarker* grabbed_marker = _view.get_ruler()->get_grabbed_cursor(); if (_view.cursors_shown() && grabbed_marker) { - grabbed_marker->set_time(_view.offset() + _view.hover_point().x() * _view.scale()); + const double cur_time = _view.offset() + _view.hover_point().x() * _view.scale(); + const double pos = cur_time * sample_rate; + const double pos_delta = pos - (int)pos; + if ( pos_delta < HitCursorTimeMargin) + grabbed_marker->set_time(1.0 / sample_rate * floor(pos)); + else if (pos_delta > (1.0 - HitCursorTimeMargin)) + grabbed_marker->set_time(1.0 / sample_rate * ceil(pos)); + else + grabbed_marker->set_time(cur_time); } measure(); } @@ -479,12 +428,17 @@ void Viewport::mouseReleaseEvent(QMouseEvent *event) _view.set_scale_offset(newScale, newOffset); } + if(_drag_sig) + _drag_sig.reset(); + update(); } void Viewport::mouseDoubleClickEvent(QMouseEvent *event) { assert (event); + (void)event; + if (_view.scale() == _view.get_maxscale()) _view.set_preScale_preOffset(); else @@ -518,7 +472,7 @@ void Viewport::leaveEvent(QEvent *) update(); } -void Viewport::on_signals_moved() +void Viewport::on_traces_moved() { update(); } @@ -530,8 +484,8 @@ void Viewport::set_receive_len(quint64 length) start_trigger_timer(333); } else { stop_trigger_timer(); - if (_total_receive_len + length > _view.session().get_total_sample_len()) - _total_receive_len = _view.session().get_total_sample_len(); + if (_total_receive_len + length > _view.session().get_device()->get_sample_limit()) + _total_receive_len = _view.session().get_device()->get_sample_limit(); else _total_receive_len += length; } @@ -540,19 +494,21 @@ void Viewport::set_receive_len(quint64 length) void Viewport::measure() { + uint64_t sample_rate = _view.session().get_device()->get_sample_rate(); + const vector< boost::shared_ptr > sigs(_view.session().get_signals()); BOOST_FOREACH(const boost::shared_ptr s, sigs) { assert(s); const int curY = _view.hover_point().y(); const double curX = _view.hover_point().x(); - if (curY <= View::SignalMargin || s->get_type() != Signal::DS_LOGIC) { + if (curY <= View::SignalMargin || s->get_type() != Trace::DS_LOGIC) { _measure_shown = false; break; - } else if ( curY < s->get_v_offset() && - curY > (s->get_v_offset() - _view.get_signalHeight())) { + } else if ( curY < s->get_y() + _view.get_signalHeight() * 0.5 && + curY > (s->get_y() - _view.get_signalHeight() * 0.5)) { if (s->cur_edges().size() > 2) { const double pixels_offset = _view.offset() / _view.scale(); - const double samples_per_pixel = _view.session().get_last_sample_rate() * _view.scale(); + const double samples_per_pixel = sample_rate * _view.scale(); uint64_t findIndex = curX / width() * s->cur_edges().size(); uint64_t left_findIndex = 0; @@ -586,7 +542,7 @@ void Viewport::measure() _cur_thdX = 0; } - _cur_midY = s->get_v_offset() - 0.5 * _view.get_signalHeight(); + _cur_midY = s->get_y(); break; } } else if (curX < pre_edge_x) { @@ -600,8 +556,8 @@ void Viewport::measure() } } break; - } else if (curY >= s->get_v_offset() && - curY <= (s->get_v_offset() + 2 * View::SignalMargin)){ + } else if (curY >= s->get_y() + _view.get_signalHeight() && + curY <= (s->get_y() + _view.get_signalHeight() + 2 * View::SignalMargin)){ _measure_shown = false; break; }else { @@ -613,8 +569,8 @@ void Viewport::measure() const uint64_t delta_sample = _nxt_sample - _cur_sample; const uint64_t delta1_sample = _thd_sample - _cur_sample; //assert(delta_sample >= 0); - const double delta_time = delta_sample * 1.0f / _view.session().get_last_sample_rate(); - const double delta1_time = delta1_sample * 1.0f / _view.session().get_last_sample_rate(); + const double delta_time = delta_sample * 1.0f / sample_rate; + const double delta1_time = delta1_sample * 1.0f / sample_rate; const int order = (int)floorf(log10f(delta_time)); unsigned int prefix = (15 + order) / 3; assert(prefix < 9); diff --git a/DSLogic-gui/pv/view/viewport.h b/DSLogic-gui/pv/view/viewport.h index c373c78..0d89e9e 100644 --- a/DSLogic-gui/pv/view/viewport.h +++ b/DSLogic-gui/pv/view/viewport.h @@ -46,16 +46,16 @@ class Viewport : public QWidget Q_OBJECT public: - static const int HitCursorMargin; - static const int NumSpanY; - static const int NumMiniSpanY; - static const int NumSpanX; + static const int HitCursorMargin = 10; + static const double HitCursorTimeMargin = 0.3; public: explicit Viewport(View &parent); int get_total_height() const; + QPoint get_mouse_point() const; + void set_receive_len(quint64 length); QString get_mm_width(); @@ -85,7 +85,7 @@ private: void measure(); private slots: - void on_signals_moved(); + void on_traces_moved(); void on_trigger_timer(); private: diff --git a/DSLogic-gui/pv/widgets/decodergroupbox.cpp b/DSLogic-gui/pv/widgets/decodergroupbox.cpp new file mode 100644 index 0000000..7f90ad0 --- /dev/null +++ b/DSLogic-gui/pv/widgets/decodergroupbox.cpp @@ -0,0 +1,69 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2013 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "decodergroupbox.h" + +#include +#include +#include +#include + +#include + +namespace pv { +namespace widgets { + +DecoderGroupBox::DecoderGroupBox(QString title, QWidget *parent) : + QWidget(parent), + _layout(new QGridLayout), + _show_hide_button(QIcon(":/icons/decoder-shown.png"), QString(), this) +{ + _layout->setContentsMargins(0, 0, 0, 0); + setLayout(_layout); + + _layout->addWidget(new QLabel(QString("

%1

").arg(title)), + 0, 0); + _layout->setColumnStretch(0, 1); + + QHBoxLayout *const toolbar = new QHBoxLayout; + _layout->addLayout(toolbar, 0, 1); + + _show_hide_button.setFlat(true); + //_show_hide_button.setIconSize(QSize(16, 16)); + connect(&_show_hide_button, SIGNAL(clicked()), + this, SIGNAL(show_hide_decoder())); + toolbar->addWidget(&_show_hide_button); +} + +void DecoderGroupBox::add_layout(QLayout *layout) +{ + assert(layout); + _layout->addLayout(layout, 1, 0, 1, 2); +} + +void DecoderGroupBox::set_decoder_visible(bool visible) +{ + _show_hide_button.setIcon(QIcon(visible ? + ":/icons/decoder-shown.png" : + ":/icons/decoder-hidden.png")); +} + +} // widgets +} // pv diff --git a/DSLogic-gui/pv/widgets/decodergroupbox.h b/DSLogic-gui/pv/widgets/decodergroupbox.h new file mode 100644 index 0000000..c63d6b9 --- /dev/null +++ b/DSLogic-gui/pv/widgets/decodergroupbox.h @@ -0,0 +1,54 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2013 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DSLOGIC_PV_WIDGETS_DECODERGROUPBOX_H +#define DSLOGIC_PV_WIDGETS_DECODERGROUPBOX_H + +#include + +class QGridLayout; +class QToolBar; + +namespace pv { +namespace widgets { + +class DecoderGroupBox : public QWidget +{ + Q_OBJECT + +public: + DecoderGroupBox(QString title, QWidget *parent = NULL); + + void add_layout(QLayout *layout); + + void set_decoder_visible(bool visible); + +signals: + void show_hide_decoder(); + +private: + QGridLayout *const _layout; + QPushButton _show_hide_button; +}; + +} // widgets +} // pv + +#endif // DSLOGIC_PV_WIDGETS_DECODERGROUPBOX_H diff --git a/DSLogic-gui/pv/widgets/decodermenu.cpp b/DSLogic-gui/pv/widgets/decodermenu.cpp new file mode 100644 index 0000000..94bdd14 --- /dev/null +++ b/DSLogic-gui/pv/widgets/decodermenu.cpp @@ -0,0 +1,75 @@ +/* + * This file is part of the DSLogic-gui project. + * DSLogic-gui is based on PulseView. + * + * Copyright (C) 2013 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "decodermenu.h" +#include + +namespace pv { +namespace widgets { + +DecoderMenu::DecoderMenu(QWidget *parent, bool first_level_decoder) : + QMenu(parent), + _mapper(this) +{ + GSList *l = g_slist_sort(g_slist_copy( + (GSList*)srd_decoder_list()), decoder_name_cmp); + for(; l; l = l->next) + { + const srd_decoder *const d = (srd_decoder*)l->data; + assert(d); + + const bool have_probes = (d->channels || d->opt_channels) != 0; + if (first_level_decoder == have_probes) { + QAction *const action = + addAction(QString::fromUtf8(d->name)); + action->setData(qVariantFromValue(l->data)); + _mapper.setMapping(action, action); + connect(action, SIGNAL(triggered()), + &_mapper, SLOT(map())); + } + } + g_slist_free(l); + + connect(&_mapper, SIGNAL(mapped(QObject*)), + this, SLOT(on_action(QObject*))); +} + +int DecoderMenu::decoder_name_cmp(const void *a, const void *b) +{ + return strcmp(((const srd_decoder*)a)->name, + ((const srd_decoder*)b)->name); +} + +void DecoderMenu::on_action(QObject *action) +{ + assert(action); + srd_decoder *const dec = + (srd_decoder*)((QAction*)action)->data().value(); + assert(dec); + + selected(); + decoder_selected(dec); +} + +} // widgets +} // pv diff --git a/DSLogic-gui/pv/widgets/decodermenu.h b/DSLogic-gui/pv/widgets/decodermenu.h new file mode 100644 index 0000000..a4aca66 --- /dev/null +++ b/DSLogic-gui/pv/widgets/decodermenu.h @@ -0,0 +1,58 @@ +/* + * This file is part of the DSLogic-gui project. + * DSLogic-gui is based on PulseView. + * + * Copyright (C) 2013 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DSLOGIC_PV_WIDGETS_DECODERMENU_H +#define DSLOGIC_PV_WIDGETS_DECODERMENU_H + +#include +#include + +struct srd_decoder; + +namespace pv { +namespace widgets { + +class DecoderMenu : public QMenu +{ + Q_OBJECT; + +public: + DecoderMenu(QWidget *parent, bool first_level_decoder = false); + +private: + static int decoder_name_cmp(const void *a, const void *b); + + +private slots: + void on_action(QObject *action); + +signals: + void decoder_selected(srd_decoder *decoder); + void selected(); + +private: + QSignalMapper _mapper; +}; + +} // widgets +} // pv + +#endif // DSLOGIC_PV_WIDGETS_DECODERMENU_H diff --git a/DSLogic-gui/pv/widgets/fakelineedit.cpp b/DSLogic-gui/pv/widgets/fakelineedit.cpp new file mode 100644 index 0000000..7423b44 --- /dev/null +++ b/DSLogic-gui/pv/widgets/fakelineedit.cpp @@ -0,0 +1,42 @@ +/* + * This file is part of the DSLogic-gui project. + * DSLogic-gui is based on PulseView. + * + * Copyright (C) 2012 Joel Holdsworth + * Copyright (C) 2013 DreamSourceLab + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "fakelineedit.h" +#include + +namespace pv { +namespace widgets { + +FakeLineEdit::FakeLineEdit(QLineEdit *parent) : + QLineEdit(parent) +{ +} + +void FakeLineEdit::mousePressEvent(QMouseEvent *event) +{ + if (event->button() & Qt::LeftButton) { + trigger(); + } +} + +} // namespace widgets +} // namespace pv diff --git a/DSLogic-gui/pv/widgets/fakelineedit.h b/DSLogic-gui/pv/widgets/fakelineedit.h new file mode 100644 index 0000000..d6b3404 --- /dev/null +++ b/DSLogic-gui/pv/widgets/fakelineedit.h @@ -0,0 +1,53 @@ +/* + * This file is part of the DSLogic-gui project. + * DSLogic-gui is based on PulseView. + * + * Copyright (C) 2012 Joel Holdsworth + * Copyright (C) 2013 DreamSourceLab + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DSLOGIC_PV_WIDGETS_FAKELINEEDIT_H +#define DSLOGIC_PV_WIDGETS_FAKELINEEDIT_H + +#include + +namespace pv { + +class SigSession; + +namespace widgets { + +class FakeLineEdit : public QLineEdit +{ + Q_OBJECT +public: + explicit FakeLineEdit(QLineEdit *parent = 0); + +private: + void mousePressEvent(QMouseEvent * event); + +signals: + void trigger(); + +public slots: + +}; + +} // namespace widgets +} // namespace pv + +#endif // DSLOGIC_PV_WIDGETS_FAKELINEEDIT_H diff --git a/DSLogic-gui/res/DSLogic.fw b/DSLogic-gui/res/DSLogic.fw index 813f8fe2c36e37d4f255b55b7161f3a8a669f335..fa7e5f31cb8e178d304a49af7df0730777e622ba 100644 GIT binary patch delta 3294 zcmZu!dvFuS8Q)tyPPT=SPm<+V3T4ZhRBH;nm&fu{}p#K3a~ zwi|fFz#apS>1ZF+FoNAnbwq1l9))4TbR5Na;n z!-(#c?=s{-nJj&E$aV%{%YnZ?{QN6&AS+8FOhx5vM0|+&5t)O?Ttuo6S$vHwd7Uh6 z8e&KRA{-*lwZv6U+-0OtT8FPojJfcDwbJPwLd1*26rT*`s-oo|oNG{;Z}Cg7SR4^C zwIdl~)SGb*hp=U`3RyTu{}2kFNO&<{5|0*gPtx4koizw}Jbm zm}*aE2TkSTa4oQgZ4ex1z0c?CW7$OJGMx3-5P>H9ziQtdG*xrfX3mr^riPM@k5v&d z5FQ<5LuQqxOicYnHaa)LjM2NUv&d}b8M{)ubynngad zi+<+qYCUxGLM_x-)s#{;ws=Y_8(Sg{2XzbC*ite_GhqTd%IxNC%K56{ElO`yvR+I* zgykn|#1s=|K9aiF!k1(%4Nc*RWX$gXH_Y~mfMCHFJ1IU(g(JAc{gE?w)0Pd zJoZZA+(pZ>jS-M73Fm0*fK}7w=d}?5bM_-QnILceD`@{K8R9|hv0>Gbw=NPF29Ki# z6K0cRzRd>_HeB-pUNu>psiCXp$#evp$#dE)EQIK7EL+^X4++~8{|zfuheDolz0Y;;>Ddp zS8Gg_Iadd@stDO#X2l~7nr>m|Zt0+TD}^z0MQ{l`QK2N-S{hZ$AYsYI2{jqQ5E0Gf zF;8z%UvZnz-rAwUv4xbVVWwhSUX$ryKNR+Z2jEJ_^V~1e$t!BuRPjV!GugrF?lqMm zHEem7dsz)zD;~*f9#0v61P4-u9qlg}D3zS{pA~wDnsa!fG9y<%ikbEcZLRH^w_n-I zY}DJQY8Xog?JjL_4P4U!hHyshB^vKf;CM>l&)|;^%BbpRYM5Ox0nYDNKDk#tITft5 zV_HL`INDkgRZHpdhn@d2Ik6W-i-l-OYqV6o5BWTF4;sbe%RW|$mSrdQpfoBIcO+<&Q)vg)i4>ey(R5PhySTCP&{Lu1h7;=G&Qs9?nV9_qk*(bc=%o_i2S ztro|9J&p{G<56$vR1P(7<6=s^qfYTMl(`3q(y@GR@shZpto8@eHOiX6@^r0OhGcbY zQI@QixQ%cl>L|M*{0I1J!i8OWW`B(-%&& ze<7ycO-|C%KJ6{3z~PamIaXWhY;qzfZE;j$_;&1dyzZ<|ZwfR1`f)k1DFZuFH_{!H z#vJ~Q`J2PoVBbm9*+&vV8?~1S%~X5pk8ZNm%ZYky+0<9xzv+%Y1QBe=^g=n_oqR;Y zfMe(C!1;fZ9Mfm`tNsb=*-W)Vwd#F;XyQR&!;`osJlP?1(l^4@A^o;sW~4z*H>R6< zH}}18RcY-0j?&b<1#;mCL5@K?TP0ifFdb}cTN|4@=G&HOw;r(?-GRn#vI$D9B1CSOZkTtO(t3jKzxl6aZ^BKD_0k)PJy}@c0qu;BG zem@v}H5C29hUkusF@ejkR0M5GFKuU&*@*8U)W?FJF<)+LS{U;=TGOraJGUP8s|1|lZh^Y(7Eb%x<5hBI4un<;;h2%aB0zS%6j~(AE(mZ@PK}R#`0R?>fQr6jI<^0L4 zQXswRnt==cAnkQ+^>Bz^ti4wd`>qn|6Rg7AEms+4S4mG?vl&jB<#ti|74G`^>?(bK zZ(>Ir58<)qeBvzAE?#R%VL5OA<~8i7yS&M@bC5M**Uz!*{T37{k6Fdk$x)Sd#nh=$ z)f;yKV@w@z>cf^D`^qD{zUsZG9p~Lmcrahrg*urS?$QrF)ZVhfQn%ZaIcvhawFT{i zJL>4xX6se>^lT|yVk^{5x}^^aTO6LwBDW`;1vBm<#j~MEa(Nc5vtel^!!Rtvm>7EP zU|9=GpI8pmv}mKs`Xm5Qb~MCI`2C3^#n7^Cs{v0bK^V zf-v0Xbvx7+rISE#Lw219x`v E4-^2~RsaA1 literal 8120 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 L@D{*6fHw4Rm!zt7 diff --git a/DSLogic-gui/res/DSLogic33.bin b/DSLogic-gui/res/DSLogic33.bin new file mode 100644 index 0000000000000000000000000000000000000000..bed5785ccb3e162618cf117606256e790dca3234 GIT binary patch literal 340604 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

Do9$p#c0>{-3YbHGUS!u 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 Ba9;ob literal 0 HcmV?d00001 diff --git a/DSLogic-gui/res/DSLogic50.bin b/DSLogic-gui/res/DSLogic50.bin new file mode 100644 index 0000000000000000000000000000000000000000..421c3bf0037aede8fe7218b142017468b6e2bf24 GIT binary patch literal 340604 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 literal 0 HcmV?d00001 diff --git a/DSLogic-gui/stylesheet.qss b/DSLogic-gui/stylesheet.qss index 7245f27..c3465a9 100644 --- a/DSLogic-gui/stylesheet.qss +++ b/DSLogic-gui/stylesheet.qss @@ -41,14 +41,14 @@ QToolButton { min-width: 50px; } -QPushButton:hover, -QToolButton:hover { +QPushButton:hover, QPushButton:pressed, +QToolButton:hover, QToolButton:pressed { background-color: rgb(238, 178, 17, 200); } -QPushButton:pressed, -QToolButton:pressed { - background-color: rgb(238, 178, 17, 255); +QPushButton:checked, +QToolButton:checked { + background-color: rgb(255, 255, 255, 50); } QPushButton { diff --git a/INSTALL b/INSTALL index 9868881..df6a9a4 100644 --- a/INSTALL +++ b/INSTALL @@ -76,8 +76,29 @@ Building: $ sudo make install +Step3: Build libsigrokdecode -Step3: Build DSLogic-gui +Installing the requirements: + +Example on Debian/Ubuntu (please check your respective distro's package manager tool if you use other distros): + +$ sudo apt-get install git-core gcc make autoconf automake libtool pkg-config libglib2.0-dev python3-dev + +Fedora (18, 19, 20): + +$ sudo yum install git gcc make autoconf automake libtool pkgconfig glib2-devel python3-devel check-devel + +Building: + +$ git clone git://sigrok.org/libsigrokdecode +$ cd libsigrokdecode +$ ./autogen.sh +$ ./configure +$ make +$ sudo make install + + +Step4: Build DSLogic-gui Installing the requirements: diff --git a/NEWS b/NEWS index 9b9ff08..c45e943 100644 --- a/NEWS +++ b/NEWS @@ -1,30 +1,46 @@ -0.1.0 (2013-12-15) ------------------- +0.4 (2014-09-24) +----------------- + * Add protocol decoders (38) support + * Improve the hardware configuration under various platforms + * Add different thresholds support + * Add options for input filter under logic analyzer mode + * Add instant capture(trigger ignore) under logic analyzer mode + * Add detail capture status display under logic analyzer mode + * Add quick button for mode shift + * Redesign the GUI for oscilloscope + * Add flexible sample rate support under oscilloscope mode + * Fix trigger value setting issue + * Improve trigger method under oscilloscope mode + * Add shortcut keys for major operations + * Fix other bugs - * Initial release. - -0.2.0 (2014-04-13) ------------------- - - * Add DSLogic hardware support. +0.3 (2014-06-29) +----------------- + * Add DSLogic Oscilloscope module support. + * Fix data display issue when not all channels be enabled. + * Fix issue of data mirrored to other channels. + * Fix radiobutton/checkbox display issue under certain windows theme. + * Add new simple trigger type + * Fix bugs of trigger setting and detection + * Fix other minior issues. + * Clean up the git repository 0.2.1 (2014-05-08) ------------------ - * Add wireless extension hardware support. * Fix libusb_error_io issue on Linux when sample rate >= 100MHz. * Fix channel enable/disable bug. * Fix device options config issue. * Fix some display issues of UI. + +0.2.0 (2014-04-13) +------------------ + * Add DSLogic hardware support. + +0.1.0 (2013-12-15) +------------------ + * Initial release. + + - 0.3 (2014-06-29) - ----------------- - * Add DSLogic Oscilloscope module support. - * Fix data display issue when not all channels be enabled. - * Fix issue of data mirrored to other channels. - * Fix radiobutton/checkbox display issue under certain windows theme. - * Add new simple trigger type - * Fix bugs of trigger setting and detection - * Fix other minior issues. - * Clean up the git repository diff --git a/libsigrok4DSLogic/device.c b/libsigrok4DSLogic/device.c index cca0bcd..39f8125 100644 --- a/libsigrok4DSLogic/device.c +++ b/libsigrok4DSLogic/device.c @@ -47,12 +47,12 @@ */ /** @private */ -SR_PRIV struct sr_probe *sr_probe_new(int index, int type, - gboolean enabled, const char *name) +SR_PRIV struct sr_channel *sr_channel_new(int index, int type, + gboolean enabled, const char *name) { - struct sr_probe *probe; + struct sr_channel *probe; - if (!(probe = g_try_malloc0(sizeof(struct sr_probe)))) { + if (!(probe = g_try_malloc0(sizeof(struct sr_channel)))) { sr_err("Probe malloc failed."); return NULL; } @@ -86,7 +86,7 @@ SR_API int sr_dev_probe_name_set(const struct sr_dev_inst *sdi, int probenum, const char *name) { GSList *l; - struct sr_probe *probe; + struct sr_channel *probe; int ret; if (!sdi) { @@ -95,7 +95,7 @@ SR_API int sr_dev_probe_name_set(const struct sr_dev_inst *sdi, } ret = SR_ERR_ARG; - for (l = sdi->probes; l; l = l->next) { + for (l = sdi->channels; l; l = l->next) { probe = l->data; if (probe->index == probenum) { g_free(probe->name); @@ -123,14 +123,14 @@ SR_API int sr_dev_probe_enable(const struct sr_dev_inst *sdi, int probenum, gboolean state) { GSList *l; - struct sr_probe *probe; + struct sr_channel *probe; int ret; if (!sdi) return SR_ERR_ARG; ret = SR_ERR_ARG; - for (l = sdi->probes; l; l = l->next) { + for (l = sdi->channels; l; l = l->next) { probe = l->data; if (probe->index == probenum) { probe->enabled = state; @@ -160,14 +160,14 @@ SR_API int sr_dev_trigger_set(const struct sr_dev_inst *sdi, int probenum, const char *trigger) { GSList *l; - struct sr_probe *probe; + struct sr_channel *probe; int ret; if (!sdi) return SR_ERR_ARG; ret = SR_ERR_ARG; - for (l = sdi->probes; l; l = l->next) { + for (l = sdi->channels; l; l = l->next) { probe = l->data; if (probe->index == probenum) { /* If the probe already has a trigger, kill it first. */ @@ -208,7 +208,7 @@ SR_API gboolean sr_dev_has_option(const struct sr_dev_inst *sdi, int key) if (!sdi || !sdi->driver || !sdi->driver->config_list) return FALSE; - if (sdi->driver->config_list(SR_CONF_DEVICE_OPTIONS, &gvar, NULL) != SR_OK) + if (sdi->driver->config_list(SR_CONF_DEVICE_OPTIONS, &gvar, NULL, NULL) != SR_OK) return FALSE; ret = FALSE; @@ -243,7 +243,7 @@ SR_PRIV struct sr_dev_inst *sr_dev_inst_new(int mode, int index, int status, sdi->vendor = vendor ? g_strdup(vendor) : NULL; sdi->model = model ? g_strdup(model) : NULL; sdi->version = version ? g_strdup(version) : NULL; - sdi->probes = NULL; + sdi->channels = NULL; sdi->conn = NULL; sdi->priv = NULL; @@ -253,24 +253,24 @@ SR_PRIV struct sr_dev_inst *sr_dev_inst_new(int mode, int index, int status, /** @private */ SR_PRIV void sr_dev_probes_free(struct sr_dev_inst *sdi) { - struct sr_probe *probe; + struct sr_channel *probe; GSList *l; - for (l = sdi->probes; l; l = l->next) { + for (l = sdi->channels; l; l = l->next) { probe = l->data; g_free(probe->name); g_free(probe); } - sdi->probes = NULL; + sdi->channels = NULL; } SR_PRIV void sr_dev_inst_free(struct sr_dev_inst *sdi) { - struct sr_probe *probe; + struct sr_channel *probe; GSList *l; - for (l = sdi->probes; l; l = l->next) { + for (l = sdi->channels; l; l = l->next) { probe = l->data; g_free(probe->name); g_free(probe); @@ -368,6 +368,14 @@ SR_API GSList *sr_dev_list(const struct sr_dev_driver *driver) return NULL; } +SR_API GSList *sr_dev_mode_list(const struct sr_dev_driver *driver) +{ + if (driver && driver->dev_mode_list) + return driver->dev_mode_list(); + else + return NULL; +} + SR_API int sr_dev_clear(const struct sr_dev_driver *driver) { if (driver && driver->dev_clear) diff --git a/libsigrok4DSLogic/hardware/DSLogic/command.c b/libsigrok4DSLogic/hardware/DSLogic/command.c index 33b95e3..dbdb06a 100644 --- a/libsigrok4DSLogic/hardware/DSLogic/command.c +++ b/libsigrok4DSLogic/hardware/DSLogic/command.c @@ -59,30 +59,16 @@ SR_PRIV int command_get_revid_version(libusb_device_handle *devhdl, } SR_PRIV int command_start_acquisition(libusb_device_handle *devhdl, - uint64_t samplerate, gboolean samplewide) + uint64_t samplerate, gboolean samplewide, gboolean la_mode) { struct cmd_start_acquisition cmd; int delay = 0, ret; - /* Compute the sample rate. */ -// if (samplewide && samplerate > MAX_16BIT_SAMPLE_RATE) { -// sr_err("Unable to sample at %" PRIu64 "Hz " -// "when collecting 16-bit samples.", samplerate); -// return SR_ERR; -// } + (void) samplerate; -// if ((SR_MHZ(48) % samplerate) == 0) { -// cmd.flags = CMD_START_FLAGS_CLK_48MHZ; -// delay = SR_MHZ(48) / samplerate - 1; -// if (delay > MAX_SAMPLE_DELAY) -// delay = 0; -// } + cmd.flags = la_mode ? CMD_START_FLAGS_MODE_LA : 0; -// if (delay == 0 && (SR_MHZ(30) % samplerate) == 0) { -// cmd.flags = CMD_START_FLAGS_CLK_30MHZ; -// delay = SR_MHZ(30) / samplerate - 1; -// } - cmd.flags = CMD_START_FLAGS_CLK_30MHZ; + cmd.flags |= CMD_START_FLAGS_CLK_30MHZ; delay = 0; sr_info("GPIF delay = %d, clocksource = %sMHz.", delay, @@ -142,9 +128,9 @@ SR_PRIV int command_fpga_config(libusb_device_handle *devhdl) int ret; /* ... */ - cmd.byte0 = (uint8_t)XC6SLX9_BYTE_CNT; - cmd.byte1 = (uint8_t)(XC6SLX9_BYTE_CNT >> 8); - cmd.byte2 = (uint8_t)(XC6SLX9_BYTE_CNT >> 16); + cmd.byte0 = (uint8_t)0; + cmd.byte1 = (uint8_t)0; + cmd.byte2 = (uint8_t)0; /* Send the control message. */ ret = libusb_control_transfer(devhdl, LIBUSB_REQUEST_TYPE_VENDOR | @@ -206,3 +192,21 @@ SR_PRIV int command_dso_ctrl(libusb_device_handle *devhdl, uint32_t command) return SR_OK; } + +SR_PRIV int command_get_status(libusb_device_handle *devhdl, + struct sr_status *status) +{ + int ret; + + ret = libusb_control_transfer(devhdl, LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_ENDPOINT_IN, CMD_STATUS, 0x0000, 0x0000, + (unsigned char *)status, sizeof(struct sr_status), 3000); + + if (ret < 0) { + sr_err("Unable to get version info: %s.", + libusb_error_name(ret)); + return SR_ERR; + } + + return SR_OK; +} diff --git a/libsigrok4DSLogic/hardware/DSLogic/command.h b/libsigrok4DSLogic/hardware/DSLogic/command.h index 37f00c7..e139014 100644 --- a/libsigrok4DSLogic/hardware/DSLogic/command.h +++ b/libsigrok4DSLogic/hardware/DSLogic/command.h @@ -30,12 +30,16 @@ #define CMD_CONFIG 0xb3 #define CMD_SETTING 0xb4 #define CMD_CONTROL 0xb5 +#define CMD_STATUS 0xb6 +#define CMD_START_FLAGS_MODE_POS 4 #define CMD_START_FLAGS_WIDE_POS 5 #define CMD_START_FLAGS_CLK_SRC_POS 6 #define CMD_START_FLAGS_STOP_POS 7 -#define CMD_START_FLAGS_SAMPLE_8BIT (0 << CMD_START_FLAGS_WIDE_POS) +#define CMD_START_FLAGS_MODE_LA (1 << CMD_START_FLAGS_MODE_POS) + +#define CMD_START_FLAGS_SAMPLE_8BIT (0 << CMD_START_FLAGS_WIDE_POS) #define CMD_START_FLAGS_SAMPLE_16BIT (1 << CMD_START_FLAGS_WIDE_POS) #define CMD_START_FLAGS_CLK_30MHZ (0 << CMD_START_FLAGS_CLK_SRC_POS) @@ -82,7 +86,7 @@ SR_PRIV int command_get_fw_version(libusb_device_handle *devhdl, SR_PRIV int command_get_revid_version(libusb_device_handle *devhdl, uint8_t *revid); SR_PRIV int command_start_acquisition(libusb_device_handle *devhdl, - uint64_t samplerate, gboolean samplewide); + uint64_t samplerate, gboolean samplewide, gboolean la_mode); SR_PRIV int command_stop_acquistition(libusb_device_handle *devhdl); SR_PRIV int command_fpga_config(libusb_device_handle *devhdl); @@ -90,4 +94,7 @@ SR_PRIV int command_fpga_setting(libusb_device_handle *devhdl, uint32_t setting_ SR_PRIV int command_dso_ctrl(libusb_device_handle *devhdl, uint32_t command); +SR_PRIV int command_get_status(libusb_device_handle *devhdl, + struct sr_status *status); + #endif diff --git a/libsigrok4DSLogic/hardware/DSLogic/dslogic.c b/libsigrok4DSLogic/hardware/DSLogic/dslogic.c index f107406..fb5d512 100644 --- a/libsigrok4DSLogic/hardware/DSLogic/dslogic.c +++ b/libsigrok4DSLogic/hardware/DSLogic/dslogic.c @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -38,7 +39,29 @@ #endif static const int cons_buffer_size = 1024 * 16; -static const int dso_buffer_size = 1024 * 16; +static const int dso_buffer_size = SR_KB(32); +static struct sr_dev_mode mode_list[] = { + {"LA", LOGIC}, + {"DAQ", ANALOG}, + {"OSC", DSO}, +}; + +static const char *opmodes[] = { + "Normal", + "Internal Test", + "External Test", + "DRAM Loopback Test", +}; + +static const char *thresholds[] = { + "1.8/2.5/3.3V Level", + "5.0V Level", +}; + +static const char *filters[] = { + "None", + "1 Sample Clock", +}; static const int32_t hwopts[] = { SR_CONF_CONN, @@ -55,8 +78,11 @@ static const int32_t hwcaps[] = { }; static const int32_t hwoptions[] = { - SR_CONF_CLOCK_TYPE, SR_CONF_OPERATION_MODE, + SR_CONF_THRESHOLD, + SR_CONF_FILTER, + SR_CONF_CLOCK_TYPE, + SR_CONF_CLOCK_EDGE, }; static const char *probe_names[] = { @@ -87,6 +113,41 @@ static const uint64_t samplerates[] = { SR_MHZ(400), }; +//static const uint64_t samplecounts[] = { +// SR_KB(1), +// SR_KB(2), +// SR_KB(5), +// SR_KB(10), +// SR_KB(20), +// SR_KB(50), +// SR_KB(100), +// SR_KB(200), +// SR_KB(500), +// SR_MB(1), +// SR_MB(2), +// SR_MB(5), +// SR_MB(10), +// SR_MB(16), +//}; + +static const uint64_t samplecounts[] = { + SR_KB(1), + SR_KB(2), + SR_KB(8), + SR_KB(8), + SR_KB(16), + SR_KB(32), + SR_KB(64), + SR_KB(128), + SR_KB(256), + SR_KB(512), + SR_MB(1), + SR_MB(2), + SR_MB(4), + SR_MB(8), + SR_MB(16), +}; + SR_PRIV struct sr_dev_driver DSLogic_driver_info; static struct sr_dev_driver *di = &DSLogic_driver_info; @@ -186,10 +247,11 @@ static int fpga_setting(const struct sr_dev_inst *sdi) ((devc->op_mode == SR_OP_EXTERNAL_TEST) << 14) + ((devc->op_mode == SR_OP_LOOPBACK_TEST) << 13) + trigger->trigger_en + - ((sdi->mode > 0) << 4) + (devc->clock_type << 1) + + ((sdi->mode > 0) << 4) + (devc->clock_type << 1) + (devc->clock_edge << 1) + (((devc->cur_samplerate == SR_MHZ(200) && sdi->mode != DSO) || (sdi->mode == ANALOG)) << 5) + ((devc->cur_samplerate == SR_MHZ(400)) << 6) + - ((sdi->mode == ANALOG) << 7); + ((sdi->mode == ANALOG) << 7) + + ((devc->filter == SR_FILTER_1T) << 8); setting.divider = (uint32_t)ceil(SR_MHZ(100) * 1.0 / devc->cur_samplerate); setting.count = (uint32_t)(devc->limit_samples); setting.trig_pos = (uint32_t)(trigger->trigger_pos / 100.0f * devc->limit_samples); @@ -213,8 +275,8 @@ static int fpga_setting(const struct sr_dev_inst *sdi) setting.trig_logic1[0] = (trigger->trigger_logic[TriggerStages] << 1) + trigger->trigger1_inv[TriggerStages]; for (i = 1; i < NUM_TRIGGER_STAGES; i++) { - setting.trig_mask0[i] = 1; - setting.trig_mask1[i] = 1; + setting.trig_mask0[i] = 0xff; + setting.trig_mask1[i] = 0xff; setting.trig_value0[i] = 0; setting.trig_value1[i] = 0; @@ -274,22 +336,28 @@ static int fpga_config(struct libusb_device_handle *hdl, const char *filename) int offset, chunksize, ret, result; unsigned char *buf; int transferred; + uint64_t filesize; + struct stat f_stat; - if (!(buf = g_try_malloc(XC6SLX9_BYTE_CNT))) { - sr_err("FPGA configure bit malloc failed."); - return SR_ERR; - } sr_info("Configure FPGA using %s", filename); if ((fw = g_fopen(filename, "rb")) == NULL) { sr_err("Unable to open FPGA bit file %s for reading: %s", filename, strerror(errno)); return SR_ERR; } + if (stat(filename, &f_stat) == -1) + return SR_ERR; + + filesize = (uint64_t)f_stat.st_size; + if (!(buf = g_try_malloc(filesize))) { + sr_err("FPGA configure bit malloc failed."); + return SR_ERR; + } result = SR_OK; offset = 0; while (1) { - chunksize = fread(buf, 1, XC6SLX9_BYTE_CNT, fw); + chunksize = fread(buf, 1, filesize, fw); if (chunksize == 0) break; @@ -434,7 +502,7 @@ static int DSLogic_dev_open(struct sr_dev_inst *sdi) static int configure_probes(const struct sr_dev_inst *sdi) { struct dev_context *devc; - struct sr_probe *probe; + struct sr_channel *probe; GSList *l; int probe_bit, stage, i; char *tc; @@ -446,13 +514,13 @@ static int configure_probes(const struct sr_dev_inst *sdi) } stage = -1; - for (l = sdi->probes; l; l = l->next) { - probe = (struct sr_probe *)l->data; + for (l = sdi->channels; l; l = l->next) { + probe = (struct sr_channel *)l->data; if (probe->enabled == FALSE) continue; - if ((probe->index > 7 && probe->type == SR_PROBE_LOGIC) || - (probe->type == SR_PROBE_ANALOG || probe->type == SR_PROBE_DSO)) + if ((probe->index > 7 && probe->type == SR_CHANNEL_LOGIC) || + (probe->type == SR_CHANNEL_ANALOG || probe->type == SR_CHANNEL_DSO)) devc->sample_wide = TRUE; else devc->sample_wide = FALSE; @@ -495,22 +563,18 @@ static struct dev_context *DSLogic_dev_new(void) devc->profile = NULL; devc->fw_updated = 0; - devc->cur_samplerate = 0; - devc->limit_samples = 0; + devc->cur_samplerate = DEFAULT_SAMPLERATE; + devc->limit_samples = DEFAULT_SAMPLELIMIT; devc->sample_wide = 0; devc->clock_type = FALSE; + devc->clock_edge = FALSE; devc->op_mode = SR_OP_NORMAL; - devc->vdiv0 = 1000; - devc->vdiv1 = 1000; - devc->timebase = 100; - devc->coupling0 = FALSE; - devc->coupling1 = FALSE; - devc->en_ch0 = TRUE; - devc->en_ch1 = FALSE; + devc->th_level = SR_TH_3V3; + devc->filter = SR_FILTER_NONE; + devc->timebase = 10000; devc->trigger_slope = DSO_TRIGGER_RISING; devc->trigger_source = DSO_TRIGGER_AUTO; - devc->trigger_vpos = 0xffff; - devc->trigger_hpos = 0x80; + devc->trigger_hpos = 0x0; devc->zero = FALSE; return devc; @@ -529,13 +593,18 @@ static int init(struct sr_context *sr_ctx) static int set_probes(struct sr_dev_inst *sdi, int num_probes) { int j; - struct sr_probe *probe; + struct sr_channel *probe; for (j = 0; j < num_probes; j++) { - if (!(probe = sr_probe_new(j, (sdi->mode == LOGIC) ? SR_PROBE_LOGIC : ((sdi->mode == DSO) ? SR_PROBE_DSO : SR_PROBE_ANALOG), + if (!(probe = sr_channel_new(j, (sdi->mode == LOGIC) ? SR_CHANNEL_LOGIC : ((sdi->mode == DSO) ? SR_CHANNEL_DSO : SR_CHANNEL_ANALOG), TRUE, probe_names[j]))) return SR_ERR; - sdi->probes = g_slist_append(sdi->probes, probe); + if (sdi->mode == DSO) { + probe->vdiv = 1000; + probe->coupling = FALSE; + probe->trig_value = 0x80; + } + sdi->channels = g_slist_append(sdi->channels, probe); } return SR_OK; } @@ -544,22 +613,22 @@ static int adjust_probes(struct sr_dev_inst *sdi, int num_probes) { int j; GSList *l; - struct sr_probe *probe; + struct sr_channel *probe; GSList *p; assert(num_probes > 0); - j = g_slist_length(sdi->probes); + j = g_slist_length(sdi->channels); while(j < num_probes) { - if (!(probe = sr_probe_new(j, (sdi->mode == LOGIC) ? SR_PROBE_LOGIC : ((sdi->mode == DSO) ? SR_PROBE_DSO : SR_PROBE_ANALOG), + if (!(probe = sr_channel_new(j, (sdi->mode == LOGIC) ? SR_CHANNEL_LOGIC : ((sdi->mode == DSO) ? SR_CHANNEL_DSO : SR_CHANNEL_ANALOG), TRUE, probe_names[j]))) return SR_ERR; - sdi->probes = g_slist_append(sdi->probes, probe); + sdi->channels = g_slist_append(sdi->channels, probe); j++; } while(j > num_probes) { - g_slist_delete_link(sdi->probes, g_slist_last(sdi->probes)); + g_slist_delete_link(sdi->channels, g_slist_last(sdi->channels)); j--; } @@ -684,6 +753,18 @@ static GSList *dev_list(void) return ((struct drv_context *)(di->priv))->instances; } +static GSList *dev_mode_list(void) +{ + GSList *l = NULL; + int i; + + for(i = 0; i < ARRAY_SIZE(mode_list); i++) { + l = g_slist_append(l, &mode_list[i]); + } + + return l; +} + static int dev_open(struct sr_dev_inst *sdi) { struct sr_usb_dev_inst *usb; @@ -753,7 +834,16 @@ static int dev_open(struct sr_dev_inst *sdi) /* Takes >= 10ms for the FX2 to be ready for FPGA configure. */ g_usleep(10 * 1000); char filename[256]; - sprintf(filename,"%s%s",config_path,devc->profile->fpga_bit); + switch(devc->th_level) { + case SR_TH_3V3: + sprintf(filename,"%s%s",config_path,devc->profile->fpga_bit33); + break; + case SR_TH_5V0: + sprintf(filename,"%s%s",config_path,devc->profile->fpga_bit50); + break; + default: + return SR_ERR; + } const char *fpga_bit = filename; ret = fpga_config(usb->devhdl, fpga_bit); if (ret != SR_OK) { @@ -761,12 +851,6 @@ static int dev_open(struct sr_dev_inst *sdi) } } - if (devc->cur_samplerate == 0) { - /* Samplerate hasn't been set; default to the slowest one. */ - //devc->cur_samplerate = samplerates[0]; - devc->cur_samplerate = samplerates[sizeof(samplerates)/sizeof(uint64_t) - 3]; - } - return SR_OK; } @@ -804,12 +888,16 @@ static int cleanup(void) return ret; } -static int config_get(int id, GVariant **data, const struct sr_dev_inst *sdi) +static int config_get(int id, GVariant **data, const struct sr_dev_inst *sdi, + const struct sr_channel *ch, + const struct sr_channel_group *cg) { struct dev_context *devc; struct sr_usb_dev_inst *usb; char str[128]; + (void)cg; + switch (id) { case SR_CONF_CONN: if (!sdi || !sdi->conn) @@ -840,53 +928,50 @@ static int config_get(int id, GVariant **data, const struct sr_dev_inst *sdi) devc = sdi->priv; *data = g_variant_new_boolean(devc->clock_type); break; - case SR_CONF_OPERATION_MODE: + case SR_CONF_CLOCK_EDGE: if (!sdi) return SR_ERR; devc = sdi->priv; - *data = g_variant_new_string(opmodes[devc->op_mode]); + *data = g_variant_new_boolean(devc->clock_edge); break; - case SR_CONF_VDIV0: + case SR_CONF_OPERATION_MODE: if (!sdi) return SR_ERR; devc = sdi->priv; - *data = g_variant_new_uint64(devc->vdiv0); + *data = g_variant_new_string(opmodes[devc->op_mode]); break; - case SR_CONF_VDIV1: + case SR_CONF_FILTER: if (!sdi) return SR_ERR; devc = sdi->priv; - *data = g_variant_new_uint64(devc->vdiv1); + *data = g_variant_new_string(filters[devc->filter]); break; - case SR_CONF_TIMEBASE: + case SR_CONF_THRESHOLD: if (!sdi) return SR_ERR; devc = sdi->priv; - *data = g_variant_new_uint64(devc->timebase); + *data = g_variant_new_string(thresholds[devc->th_level]); break; - case SR_CONF_COUPLING0: - if (!sdi) + case SR_CONF_VDIV: + if (!ch) return SR_ERR; - devc = sdi->priv; - *data = g_variant_new_boolean(devc->coupling0); + *data = g_variant_new_uint64(ch->vdiv); break; - case SR_CONF_COUPLING1: + case SR_CONF_TIMEBASE: if (!sdi) return SR_ERR; devc = sdi->priv; - *data = g_variant_new_boolean(devc->coupling1); + *data = g_variant_new_uint64(devc->timebase); break; - case SR_CONF_EN_CH0: - if (!sdi) + case SR_CONF_COUPLING: + if (!ch) return SR_ERR; - devc = sdi->priv; - *data = g_variant_new_boolean(devc->en_ch0); + *data = g_variant_new_boolean(ch->coupling); break; - case SR_CONF_EN_CH1: - if (!sdi) + case SR_CONF_EN_CH: + if (!ch) return SR_ERR; - devc = sdi->priv; - *data = g_variant_new_boolean(devc->en_ch1); + *data = g_variant_new_boolean(ch->enabled); break; case SR_CONF_TRIGGER_SLOPE: if (!sdi) @@ -901,10 +986,9 @@ static int config_get(int id, GVariant **data, const struct sr_dev_inst *sdi) *data = g_variant_new_byte(devc->trigger_source); break; case SR_CONF_TRIGGER_VALUE: - if (!sdi) + if (!ch) return SR_ERR; - devc = sdi->priv; - *data = g_variant_new_uint16(devc->trigger_vpos); + *data = g_variant_new_uint16(ch->trig_value); break; case SR_CONF_HORIZ_TRIGGERPOS: if (!sdi) @@ -912,6 +996,12 @@ static int config_get(int id, GVariant **data, const struct sr_dev_inst *sdi) devc = sdi->priv; *data = g_variant_new_uint16(devc->trigger_hpos); break; + case SR_CONF_ZERO: + if (!sdi) + return SR_ERR; + devc = sdi->priv; + *data = g_variant_new_boolean(devc->zero); + break; default: return SR_ERR_NA; } @@ -919,29 +1009,30 @@ static int config_get(int id, GVariant **data, const struct sr_dev_inst *sdi) return SR_OK; } -static uint32_t dso_cmd_gen(struct sr_dev_inst *sdi, int channel, int id) +static uint32_t dso_cmd_gen(struct sr_dev_inst *sdi, struct sr_channel* ch, int id) { struct dev_context *devc; uint32_t cmd = 0; - int cmd_channel; + int channel_cnt = 0; + GSList *l; devc = sdi->priv; switch (id) { - case SR_CONF_VDIV0: - case SR_CONF_VDIV1: - case SR_CONF_EN_CH0: - case SR_CONF_EN_CH1: + case SR_CONF_VDIV: + case SR_CONF_EN_CH: case SR_CONF_TIMEBASE: - case SR_CONF_COUPLING0: - case SR_CONF_COUPLING1: - if (devc->en_ch0 && !devc->en_ch1) - cmd_channel = -1; - else if (devc->en_ch0 && devc->en_ch1) - cmd_channel = channel; + case SR_CONF_COUPLING: + for (l = sdi->channels; l; l = l->next) { + struct sr_channel *probe = (struct sr_channel *)l->data; + if (probe->enabled) + channel_cnt += probe->index + 0x1; + } + if (channel_cnt == 0) + return 0x0; // --VDBS - switch((cmd_channel == 1) ? devc->vdiv1 : devc->vdiv0){ + switch(ch->vdiv){ case 5: cmd += 0x247000; break; case 10: cmd += 0x23D000; break; case 20: cmd += 0x22F000; break; @@ -955,22 +1046,33 @@ static uint32_t dso_cmd_gen(struct sr_dev_inst *sdi, int channel, int id) default: cmd += 0x21100; break; } // --DC/AC - if(((cmd_channel == 1) ? devc->coupling1 : devc->coupling0)) + if(ch->coupling) cmd += 0x100000; // --Channel - if(cmd_channel == 0) +// if(channel_cnt == 1) +// cmd += 0xC00000; +// else if(ch->index == 0) +// cmd += 0x400000; +// else if(ch->index == 1) +// cmd += 0x800000; +// else +// cmd += 0x000000; + if(ch->index == 0) cmd += 0x400000; - else if(cmd_channel == 1) + else if(ch->index == 1) cmd += 0x800000; - else if(cmd_channel == -1) - cmd += 0xC00000; else cmd += 0x000000; // --Header cmd += 0x55000000; break; + case SR_CONF_SAMPLERATE: + cmd += 0x4; + uint32_t divider = (uint32_t)ceil(SR_MHZ(100) * 1.0 / devc->cur_samplerate - 1); + cmd += divider << 8; + break; case SR_CONF_HORIZ_TRIGGERPOS: cmd += 0x8; cmd += devc->trigger_hpos << 8; @@ -985,7 +1087,10 @@ static uint32_t dso_cmd_gen(struct sr_dev_inst *sdi, int channel, int id) break; case SR_CONF_TRIGGER_VALUE: cmd += 0x14; - cmd += devc->trigger_vpos << 8; + for (l = sdi->channels; l; l = l->next) { + struct sr_channel *probe = (struct sr_channel *)l->data; + cmd += probe->trig_value << (8 * (probe->index + 1)); + } break; case SR_CONF_ZERO: cmd += 0x18; @@ -1000,13 +1105,17 @@ static uint32_t dso_cmd_gen(struct sr_dev_inst *sdi, int channel, int id) return cmd; } -static int config_set(int id, GVariant *data, struct sr_dev_inst *sdi) +static int config_set(int id, GVariant *data, struct sr_dev_inst *sdi, + struct sr_channel *ch, + const struct sr_channel_group *cg ) { struct dev_context *devc; const char *stropt; int ret, num_probes; struct sr_usb_dev_inst *usb; + (void)cg; + if (sdi->status != SR_ST_ACTIVE) return SR_ERR; @@ -1021,33 +1130,39 @@ static int config_set(int id, GVariant *data, struct sr_dev_inst *sdi) } else { adjust_probes(sdi, 16); } + ret = SR_OK; + } else if(sdi->mode == DSO) { + ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, 0, SR_CONF_SAMPLERATE)); } - ret = SR_OK; + } else if (id == SR_CONF_CLOCK_TYPE) { devc->clock_type = g_variant_get_boolean(data); ret = SR_OK; - }else if (id == SR_CONF_LIMIT_SAMPLES) { + } else if (id == SR_CONF_CLOCK_EDGE) { + devc->clock_edge = g_variant_get_boolean(data); + ret = SR_OK; + } else if (id == SR_CONF_LIMIT_SAMPLES) { devc->limit_samples = g_variant_get_uint64(data); ret = SR_OK; } else if (id == SR_CONF_DEVICE_MODE) { - stropt = g_variant_get_string(data, NULL); + sdi->mode = g_variant_get_int16(data); ret = SR_OK; - if (!strcmp(stropt, mode_strings[LOGIC])) { - sdi->mode = LOGIC; + if (sdi->mode == LOGIC) { num_probes = devc->profile->dev_caps & DEV_CAPS_16BIT ? 16 : 8; - } else if (!strcmp(stropt, mode_strings[DSO])) { + } else if (sdi->mode == DSO) { sdi->mode = DSO; num_probes = devc->profile->dev_caps & DEV_CAPS_16BIT ? MAX_DSO_PROBES_NUM : 1; - ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, 0, SR_CONF_DSO_SYNC)); + ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, NULL, SR_CONF_DSO_SYNC)); if (ret != SR_OK) sr_dbg("%s: DSO configuration sync failed", __func__); - ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, 0, SR_CONF_VDIV0)); + ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, sdi->channels->data, SR_CONF_VDIV)); if (ret == SR_OK) sr_dbg("%s: Initial setting for DSO mode", __func__); else sr_dbg("%s: Initial setting for DSO mode failed", __func__); - } else { - sdi->mode = ANALOG; + devc->cur_samplerate = DS_MAX_DSO_SAMPLERATE / num_probes; + devc->limit_samples = DS_MAX_DSO_DEPTH / num_probes; + } else if (sdi->mode == ANALOG){ num_probes = devc->profile->dev_caps & DEV_CAPS_16BIT ? MAX_ANALOG_PROBES_NUM : 1; } sr_dev_probes_free(sdi); @@ -1069,78 +1184,91 @@ static int config_set(int id, GVariant *data, struct sr_dev_inst *sdi) } sr_dbg("%s: setting pattern to %d", __func__, devc->op_mode); - } else if (id == SR_CONF_EN_CH0) { - devc->en_ch0 = g_variant_get_boolean(data); - if (sdi->mode == DSO) { - ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, 0, SR_CONF_EN_CH0)); + } else if (id == SR_CONF_THRESHOLD) { + stropt = g_variant_get_string(data, NULL); + ret = SR_OK; + if (!strcmp(stropt, thresholds[SR_TH_3V3])) { + devc->th_level = SR_TH_3V3; + } else if (!strcmp(stropt, thresholds[SR_TH_5V0])) { + devc->th_level = SR_TH_5V0; + } else { + ret = SR_ERR; } - if (ret == SR_OK) - sr_dbg("%s: setting ENABLE of channel 0 to %d", - __func__, devc->en_ch0); - else - sr_dbg("%s: setting ENABLE of channel 0 to %d", - __func__, devc->en_ch0); - } else if (id == SR_CONF_EN_CH1) { - devc->en_ch1 = g_variant_get_boolean(data); - if (sdi->mode == DSO) { - ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, 1, SR_CONF_EN_CH1)); + if ((ret = command_fpga_config(usb->devhdl)) != SR_OK) { + sr_err("Send FPGA configure command failed!"); + } else { + /* Takes >= 10ms for the FX2 to be ready for FPGA configure. */ + g_usleep(10 * 1000); + char filename[256]; + switch(devc->th_level) { + case SR_TH_3V3: + sprintf(filename,"%s%s",config_path,devc->profile->fpga_bit33); + break; + case SR_TH_5V0: + sprintf(filename,"%s%s",config_path,devc->profile->fpga_bit50); + break; + default: + return SR_ERR; + } + const char *fpga_bit = filename; + ret = fpga_config(usb->devhdl, fpga_bit); + if (ret != SR_OK) { + sr_err("Configure FPGA failed!"); + } } - if (ret == SR_OK) - sr_dbg("%s: setting ENABLE of channel 1 to %d", - __func__, devc->en_ch1); - else - sr_dbg("%s: setting ENABLE of channel 1 to %d", - __func__, devc->en_ch1); - } else if (id == SR_CONF_VDIV0) { - devc->vdiv0 = g_variant_get_uint64(data); - if (sdi->mode == DSO) { - ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, 0, SR_CONF_VDIV0)); + sr_dbg("%s: setting threshold to %d", + __func__, devc->th_level); + } else if (id == SR_CONF_FILTER) { + stropt = g_variant_get_string(data, NULL); + ret = SR_OK; + if (!strcmp(stropt, filters[SR_FILTER_NONE])) { + devc->filter = SR_FILTER_NONE; + } else if (!strcmp(stropt, filters[SR_FILTER_1T])) { + devc->filter = SR_FILTER_1T; + } else { + ret = SR_ERR; } - if (ret == SR_OK) - sr_dbg("%s: setting VDIV of channel 0 to %d mv", - __func__, devc->vdiv0); - else - sr_dbg("%s: setting VDIV of channel 0 to %d mv failed", - __func__, devc->vdiv0); - } else if (id == SR_CONF_VDIV1) { - devc->vdiv1 = g_variant_get_uint64(data); + sr_dbg("%s: setting threshold to %d", + __func__, devc->th_level); + } else if (id == SR_CONF_EN_CH) { + ch->enabled = g_variant_get_boolean(data); if (sdi->mode == DSO) { - ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, 1, SR_CONF_VDIV1)); + ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, ch, SR_CONF_EN_CH)); } if (ret == SR_OK) - sr_dbg("%s: setting VDIV of channel 1 to %d mv", - __func__, devc->vdiv1); + sr_dbg("%s: setting ENABLE of channel %d to %d", + __func__, ch->index, ch->enabled); else - sr_dbg("%s: setting VDIV of channel 1 to %d mv failed", - __func__, devc->vdiv1); - } else if (id == SR_CONF_TIMEBASE) { - devc->timebase = g_variant_get_uint64(data); - } else if (id == SR_CONF_COUPLING0) { - devc->coupling0 = g_variant_get_boolean(data); + sr_dbg("%s: setting ENABLE of channel %d to %d", + __func__, ch->index, ch->enabled); + } else if (id == SR_CONF_VDIV) { + ch->vdiv = g_variant_get_uint64(data); if (sdi->mode == DSO) { - ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, 0, SR_CONF_COUPLING0)); + ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, ch, SR_CONF_VDIV)); } if (ret == SR_OK) - sr_dbg("%s: setting AC COUPLING of channel 0 to %d", - __func__, devc->coupling0); + sr_dbg("%s: setting VDIV of channel %d to %d mv", + __func__, ch->index, ch->vdiv); else - sr_dbg("%s: setting AC COUPLING of channel 0 to %d failed", - __func__, devc->coupling0); - } else if (id == SR_CONF_COUPLING1) { - devc->coupling1 = g_variant_get_boolean(data); + sr_dbg("%s: setting VDIV of channel %d to %d mv failed", + __func__, ch->index, ch->vdiv); + } else if (id == SR_CONF_TIMEBASE) { + devc->timebase = g_variant_get_uint64(data); + } else if (id == SR_CONF_COUPLING) { + ch->coupling = g_variant_get_boolean(data); if (sdi->mode == DSO) { - ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, 1, SR_CONF_COUPLING0)); + ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, ch, SR_CONF_COUPLING)); } if (ret == SR_OK) - sr_dbg("%s: setting AC COUPLING of channel 1 to %d", - __func__, devc->coupling1); + sr_dbg("%s: setting AC COUPLING of channel %d to %d", + __func__, ch->index, ch->coupling); else - sr_dbg("%s: setting AC COUPLING of channel 1 to %d failed", - __func__, devc->coupling1); - } else if (id == SR_CONF_TRIGGER_SLOPE) { + sr_dbg("%s: setting AC COUPLING of channel %d to %d failed", + __func__, ch->index, ch->coupling); + } else if (id == SR_CONF_TRIGGER_SLOPE) { devc->trigger_slope = g_variant_get_byte(data); if (sdi->mode == DSO) { - ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, 1, SR_CONF_TRIGGER_SLOPE)); + ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, NULL, SR_CONF_TRIGGER_SLOPE)); } if (ret == SR_OK) sr_dbg("%s: setting DSO Trigger Slope to %d", @@ -1151,7 +1279,7 @@ static int config_set(int id, GVariant *data, struct sr_dev_inst *sdi) } else if (id == SR_CONF_TRIGGER_SOURCE) { devc->trigger_source = g_variant_get_byte(data); if (sdi->mode == DSO) { - ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, 1, SR_CONF_TRIGGER_SOURCE)); + ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, NULL, SR_CONF_TRIGGER_SOURCE)); } if (ret == SR_OK) sr_dbg("%s: setting DSO Trigger Source to %d", @@ -1160,18 +1288,18 @@ static int config_set(int id, GVariant *data, struct sr_dev_inst *sdi) sr_dbg("%s: setting DSO Trigger Source to %d failed", __func__, devc->trigger_source); } else if (id == SR_CONF_TRIGGER_VALUE) { - devc->trigger_vpos = g_variant_get_uint16(data); + ch->trig_value = g_variant_get_uint16(data); if (sdi->mode == DSO) { - ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, 1, SR_CONF_TRIGGER_VALUE)); + ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, ch, SR_CONF_TRIGGER_VALUE)); } if (ret == SR_OK) - sr_dbg("%s: setting DSO Trigger Value to %d", - __func__, devc->trigger_vpos); + sr_dbg("%s: setting channel %d Trigger Value to %d", + __func__, ch->index, ch->trig_value); else sr_dbg("%s: setting DSO Trigger Value to %d failed", - __func__, devc->trigger_vpos); + __func__, ch->index, ch->trig_value); } else if (id == SR_CONF_HORIZ_TRIGGERPOS) { - devc->trigger_hpos = g_variant_get_uint32(data); + devc->trigger_hpos = g_variant_get_uint16(data) * devc->limit_samples / 100.0f; if (sdi->mode == DSO) { ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, 1, SR_CONF_HORIZ_TRIGGERPOS)); } @@ -1199,12 +1327,14 @@ static int config_set(int id, GVariant *data, struct sr_dev_inst *sdi) return ret; } -static int config_list(int key, GVariant **data, const struct sr_dev_inst *sdi) +static int config_list(int key, GVariant **data, const struct sr_dev_inst *sdi, + const struct sr_channel_group *cg) { GVariant *gvar; GVariantBuilder gvb; (void)sdi; + (void)cg; switch (key) { case SR_CONF_SCAN_OPTIONS: @@ -1234,12 +1364,25 @@ static int config_list(int key, GVariant **data, const struct sr_dev_inst *sdi) g_variant_builder_add(&gvb, "{sv}", "samplerates", gvar); *data = g_variant_builder_end(&gvb); break; + case SR_CONF_LIMIT_SAMPLES: + g_variant_builder_init(&gvb, G_VARIANT_TYPE("a{sv}")); + gvar = g_variant_new_from_data(G_VARIANT_TYPE("at"), + samplecounts, ARRAY_SIZE(samplecounts)*sizeof(uint64_t), TRUE, NULL, NULL); + g_variant_builder_add(&gvb, "{sv}", "samplecounts", gvar); + *data = g_variant_builder_end(&gvb); + break; case SR_CONF_TRIGGER_TYPE: *data = g_variant_new_string(TRIGGER_TYPE); break; case SR_CONF_OPERATION_MODE: *data = g_variant_new_strv(opmodes, ARRAY_SIZE(opmodes)); break; + case SR_CONF_THRESHOLD: + *data = g_variant_new_strv(thresholds, ARRAY_SIZE(thresholds)); + break; + case SR_CONF_FILTER: + *data = g_variant_new_strv(filters, ARRAY_SIZE(filters)); + break; default: return SR_ERR_NA; } @@ -1495,7 +1638,7 @@ static void receive_transfer(struct libusb_transfer *transfer) } else if ((*(struct sr_dev_inst *)(devc->cb_data)).mode == DSO) { packet.type = SR_DF_DSO; packet.payload = &dso; - dso.probes = (*(struct sr_dev_inst *)(devc->cb_data)).probes; + dso.probes = (*(struct sr_dev_inst *)(devc->cb_data)).channels; dso.num_samples = transfer->actual_length / sample_width; dso.mq = SR_MQ_VOLTAGE; dso.unit = SR_UNIT_VOLT; @@ -1504,7 +1647,7 @@ static void receive_transfer(struct libusb_transfer *transfer) } else { packet.type = SR_DF_ANALOG; packet.payload = &analog; - analog.probes = (*(struct sr_dev_inst *)(devc->cb_data)).probes; + analog.probes = (*(struct sr_dev_inst *)(devc->cb_data)).channels; analog.num_samples = transfer->actual_length / sample_width; analog.mq = SR_MQ_VOLTAGE; analog.unit = SR_UNIT_VOLT; @@ -1813,32 +1956,31 @@ static int dev_acquisition_start(const struct sr_dev_inst *sdi, void *cb_data) } } if (sdi->mode == DSO) { - ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, 0, SR_CONF_VDIV0)); - if (ret != SR_OK) { - sr_err("Set VDIV of channel 0 command failed!"); - return ret; - } - ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, 1, SR_CONF_VDIV1)); - if (ret != SR_OK) { - sr_err("Set VDIV of channel 1 command failed!"); - return ret; + GSList *l; + for(l = sdi->channels; l; l = l->next) { + struct sr_channel *probe = (struct sr_channel *)l->data; + ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, probe, SR_CONF_VDIV)); + if (ret != SR_OK) { + sr_err("Set VDIV of channel %d command failed!", probe->index); + return ret; + } } - ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, 0, SR_CONF_HORIZ_TRIGGERPOS)); + ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, NULL, SR_CONF_HORIZ_TRIGGERPOS)); if (ret != SR_OK) { sr_err("Set Horiz Trigger Position command failed!"); return ret; } - ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, 0, SR_CONF_TRIGGER_SLOPE)); + ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, NULL, SR_CONF_TRIGGER_SLOPE)); if (ret != SR_OK) { sr_err("Set Trigger Slope command failed!"); return ret; } - ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, 0, SR_CONF_TRIGGER_SOURCE)); + ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, NULL, SR_CONF_TRIGGER_SOURCE)); if (ret != SR_OK) { sr_err("Set Trigger Source command failed!"); return ret; } - ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, 0, SR_CONF_TRIGGER_VALUE)); + ret = command_dso_ctrl(usb->devhdl, dso_cmd_gen(sdi, NULL, SR_CONF_TRIGGER_VALUE)); if (ret != SR_OK) { sr_err("Set Trigger Value command failed!"); return ret; @@ -1846,7 +1988,7 @@ static int dev_acquisition_start(const struct sr_dev_inst *sdi, void *cb_data) } if ((ret = command_start_acquisition (usb->devhdl, - devc->cur_samplerate, devc->sample_wide)) != SR_OK) { + devc->cur_samplerate, devc->sample_wide, (sdi->mode == LOGIC))) != SR_OK) { abort_acquisition(devc); return ret; } @@ -1935,6 +2077,25 @@ static int dev_test(struct sr_dev_inst *sdi) } } +static int dev_status_get(struct sr_dev_inst *sdi, struct sr_status *status) +{ + if (sdi) { + struct sr_usb_dev_inst *usb; + int ret; + + usb = sdi->conn; + ret = command_get_status(usb->devhdl, status); + if (ret != SR_OK) { + sr_err("Device don't exist!"); + return SR_ERR; + } else { + return SR_OK; + } + } else { + return SR_ERR; + } +} + SR_PRIV struct sr_dev_driver DSLogic_driver_info = { .name = "DSLogic", .longname = "DSLogic (generic driver for DSLogic LA)", @@ -1943,13 +2104,15 @@ SR_PRIV struct sr_dev_driver DSLogic_driver_info = { .cleanup = cleanup, .scan = scan, .dev_list = dev_list, + .dev_mode_list = dev_mode_list, .dev_clear = dev_clear, .config_get = config_get, .config_set = config_set, .config_list = config_list, .dev_open = dev_open, .dev_close = dev_close, - .dev_test = dev_test, + .dev_test = dev_test, + .dev_status_get = dev_status_get, .dev_acquisition_start = dev_acquisition_start, .dev_acquisition_stop = dev_acquisition_stop, .priv = NULL, diff --git a/libsigrok4DSLogic/hardware/DSLogic/dslogic.h b/libsigrok4DSLogic/hardware/DSLogic/dslogic.h index a62dc65..1066c8d 100644 --- a/libsigrok4DSLogic/hardware/DSLogic/dslogic.h +++ b/libsigrok4DSLogic/hardware/DSLogic/dslogic.h @@ -56,14 +56,12 @@ #define DEV_CAPS_16BIT (1 << DEV_CAPS_16BIT_POS) -#define XC3S250E_BYTE_CNT 169216 -//#define XC6SLX9_BYTE_CNT 341160 -#define XC6SLX9_BYTE_CNT 340884 -//#define XC6SLX9_BYTE_CNT 340604 - #define MAX_ANALOG_PROBES_NUM 9 #define MAX_DSO_PROBES_NUM 2 +#define DEFAULT_SAMPLERATE SR_MHZ(100) +#define DEFAULT_SAMPLELIMIT SR_MB(16) + struct DSLogic_profile { uint16_t vid; uint16_t pid; @@ -74,7 +72,8 @@ struct DSLogic_profile { const char *firmware; - const char *fpga_bit; + const char *fpga_bit33; + const char *fpga_bit50; uint32_t dev_caps; }; @@ -85,10 +84,11 @@ static const struct DSLogic_profile supported_fx2[3] = { */ {0x2A0E, 0x0001, NULL, "DSLogic", NULL, "DSLogic.fw", - "DSLogic.bin", + "DSLogic33.bin", + "DSLogic50.bin", DEV_CAPS_16BIT}, - { 0, 0, 0, 0, 0, 0, 0 } + { 0, 0, 0, 0, 0, 0, 0, 0, 0 } }; enum { @@ -102,7 +102,7 @@ enum { struct dev_context { const struct DSLogic_profile *profile; - /* + /* * Since we can't keep track of an DSLogic device after upgrading * the firmware (it renumerates into a different device address * after the upgrade) this is like a global lock. No device will open @@ -117,21 +117,17 @@ struct dev_context { /* Operational settings */ gboolean sample_wide; gboolean clock_type; + gboolean clock_edge; uint16_t op_mode; + uint16_t th_level; + uint16_t filter; uint16_t trigger_mask[NUM_TRIGGER_STAGES]; uint16_t trigger_value[NUM_TRIGGER_STAGES]; int trigger_stage; uint16_t trigger_buffer[NUM_TRIGGER_STAGES]; - uint64_t vdiv0; - uint64_t vdiv1; uint64_t timebase; - gboolean coupling0; - gboolean coupling1; - gboolean en_ch0; - gboolean en_ch1; uint8_t trigger_slope; uint8_t trigger_source; - uint16_t trigger_vpos; uint32_t trigger_hpos; gboolean zero; diff --git a/libsigrok4DSLogic/hardware/demo/demo.c b/libsigrok4DSLogic/hardware/demo/demo.c index 15ef903..faa786f 100644 --- a/libsigrok4DSLogic/hardware/demo/demo.c +++ b/libsigrok4DSLogic/hardware/demo/demo.c @@ -72,6 +72,12 @@ static const char *pattern_strings[] = { "Random", }; +static struct sr_dev_mode mode_list[] = { + {"LA", LOGIC}, + {"DAQ", ANALOG}, + {"OSC", DSO}, +}; + /* Private, per-device-instance driver context. */ struct dev_context { struct sr_dev_inst *sdi; @@ -85,13 +91,7 @@ struct dev_context { void *cb_data; int64_t starttime; int stop; - gboolean en_ch0; - gboolean en_ch1; - uint64_t vdiv0; - uint64_t vdiv1; uint64_t timebase; - gboolean coupling0; - gboolean coupling1; int trigger_stage; uint16_t trigger_mask; @@ -128,8 +128,26 @@ static const uint64_t samplerates[] = { SR_MHZ(50), SR_MHZ(100), SR_MHZ(200), + SR_MHZ(400), }; +static const uint64_t samplecounts[] = { + SR_KB(1), + SR_KB(2), + SR_KB(4), + SR_KB(8), + SR_KB(16), + SR_KB(32), + SR_KB(64), + SR_KB(128), + SR_KB(256), + SR_KB(512), + SR_MB(1), + SR_MB(2), + SR_MB(4), + SR_MB(8), + SR_MB(16), +}; /* We name the probes 0-7 on our demo driver. */ @@ -167,7 +185,7 @@ static int hw_init(struct sr_context *sr_ctx) static GSList *hw_scan(GSList *options) { struct sr_dev_inst *sdi; - struct sr_probe *probe; + struct sr_channel *probe; struct drv_context *drvc; struct dev_context *devc; GSList *devices; @@ -195,38 +213,34 @@ static GSList *hw_scan(GSList *options) } devc->sdi = sdi; - devc->cur_samplerate = SR_MHZ(200); - devc->limit_samples = 0; + devc->cur_samplerate = SR_MHZ(100); + devc->limit_samples = SR_MB(1); devc->limit_msec = 0; devc->sample_generator = PATTERN_SINE; - devc->vdiv0 = 1000; - devc->vdiv1 = 1000; - devc->timebase = 100; - devc->coupling0 = FALSE; - devc->coupling1 = FALSE; + devc->timebase = 100000; sdi->priv = devc; if (sdi->mode == LOGIC) { for (i = 0; probe_names[i]; i++) { - if (!(probe = sr_probe_new(i, SR_PROBE_LOGIC, TRUE, + if (!(probe = sr_channel_new(i, SR_CHANNEL_LOGIC, TRUE, probe_names[i]))) return NULL; - sdi->probes = g_slist_append(sdi->probes, probe); + sdi->channels = g_slist_append(sdi->channels, probe); } } else if (sdi->mode == DSO) { for (i = 0; i < DS_MAX_DSO_PROBES_NUM; i++) { - if (!(probe = sr_probe_new(i, SR_PROBE_DSO, TRUE, + if (!(probe = sr_channel_new(i, SR_CHANNEL_DSO, TRUE, probe_names[i]))) return NULL; - sdi->probes = g_slist_append(sdi->probes, probe); + sdi->channels = g_slist_append(sdi->channels, probe); } } else if (sdi->mode == ANALOG) { for (i = 0; i < DS_MAX_ANALOG_PROBES_NUM; i++) { - if (!(probe = sr_probe_new(i, SR_PROBE_ANALOG, TRUE, + if (!(probe = sr_channel_new(i, SR_CHANNEL_ANALOG, TRUE, probe_names[i]))) return NULL; - sdi->probes = g_slist_append(sdi->probes, probe); + sdi->channels = g_slist_append(sdi->channels, probe); } } @@ -238,6 +252,18 @@ static GSList *hw_dev_list(void) return ((struct drv_context *)(di->priv))->instances; } +static GSList *hw_dev_mode_list(void) +{ + GSList *l = NULL; + int i; + + for(i = 0; i < ARRAY_SIZE(mode_list); i++) { + l = g_slist_append(l, &mode_list[i]); + } + + return l; +} + static int hw_dev_open(struct sr_dev_inst *sdi) { (void)sdi; @@ -282,9 +308,13 @@ static int hw_cleanup(void) return ret; } -static int config_get(int id, GVariant **data, const struct sr_dev_inst *sdi) +static int config_get(int id, GVariant **data, const struct sr_dev_inst *sdi, + const struct sr_channel *ch, + const struct sr_channel_group *cg) { - struct dev_context *const devc = sdi->priv; + (void) cg; + + struct dev_context *const devc = sdi->priv; switch (id) { case SR_CONF_SAMPLERATE: @@ -297,31 +327,22 @@ static int config_get(int id, GVariant **data, const struct sr_dev_inst *sdi) *data = g_variant_new_uint64(devc->limit_msec); break; case SR_CONF_DEVICE_MODE: - *data = g_variant_new_string(mode_strings[sdi->mode]); + *data = g_variant_new_int16(sdi->mode); break; case SR_CONF_PATTERN_MODE: *data = g_variant_new_string(pattern_strings[devc->sample_generator]); break; - case SR_CONF_VDIV0: - *data = g_variant_new_uint64(devc->vdiv0); - break; - case SR_CONF_VDIV1: - *data = g_variant_new_uint64(devc->vdiv1); + case SR_CONF_VDIV: + *data = g_variant_new_uint64(ch->vdiv); break; case SR_CONF_TIMEBASE: *data = g_variant_new_uint64(devc->timebase); break; - case SR_CONF_COUPLING0: - *data = g_variant_new_uint64(devc->coupling0); - break; - case SR_CONF_COUPLING1: - *data = g_variant_new_uint64(devc->coupling1); - break; - case SR_CONF_EN_CH0: - *data = g_variant_new_uint64(devc->en_ch0); + case SR_CONF_COUPLING: + *data = g_variant_new_uint64(ch->coupling); break; - case SR_CONF_EN_CH1: - *data = g_variant_new_uint64(devc->en_ch1); + case SR_CONF_EN_CH: + *data = g_variant_new_uint64(ch->enabled); break; default: return SR_ERR_NA; @@ -330,11 +351,15 @@ static int config_get(int id, GVariant **data, const struct sr_dev_inst *sdi) return SR_OK; } -static int config_set(int id, GVariant *data, struct sr_dev_inst *sdi) +static int config_set(int id, GVariant *data, struct sr_dev_inst *sdi, + struct sr_channel *ch, + const struct sr_channel_group *cg) { int i, ret; const char *stropt; - struct sr_probe *probe; + struct sr_channel *probe; + + (void) cg; struct dev_context *const devc = sdi->priv; @@ -359,37 +384,39 @@ static int config_set(int id, GVariant *data, struct sr_dev_inst *sdi) devc->limit_msec); ret = SR_OK; } else if (id == SR_CONF_DEVICE_MODE) { - stropt = g_variant_get_string(data, NULL); + sdi->mode = g_variant_get_int16(data); ret = SR_OK; - if (!strcmp(stropt, mode_strings[LOGIC])) { - sdi->mode = LOGIC; + if (sdi->mode == LOGIC) { sr_dev_probes_free(sdi); for (i = 0; probe_names[i]; i++) { - if (!(probe = sr_probe_new(i, SR_PROBE_LOGIC, TRUE, + if (!(probe = sr_channel_new(i, SR_CHANNEL_LOGIC, TRUE, probe_names[i]))) ret = SR_ERR; else - sdi->probes = g_slist_append(sdi->probes, probe); + sdi->channels = g_slist_append(sdi->channels, probe); } - } else if (!strcmp(stropt, mode_strings[DSO])) { - sdi->mode = DSO; + } else if (sdi->mode == DSO) { sr_dev_probes_free(sdi); for (i = 0; i < DS_MAX_DSO_PROBES_NUM; i++) { - if (!(probe = sr_probe_new(i, SR_PROBE_DSO, TRUE, + if (!(probe = sr_channel_new(i, SR_CHANNEL_DSO, TRUE, probe_names[i]))) ret = SR_ERR; - else - sdi->probes = g_slist_append(sdi->probes, probe); + else { + probe->vdiv = 1000; + probe->coupling = FALSE; + probe->trig_value = 0x80; + sdi->channels = g_slist_append(sdi->channels, probe); + } } - } else if (!strcmp(stropt, mode_strings[ANALOG])) { - sdi->mode = ANALOG; + devc->limit_samples = SR_MB(1); + } else if (sdi->mode == ANALOG) { sr_dev_probes_free(sdi); for (i = 0; i < DS_MAX_ANALOG_PROBES_NUM; i++) { - if (!(probe = sr_probe_new(i, SR_PROBE_ANALOG, TRUE, + if (!(probe = sr_channel_new(i, SR_CHANNEL_ANALOG, TRUE, probe_names[i]))) ret = SR_ERR; else - sdi->probes = g_slist_append(sdi->probes, probe); + sdi->channels = g_slist_append(sdi->channels, probe); } } else { ret = SR_ERR; @@ -413,40 +440,25 @@ static int config_set(int id, GVariant *data, struct sr_dev_inst *sdi) } sr_dbg("%s: setting pattern to %d", __func__, devc->sample_generator); - } else if (id == SR_CONF_EN_CH0) { - devc->en_ch0 = g_variant_get_boolean(data); - sr_dbg("%s: setting ENABLE of channel 0 to %d", __func__, - devc->en_ch0); - ret = SR_OK; - } else if (id == SR_CONF_EN_CH1) { - devc->en_ch1 = g_variant_get_boolean(data); - sr_dbg("%s: setting ENABLE of channel 1 to %d", __func__, - devc->en_ch1); + } else if (id == SR_CONF_EN_CH) { + ch->enabled = g_variant_get_boolean(data); + sr_dbg("%s: setting ENABLE of channel %d to %d", __func__, + ch->index, ch->enabled); ret = SR_OK; - } else if (id == SR_CONF_VDIV0) { - devc->vdiv0 = g_variant_get_uint64(data); - sr_dbg("%s: setting VDIV of channel 0 to %" PRIu64, __func__, - devc->vdiv0); - ret = SR_OK; - } else if (id == SR_CONF_VDIV1) { - devc->vdiv1 = g_variant_get_uint64(data); - sr_dbg("%s: setting VDIV of channel 1 to %" PRIu64, __func__, - devc->vdiv1); + } else if (id == SR_CONF_VDIV) { + ch->vdiv = g_variant_get_uint64(data); + sr_dbg("%s: setting VDIV of channel %d to %" PRIu64, __func__, + ch->index, ch->vdiv); ret = SR_OK; } else if (id == SR_CONF_TIMEBASE) { devc->timebase = g_variant_get_uint64(data); sr_dbg("%s: setting TIMEBASE to %" PRIu64, __func__, devc->timebase); ret = SR_OK; - } else if (id == SR_CONF_COUPLING0) { - devc->coupling0 = g_variant_get_boolean(data); - sr_dbg("%s: setting AC COUPLING of channel 0 to %d", __func__, - devc->coupling0); - ret = SR_OK; - } else if (id == SR_CONF_COUPLING1) { - devc->coupling1 = g_variant_get_boolean(data); - sr_dbg("%s: setting AC COUPLING of channel 1 to %d", __func__, - devc->coupling1); + } else if (id == SR_CONF_COUPLING) { + ch->coupling = g_variant_get_boolean(data); + sr_dbg("%s: setting AC COUPLING of channel %d to %d", __func__, + ch->index, ch->coupling); ret = SR_OK; } else { ret = SR_ERR_NA; @@ -455,12 +467,14 @@ static int config_set(int id, GVariant *data, struct sr_dev_inst *sdi) return ret; } -static int config_list(int key, GVariant **data, const struct sr_dev_inst *sdi) +static int config_list(int key, GVariant **data, const struct sr_dev_inst *sdi, + const struct sr_channel_group *cg) { GVariant *gvar; GVariantBuilder gvb; (void)sdi; + (void)cg; switch (key) { case SR_CONF_DEVICE_OPTIONS: @@ -484,8 +498,12 @@ static int config_list(int key, GVariant **data, const struct sr_dev_inst *sdi) g_variant_builder_add(&gvb, "{sv}", "samplerates", gvar); *data = g_variant_builder_end(&gvb); break; - case SR_CONF_DEVICE_MODE: - *data = g_variant_new_strv(mode_strings, ARRAY_SIZE(mode_strings)); + case SR_CONF_LIMIT_SAMPLES: + g_variant_builder_init(&gvb, G_VARIANT_TYPE("a{sv}")); + gvar = g_variant_new_from_data(G_VARIANT_TYPE("at"), + samplecounts, ARRAY_SIZE(samplecounts)*sizeof(uint64_t), TRUE, NULL, NULL); + g_variant_builder_add(&gvb, "{sv}", "samplecounts", gvar); + *data = g_variant_builder_end(&gvb); break; case SR_CONF_PATTERN_MODE: *data = g_variant_new_strv(pattern_strings, ARRAY_SIZE(pattern_strings)); @@ -498,7 +516,8 @@ static int config_list(int key, GVariant **data, const struct sr_dev_inst *sdi) } static void samples_generator(uint16_t *buf, uint64_t size, - struct dev_context *devc) + const struct sr_dev_inst *sdi, + struct dev_context *devc) { static uint16_t p = 0; uint64_t i; @@ -508,45 +527,75 @@ static void samples_generator(uint16_t *buf, uint64_t size, case PATTERN_SINE: /* Sine */ for (i = 0; i < size; i++) { if (i%CONST_LEN == 0) { - demo_data = 0x8000 * sin(2 * PI * p / 0xffff) + 0x8000; - p += CONST_LEN; + //demo_data = 0x8000 * sin(2 * PI * p / 0xffff) + 0x8000; + demo_data = 0x20 * (sin(2 * PI * p / 0xff) + 1); + p++; + } + GSList *l; + struct sr_channel *probe; + for (l = sdi->channels; l; l = l->next) { + probe = (struct sr_channel *)l->data; + *(buf + i) += ((probe->coupling ? 0x60 : 0x80) + demo_data) << (probe->index * 8); } - *(buf + i) = demo_data; } break; case PATTERN_SQUARE: for (i = 0; i < size; i++) { if (i%CONST_LEN == 0) { - demo_data = p > 0x7fff ? 0xf000 : 0x1000; - p += CONST_LEN; + demo_data = p > 0x7fff ? 0xa0a0 : 0x6060; + p += CONST_LEN * 10; } *(buf + i) = demo_data; + GSList *l; + struct sr_channel *probe; + for (l = sdi->channels; l; l = l->next) { + probe = (struct sr_channel *)l->data; + *(buf + i) += (probe->coupling ? 0x00 : 0x20) << (probe->index * 8); + } } break; case PATTERN_TRIANGLE: for (i = 0; i < size; i++) { if (i%CONST_LEN == 0) { - demo_data = p > 0x7fff ? 0xffff * (2.0f * (0x10000 - p) / 0x10000) : - 0xffff * (2.0f * p / 0x10000); - p += CONST_LEN; + demo_data = p > 0x7fff ? 0x40 * (1 + (0x8000 - p * 1.0f) / 0x8000) : + 0x40 * (p * 1.0f / 0x8000); + p += CONST_LEN * 10; + } + *(buf + i) = demo_data + (demo_data << 8); + GSList *l; + struct sr_channel *probe; + for (l = sdi->channels; l; l = l->next) { + probe = (struct sr_channel *)l->data; + *(buf + i) += (probe->coupling ? 0x60 : 0x80) << (probe->index * 8); } - *(buf + i) = demo_data; } break; case PATTERN_SAWTOOTH: for (i = 0; i < size; i++) { if (i%CONST_LEN == 0) { - demo_data = p; - p += CONST_LEN; + demo_data = p & 0x003f; + p ++; + } + *(buf + i) = demo_data + (demo_data << 8); + GSList *l; + struct sr_channel *probe; + for (l = sdi->channels; l; l = l->next) { + probe = (struct sr_channel *)l->data; + *(buf + i) += (probe->coupling ? 0x60 : 0x80) << (probe->index * 8); } - *(buf + i) = demo_data; } break; case PATTERN_RANDOM: /* Random */ for (i = 0; i < size; i++) { if (i%CONST_LEN == 0) - demo_data = (uint16_t)(rand() * (0x10000 * 1.0f / RAND_MAX)); - *(buf + i) = demo_data; + demo_data = (uint16_t)(rand() * (0x40 * 1.0f / RAND_MAX)); + *(buf + i) = demo_data + (demo_data << 8); + GSList *l; + struct sr_channel *probe; + for (l = sdi->channels; l; l = l->next) { + probe = (struct sr_channel *)l->data; + *(buf + i) += (probe->coupling ? 0x60 : 0x80) << (probe->index * 8); + } } break; default: @@ -599,7 +648,7 @@ static int receive_data(int fd, int revents, const struct sr_dev_inst *sdi) while (samples_to_send > 0) { sending_now = MIN(samples_to_send, BUFSIZE); - samples_generator(buf, sending_now, devc); + samples_generator(buf, sending_now, sdi, devc); if (devc->trigger_stage != 0) { for (i = 0; i < sending_now; i++) { @@ -643,7 +692,7 @@ static int receive_data(int fd, int revents, const struct sr_dev_inst *sdi) } else if (sdi->mode == DSO) { packet.type = SR_DF_DSO; packet.payload = &dso; - dso.probes = sdi->probes; + dso.probes = sdi->channels; dso.num_samples = sending_now; dso.mq = SR_MQ_VOLTAGE; dso.unit = SR_UNIT_VOLT; @@ -652,7 +701,7 @@ static int receive_data(int fd, int revents, const struct sr_dev_inst *sdi) }else { packet.type = SR_DF_ANALOG; packet.payload = &analog; - analog.probes = sdi->probes; + analog.probes = sdi->channels; analog.num_samples = sending_now; analog.mq = SR_MQ_VOLTAGE; analog.unit = SR_UNIT_VOLT; @@ -779,6 +828,21 @@ static int hw_dev_test(struct sr_dev_inst *sdi) return SR_ERR; } +static int hw_dev_status_get(struct sr_dev_inst *sdi, struct sr_status *status) +{ + if (sdi) { + struct dev_context *const devc = sdi->priv; + status->trig_hit = (devc->trigger_stage == 0); + status->captured_cnt0 = devc->samples_counter; + status->captured_cnt1 = devc->samples_counter >> 8; + status->captured_cnt2 = devc->samples_counter >> 16; + status->captured_cnt3 = devc->samples_counter >> 32; + return SR_OK; + } else { + return SR_ERR; + } +} + SR_PRIV struct sr_dev_driver demo_driver_info = { .name = "demo", .longname = "Demo driver and pattern generator", @@ -787,6 +851,7 @@ SR_PRIV struct sr_dev_driver demo_driver_info = { .cleanup = hw_cleanup, .scan = hw_scan, .dev_list = hw_dev_list, + .dev_mode_list = hw_dev_mode_list, .dev_clear = clear_instances, .config_get = config_get, .config_set = config_set, @@ -794,6 +859,7 @@ SR_PRIV struct sr_dev_driver demo_driver_info = { .dev_open = hw_dev_open, .dev_close = hw_dev_close, .dev_test = hw_dev_test, + .dev_status_get = hw_dev_status_get, .dev_acquisition_start = hw_dev_acquisition_start, .dev_acquisition_stop = hw_dev_acquisition_stop, .priv = NULL, diff --git a/libsigrok4DSLogic/hwdriver.c b/libsigrok4DSLogic/hwdriver.c index b0c2f41..d42063b 100644 --- a/libsigrok4DSLogic/hwdriver.c +++ b/libsigrok4DSLogic/hwdriver.c @@ -59,10 +59,10 @@ static struct sr_config_info sr_config_info_data[] = { "Sample rate", NULL}, {SR_CONF_CLOCK_TYPE, SR_T_BOOL, "clocktype", "Using External Clock", NULL}, + {SR_CONF_CLOCK_EDGE, SR_T_BOOL, "clockedge", + "Using Clock Negedge", NULL}, {SR_CONF_CAPTURE_RATIO, SR_T_UINT64, "captureratio", "Pre-trigger capture ratio", NULL}, - {SR_CONF_DEVICE_MODE, SR_T_CHAR, "device", - "Device Mode", NULL}, {SR_CONF_PATTERN_MODE, SR_T_CHAR, "pattern", "Pattern mode", NULL}, {SR_CONF_TRIGGER_TYPE, SR_T_CHAR, "triggertype", @@ -80,19 +80,17 @@ static struct sr_config_info sr_config_info_data[] = { {SR_CONF_TIMEBASE, SR_T_RATIONAL_PERIOD, "timebase", "Time base", NULL}, {SR_CONF_FILTER, SR_T_CHAR, "filter", - "Filter targets", NULL}, - {SR_CONF_VDIV0, SR_T_RATIONAL_VOLT, "vdiv", + "Filter Targets", NULL}, + {SR_CONF_VDIV, SR_T_RATIONAL_VOLT, "vdiv", "Volts/div", NULL}, - {SR_CONF_VDIV1, SR_T_RATIONAL_VOLT, "vdiv", - "Volts/div", NULL}, - {SR_CONF_COUPLING0, SR_T_CHAR, "coupling", - "Coupling", NULL}, - {SR_CONF_COUPLING1, SR_T_CHAR, "coupling", + {SR_CONF_COUPLING, SR_T_CHAR, "coupling", "Coupling", NULL}, {SR_CONF_DATALOG, SR_T_BOOL, "datalog", "Datalog", NULL}, {SR_CONF_OPERATION_MODE, SR_T_CHAR, "operation", "Operation Mode", NULL}, + {SR_CONF_THRESHOLD, SR_T_CHAR, "threshold", + "Threshold Level", NULL}, {0, 0, NULL, NULL, NULL}, }; @@ -266,8 +264,11 @@ SR_PRIV void sr_config_free(struct sr_config *src) * but this is not to be flagged as an error by the caller; merely * as an indication that it's not applicable. */ -SR_API int sr_config_get(const struct sr_dev_driver *driver, int key, - GVariant **data, const struct sr_dev_inst *sdi) +SR_API int sr_config_get(const struct sr_dev_driver *driver, + const struct sr_dev_inst *sdi, + const struct sr_channel *ch, + const struct sr_channel_group *cg, + int key, GVariant **data) { int ret; @@ -277,7 +278,7 @@ SR_API int sr_config_get(const struct sr_dev_driver *driver, int key, if (!driver->config_get) return SR_ERR_ARG; - if ((ret = driver->config_get(key, data, sdi)) == SR_OK) { + if ((ret = driver->config_get(key, data, sdi, ch, cg)) == SR_OK) { /* Got a floating reference from the driver. Sink it here, * caller will need to unref when done with it. */ g_variant_ref_sink(*data); @@ -300,7 +301,10 @@ SR_API int sr_config_get(const struct sr_dev_driver *driver, int key, * but this is not to be flagged as an error by the caller; merely * as an indication that it's not applicable. */ -SR_API int sr_config_set(const struct sr_dev_inst *sdi, int key, GVariant *data) +SR_API int sr_config_set(const struct sr_dev_inst *sdi, + const struct sr_channel *ch, + const struct sr_channel_group *cg, + int key, GVariant *data) { int ret; @@ -311,7 +315,7 @@ SR_API int sr_config_set(const struct sr_dev_inst *sdi, int key, GVariant *data) else if (!sdi->driver->config_set) ret = SR_ERR_ARG; else - ret = sdi->driver->config_set(key, data, sdi); + ret = sdi->driver->config_set(key, data, sdi, ch, cg); g_variant_unref(data); @@ -336,8 +340,10 @@ SR_API int sr_config_set(const struct sr_dev_inst *sdi, int key, GVariant *data) * but this is not to be flagged as an error by the caller; merely * as an indication that it's not applicable. */ -SR_API int sr_config_list(const struct sr_dev_driver *driver, int key, - GVariant **data, const struct sr_dev_inst *sdi) +SR_API int sr_config_list(const struct sr_dev_driver *driver, + const struct sr_dev_inst *sdi, + const struct sr_channel_group *cg, + int key, GVariant **data) { int ret; @@ -345,7 +351,7 @@ SR_API int sr_config_list(const struct sr_dev_driver *driver, int key, ret = SR_ERR; else if (!driver->config_list) ret = SR_ERR_ARG; - else if ((ret = driver->config_list(key, data, sdi)) == SR_OK) + else if ((ret = driver->config_list(key, data, sdi, cg)) == SR_OK) g_variant_ref_sink(*data); return ret; @@ -372,7 +378,33 @@ SR_API const struct sr_config_info *sr_config_info_get(int key) } /** - * Get information about an configuration key, by name. + * Get status about an acquisition + * + * @param sdi The device instance. + * @param status A pointer to a struct sr_capture_status. + * + * @return SR_OK upon success or SR_ERR in case of error. Note SR_ERR_ARG + * may be returned by the driver indicating it doesn't know that key, + * but this is not to be flagged as an error by the caller; merely + * as an indication that it's not applicable. + */ +SR_API int sr_status_get(const struct sr_dev_inst *sdi, + struct sr_status *status) +{ + int ret; + + if (!sdi->driver) + ret = SR_ERR; + else if (!sdi->driver->dev_status_get) + ret = SR_ERR_ARG; + else + ret = sdi->driver->dev_status_get(sdi, status); + + return ret; +} + +/** + * Get status about an acquisition. * * @param optname The configuration key. * @@ -381,14 +413,14 @@ SR_API const struct sr_config_info *sr_config_info_get(int key) */ SR_API const struct sr_config_info *sr_config_info_name_get(const char *optname) { - int i; + int i; - for (i = 0; sr_config_info_data[i].key; i++) { - if (!strcmp(sr_config_info_data[i].id, optname)) - return &sr_config_info_data[i]; - } + for (i = 0; sr_config_info_data[i].key; i++) { + if (!strcmp(sr_config_info_data[i].id, optname)) + return &sr_config_info_data[i]; + } - return NULL; + return NULL; } /* Unnecessary level of indirection follows. */ diff --git a/libsigrok4DSLogic/input/in_binary.c b/libsigrok4DSLogic/input/in_binary.c index 2d52c30..261156c 100644 --- a/libsigrok4DSLogic/input/in_binary.c +++ b/libsigrok4DSLogic/input/in_binary.c @@ -52,7 +52,7 @@ static int format_match(const char *filename) static int init(struct sr_input *in, const char *filename) { - struct sr_probe *probe; + struct sr_channel *probe; int num_probes, i; char name[SR_MAX_PROBENAME_LEN + 1]; char *param; @@ -90,9 +90,9 @@ static int init(struct sr_input *in, const char *filename) for (i = 0; i < num_probes; i++) { snprintf(name, SR_MAX_PROBENAME_LEN, "%d", i); /* TODO: Check return value. */ - if (!(probe = sr_probe_new(i, SR_PROBE_LOGIC, TRUE, name))) + if (!(probe = sr_channel_new(i, SR_CHANNEL_LOGIC, TRUE, name))) return SR_ERR; - in->sdi->probes = g_slist_append(in->sdi->probes, probe); + in->sdi->channels = g_slist_append(in->sdi->channels, probe); } return SR_OK; @@ -113,7 +113,7 @@ static int loadfile(struct sr_input *in, const char *filename) if ((fd = open(filename, O_RDONLY)) == -1) return SR_ERR; - num_probes = g_slist_length(in->sdi->probes); + num_probes = g_slist_length(in->sdi->channels); /* Send header packet to the session bus. */ std_session_send_df_header(in->sdi, LOG_PREFIX); diff --git a/libsigrok4DSLogic/input/in_vcd.c b/libsigrok4DSLogic/input/in_vcd.c index a143b1c..9db0c0b 100644 --- a/libsigrok4DSLogic/input/in_vcd.c +++ b/libsigrok4DSLogic/input/in_vcd.c @@ -318,7 +318,7 @@ static int format_match(const char *filename) static int init(struct sr_input *in, const char *filename) { - struct sr_probe *probe; + struct sr_channel *probe; int num_probes, i; char name[SR_MAX_PROBENAME_LEN + 1]; char *param; @@ -377,13 +377,13 @@ static int init(struct sr_input *in, const char *filename) for (i = 0; i < num_probes; i++) { snprintf(name, SR_MAX_PROBENAME_LEN, "%d", i); - if (!(probe = sr_probe_new(i, SR_PROBE_LOGIC, TRUE, name))) + if (!(probe = sr_channel_new(i, SR_CHANNEL_LOGIC, TRUE, name))) { release_context(ctx); return SR_ERR; } - in->sdi->probes = g_slist_append(in->sdi->probes, probe); + in->sdi->channels = g_slist_append(in->sdi->channels, probe); } return SR_OK; diff --git a/libsigrok4DSLogic/input/in_wav.c b/libsigrok4DSLogic/input/in_wav.c index 773b6b8..500dc09 100644 --- a/libsigrok4DSLogic/input/in_wav.c +++ b/libsigrok4DSLogic/input/in_wav.c @@ -92,7 +92,7 @@ static int format_match(const char *filename) static int init(struct sr_input *in, const char *filename) { - struct sr_probe *probe; + struct sr_channel *probe; struct context *ctx; char buf[40], probename[8]; int i; @@ -121,9 +121,9 @@ static int init(struct sr_input *in, const char *filename) for (i = 0; i < ctx->num_channels; i++) { snprintf(probename, 8, "CH%d", i + 1); - if (!(probe = sr_probe_new(0, SR_PROBE_ANALOG, TRUE, probename))) + if (!(probe = sr_channel_new(0, SR_CHANNEL_ANALOG, TRUE, probename))) return SR_ERR; - in->sdi->probes = g_slist_append(in->sdi->probes, probe); + in->sdi->channels = g_slist_append(in->sdi->channels, probe); } return SR_OK; @@ -185,7 +185,7 @@ static int loadfile(struct sr_input *in, const char *filename) } packet.type = SR_DF_ANALOG; packet.payload = &analog; - analog.probes = in->sdi->probes; + analog.probes = in->sdi->channels; analog.num_samples = chunk_samples; analog.mq = 0; analog.unit = 0; diff --git a/libsigrok4DSLogic/libsigrok-internal.h b/libsigrok4DSLogic/libsigrok-internal.h index aa8086e..14cc713 100644 --- a/libsigrok4DSLogic/libsigrok-internal.h +++ b/libsigrok4DSLogic/libsigrok-internal.h @@ -83,8 +83,8 @@ SR_PRIV int sr_err(const char *format, ...); /*--- device.c --------------------------------------------------------------*/ -SR_PRIV struct sr_probe *sr_probe_new(int index, int type, - gboolean enabled, const char *name); +SR_PRIV struct sr_channel *sr_channel_new(int index, int type, + gboolean enabled, const char *name); SR_PRIV void sr_dev_probes_free(struct sr_dev_inst *sdi); /* Generic device instances */ diff --git a/libsigrok4DSLogic/libsigrok.h b/libsigrok4DSLogic/libsigrok.h index da82e05..1b5fd8d 100644 --- a/libsigrok4DSLogic/libsigrok.h +++ b/libsigrok4DSLogic/libsigrok.h @@ -87,13 +87,6 @@ enum { */ }; -#define SR_MAX_PROBENAME_LEN 32 -#define DS_MAX_ANALOG_PROBES_NUM 8 -#define DS_MAX_DSO_PROBES_NUM 2 -#define TriggerStages 16 -#define TriggerProbes 16 -#define TriggerCountBits 16 - /* Handy little macros */ #define SR_HZ(n) (n) #define SR_KHZ(n) ((n) * (uint64_t)(1000ULL)) @@ -107,6 +100,19 @@ enum { #define SR_MB(n) ((n) * (uint64_t)(1048576ULL)) #define SR_GB(n) ((n) * (uint64_t)(1073741824ULL)) +#define SR_MAX_PROBENAME_LEN 32 +#define DS_MAX_ANALOG_PROBES_NUM 8 +#define DS_MAX_DSO_PROBES_NUM 2 +#define TriggerStages 16 +#define TriggerProbes 16 +#define TriggerCountBits 16 + +#define DS_MAX_DSO_SAMPLERATE SR_MHZ(200) +#define DS_MAX_DSO_DEPTH SR_KB(32) + +#define DS_CONF_DSO_HDIVS 10 +#define DS_CONF_DSO_VDIVS 10 + /** libsigrok loglevels. */ enum { SR_LOG_NONE = 0, /**< Output no messages at all. */ @@ -568,9 +574,9 @@ struct sr_output_format { }; enum { - SR_PROBE_LOGIC = 10000, - SR_PROBE_DSO, - SR_PROBE_ANALOG, + SR_CHANNEL_LOGIC = 10000, + SR_CHANNEL_DSO, + SR_CHANNEL_ANALOG, }; enum { @@ -579,19 +585,26 @@ enum { ANALOG = 2, }; -static const char *mode_strings[] = { - "Logic Analyzer", - "Oscilloscope", - "Data Acquisition", -}; - -struct sr_probe { - /* The index field will go: use g_slist_length(sdi->probes) instead. */ +struct sr_channel { + /* The index field will go: use g_slist_length(sdi->channels) instead. */ int index; int type; gboolean enabled; char *name; char *trigger; + uint64_t vdiv; + gboolean coupling; + uint8_t trig_value; +}; + +/** Structure for groups of channels that have common properties. */ +struct sr_channel_group { + /** Name of the channel group. */ + char *name; + /** List of sr_channel structs of the channels belonging to this group. */ + GSList *channels; + /** Private data for driver use. */ + void *priv; }; struct sr_config { @@ -607,6 +620,14 @@ struct sr_config_info { char *description; }; +struct sr_status { + uint8_t trig_hit; + uint8_t captured_cnt3; + uint8_t captured_cnt2; + uint8_t captured_cnt1; + uint8_t captured_cnt0; +}; + enum { /*--- Device classes ------------------------------------------------*/ @@ -710,17 +731,14 @@ enum { /** Zero */ SR_CONF_ZERO, - /** Volts/div. */ - SR_CONF_VDIV0, - SR_CONF_VDIV1, + /** Volts/div for dso channel. */ + SR_CONF_VDIV, - /** Coupling. */ - SR_CONF_COUPLING0, - SR_CONF_COUPLING1, + /** Coupling for dso channel. */ + SR_CONF_COUPLING, - /** Channel enable*/ - SR_CONF_EN_CH0, - SR_CONF_EN_CH1, + /** Channel enable for dso channel. */ + SR_CONF_EN_CH, /** Trigger types. */ SR_CONF_TRIGGER_TYPE, @@ -737,9 +755,15 @@ enum { /** clock type (internal/external) */ SR_CONF_CLOCK_TYPE, + /** clock edge (posedge/negedge) */ + SR_CONF_CLOCK_EDGE, + /** Device operation mode */ SR_CONF_OPERATION_MODE, + /** Device sample threshold */ + SR_CONF_THRESHOLD, + /*--- Special stuff -------------------------------------------------*/ /** Scan options supported by the driver. */ @@ -794,17 +818,30 @@ enum { }; struct sr_dev_inst { - struct sr_dev_driver *driver; - int index; - int status; - int inst_type; + /** Device driver. */ + struct sr_dev_driver *driver; + /** Index of device in driver. */ + int index; + /** Device instance status. SR_ST_NOT_FOUND, etc. */ + int status; + /** Device instance type. SR_INST_USB, etc. */ + int inst_type; + /** Device mode. LA/DAQ/OSC, etc. */ int mode; - char *vendor; - char *model; - char *version; - GSList *probes; - void *conn; - void *priv; + /** Device vendor. */ + char *vendor; + /** Device model. */ + char *model; + /** Device version. */ + char *version; + /** List of channels. */ + GSList *channels; + /** List of sr_channel_group structs */ + GSList *channel_groups; + /** Device instance connection data (used?) */ + void *conn; + /** Device instance private data (used?) */ + void *priv; }; /** Types of device instances (sr_dev_inst). */ @@ -841,15 +878,29 @@ enum { SR_OP_LOOPBACK_TEST = 3, }; -static const char *opmodes[] = { - "Normal", - "Internal Test", - "External Test", - "DRAM Loopback Test", +/** Device threshold level. */ +enum { + /** 1.8/2.5/3.3 level */ + SR_TH_3V3 = 0, + /** 5.0 level */ + SR_TH_5V0 = 1, +}; + +/** Device input filter. */ +enum { + /** None */ + SR_FILTER_NONE = 0, + /** One clock cycle */ + SR_FILTER_1T = 1, }; extern char config_path[256]; +struct sr_dev_mode { + char *name; + int mode; +}; + struct sr_dev_driver { /* Driver-specific */ char *name; @@ -859,18 +910,27 @@ struct sr_dev_driver { int (*cleanup) (void); GSList *(*scan) (GSList *options); GSList *(*dev_list) (void); - int (*dev_clear) (void); - int (*config_get) (int id, GVariant **data, - const struct sr_dev_inst *sdi); - int (*config_set) (int id, GVariant *data, - const struct sr_dev_inst *sdi); - int (*config_list) (int info_id, GVariant **data, - const struct sr_dev_inst *sdi); + GSList *(*dev_mode_list) (void); + int (*dev_clear) (void); + + int (*config_get) (int id, GVariant **data, + const struct sr_dev_inst *sdi, + const struct sr_channel *ch, + const struct sr_channel_group *cg); + int (*config_set) (int id, GVariant *data, + const struct sr_dev_inst *sdi, + const struct sr_channel *ch, + const struct sr_channel_group *cg); + int (*config_list) (int info_id, GVariant **data, + const struct sr_dev_inst *sdi, + const struct sr_channel_group *cg); /* Device-specific */ int (*dev_open) (struct sr_dev_inst *sdi); int (*dev_close) (struct sr_dev_inst *sdi); int (*dev_test) (struct sr_dev_inst *sdi); + int (*dev_status_get) (struct sr_dev_inst *sdi, + struct sr_status *status); int (*dev_acquisition_start) (const struct sr_dev_inst *sdi, void *cb_data); int (*dev_acquisition_stop) (struct sr_dev_inst *sdi, diff --git a/libsigrok4DSLogic/output/out_analog.c b/libsigrok4DSLogic/output/out_analog.c index 684f882..e1efdad 100644 --- a/libsigrok4DSLogic/output/out_analog.c +++ b/libsigrok4DSLogic/output/out_analog.c @@ -41,7 +41,7 @@ struct context { static int init(struct sr_output *o) { struct context *ctx; - struct sr_probe *probe; + struct sr_channel *probe; GSList *l; sr_spew("Initializing output module."); @@ -57,7 +57,7 @@ static int init(struct sr_output *o) /* Get the number of probes and their names. */ ctx->probelist = g_ptr_array_new(); - for (l = o->sdi->probes; l; l = l->next) { + for (l = o->sdi->channels; l; l = l->next) { probe = l->data; if (!probe || !probe->enabled) continue; @@ -190,7 +190,7 @@ static int receive(struct sr_output *o, const struct sr_dev_inst *sdi, const struct sr_datafeed_packet *packet, GString **out) { const struct sr_datafeed_analog *analog; - struct sr_probe *probe; + struct sr_channel *probe; GSList *l; const float *fdata; int i, p; diff --git a/libsigrok4DSLogic/output/out_csv.c b/libsigrok4DSLogic/output/out_csv.c index 0a7c504..6eec47e 100644 --- a/libsigrok4DSLogic/output/out_csv.c +++ b/libsigrok4DSLogic/output/out_csv.c @@ -57,7 +57,7 @@ struct context { static int init(struct sr_output *o) { struct context *ctx; - struct sr_probe *probe; + struct sr_channel *probe; GSList *l; GVariant *gvar; int num_probes; @@ -81,7 +81,7 @@ static int init(struct sr_output *o) o->internal = ctx; /* Get the number of probes, and the unitsize. */ - for (l = o->sdi->probes; l; l = l->next) { + for (l = o->sdi->channels; l; l = l->next) { probe = l->data; if (probe->enabled) ctx->num_enabled_probes++; @@ -89,10 +89,10 @@ static int init(struct sr_output *o) ctx->unitsize = (ctx->num_enabled_probes + 7) / 8; - num_probes = g_slist_length(o->sdi->probes); + num_probes = g_slist_length(o->sdi->channels); - if (sr_config_get(o->sdi->driver, SR_CONF_SAMPLERATE, &gvar, - o->sdi) == SR_OK) { + if (sr_config_get(o->sdi->driver, o->sdi, NULL, NULL, + SR_CONF_SAMPLERATE, &gvar) == SR_OK) { ctx->samplerate = g_variant_get_uint64(gvar); g_variant_unref(gvar); } else @@ -112,7 +112,7 @@ static int init(struct sr_output *o) /* Columns / channels */ g_string_append_printf(ctx->header, "; Channels (%d/%d): ", ctx->num_enabled_probes, num_probes); - for (l = o->sdi->probes; l; l = l->next) { + for (l = o->sdi->channels; l; l = l->next) { probe = l->data; if (probe->enabled) g_string_append_printf(ctx->header, "%s, ", probe->name); diff --git a/libsigrok4DSLogic/output/out_vcd.c b/libsigrok4DSLogic/output/out_vcd.c index 04d7fcb..7c00acb 100644 --- a/libsigrok4DSLogic/output/out_vcd.c +++ b/libsigrok4DSLogic/output/out_vcd.c @@ -51,7 +51,7 @@ $comment\n Acquisition with %d/%d probes at %s\n$end\n"; static int init(struct sr_output *o) { struct context *ctx; - struct sr_probe *probe; + struct sr_channel *probe; GSList *l; GVariant *gvar; int num_probes, i; @@ -67,7 +67,7 @@ static int init(struct sr_output *o) ctx->num_enabled_probes = 0; ctx->probeindices = g_array_new(FALSE, FALSE, sizeof(int)); - for (l = o->sdi->probes; l; l = l->next) { + for (l = o->sdi->channels; l; l = l->next) { probe = l->data; if (!probe->enabled) continue; @@ -82,7 +82,7 @@ static int init(struct sr_output *o) ctx->unitsize = (ctx->num_enabled_probes + 7) / 8; ctx->header = g_string_sized_new(512); - num_probes = g_slist_length(o->sdi->probes); + num_probes = g_slist_length(o->sdi->channels); /* timestamp */ t = time(NULL); @@ -95,8 +95,8 @@ static int init(struct sr_output *o) g_string_append_printf(ctx->header, "$version %s %s $end\n", PACKAGE, PACKAGE_VERSION); - if (sr_config_get(o->sdi->driver, SR_CONF_SAMPLERATE, &gvar, - o->sdi) == SR_OK) { + if (sr_config_get(o->sdi->driver, o->sdi, NULL, NULL, + SR_CONF_SAMPLERATE, &gvar) == SR_OK) { ctx->samplerate = g_variant_get_uint64(gvar); g_variant_unref(gvar); if (!((samplerate_s = sr_samplerate_string(ctx->samplerate)))) { @@ -129,7 +129,7 @@ static int init(struct sr_output *o) g_string_append_printf(ctx->header, "$scope module %s $end\n", PACKAGE); /* Wires / channels */ - for (i = 0, l = o->sdi->probes; l; l = l->next, i++) { + for (i = 0, l = o->sdi->channels; l; l = l->next, i++) { probe = l->data; if (!probe->enabled) continue; diff --git a/libsigrok4DSLogic/output/text/text.c b/libsigrok4DSLogic/output/text/text.c index a2b8b20..99d20df 100644 --- a/libsigrok4DSLogic/output/text/text.c +++ b/libsigrok4DSLogic/output/text/text.c @@ -81,7 +81,7 @@ SR_PRIV void flush_linebufs(struct context *ctx, uint8_t *outbuf) SR_PRIV int init(struct sr_output *o, int default_spl, enum outputmode mode) { struct context *ctx; - struct sr_probe *probe; + struct sr_channel *probe; GSList *l; GVariant *gvar; uint64_t samplerate; @@ -97,7 +97,7 @@ SR_PRIV int init(struct sr_output *o, int default_spl, enum outputmode mode) ctx->num_enabled_probes = 0; ctx->probenames = NULL; - for (l = o->sdi->probes; l; l = l->next) { + for (l = o->sdi->channels; l; l = l->next) { probe = l->data; if (!probe->enabled) continue; @@ -128,9 +128,9 @@ SR_PRIV int init(struct sr_output *o, int default_spl, enum outputmode mode) } snprintf(ctx->header, 511, "%s\n", PACKAGE_STRING); - num_probes = g_slist_length(o->sdi->probes); - if (sr_config_get(o->sdi->driver, SR_CONF_SAMPLERATE, &gvar, - o->sdi) == SR_OK) { + num_probes = g_slist_length(o->sdi->channels); + if (sr_config_get(o->sdi->driver, o->sdi, NULL, NULL, + SR_CONF_SAMPLERATE, &gvar) == SR_OK) { samplerate = g_variant_get_uint64(gvar); g_variant_unref(gvar); if (!(samplerate_s = sr_samplerate_string(samplerate))) { diff --git a/libsigrok4DSLogic/proto.h b/libsigrok4DSLogic/proto.h index 93b9b88..6e87d0e 100644 --- a/libsigrok4DSLogic/proto.h +++ b/libsigrok4DSLogic/proto.h @@ -53,6 +53,7 @@ SR_API int sr_dev_trigger_set(const struct sr_dev_inst *sdi, int probenum, const char *trigger); SR_API gboolean sr_dev_has_option(const struct sr_dev_inst *sdi, int key); SR_API GSList *sr_dev_list(const struct sr_dev_driver *driver); +SR_API GSList *sr_dev_mode_list(const struct sr_dev_driver *driver); SR_API int sr_dev_clear(const struct sr_dev_driver *driver); SR_API int sr_dev_open(struct sr_dev_inst *sdi); SR_API int sr_dev_close(struct sr_dev_inst *sdi); @@ -70,14 +71,22 @@ SR_API struct sr_dev_driver **sr_driver_list(void); SR_API int sr_driver_init(struct sr_context *ctx, struct sr_dev_driver *driver); SR_API GSList *sr_driver_scan(struct sr_dev_driver *driver, GSList *options); -SR_API int sr_config_get(const struct sr_dev_driver *driver, int key, - GVariant **data, const struct sr_dev_inst *sdi); -SR_API int sr_config_set(const struct sr_dev_inst *sdi, int key, - GVariant *data); -SR_API int sr_config_list(const struct sr_dev_driver *driver, int key, - GVariant **data, const struct sr_dev_inst *sdi); +SR_API int sr_config_get(const struct sr_dev_driver *driver, + const struct sr_dev_inst *sdi, + const struct sr_channel *ch, + const struct sr_channel_group *cg, + int key, GVariant **data); +SR_API int sr_config_set(const struct sr_dev_inst *sdi, + const struct sr_channel *ch, + const struct sr_channel_group *cg, + int key, GVariant *data); +SR_API int sr_config_list(const struct sr_dev_driver *driver, + const struct sr_dev_inst *sdi, + const struct sr_channel_group *cg, + int key, GVariant **data); SR_API const struct sr_config_info *sr_config_info_get(int key); SR_API const struct sr_config_info *sr_config_info_name_get(const char *optname); +SR_API int sr_status_get(const struct sr_dev_inst *sdi, struct sr_status *status); /*--- session.c -------------------------------------------------------------*/ @@ -90,6 +99,7 @@ SR_API struct sr_session *sr_session_new(void); SR_API int sr_session_destroy(void); SR_API int sr_session_dev_remove_all(void); SR_API int sr_session_dev_add(const struct sr_dev_inst *sdi); +SR_API int sr_session_dev_list(GSList **devlist); /* Datafeed setup */ SR_API int sr_session_datafeed_callback_remove_all(void); @@ -102,6 +112,10 @@ SR_API int sr_session_run(void); SR_API int sr_session_stop(void); SR_API int sr_session_save(const char *filename, const struct sr_dev_inst *sdi, unsigned char *buf, int unitsize, int units); +SR_API int sr_session_save_init(const char *filename, uint64_t samplerate, + char **channels); +SR_API int sr_session_append(const char *filename, unsigned char *buf, + int unitsize, int units); SR_API int sr_session_source_add(int fd, int events, int timeout, sr_receive_data_callback_t cb, const struct sr_dev_inst *sdi); SR_API int sr_session_source_add_pollfd(GPollFD *pollfd, int timeout, @@ -125,6 +139,7 @@ SR_API struct sr_output_format **sr_output_list(void); SR_API char *sr_si_string_u64(uint64_t x, const char *unit); SR_API char *sr_iec_string_u64(uint64_t x, const char *unit); SR_API char *sr_samplerate_string(uint64_t samplerate); +SR_API char *sr_samplecount_string(uint64_t samplecount); SR_API char *sr_period_string(uint64_t frequency); SR_API char *sr_voltage_string(uint64_t v_p, uint64_t v_q); SR_API char **sr_parse_triggerstring(const struct sr_dev_inst *sdi, diff --git a/libsigrok4DSLogic/session.c b/libsigrok4DSLogic/session.c index 13f39a4..3a44238 100644 --- a/libsigrok4DSLogic/session.c +++ b/libsigrok4DSLogic/session.c @@ -129,6 +129,34 @@ SR_API int sr_session_destroy(void) return SR_OK; } +/** + * List all device instances attached to the current session. + * + * @param devlist A pointer where the device instance list will be + * stored on return. If no devices are in the session, + * this will be NULL. Each element in the list points + * to a struct sr_dev_inst *. + * The list must be freed by the caller, but not the + * elements pointed to. + * + * @retval SR_OK Success. + * @retval SR_ERR Invalid argument. + * + * @since 0.3.0 + */ +SR_API int sr_session_dev_list(GSList **devlist) +{ + + *devlist = NULL; + + if (!session) + return SR_ERR; + + *devlist = g_slist_copy(session->devs); + + return SR_OK; +} + /** * Remove all the devices from the current session. * diff --git a/libsigrok4DSLogic/session_driver.c b/libsigrok4DSLogic/session_driver.c index b040c07..5963d42 100644 --- a/libsigrok4DSLogic/session_driver.c +++ b/libsigrok4DSLogic/session_driver.c @@ -40,6 +40,9 @@ #define CHUNKSIZE (512 * 1024) /** @endcond */ +static uint64_t samplerates[1]; +static uint64_t samplecounts[1]; + struct session_vdev { char *sessionfile; char *capturefile; @@ -100,9 +103,9 @@ static int receive_data(int fd, int revents, const struct sr_dev_inst *cb_sdi) } else { /* done with this capture file */ zip_fclose(vdev->capfile); - g_free(vdev->capturefile); - g_free(vdev); - sdi->priv = NULL; + //g_free(vdev->capturefile); + //g_free(vdev); + //sdi->priv = NULL; } } @@ -116,16 +119,16 @@ static int receive_data(int fd, int revents, const struct sr_dev_inst *cb_sdi) } /* driver callbacks */ -static int hw_cleanup(void); +static int dev_clear(void); -static int hw_init(struct sr_context *sr_ctx) +static int init(struct sr_context *sr_ctx) { (void)sr_ctx; return SR_OK; } -static int hw_cleanup(void) +static int dev_clear(void) { GSList *l; @@ -137,7 +140,7 @@ static int hw_cleanup(void) return SR_OK; } -static int hw_dev_open(struct sr_dev_inst *sdi) +static int dev_open(struct sr_dev_inst *sdi) { if (!(sdi->priv = g_try_malloc0(sizeof(struct session_vdev)))) { sr_err("%s: sdi->priv malloc failed", __func__); @@ -149,6 +152,18 @@ static int hw_dev_open(struct sr_dev_inst *sdi) return SR_OK; } +static int dev_close(struct sr_dev_inst *sdi) +{ + const struct session_vdev *const vdev = sdi->priv; + g_free(vdev->sessionfile); + g_free(vdev->capturefile); + + g_free(sdi->priv); + sdi->priv = NULL; + + return SR_OK; +} + static int config_get(int id, GVariant **data, const struct sr_dev_inst *sdi) { struct session_vdev *vdev; @@ -184,6 +199,7 @@ static int config_set(int id, GVariant *data, const struct sr_dev_inst *sdi) switch (id) { case SR_CONF_SAMPLERATE: vdev->samplerate = g_variant_get_uint64(data); + samplerates[0] = vdev->samplerate; sr_info("Setting samplerate to %" PRIu64 ".", vdev->samplerate); break; case SR_CONF_SESSIONFILE: @@ -199,6 +215,8 @@ static int config_set(int id, GVariant *data, const struct sr_dev_inst *sdi) break; case SR_CONF_LIMIT_SAMPLES: vdev->total_samples = g_variant_get_uint64(data); + samplecounts[0] = vdev->total_samples; + sr_info("Setting limit samples to %" PRIu64 ".", vdev->total_samples); break; case SR_CONF_CAPTURE_NUM_PROBES: vdev->num_probes = g_variant_get_uint64(data); @@ -213,6 +231,8 @@ static int config_set(int id, GVariant *data, const struct sr_dev_inst *sdi) static int config_list(int key, GVariant **data, const struct sr_dev_inst *sdi) { + GVariant *gvar; + GVariantBuilder gvb; (void)sdi; @@ -223,6 +243,22 @@ static int config_list(int key, GVariant **data, const struct sr_dev_inst *sdi) *data = g_variant_new_from_data(G_VARIANT_TYPE("ai"), hwcaps, ARRAY_SIZE(hwcaps)*sizeof(int32_t), TRUE, NULL, NULL); break; + case SR_CONF_SAMPLERATE: + g_variant_builder_init(&gvb, G_VARIANT_TYPE("a{sv}")); +// gvar = g_variant_new_fixed_array(G_VARIANT_TYPE("t"), samplerates, +// ARRAY_SIZE(samplerates), sizeof(uint64_t)); + gvar = g_variant_new_from_data(G_VARIANT_TYPE("at"), + samplerates, ARRAY_SIZE(samplerates)*sizeof(uint64_t), TRUE, NULL, NULL); + g_variant_builder_add(&gvb, "{sv}", "samplerates", gvar); + *data = g_variant_builder_end(&gvb); + break; + case SR_CONF_LIMIT_SAMPLES: + g_variant_builder_init(&gvb, G_VARIANT_TYPE("a{sv}")); + gvar = g_variant_new_from_data(G_VARIANT_TYPE("at"), + samplecounts, ARRAY_SIZE(samplecounts)*sizeof(uint64_t), TRUE, NULL, NULL); + g_variant_builder_add(&gvb, "{sv}", "samplecounts", gvar); + *data = g_variant_builder_end(&gvb); + break; default: return SR_ERR_ARG; } @@ -230,7 +266,7 @@ static int config_list(int key, GVariant **data, const struct sr_dev_inst *sdi) return SR_OK; } -static int hw_dev_acquisition_start(const struct sr_dev_inst *sdi, +static int dev_acquisition_start(const struct sr_dev_inst *sdi, void *cb_data) { struct zip_stat zs; @@ -271,16 +307,23 @@ static int hw_dev_acquisition_start(const struct sr_dev_inst *sdi, /** @private */ SR_PRIV struct sr_dev_driver session_driver = { - .name = "virtual-session", - .longname = "Session-emulating driver", - .api_version = 1, - .init = hw_init, - .cleanup = hw_cleanup, - .config_get = config_get, - .config_set = config_set, - .config_list = config_list, - .dev_open = hw_dev_open, - .dev_close = NULL, - .dev_acquisition_start = hw_dev_acquisition_start, - .dev_acquisition_stop = NULL, + .name = "virtual-session", + .longname = "Session-emulating driver", + .api_version = 1, + .init = init, + .cleanup = dev_clear, + .scan = NULL, + .dev_list = NULL, + .dev_mode_list = NULL, + .dev_clear = dev_clear, + .config_get = config_get, + .config_set = config_set, + .config_list = config_list, + .dev_open = dev_open, + .dev_close = dev_close, + .dev_test = NULL, + .dev_status_get = NULL, + .dev_acquisition_start = dev_acquisition_start, + .dev_acquisition_stop = NULL, + .priv = NULL, }; diff --git a/libsigrok4DSLogic/session_file.c b/libsigrok4DSLogic/session_file.c index 43ba01c..f5592e4 100644 --- a/libsigrok4DSLogic/session_file.c +++ b/libsigrok4DSLogic/session_file.c @@ -21,6 +21,9 @@ #include #include #include +#include +#include +#include #include #include #include "config.h" /* Needed for PACKAGE_VERSION and others. */ @@ -51,6 +54,55 @@ extern struct sr_session *session; extern SR_PRIV struct sr_dev_driver session_driver; +/** @private */ +SR_PRIV int sr_sessionfile_check(const char *filename) +{ + struct stat st; + struct zip *archive; + struct zip_file *zf; + struct zip_stat zs; + int version, ret; + char s[11]; + + if (!filename) + return SR_ERR_ARG; + + if (stat(filename, &st) == -1) { + sr_err("Couldn't stat %s: %s", filename, strerror(errno)); + return SR_ERR; + } + + if (!(archive = zip_open(filename, 0, &ret))) + /* No logging: this can be used just to check if it's + * a sigrok session file or not. */ + return SR_ERR; + + /* check "version" */ + version = 0; + if (!(zf = zip_fopen(archive, "version", 0))) { + sr_dbg("Not a sigrok session file: no version found."); + return SR_ERR; + } + if ((ret = zip_fread(zf, s, 10)) == -1) + return SR_ERR; + zip_fclose(zf); + s[ret] = 0; + version = strtoull(s, NULL, 10); + if (version > 2) { + sr_dbg("Cannot handle sigrok session file version %d.", version); + return SR_ERR; + } + sr_spew("Detected sigrok session file version %d.", version); + + /* read "metadata" */ + if (zip_stat(archive, "metadata", 0, &zs) == -1) { + sr_dbg("Not a valid sigrok session file."); + return SR_ERR; + } + + return SR_OK; +} + /** * Load the session from the specified filename. * @@ -68,7 +120,7 @@ SR_API int sr_session_load(const char *filename) struct zip_file *zf; struct zip_stat zs; struct sr_dev_inst *sdi; - struct sr_probe *probe; + struct sr_channel *probe; int ret, probenum, devcnt, version, i, j; uint64_t tmp_u64, total_probes, enabled_probes, p; char **sections, **keys, *metafile, *val, s[11]; @@ -130,32 +182,32 @@ SR_API int sr_session_load(const char *filename) sr_dev_open(sdi); sr_session_dev_add(sdi); sdi->driver->config_set(SR_CONF_SESSIONFILE, - g_variant_new_string(filename), sdi); + g_variant_new_string(filename), sdi, NULL, NULL); sdi->driver->config_set(SR_CONF_CAPTUREFILE, - g_variant_new_string(val), sdi); + g_variant_new_string(val), sdi, NULL, NULL); g_ptr_array_add(capturefiles, val); } else if (!strcmp(keys[j], "samplerate")) { sr_parse_sizestring(val, &tmp_u64); sdi->driver->config_set(SR_CONF_SAMPLERATE, - g_variant_new_uint64(tmp_u64), sdi); + g_variant_new_uint64(tmp_u64), sdi, NULL, NULL); } else if (!strcmp(keys[j], "unitsize")) { tmp_u64 = strtoull(val, NULL, 10); sdi->driver->config_set(SR_CONF_CAPTURE_UNITSIZE, - g_variant_new_uint64(tmp_u64), sdi); + g_variant_new_uint64(tmp_u64), sdi, NULL, NULL); } else if (!strcmp(keys[j], "total samples")) { tmp_u64 = strtoull(val, NULL, 10); sdi->driver->config_set(SR_CONF_LIMIT_SAMPLES, - g_variant_new_uint64(tmp_u64), sdi); + g_variant_new_uint64(tmp_u64), sdi, NULL, NULL); } else if (!strcmp(keys[j], "total probes")) { total_probes = strtoull(val, NULL, 10); sdi->driver->config_set(SR_CONF_CAPTURE_NUM_PROBES, - g_variant_new_uint64(total_probes), sdi); + g_variant_new_uint64(total_probes), sdi, NULL, NULL); for (p = 0; p < total_probes; p++) { snprintf(probename, SR_MAX_PROBENAME_LEN, "%" PRIu64, p); - if (!(probe = sr_probe_new(p, SR_PROBE_LOGIC, TRUE, + if (!(probe = sr_channel_new(p, SR_CHANNEL_LOGIC, TRUE, probename))) return SR_ERR; - sdi->probes = g_slist_append(sdi->probes, probe); + sdi->channels = g_slist_append(sdi->channels, probe); } } else if (!strncmp(keys[j], "probe", 5)) { if (!sdi) @@ -202,7 +254,7 @@ SR_API int sr_session_save(const char *filename, const struct sr_dev_inst *sdi, GSList *l; GVariant *gvar; FILE *meta; - struct sr_probe *probe; + struct sr_channel *probe; struct zip *zipfile; struct zip_source *versrc, *metasrc, *logicsrc; int tmpfile, ret, probecnt; @@ -237,10 +289,10 @@ SR_API int sr_session_save(const char *filename, const struct sr_dev_inst *sdi, fprintf(meta, "capturefile = data\n"); fprintf(meta, "unitsize = %d\n", unitsize); fprintf(meta, "total samples = %d\n", units); - fprintf(meta, "total probes = %d\n", g_slist_length(sdi->probes)); + fprintf(meta, "total probes = %d\n", g_slist_length(sdi->channels)); if (sr_dev_has_option(sdi, SR_CONF_SAMPLERATE)) { - if (sr_config_get(sdi->driver, SR_CONF_SAMPLERATE, - &gvar, sdi) == SR_OK) { + if (sr_config_get(sdi->driver, sdi, NULL, NULL, SR_CONF_SAMPLERATE, + &gvar) == SR_OK) { samplerate = g_variant_get_uint64(gvar); s = sr_samplerate_string(samplerate); fprintf(meta, "samplerate = %s\n", s); @@ -249,7 +301,7 @@ SR_API int sr_session_save(const char *filename, const struct sr_dev_inst *sdi, } } probecnt = 1; - for (l = sdi->probes; l; l = l->next) { + for (l = sdi->channels; l; l = l->next) { probe = l->data; if (probe->enabled) { if (probe->name) @@ -283,4 +335,229 @@ SR_API int sr_session_save(const char *filename, const struct sr_dev_inst *sdi, return SR_OK; } +/** + * Initialize a saved session file. + * + * @param filename The name of the filename to save the current session as. + * Must not be NULL. + * @param samplerate The samplerate to store for this session. + * @param channels A NULL-terminated array of strings containing the names + * of all the channels active in this session. + * + * @retval SR_OK Success + * @retval SR_ERR_ARG Invalid arguments + * @retval SR_ERR Other errors + * + * @since 0.3.0 + */ +SR_API int sr_session_save_init(const char *filename, uint64_t samplerate, + char **channels) +{ + FILE *meta; + struct zip *zipfile; + struct zip_source *versrc, *metasrc; + int tmpfile, cnt, ret, i; + char version[1], metafile[32], *s; + + if (!filename) { + sr_err("%s: filename was NULL", __func__); + return SR_ERR_ARG; + } + + /* Quietly delete it first, libzip wants replace ops otherwise. */ + unlink(filename); + if (!(zipfile = zip_open(filename, ZIP_CREATE, &ret))) + return SR_ERR; + + /* "version" */ + version[0] = '2'; + if (!(versrc = zip_source_buffer(zipfile, version, 1, 0))) + return SR_ERR; + if (zip_add(zipfile, "version", versrc) == -1) { + sr_info("error saving version into zipfile: %s", + zip_strerror(zipfile)); + return SR_ERR; + } + + /* init "metadata" */ + strcpy(metafile, "sigrok-meta-XXXXXX"); + if ((tmpfile = g_mkstemp(metafile)) == -1) + return SR_ERR; + close(tmpfile); + meta = g_fopen(metafile, "wb"); + fprintf(meta, "[global]\n"); + fprintf(meta, "sigrok version = %s\n", PACKAGE_VERSION); + + /* metadata */ + fprintf(meta, "[device 1]\n"); + + /* metadata */ + fprintf(meta, "capturefile = logic-1\n"); + cnt = 0; + for (i = 0; channels[i]; i++) + cnt++; + fprintf(meta, "total probes = %d\n", cnt); + s = sr_samplerate_string(samplerate); + fprintf(meta, "samplerate = %s\n", s); + g_free(s); + + for (i = 0; channels[i]; i++) + fprintf(meta, "probe%d = %s\n", i + 1, channels[i]); + + fclose(meta); + + if (!(metasrc = zip_source_file(zipfile, metafile, 0, -1))) { + unlink(metafile); + return SR_ERR; + } + if (zip_add(zipfile, "metadata", metasrc) == -1) { + unlink(metafile); + return SR_ERR; + } + + if ((ret = zip_close(zipfile)) == -1) { + sr_info("error saving zipfile: %s", zip_strerror(zipfile)); + unlink(metafile); + return SR_ERR; + } + + unlink(metafile); + + return SR_OK; +} + +/** + * Append data to an existing session file. + * + * The session file must have been created with sr_session_save_init() + * or sr_session_save() beforehand. + * + * @param filename The name of the filename to append to. Must not be NULL. + * @param buf The data to be appended. + * @param unitsize The number of bytes per sample. + * @param units The number of samples. + * + * @retval SR_OK Success + * @retval SR_ERR_ARG Invalid arguments + * @retval SR_ERR Other errors + * + * @since 0.3.0 + */ +SR_API int sr_session_append(const char *filename, unsigned char *buf, + int unitsize, int units) +{ + struct zip *archive; + struct zip_source *logicsrc; + zip_int64_t num_files; + struct zip_file *zf; + struct zip_stat zs; + struct zip_source *metasrc; + GKeyFile *kf; + GError *error; + gsize len; + int chunk_num, next_chunk_num, tmpfile, ret, i; + const char *entry_name; + char *metafile, tmpname[32], chunkname[16]; + + if ((ret = sr_sessionfile_check(filename)) != SR_OK) + return ret; + + if (!(archive = zip_open(filename, 0, &ret))) + return SR_ERR; + + if (zip_stat(archive, "metadata", 0, &zs) == -1) + return SR_ERR; + + metafile = g_malloc(zs.size); + zf = zip_fopen_index(archive, zs.index, 0); + zip_fread(zf, metafile, zs.size); + zip_fclose(zf); + + /* + * If the file was only initialized but doesn't yet have any + * data it in, it won't have a unitsize field in metadata yet. + */ + error = NULL; + kf = g_key_file_new(); + if (!g_key_file_load_from_data(kf, metafile, zs.size, 0, &error)) { + sr_err("Failed to parse metadata: %s.", error->message); + return SR_ERR; + } + g_free(metafile); + tmpname[0] = '\0'; + if (!g_key_file_has_key(kf, "device 1", "unitsize", &error)) { + if (error && error->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND) { + sr_err("Failed to check unitsize key: %s", error ? error->message : "?"); + return SR_ERR; + } + /* Add unitsize field. */ + g_key_file_set_integer(kf, "device 1", "unitsize", unitsize); + metafile = g_key_file_to_data(kf, &len, &error); + strcpy(tmpname, "sigrok-meta-XXXXXX"); + if ((tmpfile = g_mkstemp(tmpname)) == -1) + return SR_ERR; + if (write(tmpfile, metafile, len) < 0) { + sr_dbg("Failed to create new metadata: %s", strerror(errno)); + g_free(metafile); + unlink(tmpname); + return SR_ERR; + } + close(tmpfile); + if (!(metasrc = zip_source_file(archive, tmpname, 0, -1))) { + sr_err("Failed to create zip source for metadata."); + g_free(metafile); + unlink(tmpname); + return SR_ERR; + } + if (zip_replace(archive, zs.index, metasrc) == -1) { + sr_err("Failed to replace metadata file."); + g_free(metafile); + unlink(tmpname); + return SR_ERR; + } + g_free(metafile); + } + g_key_file_free(kf); + + next_chunk_num = 1; + num_files = zip_get_num_entries(archive, 0); + for (i = 0; i < num_files; i++) { + entry_name = zip_get_name(archive, i, 0); + if (strncmp(entry_name, "logic-1", 7)) + continue; + if (strlen(entry_name) == 7) { + /* This file has no extra chunks, just a single "logic-1". + * Rename it to "logic-1-1" * and continue with chunk 2. */ + if (zip_rename(archive, i, "logic-1-1") == -1) { + sr_err("Failed to rename 'logic-1' to 'logic-1-1'."); + unlink(tmpname); + return SR_ERR; + } + next_chunk_num = 2; + break; + } else if (strlen(entry_name) > 8 && entry_name[7] == '-') { + chunk_num = strtoull(entry_name + 8, NULL, 10); + if (chunk_num >= next_chunk_num) + next_chunk_num = chunk_num + 1; + } + } + snprintf(chunkname, 15, "logic-1-%d", next_chunk_num); + if (!(logicsrc = zip_source_buffer(archive, buf, units * unitsize, FALSE))) { + unlink(tmpname); + return SR_ERR; + } + if (zip_add(archive, chunkname, logicsrc) == -1) { + unlink(tmpname); + return SR_ERR; + } + if ((ret = zip_close(archive)) == -1) { + sr_info("error saving session file: %s", zip_strerror(archive)); + unlink(tmpname); + return SR_ERR; + } + unlink(tmpname); + + return SR_OK; +} + /** @} */ diff --git a/libsigrok4DSLogic/strutil.c b/libsigrok4DSLogic/strutil.c index 68efe7e..1b05ebc 100644 --- a/libsigrok4DSLogic/strutil.c +++ b/libsigrok4DSLogic/strutil.c @@ -82,7 +82,7 @@ SR_API char *sr_si_string_u64(uint64_t x, const char *unit) return g_strdup_printf("%" PRIu64 " k%s", x / SR_KHZ(1), unit); } else if ((x >= SR_KHZ(1)) && (x % SR_KHZ(1) != 0)) { - return g_strdup_printf("%" PRIu64 ".%" PRIu64 " k%s", + return g_strdup_printf("%" PRIu64 ".%" PRIu64 " K%s", x / SR_KHZ(1), x % SR_KHZ(1), unit); } else { return g_strdup_printf("%" PRIu64 " %s", x, unit); @@ -115,19 +115,19 @@ SR_API char *sr_iec_string_u64(uint64_t x, const char *unit) if ((x >= SR_GB(1)) && (x % SR_GB(1) == 0)) { return g_strdup_printf("%" PRIu64 " G%s", x / SR_GB(1), unit); } else if ((x >= SR_GB(1)) && (x % SR_GB(1) != 0)) { - return g_strdup_printf("%" PRIu64 ".%" PRIu64 " G%s", + return g_strdup_printf("%" PRIu64 ".%" PRIu64 "G%s", x / SR_GB(1), x % SR_GB(1), unit); } else if ((x >= SR_MB(1)) && (x % SR_MB(1) == 0)) { return g_strdup_printf("%" PRIu64 " M%s", x / SR_MB(1), unit); } else if ((x >= SR_MB(1)) && (x % SR_MB(1) != 0)) { - return g_strdup_printf("%" PRIu64 ".%" PRIu64 " M%s", + return g_strdup_printf("%" PRIu64 ".%" PRIu64 "M%s", x / SR_MB(1), x % SR_MB(1), unit); } else if ((x >= SR_KB(1)) && (x % SR_KB(1) == 0)) { return g_strdup_printf("%" PRIu64 " k%s", x / SR_KB(1), unit); } else if ((x >= SR_KB(1)) && (x % SR_KB(1) != 0)) { - return g_strdup_printf("%" PRIu64 ".%" PRIu64 " k%s", + return g_strdup_printf("%" PRIu64 ".%" PRIu64 "K%s", x / SR_KB(1), x % SR_KB(1), unit); } else { return g_strdup_printf("%" PRIu64 " %s", x, unit); @@ -154,6 +154,22 @@ SR_API char *sr_samplerate_string(uint64_t samplerate) return sr_si_string_u64(samplerate, "Hz"); } +/** + * Convert a numeric samplecount value to its "natural" string representation. + * + * E.g. a value of 16384 would be converted to "16 K" + * + * @param samplecount. + * + * @return A g_try_malloc()ed string representation of the samplecount value, + * or NULL upon errors. The caller is responsible to g_free() the + * memory. + */ +SR_API char *sr_samplecount_string(uint64_t samplecount) +{ + return sr_iec_string_u64(samplecount, " Samples"); +} + /** * Convert a numeric frequency value to the "natural" string representation * of its period. @@ -263,13 +279,13 @@ SR_API char **sr_parse_triggerstring(const struct sr_dev_inst *sdi, { GSList *l; GVariant *gvar; - struct sr_probe *probe; + struct sr_channel *probe; int max_probes, probenum, i; char **tokens, **triggerlist, *trigger, *tc; const char *trigger_types; gboolean error; - max_probes = g_slist_length(sdi->probes); + max_probes = g_slist_length(sdi->channels); error = FALSE; if (!(triggerlist = g_try_malloc0(max_probes * sizeof(char *)))) { @@ -277,7 +293,7 @@ SR_API char **sr_parse_triggerstring(const struct sr_dev_inst *sdi, return NULL; } - if (sdi->driver->config_list(SR_CONF_TRIGGER_TYPE, &gvar, sdi) != SR_OK) { + if (sdi->driver->config_list(SR_CONF_TRIGGER_TYPE, &gvar, sdi, NULL) != SR_OK) { sr_err("%s: Device doesn't support any triggers.", __func__); return NULL; } @@ -286,8 +302,8 @@ SR_API char **sr_parse_triggerstring(const struct sr_dev_inst *sdi, tokens = g_strsplit(triggerstring, ",", max_probes); for (i = 0; tokens[i]; i++) { probenum = -1; - for (l = sdi->probes; l; l = l->next) { - probe = (struct sr_probe *)l->data; + for (l = sdi->channels; l; l = l->next) { + probe = (struct sr_channel *)l->data; if (probe->enabled && !strncmp(probe->name, tokens[i], strlen(probe->name))) { -- 2.30.2