# HG changeset patch # User Hasan Yavuz ÖZDERYA # Date 2020-01-29 14:59:28 # Node ID 71441196d1c5d700dd901c7a8f5502a4ff25f72b # Parent a30d48dab8b816893ea901a5b94c14164be31659 # Parent e097ff97da17a7f8f6ed79195c08fee0d78fc04b Merge with default diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright © 2018 Hasan Yavuz Özderya +# Copyright © 2019 Hasan Yavuz Özderya # # This file is part of serialplot. # @@ -44,31 +44,11 @@ else (BUILD_QWT) find_package(Qwt 6.1 REQUIRED) endif (BUILD_QWT) -# If set, cmake will download QtColorWidgets over git, build and use it as a static library. -set(BUILD_QTCOLORWIDGETS true CACHE BOOL "Download and build QtColorWidgets library automatically.") -if (BUILD_QTCOLORWIDGETS) - include(BuildQColorWidgets) -else () - find_package(QtColorWidgets REQUIRED) -endif () - -set(BUILD_LEDWIDGET true CACHE BOOL "Download and build LedWidget automatically.") -if (BUILD_LEDWIDGET) - include(BuildLedWidget) -else (BUILD_LEDWIDGET) - include(FindLedWidget) -endif (BUILD_LEDWIDGET) - # includes include_directories("./src" ${QWT_INCLUDE_DIR} - ${QTCOLORWIDGETS_INCLUDE_DIRS} - ${LEDWIDGET_INCLUDE_DIR} ) -# flags -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${QTCOLORWIDGETS_FLAGS}") - # wrap UI and resource files qt5_wrap_ui(UI_FILES src/mainwindow.ui @@ -87,6 +67,7 @@ qt5_wrap_ui(UI_FILES src/framedreadersettings.ui src/demoreadersettings.ui src/updatecheckdialog.ui + src/datatextview.ui ) if (WIN32) @@ -126,6 +107,7 @@ add_executable(${PROGRAM_NAME} WIN32 src/ringbuffer.cpp src/ringbuffer.cpp src/indexbuffer.cpp + src/linindexbuffer.cpp src/readonlybuffer.cpp src/framebufferseries.cpp src/numberformatbox.cpp @@ -152,6 +134,9 @@ add_executable(${PROGRAM_NAME} WIN32 src/source.cpp src/sink.cpp src/samplecounter.cpp + src/ledwidget.cpp + src/datatextview.cpp + src/bpslabel.cpp misc/windows_icon.rc ${UI_FILES} ${RES_FILES} @@ -160,8 +145,6 @@ add_executable(${PROGRAM_NAME} WIN32 # Use the Widgets module from Qt 5. target_link_libraries(${PROGRAM_NAME} ${QWT_LIBRARY} - ${QTCOLORWIDGETS_LIBRARIES} - ${LEDWIDGET_LIBRARY} ) qt5_use_modules(${PROGRAM_NAME} Widgets SerialPort Network) @@ -169,15 +152,6 @@ if (BUILD_QWT) add_dependencies(${PROGRAM_NAME} QWT) endif () -if (BUILD_QTCOLORWIDGETS) - add_dependencies(${PROGRAM_NAME} QCW) -endif () - -if (BUILD_LEDWIDGET) - add_dependencies(${PROGRAM_NAME} LEDW) -endif (BUILD_LEDWIDGET) - - # set compiler flags set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") @@ -205,6 +179,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVERSION_MINOR=${VERSION_MINOR} ") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVERSION_PATCH=${VERSION_PATCH} ") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVERSION_REVISION=\\\"${VERSION_REVISION}\\\" ") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DPROGRAM_NAME=\\\"${PROGRAM_NAME}\\\" ") # add make run target add_custom_target(run diff --git a/cmake/modules/BuildLedWidget.cmake b/cmake/modules/BuildLedWidget.cmake deleted file mode 100644 --- a/cmake/modules/BuildLedWidget.cmake +++ /dev/null @@ -1,30 +0,0 @@ -# -# Copyright © 2017 Hasan Yavuz Özderya -# -# This file is part of serialplot. -# -# serialplot 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 3 of the License, or -# (at your option) any later version. -# -# serialplot 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 serialplot. If not, see . -# - -include(ExternalProject) - -ExternalProject_Add(LEDW - PREFIX ledw - HG_REPOSITORY https://bitbucket.org/hyOzd/ledwidget - UPDATE_COMMAND "" - INSTALL_COMMAND "") - -ExternalProject_Get_Property(LEDW binary_dir source_dir) -set(LEDWIDGET_INCLUDE_DIR ${source_dir}/src) -set(LEDWIDGET_LIBRARY ${binary_dir}/libledwidget.a) diff --git a/cmake/modules/BuildQColorWidgets.cmake b/cmake/modules/BuildQColorWidgets.cmake deleted file mode 100644 --- a/cmake/modules/BuildQColorWidgets.cmake +++ /dev/null @@ -1,36 +0,0 @@ -# -# Copyright © 2018 Hasan Yavuz Özderya -# -# This file is part of serialplot. -# -# serialplot 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 3 of the License, or -# (at your option) any later version. -# -# serialplot 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 serialplot. If not, see . -# - -include(ExternalProject) - -ExternalProject_Add(QCW - PREFIX qcw - GIT_REPOSITORY https://gitlab.com/mattia.basaglia/Qt-Color-Widgets.git - GIT_TAG 2c49e1bb4e1f591e720e2132cc2aaeef3ba73f14 - CMAKE_CACHE_ARGS "-DCMAKE_CXX_FLAGS:string=-D QTCOLORWIDGETS_STATICALLY_LINKED" - UPDATE_COMMAND "" - INSTALL_COMMAND "") - -ExternalProject_Get_Property(QCW binary_dir source_dir) -set(QTCOLORWIDGETS_FLAGS "-D QTCOLORWIDGETS_STATICALLY_LINKED") -set(QTCOLORWIDGETS_LIBRARY ${binary_dir}/libColorWidgets-qt5.a) -set(QTCOLORWIDGETS_INCLUDE_DIR ${source_dir}/include) - -set(QTCOLORWIDGETS_LIBRARIES ${QTCOLORWIDGETS_LIBRARY}) -set(QTCOLORWIDGETS_INCLUDE_DIRS ${QTCOLORWIDGETS_INCLUDE_DIR}) diff --git a/cmake/modules/BuildQwt.cmake b/cmake/modules/BuildQwt.cmake --- a/cmake/modules/BuildQwt.cmake +++ b/cmake/modules/BuildQwt.cmake @@ -1,5 +1,5 @@ # -# Copyright © 2016 Hasan Yavuz Özderya +# Copyright © 2019 Hasan Yavuz Özderya # # This file is part of serialplot. # @@ -21,7 +21,8 @@ include(ExternalProject) ExternalProject_Add(QWT PREFIX qwt - SVN_REPOSITORY svn://svn.code.sf.net/p/qwt/code/branches/qwt-6.1 + # SVN_REPOSITORY svn://svn.code.sf.net/p/qwt/code/branches/qwt-6.1 + URL https://sourceforge.net/projects/qwt/files/qwt/6.1.4/qwt-6.1.4.tar.bz2 # disable QwtDesigner plugin and enable static build PATCH_COMMAND sed -i -r -e "s/QWT_CONFIG\\s*\\+=\\s*QwtDesigner/#&/" -e "s/QWT_CONFIG\\s*\\+=\\s*QwtDll/#&/" diff --git a/cmake/modules/FindQtColorWidgets.cmake b/cmake/modules/FindQtColorWidgets.cmake deleted file mode 100644 --- a/cmake/modules/FindQtColorWidgets.cmake +++ /dev/null @@ -1,35 +0,0 @@ -# -# Copyright © 2017 Hasan Yavuz Özderya -# -# This file is part of serialplot. -# -# serialplot 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 3 of the License, or -# (at your option) any later version. -# -# serialplot 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 serialplot. If not, see . -# - -# Note: this script is intended for the debian package created for serialplot. - -find_library(QTCOLORWIDGETS_LIBRARY "libColorWidgets-qt5.a") -find_path(QTCOLORWIDGETS_INCLUDE_DIR "color_preview.hpp" PATHS "/usr/include/qtcolorwidgets/" NO_DEFAULT_PATH) - -mark_as_advanced(QTCOLORWIDGETS_LIBRARY QTCOLORWIDGETS_INCLUDE_DIR) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(QtColorWidgets DEFAULT_MSG QTCOLORWIDGETS_LIBRARY QTCOLORWIDGETS_INCLUDE_DIR) - -if (QTCOLORWIDGETS_FOUND) - set(QTCOLORWIDGETS_FLAGS "-D QTCOLORWIDGETS_STATICALLY_LINKED") - mark_as_advanced(QTCOLORWIDGETS_FLAGS) - set(QTCOLORWIDGETS_LIBRARIES ${QTCOLORWIDGETS_LIBRARY}) - set(QTCOLORWIDGETS_INCLUDE_DIRS ${QTCOLORWIDGETS_INCLUDE_DIR}) -endif (QTCOLORWIDGETS_FOUND) diff --git a/cmake/modules/FindQwt.cmake b/cmake/modules/FindQwt.cmake --- a/cmake/modules/FindQwt.cmake +++ b/cmake/modules/FindQwt.cmake @@ -1,5 +1,5 @@ # -# Copyright © 2017 Hasan Yavuz Özderya +# Copyright © 2019 Hasan Yavuz Özderya # # This file is part of serialplot. # @@ -66,8 +66,7 @@ endif(qwt_roots) if(QWT_ROOT) set(QWT_INCLUDE_DIR "${QWT_ROOT}/include") - find_library(QWT_LIBRARY "qwt-qt5" - PATHS "${QWT_ROOT}/lib") + find_library(QWT_LIBRARY NAMES "qwt-qt5" "qwt" PATHS "${QWT_ROOT}/lib") else (QWT_ROOT) ## Look into system locations find_path(QWT_INCLUDE_DIR qwt_plot.h PATHS /usr/include/qwt) @@ -90,7 +89,7 @@ else (QWT_ROOT) endif(qwt_version_string) endif (QWT_INCLUDE_DIR) # look into system locations for lib file - find_library(QWT_LIBRARY "qwt-qt5" PATHS /usr/lib) + find_library(QWT_LIBRARY NAMES "qwt-qt5" "qwt" PATHS /usr/lib) endif(QWT_ROOT) # set version variables @@ -103,8 +102,24 @@ if(QWT_VERSION) QWT_PATCH_VERSION ${QWT_VERSION}) endif(QWT_VERSION) +# check Qwt library 'Qt' version +if (QWT_LIBRARY) + include(GetPrerequisites) + GET_PREREQUISITES(${QWT_LIBRARY} qwt_lib_deps 0 0 "" "") + set(qwt_is_qt5 FALSE) + foreach (dep ${qwt_lib_deps}) + if (${dep} MATCHES "libQt5Gui") + set(qwt_is_qt5 TRUE) + endif() + endforeach () + if (NOT qwt_is_qt5) + message(WARNING "Found qwt library (${QWT_LIBRARY}) isn't compiled with Qt5!") + LIST_PREREQUISITES(${QWT_LIBRARY}) + endif() +endif (QWT_LIBRARY) + # set QWT_FOUND -if(QWT_INCLUDE_DIR AND QWT_LIBRARY) +if(QWT_INCLUDE_DIR AND QWT_LIBRARY AND qwt_is_qt5) set(QWT_INCLUDE_DIRS ${QWT_INCLUDE_DIR}) set(QWT_LIBRARIES ${QWT_LIBRARY}) set(QWT_FOUND true) @@ -114,6 +129,9 @@ endif() # errors if(NOT QWT_FOUND) + unset(QWT_INCLUDE_DIR CACHE) + unset(QWT_LIBRARY CACHE) + if(Qwt_FIND_QUIET) message(WARNING "Couldn't find Qwt.") elseif(Qwt_FIND_REQUIRED) diff --git a/serialplot.pro b/serialplot.pro --- a/serialplot.pro +++ b/serialplot.pro @@ -1,5 +1,5 @@ # -# Copyright © 2015 Hasan Yavuz Özderya +# Copyright © 2019 Hasan Yavuz Özderya # # This file is part of serialplot. # @@ -23,7 +23,7 @@ # #------------------------------------------------- -QT += core gui serialport +QT += core gui serialport network svg greaterThan(QT_MAJOR_VERSION, 4): QT += widgets @@ -31,47 +31,72 @@ TARGET = serialplot TEMPLATE = app CONFIG += qwt +# LIBS += -lqwt # enable this line if qwt pri files aren't installed + +DEFINES += PROGRAM_NAME="\\\"serialplot\\\"" + +DEFINES += VERSION_MAJOR=10 VERSION_MINOR=0 VERSION_PATCH=0 VERSION_STRING=\\\"10.0.0\\\" SOURCES += \ - src/main.cpp\ + src/main.cpp \ src/mainwindow.cpp \ src/portcontrol.cpp \ src/plot.cpp \ src/zoomer.cpp \ + src/scrollzoomer.cpp \ + src/scrollbar.cpp \ src/hidabletabwidget.cpp \ - src/framebuffer.cpp \ src/scalepicker.cpp \ src/scalezoomer.cpp \ src/portlist.cpp \ + src/snapshot.cpp \ src/snapshotview.cpp \ src/snapshotmanager.cpp \ - src/snapshot.cpp \ src/plotsnapshotoverlay.cpp \ src/commandpanel.cpp \ src/commandwidget.cpp \ src/commandedit.cpp \ src/dataformatpanel.cpp \ + src/plotcontrolpanel.cpp \ + src/recordpanel.cpp \ + src/datarecorder.cpp \ src/tooltipfilter.cpp \ src/sneakylineedit.cpp \ - src/channelmanager.cpp \ + src/stream.cpp \ + src/streamchannel.cpp \ + src/channelinfomodel.cpp \ + src/ringbuffer.cpp \ + src/indexbuffer.cpp \ + src/linindexbuffer.cpp \ + src/readonlybuffer.cpp \ src/framebufferseries.cpp \ - src/plotcontrolpanel.cpp \ src/numberformatbox.cpp \ src/endiannessbox.cpp \ - src/framedreadersettings.cpp \ src/abstractreader.cpp \ src/binarystreamreader.cpp \ src/binarystreamreadersettings.cpp \ + src/asciireader.cpp \ src/asciireadersettings.cpp \ - src/asciireader.cpp \ src/demoreader.cpp \ + src/demoreadersettings.cpp \ src/framedreader.cpp \ + src/framedreadersettings.cpp \ src/plotmanager.cpp \ + src/plotmenu.cpp \ + src/barplot.cpp \ + src/barchart.cpp \ + src/barscaledraw.cpp \ src/numberformat.cpp \ - src/recordpanel.cpp \ src/updatechecker.cpp \ + src/versionnumber.cpp \ src/updatecheckdialog.cpp \ - src/demoreadersettings.cpp + src/samplepack.cpp \ + src/source.cpp \ + src/sink.cpp \ + src/samplecounter.cpp \ + src/ledwidget.cpp \ + src/datatextview.cpp \ + src/bpslabel.cpp HEADERS += \ src/mainwindow.h \ @@ -79,7 +104,6 @@ HEADERS += \ src/portcontrol.h \ src/floatswap.h \ src/plot.h \ - src/zoomer.h \ src/hidabletabwidget.h \ src/framebuffer.h \ src/scalepicker.h \ @@ -95,7 +119,6 @@ HEADERS += \ src/dataformatpanel.h \ src/tooltipfilter.h \ src/sneakylineedit.h \ - src/channelmanager.h \ src/framebufferseries.h \ src/plotcontrolpanel.h \ src/numberformatbox.h \ @@ -114,7 +137,32 @@ HEADERS += \ src/recordpanel.h \ src/updatechecker.h \ src/updatecheckdialog.h \ - src/demoreadersettings.h + src/demoreadersettings.h \ + src/datatextview.h \ + src/bpslabel.h \ + src/barchart.h \ + src/barplot.h \ + src/barscaledraw.h \ + src/channelinfomodel.h \ + src/datarecorder.h \ + src/defines.h \ + src/indexbuffer.h \ + src/ledwidget.h \ + src/linindexbuffer.h \ + src/plotmenu.h \ + src/readonlybuffer.h \ + src/ringbuffer.h \ + src/samplecounter.h \ + src/samplepack.h \ + src/scrollbar.h \ + src/scrollzoomer.h \ + src/sink.h \ + src/source.h \ + src/streamchannel.h \ + src/stream.h \ + src/version.h \ + src/versionnumber.h \ + src/zoomer.h FORMS += \ src/mainwindow.ui \ @@ -132,7 +180,8 @@ FORMS += \ src/asciireadersettings.ui \ src/recordpanel.ui \ src/updatecheckdialog.ui \ - src/demoreadersettings.ui + src/demoreadersettings.ui \ + src/datatextview.ui INCLUDEPATH += qmake/ src/ diff --git a/src/abstractreader.cpp b/src/abstractreader.cpp --- a/src/abstractreader.cpp +++ b/src/abstractreader.cpp @@ -1,5 +1,5 @@ /* - Copyright © 2018 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -23,6 +23,7 @@ AbstractReader::AbstractReader(QIODevice QObject(parent) { _device = device; + bytesRead = 0; } void AbstractReader::pause(bool enabled) @@ -43,3 +44,15 @@ void AbstractReader::enable(bool enabled disconnectSinks(); } } + +void AbstractReader::onDataReady() +{ + bytesRead += readData(); +} + +unsigned AbstractReader::getBytesRead() +{ + unsigned r = bytesRead; + bytesRead = 0; + return r; +} diff --git a/src/abstractreader.h b/src/abstractreader.h --- a/src/abstractreader.h +++ b/src/abstractreader.h @@ -1,5 +1,5 @@ /* - Copyright © 2018 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -49,6 +49,9 @@ public: /// None of the current readers support X channel at the moment bool hasX() const final { return false; }; + /// Read and 'zero' the byte counter + unsigned getBytesRead(); + signals: // TODO: should we keep this? void numOfChannelsChanged(unsigned); @@ -63,12 +66,25 @@ public slots: void pause(bool enabled); protected: + /// Reader should read from this device in `readData()` function. QIODevice* _device; + + /// Reader should check this variable to determine if reading is + /// paused in `readData()` bool paused; -protected slots: - /// all derived readers has to override this function - virtual void onDataReady() = 0; + /** + * Called when `readyRead` is signaled by the device. This is + * where the implementors should read the data and return the + * exact number of bytes read from the device. + */ + virtual unsigned readData() = 0; + +private: + unsigned bytesRead; + +private slots: + void onDataReady(); }; #endif // ABSTRACTREADER_H diff --git a/src/asciireader.cpp b/src/asciireader.cpp --- a/src/asciireader.cpp +++ b/src/asciireader.cpp @@ -1,5 +1,5 @@ /* - Copyright © 2018 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -70,21 +70,20 @@ void AsciiReader::enable(bool enabled) if (enabled) { firstReadAfterEnable = true; - QObject::connect(_device, &QIODevice::readyRead, - this, &AsciiReader::onDataReady); } - else - { - QObject::disconnect(_device, 0, this, 0); - disconnectSinks(); - } + + AbstractReader::enable(enabled); } -void AsciiReader::onDataReady() +unsigned AsciiReader::readData() { + unsigned numBytesRead = 0; + while(_device->canReadLine()) { - QString line = QString(_device->readLine()); + QByteArray bytes = _device->readLine(); + QString line = QString(bytes); + numBytesRead += bytes.size(); // discard only once when we just started reading if (firstReadAfterEnable) @@ -127,8 +126,11 @@ void AsciiReader::onDataReady() // commit data feedOut(*samples); + delete samples; } } + + return numBytesRead; } SamplePack* AsciiReader::parseLine(const QString& line) const diff --git a/src/asciireader.h b/src/asciireader.h --- a/src/asciireader.h +++ b/src/asciireader.h @@ -1,5 +1,5 @@ /* - Copyright © 2018 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -50,8 +50,10 @@ private: bool firstReadAfterEnable = false; + unsigned readData() override; + private slots: - void onDataReady() override; + /** * Parses given line and returns sample pack. * diff --git a/src/asciireadersettings.cpp b/src/asciireadersettings.cpp --- a/src/asciireadersettings.cpp +++ b/src/asciireadersettings.cpp @@ -1,5 +1,5 @@ /* - Copyright © 2017 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -21,6 +21,7 @@ #include #include "utils.h" +#include "defines.h" #include "setting_defines.h" #include "asciireadersettings.h" @@ -35,6 +36,8 @@ AsciiReaderSettings::AsciiReaderSettings auto validator = new QRegularExpressionValidator(QRegularExpression("[^\\d]?"), this); ui->leDelimiter->setValidator(validator); + ui->spNumOfChannels->setMaximum(MAX_NUM_CHANNELS); + connect(ui->rbComma, &QAbstractButton::toggled, this, &AsciiReaderSettings::delimiterToggled); connect(ui->rbSpace, &QAbstractButton::toggled, diff --git a/src/binarystreamreader.cpp b/src/binarystreamreader.cpp --- a/src/binarystreamreader.cpp +++ b/src/binarystreamreader.cpp @@ -1,5 +1,5 @@ /* - Copyright © 2018 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -107,50 +107,58 @@ void BinaryStreamReader::onNumOfChannels emit numOfChannelsChanged(value); } -void BinaryStreamReader::onDataReady() +unsigned BinaryStreamReader::readData() { // a package is a set of channel data like {CHAN0_SAMPLE, CHAN1_SAMPLE...} - int packageSize = sampleSize * _numChannels; - int bytesAvailable = _device->bytesAvailable(); + unsigned packageSize = sampleSize * _numChannels; + unsigned bytesAvailable = _device->bytesAvailable(); + unsigned totalRead = 0; // skip 1 byte if requested if (skipByteRequested && bytesAvailable > 0) { _device->read(1); + totalRead++; skipByteRequested = false; bytesAvailable--; } // skip 1 sample (channel) if requested - if (skipSampleRequested && bytesAvailable >= (int) sampleSize) + if (skipSampleRequested && bytesAvailable >= sampleSize) { _device->read(sampleSize); + totalRead += sampleSize; skipSampleRequested = false; bytesAvailable -= sampleSize; } - if (bytesAvailable < packageSize) return; + if (bytesAvailable < packageSize) return totalRead; - int numOfPackagesToRead = + unsigned numOfPackagesToRead = (bytesAvailable - (bytesAvailable % packageSize)) / packageSize; + unsigned numBytesToRead = numOfPackagesToRead * packageSize; + + totalRead += numBytesToRead; if (paused) { // read and discard data - _device->read(numOfPackagesToRead*packageSize); - return; + _device->read(numBytesToRead); + return totalRead; } // actual reading SamplePack samples(numOfPackagesToRead, _numChannels); - for (int i = 0; i < numOfPackagesToRead; i++) + for (unsigned i = 0; i < numOfPackagesToRead; i++) { - for (unsigned int ci = 0; ci < _numChannels; ci++) + for (unsigned ci = 0; ci < _numChannels; ci++) { samples.data(ci)[i] = (this->*readSample)(); } } feedOut(samples); + + return totalRead; } template double BinaryStreamReader::readSampleAs() diff --git a/src/binarystreamreader.h b/src/binarystreamreader.h --- a/src/binarystreamreader.h +++ b/src/binarystreamreader.h @@ -1,5 +1,5 @@ /* - Copyright © 2018 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -60,10 +60,11 @@ private: */ template double readSampleAs(); + unsigned readData() override; + private slots: void onNumberFormatChanged(NumberFormat numberFormat); void onNumOfChannelsChanged(unsigned value); - void onDataReady() override; }; #endif // BINARYSTREAMREADER_H diff --git a/src/binarystreamreadersettings.cpp b/src/binarystreamreadersettings.cpp --- a/src/binarystreamreadersettings.cpp +++ b/src/binarystreamreadersettings.cpp @@ -1,5 +1,5 @@ /* - Copyright © 2016 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -21,6 +21,7 @@ #include "ui_binarystreamreadersettings.h" #include "utils.h" +#include "defines.h" #include "setting_defines.h" BinaryStreamReaderSettings::BinaryStreamReaderSettings(QWidget *parent) : @@ -29,6 +30,8 @@ BinaryStreamReaderSettings::BinaryStream { ui->setupUi(this); + ui->spNumOfChannels->setMaximum(MAX_NUM_CHANNELS); + // Note: if directly connected we get a runtime warning on incompatible signal arguments connect(ui->spNumOfChannels, SELECT::OVERLOAD_OF(&QSpinBox::valueChanged), [this](int value) diff --git a/src/bpslabel.cpp b/src/bpslabel.cpp new file mode 100644 --- /dev/null +++ b/src/bpslabel.cpp @@ -0,0 +1,80 @@ +/* + Copyright © 2019 Hasan Yavuz Özderya + + This file is part of serialplot. + + serialplot 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 3 of the License, or + (at your option) any later version. + + serialplot 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 serialplot. If not, see . +*/ + +#include "bpslabel.h" + +const char* BPS_TOOLTIP = "bits per second"; +const char* BPS_TOOLTIP_ERR = "Maximum baud rate may be reached!"; + +BPSLabel::BPSLabel(PortControl* portControl, + DataFormatPanel* dataFormatPanel, + QWidget *parent) : + QLabel(parent) +{ + _portControl = portControl; + _dataFormatPanel = dataFormatPanel; + prevBytesRead = 0; + + setText("0bps"); + setToolTip(tr(BPS_TOOLTIP)); + + connect(&bpsTimer, &QTimer::timeout, + this, &BPSLabel::onBpsTimeout); + + connect(portControl, &PortControl::portToggled, + this, &BPSLabel::onPortToggled); +} + +void BPSLabel::onBpsTimeout() +{ + uint64_t curBytesRead = _dataFormatPanel->bytesRead(); + uint64_t bytesRead = curBytesRead - prevBytesRead; + prevBytesRead = curBytesRead; + + unsigned bits = bytesRead * 8; + unsigned maxBps = _portControl->maxBitRate(); + QString str; + if (bits >= maxBps) + { + // TODO: an icon for bps warning + str = QString(tr("!%1/%2bps")).arg(bits).arg(maxBps); + setToolTip(tr(BPS_TOOLTIP_ERR)); + } + else + { + str = QString(tr("%1bps")).arg(bits); + setToolTip(tr(BPS_TOOLTIP)); + } + setText(str); +} + +void BPSLabel::onPortToggled(bool open) +{ + if (open) + { + bpsTimer.start(1000); + } + else + { + bpsTimer.stop(); + // if not cleared last displayed value is stuck + setText("0bps"); + setToolTip(tr(BPS_TOOLTIP)); + } +} diff --git a/src/bpslabel.h b/src/bpslabel.h new file mode 100644 --- /dev/null +++ b/src/bpslabel.h @@ -0,0 +1,55 @@ +/* + Copyright © 2019 Hasan Yavuz Özderya + + This file is part of serialplot. + + serialplot 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 3 of the License, or + (at your option) any later version. + + serialplot 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 serialplot. If not, see . +*/ + +#ifndef BPSLABEL_H +#define BPSLABEL_H + +#include +#include + +#include "portcontrol.h" +#include "dataformatpanel.h" + +/** + * Displays bits per second read from device. + * + * Displays a warning if maximum bit rate is reached. + */ +class BPSLabel : public QLabel +{ + Q_OBJECT + +public: + explicit BPSLabel(PortControl* portControl, + DataFormatPanel* dataFormatPanel, + QWidget *parent = 0); + +private: + PortControl* _portControl; + DataFormatPanel* _dataFormatPanel; + QTimer bpsTimer; + + uint64_t prevBytesRead; + +private slots: + void onBpsTimeout(); + void onPortToggled(bool open); +}; + +#endif // BPSLABEL_H diff --git a/src/dataformatpanel.cpp b/src/dataformatpanel.cpp --- a/src/dataformatpanel.cpp +++ b/src/dataformatpanel.cpp @@ -1,5 +1,5 @@ /* - Copyright © 2018 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -39,6 +39,7 @@ DataFormatPanel::DataFormatPanel(QSerial serialPort = port; paused = false; readerBeforeDemo = nullptr; + _bytesRead = 0; // initalize default reader currentReader = &bsReader; @@ -130,6 +131,12 @@ void DataFormatPanel::selectReader(Abstr emit sourceChanged(currentReader); } +uint64_t DataFormatPanel::bytesRead() +{ + _bytesRead += currentReader->getBytesRead(); + return _bytesRead; +} + void DataFormatPanel::saveSettings(QSettings* settings) { settings->beginGroup(SettingGroup_DataFormat); diff --git a/src/dataformatpanel.h b/src/dataformatpanel.h --- a/src/dataformatpanel.h +++ b/src/dataformatpanel.h @@ -1,5 +1,5 @@ /* - Copyright © 2018 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -20,9 +20,9 @@ #ifndef DATAFORMATPANEL_H #define DATAFORMATPANEL_H +#include #include #include -#include #include #include #include @@ -50,6 +50,8 @@ public: unsigned numChannels() const; /// Returns active source (reader) Source* activeSource(); + /// Returns total number of bytes read + uint64_t bytesRead(); /// Stores data format panel settings into a `QSettings` void saveSettings(QSettings* settings); /// Loads data format panel settings from a `QSettings`. @@ -77,6 +79,7 @@ private: void selectReader(AbstractReader* reader); bool paused; + uint64_t _bytesRead; DemoReader demoReader; AbstractReader* readerBeforeDemo; diff --git a/src/datatextview.cpp b/src/datatextview.cpp new file mode 100644 --- /dev/null +++ b/src/datatextview.cpp @@ -0,0 +1,110 @@ +/* + Copyright © 2019 Hasan Yavuz Özderya + + This file is part of serialplot. + + serialplot 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 3 of the License, or + (at your option) any later version. + + serialplot 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 serialplot. If not, see . +*/ + +#include "datatextview.h" +#include "ui_datatextview.h" + +#include "setting_defines.h" +#include "utils.h" + +class DataTextViewSink : public Sink +{ +public: + DataTextViewSink(DataTextView* textView) + { + _textView = textView; + } + +protected: + virtual void feedIn(const SamplePack& data) override + { + _textView->addData(data); + }; + +private: + DataTextView* _textView; +}; + +DataTextView::DataTextView(Stream* stream, QWidget *parent) : + QWidget(parent), + ui(new Ui::DataTextView) +{ + _stream = stream; + ui->setupUi(this); + sink = new DataTextViewSink(this); + + connect(ui->cbEnable, &QCheckBox::toggled, [this](bool checked) + { + if (checked) + { + _stream->connectFollower(sink); + } + else + { + _stream->disconnectFollower(sink); + } + }); + + ui->textView->setMaximumBlockCount(ui->spNumLines->value()); + connect(ui->spNumLines, SELECT::OVERLOAD_OF(&QSpinBox::valueChanged), + [this](int value) + { + ui->textView->setMaximumBlockCount(value); + }); + + connect(ui->pbClear, &QPushButton::clicked, ui->textView, &QPlainTextEdit::clear); +} + +DataTextView::~DataTextView() +{ + delete sink; + delete ui; +} + +void DataTextView::addData(const SamplePack& data) +{ + for (unsigned int i = 0; i < data.numSamples(); i++) + { + QString str; + for (unsigned ci = 0; ci < data.numChannels(); ci++) + { + str += QString::number(data.data(ci)[i], 'f', ui->spDecimals->value()); + if (ci != data.numChannels()-1) str += " "; + } + ui->textView->appendPlainText(str); + } +} + +void DataTextView::saveSettings(QSettings* settings) +{ + settings->beginGroup(SettingGroup_Plot); + settings->setValue(SG_TextView_NumLines, ui->spNumLines->value()); + settings->setValue(SG_TextView_Decimals, ui->spDecimals->value()); + settings->endGroup(); +} + +void DataTextView::loadSettings(QSettings* settings) +{ + settings->beginGroup(SettingGroup_Plot); + ui->spNumLines->setValue( + settings->value(SG_TextView_NumLines, ui->spNumLines->value()).toInt()); + ui->spDecimals->setValue( + settings->value(SG_TextView_Decimals, ui->spDecimals->value()).toInt()); + settings->endGroup(); +} diff --git a/src/datatextview.h b/src/datatextview.h new file mode 100644 --- /dev/null +++ b/src/datatextview.h @@ -0,0 +1,57 @@ +/* + Copyright © 2019 Hasan Yavuz Özderya + + This file is part of serialplot. + + serialplot 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 3 of the License, or + (at your option) any later version. + + serialplot 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 serialplot. If not, see . +*/ + +#ifndef DATATEXTVIEW_H +#define DATATEXTVIEW_H + +#include + +#include "stream.h" + +namespace Ui { +class DataTextView; +} + +class DataTextViewSink; + +class DataTextView : public QWidget +{ + Q_OBJECT + +public: + explicit DataTextView(Stream* stream, QWidget *parent = 0); + ~DataTextView(); + + /// Stores settings into a `QSettings` + void saveSettings(QSettings* settings); + /// Loads settings from a `QSettings`. + void loadSettings(QSettings* settings); + +protected: + void addData(const SamplePack& data); + + friend DataTextViewSink; + +private: + Ui::DataTextView *ui; + DataTextViewSink* sink; + Stream* _stream; +}; + +#endif // DATATEXTVIEW_H diff --git a/src/datatextview.ui b/src/datatextview.ui new file mode 100644 --- /dev/null +++ b/src/datatextview.ui @@ -0,0 +1,99 @@ + + + DataTextView + + + + 0 + 0 + 451 + 212 + + + + Form + + + + + + + + Enable display of plotted data as text. + + + Enable + + + + + + + Num. Lines: + + + + + + + 1 + + + 10000 + + + 1000 + + + + + + + Decimals: + + + + + + + 9 + + + 6 + + + + + + + Qt::Vertical + + + + 20 + 1 + + + + + + + + Clear + + + + + + + + + true + + + + + + + + diff --git a/src/defines.h b/src/defines.h --- a/src/defines.h +++ b/src/defines.h @@ -1,5 +1,5 @@ /* - Copyright © 2016 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -17,4 +17,12 @@ along with serialplot. If not, see . */ -const char* BUG_REPORT_URL = "https://bitbucket.org/hyOzd/serialplot/issues/new"; +#ifndef DEFINES_H +#define DEFINES_H + +const char BUG_REPORT_URL[] = "https://bitbucket.org/hyOzd/serialplot/issues/new"; + +/// Maximum number of channels that can be set by user +const unsigned MAX_NUM_CHANNELS = 64; + +#endif // DEFINES_H diff --git a/src/demoreader.cpp b/src/demoreader.cpp --- a/src/demoreader.cpp +++ b/src/demoreader.cpp @@ -1,5 +1,5 @@ /* - Copyright © 2018 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -53,8 +53,9 @@ void DemoReader::enable(bool enabled) else { timer.stop(); - disconnectSinks(); } + + AbstractReader::enable(enabled); } unsigned DemoReader::numChannels() const @@ -91,7 +92,8 @@ void DemoReader::onNumChannelsChanged(un updateNumChannels(); } -void DemoReader::onDataReady() +unsigned DemoReader::readData() { // intentionally empty, required by AbstractReader + return 0; } diff --git a/src/demoreader.h b/src/demoreader.h --- a/src/demoreader.h +++ b/src/demoreader.h @@ -1,5 +1,5 @@ /* - Copyright © 2018 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -55,10 +55,11 @@ private: QTimer timer; int count; + unsigned readData() override; + private slots: void demoTimerTimeout(); void onNumChannelsChanged(unsigned value); - void onDataReady() override; }; #endif // DEMOREADER_H diff --git a/src/demoreadersettings.cpp b/src/demoreadersettings.cpp --- a/src/demoreadersettings.cpp +++ b/src/demoreadersettings.cpp @@ -1,5 +1,5 @@ /* - Copyright © 2018 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -21,6 +21,7 @@ #include "ui_demoreadersettings.h" #include "utils.h" +#include "defines.h" DemoReaderSettings::DemoReaderSettings(QWidget *parent) : QWidget(parent), @@ -28,6 +29,8 @@ DemoReaderSettings::DemoReaderSettings(Q { ui->setupUi(this); + ui->spNumChannels->setMaximum(MAX_NUM_CHANNELS); + connect(ui->spNumChannels, SELECT::OVERLOAD_OF(&QSpinBox::valueChanged), [this](int value) { diff --git a/src/floatswap.h b/src/floatswap.h --- a/src/floatswap.h +++ b/src/floatswap.h @@ -1,5 +1,7 @@ #include +#if (QT_VERSION < QT_VERSION_CHECK(5, 12, 0)) + template <> inline float qbswap(float source) { float result; @@ -11,3 +13,5 @@ template <> inline float qbswap(f t[3] = s[0]; return result; } + +#endif diff --git a/src/framebuffer.h b/src/framebuffer.h --- a/src/framebuffer.h +++ b/src/framebuffer.h @@ -61,4 +61,24 @@ class WFrameBuffer : public ResizableBuf virtual void clear() = 0; }; +/** + * Abstract base class for X buffers. + * + * These buffers only contain increasing or equal (to previous) values. + */ +class XFrameBuffer : public ResizableBuffer +{ +public: + enum Index {OUT_OF_RANGE = -1}; + + /** + * Finds index for given value. + * + * If given value is bigger than max or smaller than minimum + * returns `OUT_OF_RANGE`. If it's in between values, smaller + * index is returned (not closer one). + */ + virtual int findIndex(double value) const = 0; +}; + #endif // FRAMEBUFFER_H diff --git a/src/framebufferseries.cpp b/src/framebufferseries.cpp --- a/src/framebufferseries.cpp +++ b/src/framebufferseries.cpp @@ -20,75 +20,64 @@ #include #include "framebufferseries.h" -FrameBufferSeries::FrameBufferSeries(const FrameBuffer* buffer) +FrameBufferSeries::FrameBufferSeries(const XFrameBuffer* x, const FrameBuffer* y) { - xAsIndex = true; - _xmin = 0; - _xmax = 1; - _buffer = buffer; + _x = x; + _y = y; + int_index_start = 0; - int_index_end = _buffer->size(); + int_index_end = _y->size(); } -void FrameBufferSeries::setXAxis(bool asIndex, double xmin, double xmax) +void FrameBufferSeries::setX(const XFrameBuffer* x) { - xAsIndex = asIndex; - _xmin = xmin; - _xmax = xmax; + _x = x; } size_t FrameBufferSeries::size() const { - return int_index_end - int_index_start; + return int_index_end - int_index_start + 1; } QPointF FrameBufferSeries::sample(size_t i) const { i += int_index_start; - if (xAsIndex) - { - return QPointF(i, _buffer->sample(i)); - } - else - { - return QPointF(i * (_xmax - _xmin) / _buffer->size() + _xmin, _buffer->sample(i)); - } + return QPointF(_x->sample(i), _y->sample(i)); } QRectF FrameBufferSeries::boundingRect() const { QRectF rect; - auto yLim = _buffer->limits(); + auto yLim = _y->limits(); + auto xLim = _x->limits(); rect.setBottom(yLim.start); rect.setTop(yLim.end); - if (xAsIndex) - { - rect.setLeft(0); - rect.setRight(size()); - } - else - { - rect.setLeft(_xmin); - rect.setRight(_xmax); - } + rect.setLeft(xLim.start); + rect.setRight(xLim.end); + return rect.normalized(); } void FrameBufferSeries::setRectOfInterest(const QRectF& rect) { - if (xAsIndex) + int_index_start = _x->findIndex(rect.left()); + int_index_end = _x->findIndex(rect.right()); + + if (int_index_start == XFrameBuffer::OUT_OF_RANGE) { - int_index_start = floor(rect.left())-1; - int_index_end = ceil(rect.right())+1; + int_index_start = 0; } - else + else if (int_index_start > 0) { - double xsize = _xmax - _xmin; - size_t bsize = _buffer->size(); - int_index_start = floor(bsize * (rect.left()-_xmin) / xsize)-1; - int_index_end = ceil(bsize * (rect.right()-_xmin) / xsize)+1; + int_index_start -= 1; } - int_index_start = std::max(int_index_start, 0); - int_index_end = std::min((int) _buffer->size(), int_index_end); + if (int_index_end == XFrameBuffer::OUT_OF_RANGE) + { + int_index_end = _x->size()-1; + } + else if (int_index_end < (int)_x->size()-1) + { + int_index_end += 1; + } } diff --git a/src/framebufferseries.h b/src/framebufferseries.h --- a/src/framebufferseries.h +++ b/src/framebufferseries.h @@ -35,10 +35,9 @@ class FrameBufferSeries : public QwtSeriesData { public: - FrameBufferSeries(const FrameBuffer* buffer); + FrameBufferSeries(const XFrameBuffer* x, const FrameBuffer* y); - /// Behavior of X axis - void setXAxis(bool asIndex, double xmin, double xmax); + void setX(const XFrameBuffer* x); // QwtSeriesData implementations size_t size() const; @@ -47,10 +46,8 @@ public: void setRectOfInterest(const QRectF& rect); private: - const FrameBuffer* _buffer; - bool xAsIndex; - double _xmin; - double _xmax; + const XFrameBuffer* _x; + const FrameBuffer* _y; int int_index_start; ///< starting index of "rectangle of interest" int int_index_end; ///< ending index of "rectangle of interest" diff --git a/src/framedreader.cpp b/src/framedreader.cpp --- a/src/framedreader.cpp +++ b/src/framedreader.cpp @@ -1,5 +1,5 @@ /* - Copyright © 2018 Hasan Yavuz Özderya + Copyright © 2020 Hasan Yavuz Özderya This file is part of serialplot. @@ -159,6 +159,7 @@ void FramedReader::onNumOfChannelsChange _numChannels = value; checkSettings(); reset(); + updateNumChannels(); emit numOfChannelsChanged(value); } @@ -184,9 +185,11 @@ void FramedReader::onFrameSizeChanged(un reset(); } -void FramedReader::onDataReady() +unsigned FramedReader::readData() { - if (settingsInvalid) return; + unsigned numBytesRead = 0; + + if (settingsInvalid) return numBytesRead; // loop until we run out of bytes or more bytes is required unsigned bytesAvailable; @@ -196,6 +199,7 @@ void FramedReader::onDataReady() { char c; _device->getChar(&c); + numBytesRead++; if (c == syncWord[sync_i]) // correct sync byte? { sync_i++; @@ -213,6 +217,7 @@ void FramedReader::onDataReady() { frameSize = 0; _device->getChar((char*) &frameSize); + numBytesRead++; if (frameSize == 0) // check size { @@ -242,10 +247,13 @@ void FramedReader::onDataReady() else // read data bytes and checksum { readFrameDataAndCheck(); + numBytesRead += checksumEnabled ? frameSize+1 : frameSize; reset(); } } } + + return numBytesRead; } void FramedReader::reset() @@ -263,7 +271,7 @@ void FramedReader::readFrameDataAndCheck // if paused just read and waste data if (paused) { - _device->read((checksumEnabled ? frameSize+1 : frameSize)); + _device->read(checksumEnabled ? frameSize+1 : frameSize); return; } diff --git a/src/framedreader.h b/src/framedreader.h --- a/src/framedreader.h +++ b/src/framedreader.h @@ -1,5 +1,5 @@ /* - Copyright © 2018 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -79,8 +79,9 @@ private: /// @note should be called only if there are enough bytes on device void readFrameDataAndCheck(); + unsigned readData() override; + private slots: - void onDataReady() override; void onNumberFormatChanged(NumberFormat numberFormat); void onNumOfChannelsChanged(unsigned value); diff --git a/src/framedreadersettings.cpp b/src/framedreadersettings.cpp --- a/src/framedreadersettings.cpp +++ b/src/framedreadersettings.cpp @@ -1,5 +1,5 @@ /* - Copyright © 2016 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -20,6 +20,7 @@ #include #include "utils.h" +#include "defines.h" #include "setting_defines.h" #include "framedreadersettings.h" #include "ui_framedreadersettings.h" @@ -32,6 +33,7 @@ FramedReaderSettings::FramedReaderSettin ui->leSyncWord->setMode(false); // hex mode ui->leSyncWord->setText("AA BB"); + ui->spNumOfChannels->setMaximum(MAX_NUM_CHANNELS); connect(ui->cbChecksum, &QCheckBox::toggled, [this](bool enabled) diff --git a/src/indexbuffer.cpp b/src/indexbuffer.cpp --- a/src/indexbuffer.cpp +++ b/src/indexbuffer.cpp @@ -1,5 +1,5 @@ /* - Copyright © 2017 Hasan Yavuz Özderya + Copyright © 2018 Hasan Yavuz Özderya This file is part of serialplot. @@ -47,3 +47,15 @@ Range IndexBuffer::limits() const { return Range{0, _size-1.}; } + +int IndexBuffer::findIndex(double value) const +{ + if (value < 0 || value > size() - 1) + { + return OUT_OF_RANGE; + } + else + { + return value; + } +} diff --git a/src/indexbuffer.h b/src/indexbuffer.h --- a/src/indexbuffer.h +++ b/src/indexbuffer.h @@ -26,15 +26,16 @@ /// sample value. /// /// @note This buffer isn't for storing data. -class IndexBuffer : public ResizableBuffer +class IndexBuffer : public XFrameBuffer { public: IndexBuffer(unsigned n); - unsigned size() const; - double sample(unsigned i) const; - Range limits() const; - void resize(unsigned n); + unsigned size() const override; + double sample(unsigned i) const override; + Range limits() const override; + void resize(unsigned n) override; + int findIndex(double value) const override; private: unsigned _size; diff --git a/src/ledwidget.cpp b/src/ledwidget.cpp new file mode 100644 --- /dev/null +++ b/src/ledwidget.cpp @@ -0,0 +1,129 @@ +/* + Copyright © 2018 Hasan Yavuz Özderya + + This file is part of serialplot. + + ledwidget 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 3 of the License, or + (at your option) any later version. + + ledwidget 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 ledwidget. If not, see . +*/ + +#include "ledwidget.h" +#include +#include +#include + +LedWidget::LedWidget(QWidget* parent) : QWidget(parent), + m_color(107,223,51), + m_on(true) +{ + resize(20,20); +} + +QSize LedWidget::sizeHint() const +{ + return QSize(20,20); +} + +QSize LedWidget::minimumSizeHint() const +{ + return QSize(10, 10); +} + +void LedWidget::setColor(QColor ledColor) +{ + if (m_color == ledColor) return; + m_color = ledColor; + update(); + emit colorChanged(m_color); +} + +bool LedWidget::isOn() +{ + return m_on; +} + +void LedWidget::setOn(bool on) +{ + if (on == m_on) return; + m_on = on; + update(); + emit onChanged(on); +} + +void LedWidget::turnOn() +{ + setOn(true); +} + +void LedWidget::turnOff() +{ + setOn(false); +} + +void LedWidget::toggle() +{ + setOn(!m_on); +} + +void LedWidget::paintEvent(QPaintEvent* event) +{ + Q_UNUSED(event) + + const qreal r = std::min(width(), height()) / 2; // maximum radius including glow + const qreal glowOffset = std::max(2., r/5.); + const qreal borderOffset = std::max(1., r/10.); + const qreal shineOffset = std::max(1., r/20.); + const QPointF center(width()/2, height()/2); + + const qreal gr = r; + const qreal br = gr - glowOffset; // border shape radius + const qreal ir = br - borderOffset; // inner fill radius + const qreal sr = ir - shineOffset; + + QColor borderColor(130,130,130); + QColor shineColor(255, 255, 255, m_on ? 200 : 50); + QColor fillColor(m_color); + + QPainter painter(this); + painter.setRenderHints(QPainter::Antialiasing); + + // draw border + painter.setPen(Qt::NoPen); + painter.setBrush(borderColor); + painter.drawEllipse(center, br, br); + + // draw infill + if (!m_on) fillColor = fillColor.darker(); + painter.setBrush(fillColor); + painter.drawEllipse(center, ir, ir); + + // draw glow + if (m_on) + { + QColor glowColor(m_color); + glowColor.setAlphaF(0.5); + QRadialGradient glowGradient(center, gr, center); + glowGradient.setColorAt(0, glowColor); + glowGradient.setColorAt((r-glowOffset)/r, glowColor); + glowGradient.setColorAt(1, Qt::transparent); + painter.setBrush(glowGradient); + painter.drawEllipse(center, gr, gr); + } + + // draw shine + QRadialGradient shineGradient(center, sr, center-QPoint(sr/2,sr/2)); + shineGradient.setColorAt(0, shineColor); + shineGradient.setColorAt(1, Qt::transparent); + painter.setBrush(shineGradient); + painter.drawEllipse(center, sr, sr); +} diff --git a/src/ledwidget.h b/src/ledwidget.h new file mode 100644 --- /dev/null +++ b/src/ledwidget.h @@ -0,0 +1,61 @@ +/* + Copyright © 2018 Hasan Yavuz Özderya + + This file is part of serialplot. + + ledwidget 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 3 of the License, or + (at your option) any later version. + + ledwidget 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 ledwidget. If not, see . +*/ + +#ifndef LEDWIDGET_H +#define LEDWIDGET_H + +#include +#include +#include + +class LedWidget : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(QColor color MEMBER m_color WRITE setColor NOTIFY colorChanged) + Q_PROPERTY(bool on READ isOn WRITE setOn NOTIFY onChanged) + +public: + explicit LedWidget(QWidget *parent = 0); + + void setColor(QColor ledColor); + bool isOn(); + + QSize sizeHint() const Q_DECL_OVERRIDE; + QSize minimumSizeHint() const Q_DECL_OVERRIDE; + +signals: + void colorChanged(QColor ledColor); + void onChanged(bool on); + +public slots: + void setOn(bool on); + void turnOn(); + void turnOff(); + void toggle(); + +protected: + void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; + +private: + QColor m_color; + bool m_on; +}; + +#endif // LEDWIDGET_H diff --git a/src/linindexbuffer.cpp b/src/linindexbuffer.cpp --- a/src/linindexbuffer.cpp +++ b/src/linindexbuffer.cpp @@ -1,5 +1,5 @@ /* - Copyright © 2017 Hasan Yavuz Özderya + Copyright © 2018 Hasan Yavuz Özderya This file is part of serialplot. @@ -18,12 +18,14 @@ */ #include +#include #include "linindexbuffer.h" LinIndexBuffer::LinIndexBuffer(unsigned n, Range lim) { - Q_ASSERT(n > 0); + // Note that calculation of _step would cause divide by 0 + Q_ASSERT(n > 1); _size = n; setLimits(lim); @@ -50,6 +52,18 @@ void LinIndexBuffer::resize(unsigned n) setLimits(_limits); // called to update `_step` } +int LinIndexBuffer::findIndex(double value) const +{ + if (value < _limits.start || value > _limits.end) + { + return OUT_OF_RANGE; + } + + int r = (value - _limits.start) / _step; + // Note: we are limiting return value because of floating point in-accuracies + return std::min(std::max(r, 0), (_size-1)); +} + void LinIndexBuffer::setLimits(Range lim) { _limits = lim; diff --git a/src/linindexbuffer.h b/src/linindexbuffer.h --- a/src/linindexbuffer.h +++ b/src/linindexbuffer.h @@ -26,17 +26,19 @@ /// intermediate values are calculated linearly. /// /// @note This buffer isn't for storing data. -class LinIndexBuffer : public ResizableBuffer +class LinIndexBuffer : public XFrameBuffer { public: LinIndexBuffer(unsigned n, Range lim); LinIndexBuffer(unsigned n, double min, double max) : LinIndexBuffer(n, {min, max}) {}; - unsigned size() const; - double sample(unsigned i) const; - Range limits() const; - void resize(unsigned n); + unsigned size() const override; + double sample(unsigned i) const override; + Range limits() const override; + void resize(unsigned n) override; + int findIndex(double value) const override; + /// Sets minimum and maximum sample values of the buffer. void setLimits(Range lim); diff --git a/src/main.cpp b/src/main.cpp --- a/src/main.cpp +++ b/src/main.cpp @@ -19,28 +19,64 @@ #include #include +#include #include "mainwindow.h" #include "tooltipfilter.h" #include "version.h" -MainWindow* pMainWindow; +MainWindow* pMainWindow = nullptr; void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { - // TODO: don't call MainWindow::messageHandler if window is destroyed - pMainWindow->messageHandler(type, context, msg); + QString logString; + + switch (type) + { +#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) + case QtInfoMsg: + logString = "[Info] " + msg; + break; +#endif + case QtDebugMsg: + logString = "[Debug] " + msg; + break; + case QtWarningMsg: + logString = "[Warning] " + msg; + break; + case QtCriticalMsg: + logString = "[Error] " + msg; + break; + case QtFatalMsg: + logString = "[Fatal] " + msg; + break; + } + + std::cerr << logString.toStdString() << std::endl; + + if (pMainWindow != nullptr) + { + // TODO: don't call MainWindow::messageHandler if window is destroyed + pMainWindow->messageHandler(type, logString, msg); + } + + if (type == QtFatalMsg) + { + __builtin_trap(); + } } int main(int argc, char *argv[]) { QApplication a(argc, argv); + QApplication::setApplicationName(PROGRAM_NAME); + QApplication::setApplicationVersion(VERSION_STRING); + + qInstallMessageHandler(messageHandler); MainWindow w; pMainWindow = &w; - qInstallMessageHandler(messageHandler); - ToolTipFilter ttf; a.installEventFilter(&ttf); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,5 +1,5 @@ /* - Copyright © 2018 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -29,10 +29,13 @@ #include #include #include +#include +#include #include #include #include #include +#include #include #include @@ -55,7 +58,8 @@ const QMap panelSettingMap {2, "Plot"}, {3, "Commands"}, {4, "Record"}, - {5, "Log"} + {5, "TextView"}, + {6, "Log"} }); MainWindow::MainWindow(QWidget *parent) : @@ -68,7 +72,9 @@ MainWindow::MainWindow(QWidget *parent) commandPanel(&serialPort), dataFormatPanel(&serialPort), recordPanel(&stream), - updateCheckDialog(this) + textView(&stream), + updateCheckDialog(this), + bpsLabel(&portControl, &dataFormatPanel, this) { ui->setupUi(this); @@ -79,6 +85,7 @@ MainWindow::MainWindow(QWidget *parent) ui->tabWidget->insertTab(2, &plotControlPanel, "Plot"); ui->tabWidget->insertTab(3, &commandPanel, "Commands"); ui->tabWidget->insertTab(4, &recordPanel, "Record"); + ui->tabWidget->insertTab(5, &textView, "Text View"); ui->tabWidget->setCurrentIndex(0); auto tbPortControl = portControl.toolBar(); addToolBar(tbPortControl); @@ -169,6 +176,9 @@ MainWindow::MainWindow(QWidget *parent) plotMan, &PlotManager::setYAxis); connect(&plotControlPanel, &PlotControlPanel::xScaleChanged, + &stream, &Stream::setXAxis); + + connect(&plotControlPanel, &PlotControlPanel::xScaleChanged, plotMan, &PlotManager::setXAxis); connect(&plotControlPanel, &PlotControlPanel::plotWidthChanged, @@ -215,6 +225,9 @@ MainWindow::MainWindow(QWidget *parent) plotControlPanel.setChannelInfoModel(stream.infoModel()); // init scales + stream.setXAxis(plotControlPanel.xAxisAsIndex(), + plotControlPanel.xMin(), plotControlPanel.xMax()); + plotMan->setYAxis(plotControlPanel.autoScale(), plotControlPanel.yMin(), plotControlPanel.yMax()); plotMan->setXAxis(plotControlPanel.xAxisAsIndex(), @@ -222,13 +235,21 @@ MainWindow::MainWindow(QWidget *parent) plotMan->setNumOfSamples(numOfSamples); plotMan->setPlotWidth(plotControlPanel.plotWidth()); + // init bps (bits per second) counter + ui->statusBar->addPermanentWidget(&bpsLabel); + // Init sps (sample per second) counter spsLabel.setText("0sps"); - spsLabel.setToolTip("samples per second (per channel)"); + spsLabel.setToolTip(tr("samples per second (per channel)")); ui->statusBar->addPermanentWidget(&spsLabel); connect(&sampleCounter, &SampleCounter::spsChanged, this, &MainWindow::onSpsChanged); + bpsLabel.setMinimumWidth(70); + bpsLabel.setAlignment(Qt::AlignRight); + spsLabel.setMinimumWidth(70); + spsLabel.setAlignment(Qt::AlignRight); + // init demo QObject::connect(ui->actionDemoMode, &QAction::toggled, this, &MainWindow::enableDemo); @@ -242,9 +263,11 @@ MainWindow::MainWindow(QWidget *parent) onSourceChanged(dataFormatPanel.activeSource()); // load default settings - QSettings settings("serialplot", "serialplot"); + QSettings settings(PROGRAM_NAME, PROGRAM_NAME); loadAllSettings(&settings); + handleCommandLineOptions(*QApplication::instance()); + // ensure command panel has 1 command if none loaded if (!commandPanel.numOfCommands()) { @@ -291,7 +314,7 @@ void MainWindow::closeEvent(QCloseEvent } // save settings - QSettings settings("serialplot", "serialplot"); + QSettings settings(PROGRAM_NAME, PROGRAM_NAME); saveAllSettings(&settings); settings.sync(); @@ -346,6 +369,11 @@ void MainWindow::onPortToggled(bool open // make sure demo mode is disabled if (open && isDemoRunning()) enableDemo(false); ui->actionDemoMode->setEnabled(!open); + + if (!open) + { + spsLabel.setText("0sps"); + } } void MainWindow::onSourceChanged(Source* source) @@ -465,44 +493,16 @@ PlotViewSettings MainWindow::viewSetting } void MainWindow::messageHandler(QtMsgType type, - const QMessageLogContext &context, + const QString &logString, const QString &msg) { - QString logString; - - switch (type) - { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) - case QtInfoMsg: - logString = "[Info] " + msg; - break; -#endif - case QtDebugMsg: - logString = "[Debug] " + msg; - break; - case QtWarningMsg: - logString = "[Warning] " + msg; - break; - case QtCriticalMsg: - logString = "[Error] " + msg; - break; - case QtFatalMsg: - logString = "[Fatal] " + msg; - break; - } - - if (ui != NULL) ui->ptLog->appendPlainText(logString); - std::cerr << logString.toStdString() << std::endl; + if (ui != NULL) + ui->ptLog->appendPlainText(logString); if (type != QtDebugMsg && ui != NULL) { ui->statusBar->showMessage(msg, 5000); } - - if (type == QtFatalMsg) - { - __builtin_trap(); - } } void MainWindow::saveAllSettings(QSettings* settings) @@ -515,6 +515,7 @@ void MainWindow::saveAllSettings(QSettin plotMenu.saveSettings(settings); commandPanel.saveSettings(settings); recordPanel.saveSettings(settings); + textView.saveSettings(settings); updateCheckDialog.saveSettings(settings); } @@ -528,6 +529,7 @@ void MainWindow::loadAllSettings(QSettin plotMenu.loadSettings(settings); commandPanel.loadSettings(settings); recordPanel.loadSettings(settings); + textView.loadSettings(settings); updateCheckDialog.loadSettings(settings); } @@ -605,3 +607,56 @@ void MainWindow::onLoadSettings() loadAllSettings(&settings); } } + +void MainWindow::handleCommandLineOptions(const QCoreApplication &app) +{ + QCommandLineParser parser; + parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsCompactedShortOptions); + parser.setApplicationDescription("Small and simple software for plotting data from serial port in realtime."); + parser.addHelpOption(); + parser.addVersionOption(); + + QCommandLineOption configOpt({"c", "config"}, "Load configuration from file.", "filename"); + QCommandLineOption portOpt({"p", "port"}, "Set port name.", "port name"); + QCommandLineOption baudrateOpt({"b" ,"baudrate"}, "Set port baud rate.", "baud rate"); + QCommandLineOption openPortOpt({"o", "open"}, "Open serial port."); + + parser.addOption(configOpt); + parser.addOption(portOpt); + parser.addOption(baudrateOpt); + parser.addOption(openPortOpt); + + parser.process(app); + + if (parser.isSet(configOpt)) + { + QString fileName = parser.value(configOpt); + QFileInfo fileInfo(fileName); + + if (fileInfo.exists() && fileInfo.isFile()) + { + QSettings settings(fileName, QSettings::IniFormat); + loadAllSettings(&settings); + } + else + { + qCritical() << "Configuration file not exist. Closing application."; + std::exit(1); + } + } + + if (parser.isSet(portOpt)) + { + portControl.selectPort(parser.value(portOpt)); + } + + if (parser.isSet(baudrateOpt)) + { + portControl.selectBaudrate(parser.value(baudrateOpt)); + } + + if (parser.isSet(openPortOpt)) + { + portControl.openPort(); + } +} diff --git a/src/mainwindow.h b/src/mainwindow.h --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -1,5 +1,5 @@ /* - Copyright © 2018 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -46,6 +46,8 @@ #include "plotmenu.h" #include "updatecheckdialog.h" #include "samplecounter.h" +#include "datatextview.h" +#include "bpslabel.h" namespace Ui { class MainWindow; @@ -61,8 +63,7 @@ public: PlotViewSettings viewSettings() const; - void messageHandler(QtMsgType type, const QMessageLogContext &context, - const QString &msg); + void messageHandler(QtMsgType type, const QString &logString, const QString &msg); private: Ui::MainWindow *ui; @@ -89,7 +90,11 @@ private: RecordPanel recordPanel; PlotControlPanel plotControlPanel; PlotMenu plotMenu; + DataTextView textView; UpdateCheckDialog updateCheckDialog; + BPSLabel bpsLabel; + + void handleCommandLineOptions(const QCoreApplication &app); /// Returns true if demo is running bool isDemoRunning(); diff --git a/src/plot.cpp b/src/plot.cpp --- a/src/plot.cpp +++ b/src/plot.cpp @@ -1,5 +1,5 @@ /* - Copyright © 2017 Hasan Yavuz Özderya + Copyright © 2018 Hasan Yavuz Özderya This file is part of serialplot. @@ -91,6 +91,11 @@ Plot::~Plot() if (snapshotOverlay != NULL) delete snapshotOverlay; } +void Plot::setDispChannels(QVector channels) +{ + zoomer.setDispChannels(channels); +} + void Plot::setYAxis(bool autoScaled, double yAxisMin, double yAxisMax) { this->isAutoScaled = autoScaled; diff --git a/src/plot.h b/src/plot.h --- a/src/plot.h +++ b/src/plot.h @@ -1,5 +1,5 @@ /* - Copyright © 2017 Hasan Yavuz Özderya + Copyright © 2018 Hasan Yavuz Özderya This file is part of serialplot. @@ -50,6 +50,9 @@ public: Plot(QWidget* parent = 0); ~Plot(); + /// Set displayed channels for value tracking (can be null) + void setDispChannels(QVector channels); + public slots: void showGrid(bool show = true); void showMinorGrid(bool show = true); diff --git a/src/plotcontrolpanel.cpp b/src/plotcontrolpanel.cpp --- a/src/plotcontrolpanel.cpp +++ b/src/plotcontrolpanel.cpp @@ -1,5 +1,5 @@ /* - Copyright © 2018 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -21,10 +21,10 @@ #include #include #include +#include #include -#include "color_selector.hpp" #include "plotcontrolpanel.h" #include "ui_plotcontrolpanel.h" #include "setting_defines.h" @@ -162,9 +162,9 @@ PlotControlPanel::PlotControlPanel(QWidg this, SLOT(onRangeSelected())); // color selector starts disabled until a channel is selected - ui->colorSelector->setColor(QColor(0,0,0,0)); - ui->colorSelector->setDisplayMode(color_widgets::ColorPreview::AllAlpha); - ui->colorSelector->setDisabled(true); + ui->pbColorSel->setDisabled(true); + setSelectorColor(QColor(0,0,0,0)); + connect(ui->pbColorSel, &QPushButton::clicked, this, &PlotControlPanel::onColorSelect); // reset buttons resetAct.setToolTip(tr("Reset channel names and colors")); @@ -241,6 +241,30 @@ bool PlotControlPanel::askNSConfirmation return mb.exec() == QMessageBox::Apply; } +void PlotControlPanel::setSelectorColor(QColor color) +{ + ui->pbColorSel->setStyleSheet(QString("background-color: %1;").arg(color.name())); +} + +void PlotControlPanel::onColorSelect() +{ + auto selection = ui->tvChannelInfo->selectionModel()->currentIndex(); + // no selection + if (!selection.isValid()) return; + + // current color + auto model = ui->tvChannelInfo->model(); + QColor color = model->data(selection, Qt::ForegroundRole).value(); + + // show dialog + color = QColorDialog::getColor(color, this); + + if (color.isValid()) // color is set to invalid if user cancels + { + ui->tvChannelInfo->model()->setData(selection, color, Qt::ForegroundRole); + } +} + void PlotControlPanel::onAutoScaleChecked(bool checked) { if (checked) @@ -373,19 +397,16 @@ void PlotControlPanel::setChannelInfoMod if (current.isValid()) { - ui->colorSelector->setEnabled(true); + ui->pbColorSel->setEnabled(true); auto model = ui->tvChannelInfo->model(); color = model->data(current, Qt::ForegroundRole).value(); } else { - ui->colorSelector->setDisabled(true); + ui->pbColorSel->setDisabled(true); } - // temporarily block signals because `setColor` emits `colorChanged` - bool wasBlocked = ui->colorSelector->blockSignals(true); - ui->colorSelector->setColor(color); - ui->colorSelector->blockSignals(wasBlocked); + setSelectorColor(color); }); connect(ui->tvChannelInfo->selectionModel(), &QItemSelectionModel::selectionChanged, @@ -393,22 +414,11 @@ void PlotControlPanel::setChannelInfoMod { if (!selected.length()) { - ui->colorSelector->setDisabled(true); - - // temporarily block signals because `setColor` emits `colorChanged` - bool wasBlocked = ui->colorSelector->blockSignals(true); - ui->colorSelector->setColor(QColor(0,0,0,0)); - ui->colorSelector->blockSignals(wasBlocked); + ui->pbColorSel->setDisabled(true); + setSelectorColor(QColor(0,0,0,0)); } }); - connect(ui->colorSelector, &color_widgets::ColorSelector::colorChanged, - [this](QColor color) - { - auto index = ui->tvChannelInfo->selectionModel()->currentIndex(); - ui->tvChannelInfo->model()->setData(index, color, Qt::ForegroundRole); - }); - connect(model, &QAbstractItemModel::dataChanged, [this](const QModelIndex & topLeft, const QModelIndex & bottomRight, const QVector & roles = QVector ()) { @@ -419,11 +429,7 @@ void PlotControlPanel::setChannelInfoMod auto mod = ui->tvChannelInfo->model(); QColor color = mod->data(current, Qt::ForegroundRole).value(); - - // temporarily block signals because `setColor` emits `colorChanged` - bool wasBlocked = ui->colorSelector->blockSignals(true); - ui->colorSelector->setColor(color); - ui->colorSelector->blockSignals(wasBlocked); + setSelectorColor(color); }); // reset actions diff --git a/src/plotcontrolpanel.h b/src/plotcontrolpanel.h --- a/src/plotcontrolpanel.h +++ b/src/plotcontrolpanel.h @@ -1,5 +1,5 @@ /* - Copyright © 2018 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -79,6 +79,9 @@ private: /// Show a confirmation dialog before setting #samples to a big value bool askNSConfirmation(int value); + /// Set the color displayed by color selector button + void setSelectorColor(QColor color); + private slots: void onNumOfSamples(int value); void onAutoScaleChecked(bool checked); @@ -87,6 +90,7 @@ private slots: void onIndexChecked(bool checked); void onXScaleChanged(); void onPlotWidthChanged(); + void onColorSelect(); }; #endif // PLOTCONTROLPANEL_H diff --git a/src/plotcontrolpanel.ui b/src/plotcontrolpanel.ui --- a/src/plotcontrolpanel.ui +++ b/src/plotcontrolpanel.ui @@ -7,7 +7,7 @@ 0 0 704 - 195 + 220 @@ -69,9 +69,9 @@ QLayout::SetMaximumSize - + - + 0 0 @@ -88,6 +88,15 @@ 20 + + background-color: rgb(70, 141, 255); + + + + + + false + @@ -411,14 +420,6 @@ - - - color_widgets::ColorSelector - QWidget -
color_selector.hpp
- 1 -
-
diff --git a/src/plotmanager.cpp b/src/plotmanager.cpp --- a/src/plotmanager.cpp +++ b/src/plotmanager.cpp @@ -1,5 +1,5 @@ /* - Copyright © 2018 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -19,7 +19,6 @@ #include #include -#include #include "qwt_symbol.h" #include "plot.h" @@ -31,9 +30,9 @@ PlotManager::PlotManager(QWidget* plotAr const Stream* stream, QObject* parent) : QObject(parent) { + _stream = stream; construct(plotArea, menu); - _stream = stream; - if (_stream == NULL) return; + if (_stream == nullptr) return; // connect to ChannelInfoModel infoModel = _stream->infoModel(); @@ -53,15 +52,15 @@ PlotManager::PlotManager(QWidget* plotAr // add initial curves if any? for (unsigned int i = 0; i < stream->numChannels(); i++) { - addCurve(stream->channel(i)->name(), stream->channel(i)->yData()); + addCurve(stream->channel(i)->name(), stream->channel(i)->xData(), stream->channel(i)->yData()); } - } PlotManager::PlotManager(QWidget* plotArea, PlotMenu* menu, Snapshot* snapshot, QObject *parent) : QObject(parent) { + _stream = nullptr; construct(plotArea, menu); setNumOfSamples(snapshot->numSamples()); @@ -70,11 +69,14 @@ PlotManager::PlotManager(QWidget* plotAr for (unsigned ci = 0; ci < snapshot->numChannels(); ci++) { - addCurve(snapshot->channelName(ci), snapshot->yData[ci]); + addCurve(snapshot->channelName(ci), snapshot->xData[ci], snapshot->yData[ci]); } connect(infoModel, &QAbstractItemModel::dataChanged, this, &PlotManager::onChannelInfoChanged); + + // TODO: remove when snapshot view supports multi display + checkNoVisChannels(); } void PlotManager::construct(QWidget* plotArea, PlotMenu* menu) @@ -94,8 +96,6 @@ void PlotManager::construct(QWidget* plo // initalize layout and single widget isMulti = false; scrollArea = NULL; - setupLayout(isMulti); - addPlotWidget(); // connect to menu connect(menu, &PlotMenu::symbolShowChanged, this, &PlotManager:: setSymbols); @@ -148,7 +148,7 @@ void PlotManager::onNumChannelsChanged(u // add new channels for (unsigned int i = oldNum; i < numOfChannels; i++) { - addCurve(_stream->channel(i)->name(), _stream->channel(i)->yData()); + addCurve(_stream->channel(i)->name(), _stream->channel(i)->xData(), _stream->channel(i)->yData()); } } else if(numOfChannels < oldNum) @@ -217,8 +217,6 @@ void PlotManager::checkNoVisChannels() void PlotManager::setMulti(bool enabled) { - if (enabled == isMulti) return; - isMulti = enabled; // detach all curves @@ -239,11 +237,14 @@ void PlotManager::setMulti(bool enabled) if (isMulti) { // add new widgets and attach + int i = 0; for (auto curve : curves) { auto plot = addPlotWidget(); plot->setVisible(curve->isVisible()); + plot->setDispChannels(QVector(1, _stream->channel(i))); curve->attach(plot); + i++; } } else @@ -251,6 +252,11 @@ void PlotManager::setMulti(bool enabled) // add a single widget auto plot = addPlotWidget(); + if (_stream != nullptr) + { + plot->setDispChannels(_stream->allChannels()); + } + // attach all curves for (auto curve : curves) { @@ -332,11 +338,10 @@ Plot* PlotManager::addPlotWidget() return plot; } -void PlotManager::addCurve(QString title, const FrameBuffer* buffer) +void PlotManager::addCurve(QString title, const XFrameBuffer* xBuf, const FrameBuffer* yBuf) { auto curve = new QwtPlotCurve(title); - auto series = new FrameBufferSeries(buffer); - series->setXAxis(_xAxisAsIndex, _xMin, _xMax); + auto series = new FrameBufferSeries(xBuf, yBuf); curve->setSamples(series); _addCurve(curve); } @@ -362,6 +367,20 @@ void PlotManager::_addCurve(QwtPlotCurve plot = plotWidgets[0]; } + if (_stream != nullptr) // not displaying snapshot + { + QVector dispChannels; + if (isMulti) + { + dispChannels = QVector(1, _stream->channel(index)); + } + else + { + dispChannels = _stream->allChannels(); + } + plot->setDispChannels(dispChannels); + } + // show the curve curve->attach(plot); plot->replot(); @@ -369,6 +388,16 @@ void PlotManager::_addCurve(QwtPlotCurve void PlotManager::removeCurves(unsigned number) { + if (_stream != nullptr) // not displaying snapshot + { + if (! isMulti) + { + QVector dispChannels; + dispChannels = _stream->allChannels(); + plotWidgets[0]->setDispChannels(dispChannels); + } + } + for (unsigned i = 0; i < number; i++) { if (!curves.isEmpty()) @@ -481,10 +510,13 @@ void PlotManager::setXAxis(bool asIndex, _xAxisAsIndex = asIndex; _xMin = xMin; _xMax = xMax; + + int ci = 0; for (auto curve : curves) { FrameBufferSeries* series = static_cast(curve->data()); - series->setXAxis(asIndex, xMin, xMax); + series->setX(_stream->channel(ci)->xData()); + ci++; } for (auto plot : plotWidgets) { diff --git a/src/plotmanager.h b/src/plotmanager.h --- a/src/plotmanager.h +++ b/src/plotmanager.h @@ -41,7 +41,7 @@ class PlotManager : public QObject public: explicit PlotManager(QWidget* plotArea, PlotMenu* menu, - const Stream* stream = NULL, + const Stream* stream = nullptr, QObject *parent = 0); explicit PlotManager(QWidget* plotArea, PlotMenu* menu, Snapshot* snapshot, @@ -49,7 +49,7 @@ public: ~PlotManager(); /// Add a new curve with title and buffer. A color is /// automatically chosen for curve. - void addCurve(QString title, const FrameBuffer* buffer); + void addCurve(QString title, const XFrameBuffer* xBuf, const FrameBuffer* yBuf); /// Removes curves from the end void removeCurves(unsigned number); /// Returns current number of curves known by plot manager @@ -82,7 +82,7 @@ private: QList curves; QList plotWidgets; Plot* emptyPlot; ///< for displaying when all channels are hidden - const Stream* _stream; ///< attached stream, can be `NULL` + const Stream* _stream; ///< attached stream, can be `nullptr` const ChannelInfoModel* infoModel; bool isDemoShown; bool _autoScaled; diff --git a/src/plotmenu.cpp b/src/plotmenu.cpp --- a/src/plotmenu.cpp +++ b/src/plotmenu.cpp @@ -1,5 +1,5 @@ /* - Copyright © 2017 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -66,7 +66,7 @@ PlotMenu::PlotMenu(QWidget* parent) : setSymbolsMenu.addAction(&setSymbolsAutoAct); setSymbolsAutoAct.setCheckable(true); setSymbolsAutoAct.setChecked(true); - connect(&setSymbolsAutoAct, SELECT::OVERLOAD_OF(&QAction::triggered), + connect(&setSymbolsAutoAct, SELECT::OVERLOAD_OF(&QAction::toggled), [this](bool checked) { if (checked) emit symbolShowChanged(Plot::ShowSymbolsAuto); @@ -74,18 +74,18 @@ PlotMenu::PlotMenu(QWidget* parent) : setSymbolsMenu.addAction(&setSymbolsShowAct); setSymbolsShowAct.setCheckable(true); - connect(&setSymbolsShowAct, SELECT::OVERLOAD_OF(&QAction::triggered), + connect(&setSymbolsShowAct, SELECT::OVERLOAD_OF(&QAction::toggled), [this](bool checked) { - if (checked) symbolShowChanged(Plot::ShowSymbolsShow); + if (checked) emit symbolShowChanged(Plot::ShowSymbolsShow); }); setSymbolsMenu.addAction(&setSymbolsHideAct); setSymbolsHideAct.setCheckable(true); - connect(&setSymbolsHideAct, SELECT::OVERLOAD_OF(&QAction::triggered), + connect(&setSymbolsHideAct, SELECT::OVERLOAD_OF(&QAction::toggled), [this](bool checked) { - if (checked) symbolShowChanged(Plot::ShowSymbolsHide); + if (checked) emit symbolShowChanged(Plot::ShowSymbolsHide); }); // add symbol actions to same group so that they appear as radio buttons @@ -202,20 +202,17 @@ void PlotMenu::loadSettings(QSettings* s QString showSymbolsStr = settings->value(SG_Plot_Symbols, QString()).toString(); if (showSymbolsStr == "auto") { - // setSymbols(Plot::ShowSymbolsAuto); setSymbolsAutoAct.setChecked(true); } else if (showSymbolsStr == "show") { - // setSymbols(Plot::ShowSymbolsShow); setSymbolsShowAct.setChecked(true); } else if (showSymbolsStr == "hide") { - // setSymbols(Plot::ShowSymbolsHide); setSymbolsHideAct.setChecked(true); } - else + else if (!showSymbolsStr.isEmpty()) { qCritical() << "Invalid symbol setting:" << showSymbolsStr; } diff --git a/src/portcontrol.cpp b/src/portcontrol.cpp --- a/src/portcontrol.cpp +++ b/src/portcontrol.cpp @@ -1,5 +1,5 @@ /* - Copyright © 2017 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -79,10 +79,10 @@ PortControl::PortControl(QSerialPort* po this, &PortControl::onTbPortListActivated); QObject::connect(ui->cbPortList, SELECT::OVERLOAD_OF(&QComboBox::activated), - this, &PortControl::selectPort); + this, &PortControl::selectListedPort); QObject::connect(&tbPortList, SELECT::OVERLOAD_OF(&QComboBox::activated), - this, &PortControl::selectPort); + this, &PortControl::selectListedPort); // setup buttons ui->pbOpenPort->setDefaultAction(&openAction); @@ -91,7 +91,7 @@ PortControl::PortControl(QSerialPort* po // setup baud rate selection widget QObject::connect(ui->cbBaudRate, SELECT::OVERLOAD_OF(&QComboBox::activated), - this, &PortControl::selectBaudRate); + this, &PortControl::_selectBaudRate); // setup parity selection buttons parityButtons.addButton(ui->rbNoParity, (int) QSerialPort::NoParity); @@ -198,7 +198,7 @@ void PortControl::loadBaudRateList() } } -void PortControl::selectBaudRate(QString baudRate) +void PortControl::_selectBaudRate(QString baudRate) { if (serialPort->isOpen()) { @@ -289,7 +289,7 @@ void PortControl::togglePort() if (serialPort->open(QIODevice::ReadWrite)) { // set port settings - selectBaudRate(ui->cbBaudRate->currentText()); + _selectBaudRate(ui->cbBaudRate->currentText()); selectParity((QSerialPort::Parity) parityButtons.checkedId()); selectDataBits((QSerialPort::DataBits) dataBitsButtons.checkedId()); selectStopBits((QSerialPort::StopBits) stopBitsButtons.checkedId()); @@ -310,10 +310,17 @@ void PortControl::togglePort() openAction.setChecked(serialPort->isOpen()); } -void PortControl::selectPort(QString portName) +void PortControl::selectListedPort(QString portName) { // portName may be coming from combobox portName = portName.split(" ")[0]; + + QSerialPortInfo portInfo(portName); + if (portInfo.isNull()) + { + qWarning() << "Device doesn't exists:" << portName; + } + // has selection actually changed if (portName != serialPort->portName()) { @@ -366,6 +373,13 @@ void PortControl::onTbPortListActivated( void PortControl::onPortError(QSerialPort::SerialPortError error) { +#ifdef Q_OS_UNIX + // For suppressing "Invalid argument" errors that happens with pseudo terminals + auto isPtsInvalidArgErr = [this] () -> bool { + return serialPort->portName().contains("pts/") && serialPort->errorString().contains("Invalid argument"); + }; +#endif + switch(error) { case QSerialPort::NoError : @@ -408,12 +422,22 @@ required privileges or device is already qCritical() << "An error occurred while reading data."; break; case QSerialPort::UnsupportedOperationError: +#ifdef Q_OS_UNIX + // Qt 5.5 gives "Invalid argument" with 'UnsupportedOperationError' + if (isPtsInvalidArgErr()) + break; +#endif qCritical() << "Operation is not supported."; break; case QSerialPort::TimeoutError: qCritical() << "A timeout error occurred."; break; case QSerialPort::UnknownError: +#ifdef Q_OS_UNIX + // Qt 5.2 gives "Invalid argument" with 'UnknownError' + if (isPtsInvalidArgErr()) + break; +#endif qCritical() << "Unknown error! Error: " << serialPort->errorString(); break; default: @@ -453,6 +477,64 @@ QString PortControl::currentFlowControlT } } +void PortControl::selectPort(QString portName) +{ + int portIndex = portList.indexOfName(portName); + if (portIndex < 0) // not in list, add to model and update the selections + { + portList.appendRow(new PortListItem(portName)); + portIndex = portList.rowCount()-1; + } + + ui->cbPortList->setCurrentIndex(portIndex); + tbPortList.setCurrentIndex(portIndex); + + selectListedPort(portName); +} + +void PortControl::selectBaudrate(QString baudRate) +{ + int baudRateIndex = ui->cbBaudRate->findText(baudRate); + if (baudRateIndex < 0) + { + ui->cbBaudRate->setCurrentText(baudRate); + } + else + { + ui->cbBaudRate->setCurrentIndex(baudRateIndex); + } + _selectBaudRate(baudRate); +} + +void PortControl::openPort() +{ + if (!serialPort->isOpen()) + { + openAction.trigger(); + } +} + +unsigned PortControl::maxBitRate() const +{ + float baud = serialPort->baudRate(); + float dataBits = serialPort->dataBits(); + float parityBits = serialPort->parity() == QSerialPort::NoParity ? 0 : 1; + + float stopBits; + if (serialPort->stopBits() == QSerialPort::OneAndHalfStop) + { + stopBits = 1.5; + } + else + { + stopBits = serialPort->stopBits(); + } + + float frame_size = 1 /* start bit */ + dataBits + parityBits + stopBits; + + return float(baud) / frame_size; +} + void PortControl::saveSettings(QSettings* settings) { settings->beginGroup(SettingGroup_Port); @@ -494,7 +576,7 @@ void PortControl::loadSettings(QSettings parityButtons.button(paritySetting)->setChecked(true); // load number of bits - int dataBits = settings->value(SG_Port_Parity, dataBitsButtons.checkedId()).toInt(); + int dataBits = settings->value(SG_Port_DataBits, dataBitsButtons.checkedId()).toInt(); if (dataBits >=5 && dataBits <= 8) { dataBitsButtons.button((QSerialPort::DataBits) dataBits)->setChecked(true); diff --git a/src/portcontrol.h b/src/portcontrol.h --- a/src/portcontrol.h +++ b/src/portcontrol.h @@ -1,5 +1,5 @@ /* - Copyright © 2017 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -47,6 +47,12 @@ public: QSerialPort* serialPort; QToolBar* toolBar(); + void selectPort(QString portName); + void selectBaudrate(QString baudRate); + void openPort(); + /// Returns maximum bit rate for current baud rate + unsigned maxBitRate() const; + /// Stores port settings into a `QSettings` void saveSettings(QSettings* settings); /// Loads port settings from a `QSettings`. If open serial port is closed. @@ -76,19 +82,18 @@ private: /// Returns currently selected flow control as text to be saved in settings QString currentFlowControlText(); -public slots: +private slots: void loadPortList(); void loadBaudRateList(); void togglePort(); - void selectPort(QString portName); + void selectListedPort(QString portName); - void selectBaudRate(QString baudRate); + void _selectBaudRate(QString baudRate); void selectParity(int parity); // parity must be one of QSerialPort::Parity void selectDataBits(int dataBits); // bits must be one of QSerialPort::DataBits void selectStopBits(int stopBits); // stopBits must be one of QSerialPort::StopBits void selectFlowControl(int flowControl); // flowControl must be one of QSerialPort::FlowControl -private slots: void openActionTriggered(bool checked); void onCbPortListActivated(int index); void onTbPortListActivated(int index); diff --git a/src/setting_defines.h b/src/setting_defines.h --- a/src/setting_defines.h +++ b/src/setting_defines.h @@ -1,5 +1,5 @@ /* - Copyright © 2018 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -30,6 +30,7 @@ const char SettingGroup_Channels[] = "Ch const char SettingGroup_Plot[] = "Plot"; const char SettingGroup_Commands[] = "Commands"; const char SettingGroup_Record[] = "Record"; +const char SettingGroup_TextView[] = "TextView"; const char SettingGroup_UpdateCheck[] = "UpdateCheck"; // mainwindow setting keys @@ -112,6 +113,10 @@ const char SG_Record_Separator[] const char SG_Record_DisableBuffering[] = "disableBuffering"; const char SG_Record_Timestamp[] = "timestamp"; +// text view settings keys +const char SG_TextView_NumLines[] = "numLines"; +const char SG_TextView_Decimals[] = "decimals"; + // update check settings keys const char SG_UpdateCheck_Periodic[] = "periodicCheck"; const char SG_UpdateCheck_LastCheck[] = "lastCheck"; diff --git a/src/snapshot.h b/src/snapshot.h --- a/src/snapshot.h +++ b/src/snapshot.h @@ -28,6 +28,7 @@ #include "channelinfomodel.h" #include "readonlybuffer.h" +#include "indexbuffer.h" class SnapshotView; class MainWindow; @@ -40,7 +41,8 @@ public: Snapshot(MainWindow* parent, QString name, ChannelInfoModel infoModel, bool saved = false); ~Snapshot(); - // TODO: yData of snapshot shouldn't be public, preferable should be handled in constructor + // TODO: yData and xData of snapshot shouldn't be public, preferable should be handled in constructor + QVector xData; QVector yData; QAction* showAction(); QAction* deleteAction(); diff --git a/src/snapshotmanager.cpp b/src/snapshotmanager.cpp --- a/src/snapshotmanager.cpp +++ b/src/snapshotmanager.cpp @@ -1,5 +1,5 @@ /* - Copyright © 2018 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -71,6 +71,7 @@ Snapshot* SnapshotManager::makeSnapshot( for (unsigned ci = 0; ci < _stream->numChannels(); ci++) { + snapshot->xData.append(new IndexBuffer(_stream->numSamples())); snapshot->yData.append(new ReadOnlyBuffer(_stream->channel(ci)->yData())); } @@ -156,8 +157,16 @@ void SnapshotManager::loadSnapshotFromFi QTextStream ts(&file); QString line; unsigned lineNum = 1; + +#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) while (ts.readLineInto(&line)) { +#else + while (true) + { + line = ts.readLine(); + if (line.isNull()) break; +#endif // parse line auto split = line.split(','); @@ -194,6 +203,7 @@ void SnapshotManager::loadSnapshotFromFi for (unsigned ci = 0; ci < numOfChannels; ci++) { + snapshot->xData.append(new IndexBuffer(data[ci].size())); snapshot->yData.append(new ReadOnlyBuffer(data[ci].data(), data[ci].size())); } diff --git a/src/stream.cpp b/src/stream.cpp --- a/src/stream.cpp +++ b/src/stream.cpp @@ -20,6 +20,7 @@ #include "stream.h" #include "ringbuffer.h" #include "indexbuffer.h" +#include "linindexbuffer.h" Stream::Stream(unsigned nc, bool x, unsigned ns) : _infoModel(nc) @@ -27,15 +28,20 @@ Stream::Stream(unsigned nc, bool x, unsi _numSamples = ns; _paused = false; + xAsIndex = true; + xMin = 0; + xMax = 1; + // create xdata buffer _hasx = x; if (x) { - xData = new RingBuffer(ns); + // TODO: implement XRingBuffer (binary search) + Q_ASSERT(false); } else { - xData = new IndexBuffer(ns); + xData = makeXBuffer(); } // create channels @@ -81,6 +87,16 @@ StreamChannel* Stream::channel(unsigned return const_cast(static_cast(*this).channel(index)); } +QVector Stream::allChannels() const +{ + QVector result(numChannels()); + for (unsigned ci = 0; ci < numChannels(); ci++) + { + result[ci] = channel(ci); + } + return result; +} + const ChannelInfoModel* Stream::infoModel() const { return &_infoModel; @@ -118,11 +134,12 @@ void Stream::setNumChannels(unsigned nc, { if (x) { - xData = new RingBuffer(_numSamples); + // TODO: implement XRingBuffer (binary search) + Q_ASSERT(false); } else { - xData = new IndexBuffer(_numSamples); + xData = makeXBuffer(); } for (auto c : channels) @@ -142,6 +159,18 @@ void Stream::setNumChannels(unsigned nc, Sink::setNumChannels(nc, x); } +XFrameBuffer* Stream::makeXBuffer() const +{ + if (xAsIndex) + { + return new IndexBuffer(_numSamples); + } + else + { + return new LinIndexBuffer(_numSamples, xMin, xMax); + } +} + const SamplePack* Stream::applyGainOffset(const SamplePack& pack) const { Q_ASSERT(infoModel()->gainOrOffsetEn()); @@ -191,7 +220,9 @@ void Stream::feedIn(const SamplePack& pa unsigned ns = pack.numSamples(); if (_hasx) { - static_cast(xData)->addSamples(pack.xData(), ns); + // TODO: implement XRingBuffer (binary search) + Q_ASSERT(false); + // static_cast(xData)->addSamples(pack.xData(), ns); } // modified pack that gain and offset is applied to @@ -237,6 +268,24 @@ void Stream::setNumSamples(unsigned valu } } +void Stream::setXAxis(bool asIndex, double min, double max) +{ + xAsIndex = asIndex; + xMin = min; + xMax = max; + + // Note that x axis scaling is ignored when X is provided from source as data + // TODO: assert (UI options for x axis should be disabled) + if (!hasX()) + { + xData = makeXBuffer(); + for (auto c : channels) + { + c->setX(xData); + } + } +} + void Stream::saveSettings(QSettings* settings) const { _infoModel.saveSettings(settings); diff --git a/src/stream.h b/src/stream.h --- a/src/stream.h +++ b/src/stream.h @@ -48,16 +48,16 @@ public: * @param x has X data input * @param ns number of samples */ - Stream(unsigned nc = 0, bool x = false, unsigned ns = 0); + Stream(unsigned nc = 1, bool x = false, unsigned ns = 2); ~Stream(); - // implementations for `Source` - virtual bool hasX() const; - virtual unsigned numChannels() const; + bool hasX() const; + unsigned numChannels() const; unsigned numSamples() const; const StreamChannel* channel(unsigned index) const; StreamChannel* channel(unsigned index); + QVector allChannels() const; const ChannelInfoModel* infoModel() const; ChannelInfoModel* infoModel(); @@ -79,10 +79,13 @@ signals: void dataAdded(); ///< emitted when data added to channel man. public slots: - // TODO: these won't be public - // void setNumChannels(unsigned number); + /// Change number of samples (buffer size) void setNumSamples(unsigned value); + /// Change X axis style + /// @note Ignored when X is provided by source (hasX == true) + void setXAxis(bool asIndex, double min, double max); + /// When paused data feed is ignored void pause(bool paused); @@ -94,11 +97,14 @@ private: bool _paused; bool _hasx; - ResizableBuffer* xData; + XFrameBuffer* xData; QList channels; ChannelInfoModel _infoModel; + bool xAsIndex; + double xMin, xMax; + /** * Applies gain and offset to given pack. * @@ -111,6 +117,9 @@ private: * @return modified data */ const SamplePack* applyGainOffset(const SamplePack& pack) const; + + /// Returns a new virtual X buffer for settings + XFrameBuffer* makeXBuffer() const; }; diff --git a/src/streamchannel.cpp b/src/streamchannel.cpp --- a/src/streamchannel.cpp +++ b/src/streamchannel.cpp @@ -17,9 +17,10 @@ along with serialplot. If not, see . */ +#include #include "streamchannel.h" -StreamChannel::StreamChannel(unsigned i, const FrameBuffer* x, +StreamChannel::StreamChannel(unsigned i, const XFrameBuffer* x, FrameBuffer* y, ChannelInfoModel* info) { _index = i; @@ -37,8 +38,37 @@ unsigned StreamChannel::index() const {r QString StreamChannel::name() const {return _info->name(_index);}; QColor StreamChannel::color() const {return _info->color(_index);}; bool StreamChannel::visible() const {return _info->isVisible(_index);}; -const FrameBuffer* StreamChannel::xData() const {return _x;} +const XFrameBuffer* StreamChannel::xData() const {return _x;} const FrameBuffer* StreamChannel::yData() const {return _y;} FrameBuffer* StreamChannel::yData() {return _y;} const ChannelInfoModel* StreamChannel::info() const {return _info;} -void StreamChannel::setX(const FrameBuffer* x) {_x = x;}; +void StreamChannel::setX(const XFrameBuffer* x) {_x = x;}; + +double StreamChannel::findValue(double x) const +{ + int index = _x->findIndex(x); + Q_ASSERT(index < (int) _x->size()); + + if (index >= 0) + { + // can't do estimation for last sample + if (index == (int) _x->size() - 1) + { + return _y->sample(index); + } + else + { + // calculate middle of the line + double prev_x = _x->sample(index); + double next_x = _x->sample(index+1); + double ratio = (x - prev_x) / (next_x - prev_x); + double prev_y = _y->sample(index); + double next_y = _y->sample(index+1); + return ratio * (next_y - prev_y) + prev_y; + } + } + else + { + return std::numeric_limits::quiet_NaN(); + } +} diff --git a/src/streamchannel.h b/src/streamchannel.h --- a/src/streamchannel.h +++ b/src/streamchannel.h @@ -35,7 +35,7 @@ public: * @param info channel info model */ StreamChannel(unsigned i, - const FrameBuffer* x, + const XFrameBuffer* x, FrameBuffer* y, ChannelInfoModel* info); ~StreamChannel(); @@ -44,15 +44,23 @@ public: QString name() const; QColor color() const; bool visible() const; - const FrameBuffer* xData() const; + const XFrameBuffer* xData() const; FrameBuffer* yData(); const FrameBuffer* yData() const; const ChannelInfoModel* info() const; - void setX(const FrameBuffer* x); + void setX(const XFrameBuffer* x); + + /** + * Returns sample value for `x`. + * + * If `x` is out of range `NaN` is returned. A calculated (linear) + * value is returned when `x` is in between two data points. + */ + double findValue(double x) const; private: unsigned _index; - const FrameBuffer* _x; + const XFrameBuffer* _x; FrameBuffer* _y; ChannelInfoModel* _info; }; diff --git a/src/updatechecker.cpp b/src/updatechecker.cpp --- a/src/updatechecker.cpp +++ b/src/updatechecker.cpp @@ -118,7 +118,7 @@ bool UpdateChecker::parseData(const QJso if (!data.isObject()) return false; - auto values = data.object()["values"]; + auto values = data.object().value("values"); if (values == QJsonValue::Undefined || !values.isArray()) return false; for (auto value : values.toArray()) diff --git a/src/zoomer.cpp b/src/zoomer.cpp --- a/src/zoomer.cpp +++ b/src/zoomer.cpp @@ -1,5 +1,5 @@ /* - Copyright © 2017 Hasan Yavuz Özderya + Copyright © 2020 Hasan Yavuz Özderya This file is part of serialplot. @@ -19,9 +19,13 @@ #include "zoomer.h" #include -#include +#include +#include +#include +#include -#include +static const int VALUE_POINT_DIAM = 4; +static const int VALUE_TEXT_MARGIN = VALUE_POINT_DIAM + 2; Zoomer::Zoomer(QWidget* widget, bool doReplot) : ScrollZoomer(widget) @@ -59,6 +63,11 @@ void Zoomer::zoom( const QRectF & rect) ScrollZoomer::zoom(rect); } +void Zoomer::setDispChannels(QVector channels) +{ + dispChannels = channels; +} + QwtText Zoomer::trackerTextF(const QPointF& pos) const { QwtText b = ScrollZoomer::trackerTextF(pos); @@ -102,6 +111,225 @@ QRegion Zoomer::rubberBandMask() const return QRegion(r); } +void Zoomer::drawTracker(QPainter* painter) const +{ + if (isActive()) + { + QwtPlotZoomer::drawTracker(painter); + } + else if (dispChannels.length()) + { + drawValues(painter); + } +} + +QList Zoomer::visChannels() const +{ + QList result; + + for (unsigned ci = 0; ci < (unsigned) dispChannels.length(); ci++) + { + if (dispChannels[ci]->visible()) + result.append(dispChannels[ci]); + } + + return result; +} + +const double ValueLabelHeight = 12; // TODO: calculate + +struct ChannelValue +{ + const StreamChannel* ch; + double value; + double y; + double top() const {return y;}; + double bottom() const {return y + ValueLabelHeight;}; +}; + +static void layoutValues(QList& values) +{ + typedef ChannelValue LayItem; + typedef QList LayItemList; + + struct LayGroup + { + struct VRange {double top, bottom;}; + LayItemList items; + LayGroup(LayItem* initialItem) {items.append(initialItem);} + unsigned numItems() const {return items.size();} + double top() const {return items.first()->top();} + double bottom() const {return items.last()->bottom();} + VRange vRange() const {return {top(), bottom()};} + double overlap(const LayGroup* otrGroup) const + { + auto myr = vRange(); + auto otr = otrGroup->vRange(); + + double a = myr.bottom - otr.top; + double b = otr.bottom - myr.top; + if (a > 0 and b > 0) + { + return std::min(a, b); + } + return 0; + } + void moveBy(double y) {for (auto it : items) it->y += y;} + void join(LayGroup* other) + { + // assumes other group is below this one and they overlap + double ovr_h = overlap(other); + + // groups are moved less if they have more items and vice versa + double ratio = double(numItems()) / double(numItems() + other->numItems()); + double self_off = ovr_h * (1. - ratio); + + // make sure we don't go out of screen (above) after the shift + double final_top = top() - self_off; + if (final_top < 0) + self_off += final_top; + + // move groups + moveBy(-self_off); // up + other->moveBy(ovr_h - self_off); // down + + // finalize the merge by gettin items from other + do + { + items.append(other->items.takeFirst()); + } while (!other->items.isEmpty()); + } + }; + + // create initial groups (1 group per item) + QList groups; + for (auto& val : values) + groups.append(new LayGroup(&val)); + + // sort groups according to their items position + struct { + bool operator()(LayGroup* a, LayGroup* b) const + { + return a->top() < b->top(); + } + } compTops; + + std::sort(groups.begin(), groups.end(), compTops); + + // do spacing + bool somethingOverlaps = true; + while (somethingOverlaps and groups.size() > 1) + { + somethingOverlaps = false; + for (int i = 0; i < groups.size() - 1; i++) + { + auto a = groups[i]; + auto b = groups[i + 1]; + + // make sure nothing is over the top + if (a->top() < 0) + a->moveBy(-a->top()); + + // join if groups overlap + if (a->overlap(b)) + { + somethingOverlaps = true; + a->join(b); + delete groups.takeAt(i + 1); + break; + } + } + } + + // cleanup + do + { + delete groups.takeFirst(); + } while (!groups.isEmpty()); +}; + +void Zoomer::drawValues(QPainter* painter) const +{ + auto tpos = trackerPosition(); + if (tpos.x() < 0) return; // cursor not on window + + // find Y values for current cursor X position + double x = invTransform(tpos).x(); + auto channels = visChannels(); + QList values; + for (auto ch : channels) + { + double value = ch->findValue(x); + if (!std::isnan(value)) + { + auto point = transform(QPointF(x, value)); + values.append({ch, value, double(point.y())}); + } + } + + // TODO should keep? + if (values.isEmpty()) + { + return; + } + + layoutValues(values); + + painter->save(); + + // draw vertical line + auto linePen = rubberBandPen(); + linePen.setStyle(Qt::DotLine); + painter->setPen(linePen); + const QRect pRect = pickArea().boundingRect().toRect(); + int px = tpos.x(); + painter->drawLine(px, pRect.top(), px, pRect.bottom()); + + // draw sample values + for (auto value : values) + { + double val = value.value; + auto ch = value.ch; + + auto point = transform(QPointF(x, val)); + + painter->setBrush(ch->color()); + painter->setPen(Qt::NoPen); + painter->drawEllipse(point, VALUE_POINT_DIAM, VALUE_POINT_DIAM); + + painter->setPen(rubberBandPen()); + // We give a very small (1x1) rectangle but disable clipping + painter->drawText(QRectF(point.x() + VALUE_TEXT_MARGIN, value.y, 1, 1), + Qt::AlignVCenter | Qt::TextDontClip, + QString("%1").arg(val)); + } + + painter->restore(); +} + +QRect Zoomer::trackerRect(const QFont& font) const +{ + if (isActive()) + { + return QwtPlotZoomer::trackerRect(font); + } + else + { + return valueTrackerRect(font); + } +} + +QRect Zoomer::valueTrackerRect(const QFont& font) const +{ + // TODO: consider using actual tracker values for width calculation + const int textWidth = qCeil(QwtText("-8.8888888").textSize(font).width()); + const int width = textWidth + VALUE_POINT_DIAM + VALUE_TEXT_MARGIN; + const int x = trackerPosition().x() - VALUE_POINT_DIAM; + const auto pickRect = pickArea().boundingRect(); + + return QRect(x, pickRect.y(), width, pickRect.height()); +} + void Zoomer::widgetMousePressEvent(QMouseEvent* mouseEvent) { if (mouseEvent->modifiers() & Qt::ControlModifier) diff --git a/src/zoomer.h b/src/zoomer.h --- a/src/zoomer.h +++ b/src/zoomer.h @@ -1,5 +1,5 @@ /* - Copyright © 2017 Hasan Yavuz Özderya + Copyright © 2019 Hasan Yavuz Özderya This file is part of serialplot. @@ -20,25 +20,36 @@ #ifndef ZOOMER_H #define ZOOMER_H -#include +#include +#include +#include + +#include "scrollzoomer.h" +#include "streamchannel.h" class Zoomer : public ScrollZoomer { Q_OBJECT public: - Zoomer(QWidget *, bool doReplot=true); + Zoomer(QWidget*, bool doReplot=true); void zoom(int up); - void zoom( const QRectF & ); + void zoom(const QRectF&); + /// Set displayed channels for value tracking (can be null) + void setDispChannels(QVector channels); signals: void unzoomed(); protected: /// Re-implemented to display selection size in the tracker text. - QwtText trackerTextF(const QPointF &pos) const; + QwtText trackerTextF(const QPointF &pos) const override; + /// Re-implemented for sample value tracker + QRect trackerRect(const QFont&) const override; /// Re-implemented for alpha background - void drawRubberBand(QPainter* painter) const; + void drawRubberBand(QPainter* painter) const override; + /// Re-implemented to draw sample values + void drawTracker(QPainter* painter) const override; /// Re-implemented for alpha background (masking is basically disabled) QRegion rubberBandMask() const; /// Overloaded for panning @@ -51,6 +62,15 @@ protected: private: bool is_panning; QPointF pan_point; + /// displayed channels for value tracking + QVector dispChannels; + + /// Get a list of visible channels + QList visChannels() const; + /// Draw sample values + void drawValues(QPainter* painter) const; + /// Returns trackerRect for value tracker + QRect valueTrackerRect(const QFont& font) const; }; #endif // ZOOMER_H diff --git a/tests/test.cpp b/tests/test.cpp --- a/tests/test.cpp +++ b/tests/test.cpp @@ -224,6 +224,14 @@ TEST_CASE("LinIndexBuffer", "[memory, bu REQUIRE(buf.sample(0) == -5.0); REQUIRE(buf.sample(19) == 5.0); + + buf.resize(10); + buf.setLimits({0., 3.}); + REQUIRE(buf.findIndex(0.01) == 0); + REQUIRE(buf.findIndex(1.51) == 4); + REQUIRE(buf.findIndex(2.99) == 8); + REQUIRE(buf.findIndex(3.01) == XFrameBuffer::OUT_OF_RANGE); + REQUIRE(buf.findIndex(-0.01) == XFrameBuffer::OUT_OF_RANGE); } TEST_CASE("RingBuffer sizing", "[memory, buffer]") diff --git a/tests/test_stream.cpp b/tests/test_stream.cpp --- a/tests/test_stream.cpp +++ b/tests/test_stream.cpp @@ -27,17 +27,17 @@ TEST_CASE("construction of stream with d // default values are an empty stream with no channels Stream s; - REQUIRE(s.numChannels() == 0); + REQUIRE(s.numChannels() == 1); REQUIRE(!s.hasX()); - REQUIRE(s.numSamples() == 0); + REQUIRE(s.numSamples() == 2); } TEST_CASE("construction of stream with parameters", "[memory, stream]") { - Stream s(4, true, 100); + Stream s(4, false, 100); REQUIRE(s.numChannels() == 4); - REQUIRE(s.hasX()); + REQUIRE(!s.hasX()); REQUIRE(s.numSamples() == 100); for (unsigned i = 0; i < 4; i++) @@ -64,6 +64,8 @@ TEST_CASE("changing stream number of cha REQUIRE(c->index() == i); } +// TODO: enable test when `Stream` supports X channel +#if 0 // increase nc value, add X so._setNumChannels(5, true); @@ -76,6 +78,7 @@ TEST_CASE("changing stream number of cha REQUIRE(c != NULL); REQUIRE(c->index() == i); } +#endif // reduce nc value, remove X so._setNumChannels(1, false); @@ -127,9 +130,11 @@ TEST_CASE("adding data to a stream with } } +// TODO: enable test when `Stream` supports X channel +#if 0 TEST_CASE("adding data to a stream with X", "[memory, stream, data, sink]") { - Stream s(3, true, 10); + Stream s(3, false, 10); // prepare data SamplePack pack(5, 3, true); @@ -147,7 +152,7 @@ TEST_CASE("adding data to a stream with } TestSource so(3, true); - so.connectSink(&s); + REQUIRE_THROWS(so.connectSink(&s)); // test so._feed(pack); @@ -178,6 +183,7 @@ TEST_CASE("adding data to a stream with REQUIRE(x->sample(i) == (i-5)+10); } } +#endif TEST_CASE("paused stream shouldn't store data", "[memory, stream, pause]") {