diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -9,3 +9,5 @@ b4d0a38444d31872633e474d89ffc15cd0fe42f0
27b0354ca2c5ea7b3870156417ce7e04e799bbf7 v0.7.1
fd5f1eb480ec372b49df58b497458de05c30057c v0.8.0
9c9a11cd15fd094e2b2b65dc51805fd8fd1d2460 v0.8.1
+4cf9a1ee1f107a38e03dbe17c4f2882c43d827c9 v0.9.0
+ef003f7af8f37f760c22dae776f5ff8e1b526deb v0.9.1
diff --git a/CMakeLists.txt b/CMakeLists.txt
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -17,7 +17,7 @@
# along with serialplot. If not, see .
#
-cmake_minimum_required(VERSION 2.8.11)
+cmake_minimum_required(VERSION 3.2.2)
project(serialplot)
@@ -38,23 +38,19 @@ find_package(Qt5Widgets)
# If set, cmake will download Qwt over SVN, build and use it as a static library.
set(BUILD_QWT true CACHE BOOL "Download and build Qwt automatically.")
-# Find QWT or use static manually provided by user
-set(QWT_USE_STATIC false CACHE BOOL "Use a static version of Qwt provided by user.")
-set(QWT_STATIC_LIBRARY "" CACHE FILEPATH "Path to the static Qwt library, libqwt.a.")
-set(QWT_STATIC_INCLUDE "" CACHE PATH "Path to the Qwt include directory when building Qwt static.")
-
if (BUILD_QWT)
include(BuildQwt)
else (BUILD_QWT)
- if (QWT_USE_STATIC)
- set(QWT_LIBRARY ${QWT_STATIC_LIBRARY})
- set(QWT_INCLUDE_DIR ${QWT_STATIC_INCLUDE})
- else (QWT_USE_STATIC)
- find_package(Qwt 6.1 REQUIRED)
- endif (QWT_USE_STATIC)
+ find_package(Qwt 6.1 REQUIRED)
endif (BUILD_QWT)
-include(BuildQColorWidgets)
+# 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)
@@ -64,10 +60,14 @@ else (BUILD_LEDWIDGET)
endif (BUILD_LEDWIDGET)
# includes
-include_directories("./src" ${QWT_INCLUDE_DIR} ${QCW_INCLUDE_DIR} ${LEDWIDGET_INCLUDE_DIR})
+include_directories("./src"
+ ${QWT_INCLUDE_DIR}
+ ${QTCOLORWIDGETS_INCLUDE_DIRS}
+ ${LEDWIDGET_INCLUDE_DIR}
+ )
# flags
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${QCW_FLAGS}")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${QTCOLORWIDGETS_FLAGS}")
# wrap UI and resource files
qt5_wrap_ui(UI_FILES
@@ -85,6 +85,7 @@ qt5_wrap_ui(UI_FILES
src/binarystreamreadersettings.ui
src/asciireadersettings.ui
src/framedreadersettings.ui
+ src/updatecheckdialog.ui
)
if (WIN32)
@@ -134,24 +135,35 @@ add_executable(${PROGRAM_NAME} WIN32
src/framedreadersettings.cpp
src/plotmanager.cpp
src/numberformat.cpp
+ src/updatechecker.cpp
+ src/versionnumber.cpp
+ src/updatecheckdialog.cpp
misc/windows_icon.rc
${UI_FILES}
${RES_FILES}
)
# Use the Widgets module from Qt 5.
-target_link_libraries(${PROGRAM_NAME} ${QWT_LIBRARY} ${QCW_LIBRARY} ${LEDWIDGET_LIBRARY})
-qt5_use_modules(${PROGRAM_NAME} Widgets SerialPort)
+target_link_libraries(${PROGRAM_NAME}
+ ${QWT_LIBRARY}
+ ${QTCOLORWIDGETS_LIBRARIES}
+ ${LEDWIDGET_LIBRARY}
+ )
+qt5_use_modules(${PROGRAM_NAME} Widgets SerialPort Network)
-# external project dependencies
if (BUILD_QWT)
add_dependencies(${PROGRAM_NAME} QWT)
-endif (BUILD_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")
@@ -167,34 +179,18 @@ else()
message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.")
endif()
-# version number
-set(MAJOR_VERSION 0 CACHE INT "Program major version number.")
-set(MINOR_VERSION 8 CACHE INT "Program minor version number.")
-set(PATCH_VERSION 1 CACHE INT "Program patch version number.")
-set(VERSION_STRING "${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION}")
-
-# get revision number from mercurial
-find_program(MERCURIAL hg)
+# default version
+set(VERSION_STRING "0.9.1")
+set(VERSION_REVISION "0")
-if (MERCURIAL)
- execute_process(COMMAND ${MERCURIAL} id -i
- WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
- RESULT_VARIABLE MERCURIAL_RESULT
- OUTPUT_VARIABLE VERSION_REVISION
- OUTPUT_STRIP_TRAILING_WHITESPACE)
- if(NOT MERCURIAL_RESULT EQUAL 0)
- set(VERSION_SCM_REVISION false)
- endif(NOT MERCURIAL_RESULT EQUAL 0)
-endif (MERCURIAL)
+# get revision number from mercurial and parse version string
+include(GetVersion)
-if (NOT VERSION_REVISION)
- set(VERSION_REVISION "0")
-endif (NOT VERSION_REVISION)
-
-message("SCM revision: ${VERSION_REVISION}")
-
-# configure version file
-configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/version.h.in" "${CMAKE_CURRENT_BINARY_DIR}/version.h")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVERSION_STRING=\\\"${VERSION_STRING}\\\" ")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVERSION_MAJOR=${VERSION_MAJOR} ")
+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}\\\" ")
# add make run target
add_custom_target(run
@@ -213,20 +209,19 @@ if (WIN32)
install(FILES ${WINDOWS_INSTALL_LIBRARIES} DESTINATION bin)
endif (WIN32)
-# install menu item and icon
+# prepare menu item and icon
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/misc/program_name.desktop.in"
"${CMAKE_BINARY_DIR}/${PROGRAM_NAME}.desktop")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/misc/program_name.png"
"${CMAKE_BINARY_DIR}/${PROGRAM_NAME}.png" COPYONLY)
+set(DESKTOP_FILE ${CMAKE_BINARY_DIR}/${PROGRAM_NAME}.desktop)
+set(ICON_FILE ${CMAKE_BINARY_DIR}/${PROGRAM_NAME}.png)
+
+# install menu item and icon
if (UNIX)
- # first copy files to share/serialplot/
- install(FILES
- ${CMAKE_BINARY_DIR}/${PROGRAM_NAME}.desktop
- DESTINATION share/applications/)
- install(FILES
- ${CMAKE_BINARY_DIR}/${PROGRAM_NAME}.png
- DESTINATION share/icons/hicolor/256x256/apps/)
+ install(FILES ${DESKTOP_FILE} DESTINATION share/applications/)
+ install(FILES ${ICON_FILE} DESTINATION share/icons/hicolor/256x256/apps/)
endif (UNIX)
# uninstalling
@@ -241,6 +236,8 @@ if (UNIX)
endif (UNIX)
# packaging
+include(BuildLinuxAppImage)
+
if (UNIX)
set(CPACK_GENERATOR "DEB")
elseif (WIN32)
@@ -252,9 +249,9 @@ include(InstallRequiredSystemLibraries)
set(CPACK_PACKAGE_NAME "${PROGRAM_NAME}")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Small and simple software for plotting data from serial port")
set(CPACK_PACKAGE_CONTACT "Hasan Yavuz Özderya ")
-set(CPACK_PACKAGE_VERSION_MAJOR ${MAJOR_VERSION})
-set(CPACK_PACKAGE_VERSION_MINOR ${MINOR_VERSION})
-set(CPACK_PACKAGE_VERSION_PATCH ${PATCH_VERSION})
+set(CPACK_PACKAGE_VERSION_MAJOR ${VERSION_MAJOR})
+set(CPACK_PACKAGE_VERSION_MINOR ${VERSION_MINOR})
+set(CPACK_PACKAGE_VERSION_PATCH ${VERSION_PATCH})
set(CPACK_STRIP_FILES TRUE)
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt5widgets5 (>= 5.2.1), libqt5svg5 (>= 5.2.1), libqt5serialport5 (>= 5.2.1), libc6 (>= 2.19)")
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "Small and simple software for plotting data from serial port
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,15 @@
+FROM ubuntu:trusty
+
+WORKDIR /serialplot
+ADD . /serialplot
+
+# Install build dependencies
+RUN apt-get update
+RUN apt-get -y install software-properties-common
+RUN add-apt-repository -y ppa:beineri/opt-qt562-trusty
+RUN add-apt-repository -y ppa:george-edison55/cmake-3.x
+RUN apt-get update
+RUN apt-get -y install build-essential mesa-common-dev qt56base qt56serialport cmake mercurial subversion git wget libfuse2
+
+# Define environment variable
+ENV PATH /opt/qt56/bin/:$PATH
diff --git a/cmake/modules/BuildLinuxAppImage.cmake b/cmake/modules/BuildLinuxAppImage.cmake
new file mode 100644
--- /dev/null
+++ b/cmake/modules/BuildLinuxAppImage.cmake
@@ -0,0 +1,36 @@
+# Based on: https://github.com/mhoeher/opentodolist
+
+set(LINUXDEPLOYQT_URL "https://github.com/probonopd/linuxdeployqt/releases/download/4/linuxdeployqt-4-x86_64.AppImage")
+set(LINUXDEPLOYQT_TOOL ${CMAKE_CURRENT_BINARY_DIR}/linuxdeployqt-4-x86_64.AppImage)
+
+set(APPIMAGE_DIR ${CMAKE_CURRENT_BINARY_DIR}/${PROGRAM_NAME}-${VERSION_STRING}-${CMAKE_HOST_SYSTEM_PROCESSOR})
+
+add_custom_command(
+ OUTPUT
+ ${LINUXDEPLOYQT_TOOL}
+ COMMAND
+ wget ${LINUXDEPLOYQT_URL}
+ COMMAND
+ chmod a+x ${LINUXDEPLOYQT_TOOL})
+
+add_custom_target(
+ appimage
+
+ DEPENDS ${LINUXDEPLOYQT_TOOL}
+
+ COMMAND
+ ${CMAKE_COMMAND} -E remove_directory ${APPIMAGE_DIR}
+ COMMAND
+ ${CMAKE_COMMAND} -E make_directory ${APPIMAGE_DIR}
+ COMMAND
+ ${CMAKE_COMMAND} -E copy $ ${APPIMAGE_DIR}
+ COMMAND
+ ${CMAKE_COMMAND} -E copy ${DESKTOP_FILE} ${APPIMAGE_DIR}
+ COMMAND
+ ${CMAKE_COMMAND} -E copy ${ICON_FILE} ${APPIMAGE_DIR}
+ COMMAND
+ ${CMAKE_COMMAND} -E env PATH=${QT_INSTALL_PREFIX}/bin:$ENV{PATH} ${LINUXDEPLOYQT_TOOL}
+ ${APPIMAGE_DIR}/${PROGRAM_NAME} -appimage
+ -always-overwrite -bundle-non-qt-libs -verbose=2
+ WORKING_DIRECTORY
+ ${CMAKE_CURRENT_BINARY_DIR})
diff --git a/cmake/modules/BuildQColorWidgets.cmake b/cmake/modules/BuildQColorWidgets.cmake
--- a/cmake/modules/BuildQColorWidgets.cmake
+++ b/cmake/modules/BuildQColorWidgets.cmake
@@ -22,12 +22,15 @@ include(ExternalProject)
ExternalProject_Add(QCW
PREFIX qcw
GIT_REPOSITORY https://github.com/mbasaglia/Qt-Color-Widgets
- PATCH_COMMAND patch -t -N -p1 -i ${CMAKE_CURRENT_LIST_DIR}/qt_5_2_moc_creation_namespace_fix.diff
+ PATCH_COMMAND patch -t -p1 -i ${CMAKE_CURRENT_LIST_DIR}/qt_5_2_moc_creation_namespace_fix.diff
CMAKE_CACHE_ARGS "-DCMAKE_CXX_FLAGS:string=-D QTCOLORWIDGETS_STATICALLY_LINKED"
UPDATE_COMMAND ""
INSTALL_COMMAND "")
ExternalProject_Get_Property(QCW binary_dir source_dir)
-set(QCW_FLAGS "-D QTCOLORWIDGETS_STATICALLY_LINKED")
-set(QCW_LIBRARY ${binary_dir}/libColorWidgets-qt5.a)
-set(QCW_INCLUDE_DIR ${source_dir}/include)
+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/FindQtColorWidgets.cmake b/cmake/modules/FindQtColorWidgets.cmake
new file mode 100644
--- /dev/null
+++ b/cmake/modules/FindQtColorWidgets.cmake
@@ -0,0 +1,35 @@
+#
+# 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 © 2015 Hasan Yavuz Özderya
+# Copyright © 2017 Hasan Yavuz Özderya
#
# This file is part of serialplot.
#
@@ -66,7 +66,7 @@ endif(qwt_roots)
if(QWT_ROOT)
set(QWT_INCLUDE_DIR "${QWT_ROOT}/include")
- find_library(QWT_LIBRARY "qwt"
+ find_library(QWT_LIBRARY "qwt-qt5"
PATHS "${QWT_ROOT}/lib")
else (QWT_ROOT)
## Look into system locations
@@ -90,7 +90,7 @@ else (QWT_ROOT)
endif(qwt_version_string)
endif (QWT_INCLUDE_DIR)
# look into system locations for lib file
- find_library(QWT_LIBRARY "qwt" PATHS /usr/lib)
+ find_library(QWT_LIBRARY "qwt-qt5" PATHS /usr/lib)
endif(QWT_ROOT)
# set version variables
diff --git a/cmake/modules/GetVersion.cmake b/cmake/modules/GetVersion.cmake
new file mode 100644
--- /dev/null
+++ b/cmake/modules/GetVersion.cmake
@@ -0,0 +1,80 @@
+#
+# 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 .
+#
+
+# try to get latest version from mercurial
+find_program(HG hg)
+
+if (HG)
+ # get latest tag
+ execute_process(COMMAND ${HG} parents --template {latesttag}
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ RESULT_VARIABLE HG_RESULT
+ OUTPUT_VARIABLE HG_LATEST_TAG
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+ if(HG_RESULT EQUAL 0)
+ if (NOT HG_LATEST_TAG MATCHES "v[0-9.]+")
+ unset(HG_LATEST_TAG)
+ endif()
+ else()
+ unset(HG_LATEST_TAG)
+ endif()
+
+ # get revision
+ execute_process(COMMAND ${HG} id -i
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ RESULT_VARIABLE HG_RESULT
+ OUTPUT_VARIABLE HG_REVISION
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+ if(NOT HG_RESULT EQUAL 0)
+ unset(HG_REVISION)
+ endif(NOT HG_RESULT EQUAL 0)
+endif (HG)
+
+# Try to get version from .hg_archival file
+if (NOT HG_LATEST_TAG)
+ set(HG_ARCHIVAL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/.hg_archival.txt)
+ if (EXISTS ${HG_ARCHIVAL_FILE})
+ # get latest tag
+ file(STRINGS ${HG_ARCHIVAL_FILE} HG_ARCHIVAL_LATEST_TAG REGEX "^latesttag:.+")
+ string(REGEX REPLACE "latesttag:[ \t]*(.+)" "\\1" HG_LATEST_TAG ${HG_ARCHIVAL_LATEST_TAG})
+
+ # get latest revision
+ file(STRINGS ${HG_ARCHIVAL_FILE} HG_ARCHIVAL_REV REGEX "^node:.+")
+ string(REGEX REPLACE "node:[ \t]*([a-fA-F0-9]+)" "\\1" HG_ARCHIVAL_REV ${HG_ARCHIVAL_REV})
+ string(SUBSTRING ${HG_ARCHIVAL_REV} 0 12 HG_REVISION)
+ endif()
+endif ()
+
+# extract version information from tag (remove 'v' prefix)
+if (HG_LATEST_TAG)
+ string(REPLACE "v" "" HG_VERSION ${HG_LATEST_TAG})
+ message("Version from mercurial: ${HG_VERSION} (${HG_REVISION})")
+
+ # replace version string
+ set(VERSION_STRING ${HG_VERSION})
+ set(VERSION_REVISION ${HG_REVISION})
+else ()
+ message("Failed to find version information from mercurial.")
+endif ()
+
+# parse version numbers
+string(REPLACE "." ";" VERSION_LIST ${VERSION_STRING})
+list(GET VERSION_LIST 0 VERSION_MAJOR)
+list(GET VERSION_LIST 1 VERSION_MINOR)
+list(GET VERSION_LIST 2 VERSION_PATCH)
diff --git a/serialplot.pro b/serialplot.pro
--- a/serialplot.pro
+++ b/serialplot.pro
@@ -68,7 +68,9 @@ SOURCES += \
src/framedreader.cpp \
src/plotmanager.cpp \
src/numberformat.cpp \
- src/recordpanel.cpp
+ src/recordpanel.cpp \
+ src/updatechecker.cpp \
+ src/updatecheckdialog.cpp
HEADERS += \
src/mainwindow.h \
@@ -108,7 +110,9 @@ HEADERS += \
src/plotmanager.h \
src/setting_defines.h \
src/numberformat.h \
- src/recordpanel.h
+ src/recordpanel.h \
+ src/updatechecker.h \
+ src/updatecheckdialog.h
FORMS += \
src/mainwindow.ui \
@@ -124,7 +128,8 @@ FORMS += \
src/framedreadersettings.ui \
src/binarystreamreadersettings.ui \
src/asciireadersettings.ui \
- src/recordpanel.ui
+ src/recordpanel.ui \
+ src/updatecheckdialog.ui
INCLUDEPATH += qmake/ src/
diff --git a/src/asciireader.cpp b/src/asciireader.cpp
--- a/src/asciireader.cpp
+++ b/src/asciireader.cpp
@@ -33,6 +33,7 @@ AsciiReader::AsciiReader(QIODevice* devi
_numOfChannels = _settingsWidget.numOfChannels();
autoNumOfChannels = (_numOfChannels == NUMOFCHANNELS_AUTO);
+ delimiter = _settingsWidget.delimiter();
connect(&_settingsWidget, &AsciiReaderSettings::numOfChannelsChanged,
[this](unsigned value)
@@ -45,6 +46,12 @@ AsciiReader::AsciiReader(QIODevice* devi
}
});
+ connect(&_settingsWidget, &AsciiReaderSettings::delimiterChanged,
+ [this](QChar d)
+ {
+ delimiter = d;
+ });
+
connect(device, &QIODevice::aboutToClose, [this](){discardFirstLine=true;});
}
@@ -90,7 +97,7 @@ void AsciiReader::onDataReady()
{
while(_device->canReadLine())
{
- QByteArray line = _device->readLine();
+ QString line = QString(_device->readLine());
// discard only once when we just started reading
if (discardFirstLine)
@@ -116,7 +123,7 @@ void AsciiReader::onDataReady()
continue;
}
- auto separatedValues = line.split(',');
+ auto separatedValues = line.split(delimiter, QString::SkipEmptyParts);
unsigned numReadChannels; // effective number of channels to read
unsigned numComingChannels = separatedValues.length();
@@ -143,6 +150,7 @@ void AsciiReader::onDataReady()
}
// parse read line
+ unsigned numDataBroken = 0;
double* channelSamples = new double[_numOfChannels]();
for (unsigned ci = 0; ci < numReadChannels; ci++)
{
@@ -153,11 +161,15 @@ void AsciiReader::onDataReady()
qWarning() << "Data parsing error for channel: " << ci;
qWarning() << "Read line: " << line;
channelSamples[ci] = 0;
+ numDataBroken++;
}
}
- // commit data
- addData(channelSamples, _numOfChannels);
+ if (numReadChannels > numDataBroken)
+ {
+ // commit data
+ addData(channelSamples, _numOfChannels);
+ }
delete[] channelSamples;
}
diff --git a/src/asciireader.h b/src/asciireader.h
--- a/src/asciireader.h
+++ b/src/asciireader.h
@@ -48,6 +48,7 @@ private:
unsigned _numOfChannels;
/// number of channels will be determined from incoming data
unsigned autoNumOfChannels;
+ QChar delimiter; ///< selected column delimiter
bool paused;
// We may have (usually true) started reading in the middle of a
diff --git a/src/asciireadersettings.cpp b/src/asciireadersettings.cpp
--- a/src/asciireadersettings.cpp
+++ b/src/asciireadersettings.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -17,20 +17,35 @@
along with serialplot. If not, see .
*/
+#include
+#include
+
#include "utils.h"
#include "setting_defines.h"
#include "asciireadersettings.h"
#include "ui_asciireadersettings.h"
-#include
-
AsciiReaderSettings::AsciiReaderSettings(QWidget *parent) :
QWidget(parent),
ui(new Ui::AsciiReaderSettings)
{
ui->setupUi(this);
+ auto validator = new QRegularExpressionValidator(QRegularExpression("[^\\d]?"), this);
+ ui->leDelimiter->setValidator(validator);
+
+ connect(ui->rbComma, &QAbstractButton::toggled,
+ this, &AsciiReaderSettings::delimiterToggled);
+ connect(ui->rbSpace, &QAbstractButton::toggled,
+ this, &AsciiReaderSettings::delimiterToggled);
+ connect(ui->rbTab, &QAbstractButton::toggled,
+ this, &AsciiReaderSettings::delimiterToggled);
+ connect(ui->rbOtherDelimiter, &QAbstractButton::toggled,
+ this, &AsciiReaderSettings::delimiterToggled);
+ connect(ui->leDelimiter, &QLineEdit::textChanged,
+ this, &AsciiReaderSettings::customDelimiterChanged);
+
// Note: if directly connected we get a runtime warning on incompatible signal arguments
connect(ui->spNumOfChannels, SELECT::OVERLOAD_OF(&QSpinBox::valueChanged),
[this](int value)
@@ -44,11 +59,51 @@ AsciiReaderSettings::~AsciiReaderSetting
delete ui;
}
-unsigned AsciiReaderSettings::numOfChannels()
+unsigned AsciiReaderSettings::numOfChannels() const
{
return ui->spNumOfChannels->value();
}
+QChar AsciiReaderSettings::delimiter() const
+{
+ if (ui->rbComma->isChecked())
+ {
+ return QChar(',');
+ }
+ else if (ui->rbSpace->isChecked())
+ {
+ return QChar(' ');
+ }
+ else if (ui->rbTab->isChecked())
+ {
+ return QChar('\t');
+ }
+ else // rbOther
+ {
+ auto t = ui->leDelimiter->text();
+ return t.isEmpty() ? QChar() : t.at(0);
+ }
+}
+
+void AsciiReaderSettings::delimiterToggled(bool checked)
+{
+ if (!checked) return;
+
+ auto d = delimiter();
+ if (!d.isNull())
+ {
+ emit delimiterChanged(d);
+ }
+}
+
+void AsciiReaderSettings::customDelimiterChanged(const QString text)
+{
+ if (ui->rbOtherDelimiter->isChecked())
+ {
+ if (!text.isEmpty()) emit delimiterChanged(text.at(0));
+ }
+}
+
void AsciiReaderSettings::saveSettings(QSettings* settings)
{
settings->beginGroup(SettingGroup_ASCII);
@@ -58,6 +113,25 @@ void AsciiReaderSettings::saveSettings(Q
if (numOfChannelsSetting == "0") numOfChannelsSetting = "auto";
settings->setValue(SG_ASCII_NumOfChannels, numOfChannelsSetting);
+ // save delimiter
+ QString delimiterS;
+ if (ui->rbOtherDelimiter->isChecked())
+ {
+ delimiterS = "other";
+ }
+ else if (ui->rbTab->isChecked())
+ {
+ // Note: \t is not correctly loaded
+ delimiterS = "TAB";
+ }
+ else
+ {
+ delimiterS = delimiter();
+ }
+
+ settings->setValue(SG_ASCII_Delimiter, delimiterS);
+ settings->setValue(SG_ASCII_CustomDelimiter, ui->leDelimiter->text());
+
settings->endGroup();
}
@@ -83,5 +157,26 @@ void AsciiReaderSettings::loadSettings(Q
}
}
+ // load delimiter
+ auto delimiterS = settings->value(SG_ASCII_Delimiter, delimiter()).toString();
+ auto customDelimiter = settings->value(SG_ASCII_CustomDelimiter, delimiter()).toString();
+ if (!customDelimiter.isEmpty()) ui->leDelimiter->setText(customDelimiter);
+ if (delimiterS == ",")
+ {
+ ui->rbComma->setChecked(true);
+ }
+ else if (delimiterS == " ")
+ {
+ ui->rbSpace->setChecked(true);
+ }
+ else if (delimiterS == "TAB")
+ {
+ ui->rbTab->setChecked(true);
+ }
+ else
+ {
+ ui->rbOtherDelimiter->setChecked(true);
+ }
+
settings->endGroup();
}
diff --git a/src/asciireadersettings.h b/src/asciireadersettings.h
--- a/src/asciireadersettings.h
+++ b/src/asciireadersettings.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -22,6 +22,7 @@
#include
#include
+#include
namespace Ui {
class AsciiReaderSettings;
@@ -35,7 +36,8 @@ public:
explicit AsciiReaderSettings(QWidget *parent = 0);
~AsciiReaderSettings();
- unsigned numOfChannels();
+ unsigned numOfChannels() const;
+ QChar delimiter() const;
/// Stores settings into a `QSettings`
void saveSettings(QSettings* settings);
/// Loads settings from a `QSettings`.
@@ -43,9 +45,15 @@ public:
signals:
void numOfChannelsChanged(unsigned);
+ /// Signaled only with a valid delimiter
+ void delimiterChanged(QChar);
private:
Ui::AsciiReaderSettings *ui;
+
+private slots:
+ void delimiterToggled(bool checked);
+ void customDelimiterChanged(const QString text);
};
#endif // ASCIIREADERSETTINGS_H
diff --git a/src/asciireadersettings.ui b/src/asciireadersettings.ui
--- a/src/asciireadersettings.ui
+++ b/src/asciireadersettings.ui
@@ -6,14 +6,17 @@
0
0
- 414
+ 493
171
Form
-
+
+
+ QFormLayout::ExpandingFieldsGrow
+
0
@@ -26,68 +29,105 @@
0
- -
+
-
+
+
+ Number Of Channels:
+
+
+
+ -
+
+
+
+ 60
+ 0
+
+
+
+ Select number of channels or set to 0 for Auto (determined from incoming data)
+
+
+ Auto
+
+
+ false
+
+
+ 0
+
+
+ 32
+
+
+
+ -
+
+
+ Column Delimiter:
+
+
+
+ -
-
-
+
- Number Of Channels:
+ comma
+
+
+ true
-
-
-
-
- 60
- 0
-
-
-
- Select number of channels or set to 0 for Auto (determined from incoming data)
+
+
+ space
-
- Auto
-
-
- false
+
+
+ -
+
+
+ tab
-
- 0
-
-
- 32
+
+
+ -
+
+
+ other:
-
-
-
- Qt::Horizontal
+
+
+
+ 0
+ 0
+
-
+
- 1
- 20
+ 30
+ 16777215
-
+
+ Enter a custom delimiter character
+
+
+
+
+
+ |
+
+
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 1
-
-
-
-
diff --git a/src/channelinfomodel.cpp b/src/channelinfomodel.cpp
--- a/src/channelinfomodel.cpp
+++ b/src/channelinfomodel.cpp
@@ -267,7 +267,7 @@ void ChannelInfoModel::setNumOfChannels(
// remember user entered info
if ((int) number > infos.length())
{
- for (unsigned ci = _numOfChannels; ci < number; ci++)
+ for (unsigned ci = infos.length(); ci < number; ci++)
{
infos.append(ChannelInfo(ci));
}
diff --git a/src/commandedit.cpp b/src/commandedit.cpp
--- a/src/commandedit.cpp
+++ b/src/commandedit.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2015 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -62,7 +62,7 @@ CommandEdit::CommandEdit(QWidget *parent
QLineEdit(parent)
{
hexValidator = new HexCommandValidator(this);
- asciiValidator = new QRegExpValidator(QRegExp("[\\x0000-\\x007F]+"));
+ asciiValidator = new QRegExpValidator(QRegExp("[\\x0000-\\x007F]+"), this);
ascii_mode = true;
setValidator(asciiValidator);
}
diff --git a/src/commandwidget.cpp b/src/commandwidget.cpp
--- a/src/commandwidget.cpp
+++ b/src/commandwidget.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -103,7 +103,14 @@ bool CommandWidget::isASCIIMode()
void CommandWidget::setASCIIMode(bool enabled)
{
- ui->pbASCII->setChecked(enabled);
+ if (enabled)
+ {
+ ui->pbASCII->setChecked(true);
+ }
+ else
+ {
+ ui->pbHEX->setChecked(true);
+ }
}
void CommandWidget::setName(QString name)
diff --git a/src/datarecorder.cpp b/src/datarecorder.cpp
--- a/src/datarecorder.cpp
+++ b/src/datarecorder.cpp
@@ -27,6 +27,7 @@ DataRecorder::DataRecorder(QObject *pare
{
lastNumChannels = 0;
disableBuffering = false;
+ windowsLE = false;
}
bool DataRecorder::startRecording(QString fileName, QString separator, QStringList channelNames)
@@ -47,7 +48,7 @@ bool DataRecorder::startRecording(QStrin
if (!channelNames.isEmpty())
{
fileStream << channelNames.join(_sep);
- fileStream << "\n";
+ fileStream << le();
lastNumChannels = channelNames.length();
}
return true;
@@ -74,7 +75,7 @@ void DataRecorder::addData(double* data,
fileStream << data[ci * numOfSamples + i];
if (ci != numOfChannels-1) fileStream << _sep;
}
- fileStream << '\n';
+ fileStream << le();
}
if (disableBuffering) fileStream.flush();
@@ -87,3 +88,8 @@ void DataRecorder::stopRecording()
file.close();
lastNumChannels = 0;
}
+
+const char* DataRecorder::le() const
+{
+ return windowsLE ? "\r\n" : "\n";
+}
diff --git a/src/datarecorder.h b/src/datarecorder.h
--- a/src/datarecorder.h
+++ b/src/datarecorder.h
@@ -34,6 +34,14 @@ public:
bool disableBuffering;
/**
+ * Use CR+LF as line ending. `false` by default.
+ *
+ * @note Toggling this variable during a recording will result in
+ * a corrupted file. Care must be taken at higher (UI) levels.
+ */
+ bool windowsLE;
+
+ /**
* @brief Starts recording data to a file in CSV format.
*
* File is opened and header line (names of channels) is written.
@@ -72,6 +80,9 @@ private:
QFile file;
QTextStream fileStream;
QString _sep;
+
+ /// Returns the selected line ending.
+ const char* le() const;
};
#endif // DATARECORDER_H
diff --git a/src/framebufferseries.cpp b/src/framebufferseries.cpp
--- a/src/framebufferseries.cpp
+++ b/src/framebufferseries.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -17,24 +17,74 @@
along with serialplot. If not, see .
*/
+#include
#include "framebufferseries.h"
FrameBufferSeries::FrameBufferSeries(FrameBuffer* buffer)
{
+ xAsIndex = true;
+ _xmin = 0;
+ _xmax = 1;
_buffer = buffer;
+ int_index_start = 0;
+ int_index_end = 0;
+}
+
+void FrameBufferSeries::setXAxis(bool asIndex, double xmin, double xmax)
+{
+ xAsIndex = asIndex;
+ _xmin = xmin;
+ _xmax = xmax;
}
size_t FrameBufferSeries::size() const
{
- return _buffer->size();
+ return int_index_end - int_index_start;
}
QPointF FrameBufferSeries::sample(size_t i) const
{
- return QPointF(i, _buffer->sample(i));
+ i += int_index_start;
+ if (xAsIndex)
+ {
+ return QPointF(i, _buffer->sample(i));
+ }
+ else
+ {
+ return QPointF(i * (_xmax - _xmin) / _buffer->size() + _xmin, _buffer->sample(i));
+ }
}
QRectF FrameBufferSeries::boundingRect() const
{
- return _buffer->boundingRect();
+ if (xAsIndex)
+ {
+ return _buffer->boundingRect();
+ }
+ else
+ {
+ auto rect = _buffer->boundingRect();
+ rect.setLeft(_xmin);
+ rect.setRight(_xmax);
+ return rect;
+ }
}
+
+void FrameBufferSeries::setRectOfInterest(const QRectF& rect)
+{
+ if (xAsIndex)
+ {
+ int_index_start = floor(rect.left())-1;
+ int_index_end = ceil(rect.right())+1;
+ }
+ else
+ {
+ 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 = std::max(int_index_start, (size_t) 0);
+ int_index_end = std::min(_buffer->size(), int_index_end);
+}
diff --git a/src/framebufferseries.h b/src/framebufferseries.h
--- a/src/framebufferseries.h
+++ b/src/framebufferseries.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -37,13 +37,23 @@ class FrameBufferSeries : public QwtSeri
public:
FrameBufferSeries(FrameBuffer* buffer);
+ /// Behavior of X axis
+ void setXAxis(bool asIndex, double xmin, double xmax);
+
// QwtSeriesData implementations
size_t size() const;
QPointF sample(size_t i) const;
QRectF boundingRect() const;
+ void setRectOfInterest(const QRectF& rect);
private:
FrameBuffer* _buffer;
+ bool xAsIndex;
+ double _xmin;
+ double _xmax;
+
+ size_t int_index_start; ///< starting index of "rectangle of interest"
+ size_t int_index_end; ///< ending index of "rectangle of interest"
};
#endif // FRAMEBUFFERSERIES_H
diff --git a/src/hidabletabwidget.cpp b/src/hidabletabwidget.cpp
--- a/src/hidabletabwidget.cpp
+++ b/src/hidabletabwidget.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2015 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -77,3 +77,8 @@ void HidableTabWidget::connectSignals()
connect(this, SIGNAL(tabBarDoubleClicked(int)), this, SLOT(onTabBarDoubleClicked()));
}
}
+
+void HidableTabWidget::showTabs()
+{
+ hideAction.setChecked(false);
+}
diff --git a/src/hidabletabwidget.h b/src/hidabletabwidget.h
--- a/src/hidabletabwidget.h
+++ b/src/hidabletabwidget.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2015 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -30,6 +30,9 @@ public:
explicit HidableTabWidget(QWidget *parent = 0);
QAction hideAction;
+public slots:
+ void showTabs();
+
private slots:
void onHideAction(bool checked);
void onTabBarClicked();
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -63,7 +63,8 @@ MainWindow::MainWindow(QWidget *parent)
snapshotMan(this, &channelMan),
commandPanel(&serialPort),
dataFormatPanel(&serialPort, &channelMan, &recorder),
- recordPanel(&recorder, &channelMan)
+ recordPanel(&recorder, &channelMan),
+ updateCheckDialog(this)
{
ui->setupUi(this);
@@ -86,6 +87,7 @@ MainWindow::MainWindow(QWidget *parent)
connect(&commandPanel, &CommandPanel::focusRequested, [this]()
{
this->ui->tabWidget->setCurrentWidget(&commandPanel);
+ this->ui->tabWidget->showTabs();
});
tbPortControl->setObjectName("tbPortControl");
@@ -111,6 +113,9 @@ MainWindow::MainWindow(QWidget *parent)
QObject::connect(ui->actionHelpAbout, &QAction::triggered,
&aboutDialog, &QWidget::show);
+ QObject::connect(ui->actionCheckUpdate, &QAction::triggered,
+ &updateCheckDialog, &QWidget::show);
+
QObject::connect(ui->actionReportBug, &QAction::triggered,
[](){QDesktopServices::openUrl(QUrl(BUG_REPORT_URL));});
@@ -133,14 +138,18 @@ MainWindow::MainWindow(QWidget *parent)
QObject::connect(&portControl, &PortControl::portToggled,
this, &MainWindow::onPortToggled);
+ // plot control signals
connect(&plotControlPanel, &PlotControlPanel::numOfSamplesChanged,
this, &MainWindow::onNumOfSamplesChanged);
connect(&plotControlPanel, &PlotControlPanel::numOfSamplesChanged,
- plotMan, &PlotManager::onNumOfSamplesChanged);
+ plotMan, &PlotManager::setNumOfSamples);
- connect(&plotControlPanel, &PlotControlPanel::scaleChanged,
- plotMan, &PlotManager::setAxis);
+ connect(&plotControlPanel, &PlotControlPanel::yScaleChanged,
+ plotMan, &PlotManager::setYAxis);
+
+ connect(&plotControlPanel, &PlotControlPanel::xScaleChanged,
+ plotMan, &PlotManager::setXAxis);
QObject::connect(ui->actionClear, SIGNAL(triggered(bool)),
this, SLOT(clearPlot()));
@@ -211,9 +220,12 @@ MainWindow::MainWindow(QWidget *parent)
plotMan->addCurve(channelMan.channelName(i), channelMan.channelBuffer(i));
}
- // init auto scale
- plotMan->setAxis(plotControlPanel.autoScale(),
- plotControlPanel.yMin(), plotControlPanel.yMax());
+ // init scales
+ plotMan->setYAxis(plotControlPanel.autoScale(),
+ plotControlPanel.yMin(), plotControlPanel.yMax());
+ plotMan->setXAxis(plotControlPanel.xAxisAsIndex(),
+ plotControlPanel.xMin(), plotControlPanel.xMax());
+ plotMan->setNumOfSamples(numOfSamples);
// Init sps (sample per second) counter
spsLabel.setText("0sps");
@@ -246,15 +258,12 @@ MainWindow::MainWindow(QWidget *parent)
connect(commandPanel.newCommandAction(), &QAction::triggered, [this]()
{
this->ui->tabWidget->setCurrentWidget(&commandPanel);
+ this->ui->tabWidget->showTabs();
});
}
MainWindow::~MainWindow()
{
- // save settings
- QSettings settings("serialplot", "serialplot");
- saveAllSettings(&settings);
-
if (serialPort.isOpen())
{
serialPort.close();
@@ -268,19 +277,54 @@ MainWindow::~MainWindow()
void MainWindow::closeEvent(QCloseEvent * event)
{
+ // save snapshots
if (!snapshotMan.isAllSaved())
{
auto clickedButton = QMessageBox::warning(
this, "Closing SerialPlot",
"There are un-saved snapshots. If you close you will loose the data.",
- QMessageBox::Discard | QMessageBox::Discard,
- QMessageBox::Cancel);
+ QMessageBox::Discard, QMessageBox::Cancel);
if (clickedButton == QMessageBox::Cancel)
{
event->ignore();
return;
}
}
+
+ // save settings
+ QSettings settings("serialplot", "serialplot");
+ saveAllSettings(&settings);
+ settings.sync();
+
+ if (settings.status() != QSettings::NoError)
+ {
+ QString errorText;
+
+ if (settings.status() == QSettings::AccessError)
+ {
+ QString file = settings.fileName();
+ errorText = QString("Serialplot cannot save settings due to access error. \
+This happens if you have run serialplot as root (with sudo for ex.) previously. \
+Try fixing the permissions of file: %1, or just delete it.").arg(file);
+ }
+ else
+ {
+ errorText = QString("Serialplot cannot save settings due to unknown error: %1").\
+ arg(settings.status());
+ }
+
+ auto button = QMessageBox::critical(
+ NULL,
+ "Failed to save settings!", errorText,
+ QMessageBox::Cancel | QMessageBox::Ok);
+
+ if (button == QMessageBox::Cancel)
+ {
+ event->ignore();
+ return;
+ }
+ }
+
QMainWindow::closeEvent(event);
}
@@ -449,6 +493,11 @@ void MainWindow::onExportCsv()
}
}
+PlotViewSettings MainWindow::viewSettings() const
+{
+ return plotMan->viewSettings();
+}
+
void MainWindow::messageHandler(QtMsgType type,
const QMessageLogContext &context,
const QString &msg)
@@ -500,6 +549,7 @@ void MainWindow::saveAllSettings(QSettin
plotMan->saveSettings(settings);
commandPanel.saveSettings(settings);
recordPanel.saveSettings(settings);
+ updateCheckDialog.saveSettings(settings);
}
void MainWindow::loadAllSettings(QSettings* settings)
@@ -512,6 +562,7 @@ void MainWindow::loadAllSettings(QSettin
plotMan->loadSettings(settings);
commandPanel.loadSettings(settings);
recordPanel.loadSettings(settings);
+ updateCheckDialog.loadSettings(settings);
}
void MainWindow::saveMWSettings(QSettings* settings)
diff --git a/src/mainwindow.h b/src/mainwindow.h
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -45,6 +45,7 @@
#include "snapshotmanager.h"
#include "plotmanager.h"
#include "datarecorder.h"
+#include "updatecheckdialog.h"
namespace Ui {
class MainWindow;
@@ -58,6 +59,8 @@ public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
+ PlotViewSettings viewSettings() const;
+
void messageHandler(QtMsgType type, const QMessageLogContext &context,
const QString &msg);
@@ -83,6 +86,7 @@ private:
DataFormatPanel dataFormatPanel;
RecordPanel recordPanel;
PlotControlPanel plotControlPanel;
+ UpdateCheckDialog updateCheckDialog;
bool isDemoRunning();
/// Stores settings for all modules
diff --git a/src/mainwindow.ui b/src/mainwindow.ui
--- a/src/mainwindow.ui
+++ b/src/mainwindow.ui
@@ -90,7 +90,7 @@
0
0
653
- 27
+ 25
diff --git a/src/plot.cpp b/src/plot.cpp
--- a/src/plot.cpp
+++ b/src/plot.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2015 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -38,6 +38,8 @@ Plot::Plot(QWidget* parent) :
{
isAutoScaled = true;
symbolSize = 0;
+ numOfSamples = 1;
+ showSymbols = Plot::ShowSymbolsAuto;
QObject::connect(&zoomer, &Zoomer::unzoomed, this, &Plot::unzoomed);
@@ -71,6 +73,16 @@ Plot::Plot(QWidget* parent) :
demoIndicator.setText(demoText);
demoIndicator.hide();
demoIndicator.attach(this);
+
+ // init no channels are visible indicator
+ QwtText noChannelText(" No Visible Channels ");
+ noChannelText.setColor(QColor("white"));
+ noChannelText.setBackgroundBrush(Qt::darkBlue);
+ noChannelText.setBorderRadius(4);
+ noChannelText.setRenderFlags(Qt::AlignHCenter | Qt::AlignVCenter);
+ noChannelIndicator.setText(noChannelText);
+ noChannelIndicator.hide();
+ noChannelIndicator.attach(this);
}
Plot::~Plot()
@@ -78,7 +90,7 @@ Plot::~Plot()
if (snapshotOverlay != NULL) delete snapshotOverlay;
}
-void Plot::setAxis(bool autoScaled, double yAxisMin, double yAxisMax)
+void Plot::setYAxis(bool autoScaled, double yAxisMin, double yAxisMax)
{
this->isAutoScaled = autoScaled;
@@ -92,8 +104,29 @@ void Plot::setAxis(bool autoScaled, doub
resetAxes();
}
+void Plot::setXAxis(double xMin, double xMax)
+{
+ _xMin = xMin;
+ _xMax = xMax;
+
+ zoomer.zoom(0); // unzoom
+
+ // set axis
+ setAxisScale(QwtPlot::xBottom, xMin, xMax);
+ replot(); // Note: if we don't replot here scale at startup isn't set correctly
+
+ // reset zoom base
+ auto base = zoomer.zoomBase();
+ base.setLeft(xMin);
+ base.setRight(xMax);
+ zoomer.setZoomBase(base);
+
+ onXScaleChanged();
+}
+
void Plot::resetAxes()
{
+ // reset y axis
if (isAutoScaled)
{
setAxisAutoScale(QwtPlot::yLeft);
@@ -103,12 +136,13 @@ void Plot::resetAxes()
setAxisScale(QwtPlot::yLeft, yMin, yMax);
}
+ zoomer.setZoomBase();
+
replot();
}
void Plot::unzoomed()
{
- setAxisAutoScale(QwtPlot::xBottom);
resetAxes();
onXScaleChanged();
}
@@ -139,6 +173,12 @@ void Plot::showDemoIndicator(bool show)
replot();
}
+void Plot::showNoChannel(bool show)
+{
+ noChannelIndicator.setVisible(show);
+ replot();
+}
+
void Plot::unzoom()
{
zoomer.zoom(0);
@@ -224,12 +264,45 @@ void Plot::flashSnapshotOverlay(bool lig
});
}
+void Plot::setSymbols(ShowSymbols shown)
+{
+ showSymbols = shown;
+
+ if (showSymbols == Plot::ShowSymbolsAuto)
+ {
+ calcSymbolSize();
+ }
+ else if (showSymbols == Plot::ShowSymbolsShow)
+ {
+ symbolSize = SYMBOL_SIZE_MAX;
+ }
+ else
+ {
+ symbolSize = 0;
+ }
+
+ updateSymbols();
+ replot();
+}
+
void Plot::onXScaleChanged()
{
+ if (showSymbols == Plot::ShowSymbolsAuto)
+ {
+ calcSymbolSize();
+ updateSymbols();
+ }
+}
+
+void Plot::calcSymbolSize()
+{
auto sw = axisWidget(QwtPlot::xBottom);
auto paintDist = sw->scaleDraw()->scaleMap().pDist();
auto scaleDist = sw->scaleDraw()->scaleMap().sDist();
- int symDisPx = round(paintDist / scaleDist);
+ auto fullScaleDist = zoomer.zoomBase().width();
+ auto zoomRate = fullScaleDist / scaleDist;
+ float samplesInView = numOfSamples / zoomRate;
+ int symDisPx = round(paintDist / samplesInView);
if (symDisPx < SYMBOL_SHOW_AT_WIDTH)
{
@@ -239,8 +312,6 @@ void Plot::onXScaleChanged()
{
symbolSize = std::min(SYMBOL_SIZE_MAX, symDisPx-SYMBOL_SHOW_AT_WIDTH+1);
}
-
- updateSymbols();
}
void Plot::updateSymbols()
@@ -271,10 +342,8 @@ void Plot::resizeEvent(QResizeEvent * ev
onXScaleChanged();
}
-void Plot::onNumOfSamplesChanged(unsigned value)
+void Plot::setNumOfSamples(unsigned value)
{
- auto currentBase = zoomer.zoomBase();
- currentBase.setWidth(value);
- zoomer.setZoomBase(currentBase);
+ numOfSamples = value;
onXScaleChanged();
}
diff --git a/src/plot.h b/src/plot.h
--- a/src/plot.h
+++ b/src/plot.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -40,6 +40,13 @@ class Plot : public QwtPlot
friend class PlotManager;
public:
+ enum ShowSymbols
+ {
+ ShowSymbolsAuto,
+ ShowSymbolsShow,
+ ShowSymbolsHide
+ };
+
Plot(QWidget* parent = 0);
~Plot();
@@ -50,9 +57,12 @@ public slots:
void showMinorGrid(bool show = true);
void showLegend(bool show = true);
void showDemoIndicator(bool show = true);
+ void showNoChannel(bool show = true);
void unzoom();
void darkBackground(bool enabled = true);
- void setAxis(bool autoScaled, double yMin = 0, double yMax = 1);
+ void setYAxis(bool autoScaled, double yMin = 0, double yMax = 1);
+ void setXAxis(double xMin, double xMax);
+ void setSymbols(ShowSymbols shown);
/**
* Displays an animation for snapshot.
@@ -61,7 +71,7 @@ public slots:
*/
void flashSnapshotOverlay(bool light);
- void onNumOfSamplesChanged(unsigned value);
+ void setNumOfSamples(unsigned value);
protected:
/// update the display of symbols depending on `symbolSize`
@@ -70,6 +80,8 @@ protected:
private:
bool isAutoScaled;
double yMin, yMax;
+ double _xMin, _xMax;
+ unsigned numOfSamples;
int symbolSize;
Zoomer zoomer;
ScaleZoomer sZoomer;
@@ -77,9 +89,12 @@ private:
PlotSnapshotOverlay* snapshotOverlay;
QwtPlotLegendItem legend;
QwtPlotTextLabel demoIndicator;
+ QwtPlotTextLabel noChannelIndicator;
+ ShowSymbols showSymbols;
void resetAxes();
void resizeEvent(QResizeEvent * event);
+ void calcSymbolSize();
private slots:
void unzoomed();
diff --git a/src/plotcontrolpanel.cpp b/src/plotcontrolpanel.cpp
--- a/src/plotcontrolpanel.cpp
+++ b/src/plotcontrolpanel.cpp
@@ -61,6 +61,12 @@ PlotControlPanel::PlotControlPanel(QWidg
ui->spYmax->setRange((-1) * std::numeric_limits::max(),
std::numeric_limits::max());
+ ui->spXmin->setRange((-1) * std::numeric_limits::max(),
+ std::numeric_limits::max());
+
+ ui->spXmax->setRange((-1) * std::numeric_limits::max(),
+ std::numeric_limits::max());
+
// connect signals
connect(ui->spNumOfSamples, SIGNAL(valueChanged(int)),
this, SLOT(onNumOfSamples(int)));
@@ -74,6 +80,15 @@ PlotControlPanel::PlotControlPanel(QWidg
connect(ui->spYmin, SIGNAL(valueChanged(double)),
this, SLOT(onYScaleChanged()));
+ connect(ui->cbIndex, &QCheckBox::toggled,
+ this, &PlotControlPanel::onIndexChecked);
+
+ connect(ui->spXmax, SIGNAL(valueChanged(double)),
+ this, SLOT(onXScaleChanged()));
+
+ connect(ui->spXmin, SIGNAL(valueChanged(double)),
+ this, SLOT(onXScaleChanged()));
+
// init scale range preset list
for (int nbits = 8; nbits <= 24; nbits++) // signed binary formats
{
@@ -181,7 +196,7 @@ void PlotControlPanel::onAutoScaleChecke
ui->spYmin->setEnabled(false);
ui->spYmax->setEnabled(false);
- emit scaleChanged(true); // autoscale
+ emit yScaleChanged(true); // autoscale
}
else
{
@@ -190,30 +205,48 @@ void PlotControlPanel::onAutoScaleChecke
ui->spYmin->setEnabled(true);
ui->spYmax->setEnabled(true);
- emit scaleChanged(false, ui->spYmin->value(), ui->spYmax->value());
+ emit yScaleChanged(false, ui->spYmin->value(), ui->spYmax->value());
}
}
void PlotControlPanel::onYScaleChanged()
{
- emit scaleChanged(false, ui->spYmin->value(), ui->spYmax->value());
+ if (!autoScale())
+ {
+ emit yScaleChanged(false, ui->spYmin->value(), ui->spYmax->value());
+ }
}
-bool PlotControlPanel::autoScale()
+bool PlotControlPanel::autoScale() const
{
return ui->cbAutoScale->isChecked();
}
-double PlotControlPanel::yMax()
+double PlotControlPanel::yMax() const
{
return ui->spYmax->value();
}
-double PlotControlPanel::yMin()
+double PlotControlPanel::yMin() const
{
return ui->spYmin->value();
}
+bool PlotControlPanel::xAxisAsIndex() const
+{
+ return ui->cbIndex->isChecked();
+}
+
+double PlotControlPanel::xMax() const
+{
+ return ui->spXmax->value();
+}
+
+double PlotControlPanel::xMin() const
+{
+ return ui->spXmin->value();
+}
+
void PlotControlPanel::onRangeSelected()
{
Range r = ui->cbRangePresets->currentData().value();
@@ -222,6 +255,36 @@ void PlotControlPanel::onRangeSelected()
ui->cbAutoScale->setChecked(false);
}
+void PlotControlPanel::onIndexChecked(bool checked)
+{
+ if (checked)
+ {
+ ui->lXmin->setEnabled(false);
+ ui->lXmax->setEnabled(false);
+ ui->spXmin->setEnabled(false);
+ ui->spXmax->setEnabled(false);
+
+ emit xScaleChanged(true); // use index
+ }
+ else
+ {
+ ui->lXmin->setEnabled(true);
+ ui->lXmax->setEnabled(true);
+ ui->spXmin->setEnabled(true);
+ ui->spXmax->setEnabled(true);
+
+ emit xScaleChanged(false, ui->spXmin->value(), ui->spXmax->value());
+ }
+}
+
+void PlotControlPanel::onXScaleChanged()
+{
+ if (!xAxisAsIndex())
+ {
+ emit xScaleChanged(false, ui->spXmin->value(), ui->spXmax->value());
+ }
+}
+
void PlotControlPanel::setChannelInfoModel(ChannelInfoModel* model)
{
ui->tvChannelInfo->setModel(model);
@@ -299,6 +362,9 @@ void PlotControlPanel::saveSettings(QSet
{
settings->beginGroup(SettingGroup_Plot);
settings->setValue(SG_Plot_NumOfSamples, numOfSamples());
+ settings->setValue(SG_Plot_IndexAsX, xAxisAsIndex());
+ settings->setValue(SG_Plot_XMax, xMax());
+ settings->setValue(SG_Plot_XMin, xMin());
settings->setValue(SG_Plot_AutoScale, autoScale());
settings->setValue(SG_Plot_YMax, yMax());
settings->setValue(SG_Plot_YMin, yMin());
@@ -310,6 +376,10 @@ void PlotControlPanel::loadSettings(QSet
settings->beginGroup(SettingGroup_Plot);
ui->spNumOfSamples->setValue(
settings->value(SG_Plot_NumOfSamples, numOfSamples()).toInt());
+ ui->cbIndex->setChecked(
+ settings->value(SG_Plot_IndexAsX, xAxisAsIndex()).toBool());
+ ui->spXmax->setValue(settings->value(SG_Plot_XMax, xMax()).toDouble());
+ ui->spXmin->setValue(settings->value(SG_Plot_XMin, xMin()).toDouble());
ui->cbAutoScale->setChecked(
settings->value(SG_Plot_AutoScale, autoScale()).toBool());
ui->spYmax->setValue(settings->value(SG_Plot_YMax, yMax()).toDouble());
diff --git a/src/plotcontrolpanel.h b/src/plotcontrolpanel.h
--- a/src/plotcontrolpanel.h
+++ b/src/plotcontrolpanel.h
@@ -40,9 +40,12 @@ public:
~PlotControlPanel();
unsigned numOfSamples();
- bool autoScale();
- double yMax();
- double yMin();
+ bool autoScale() const;
+ double yMax() const;
+ double yMin() const;
+ bool xAxisAsIndex() const;
+ double xMax() const;
+ double xMin() const;
void setChannelInfoModel(ChannelInfoModel* model);
@@ -53,7 +56,8 @@ public:
signals:
void numOfSamplesChanged(int value);
- void scaleChanged(bool autoScaled, double yMin = 0, double yMax = 1);
+ void yScaleChanged(bool autoScaled, double yMin = 0, double yMax = 1);
+ void xScaleChanged(bool asIndex, double xMin = 0, double xMax = 1);
private:
Ui::PlotControlPanel *ui;
@@ -74,6 +78,8 @@ private slots:
void onAutoScaleChecked(bool checked);
void onYScaleChanged();
void onRangeSelected();
+ void onIndexChecked(bool checked);
+ void onXScaleChanged();
};
#endif // PLOTCONTROLPANEL_H
diff --git a/src/plotcontrolpanel.ui b/src/plotcontrolpanel.ui
--- a/src/plotcontrolpanel.ui
+++ b/src/plotcontrolpanel.ui
@@ -6,7 +6,7 @@
0
0
- 590
+ 706
187
@@ -157,6 +157,87 @@
-
+
+
+ Index as X AXis
+
+
+ true
+
+
+
+ -
+
+
-
+
+
+ false
+
+
+ Xmin
+
+
+
+ -
+
+
+ false
+
+
+
+ 75
+ 0
+
+
+
+
+ 75
+ 16777215
+
+
+
+ lower limit of Y axis
+
+
+ 0.000000000000000
+
+
+
+ -
+
+
+ false
+
+
+ Xmax
+
+
+
+ -
+
+
+ false
+
+
+
+ 75
+ 16777215
+
+
+
+ upper limit of Y axis
+
+
+ 1000.000000000000000
+
+
+ 1000.000000000000000
+
+
+
+
+
+ -
Auto Scale Y Axis
@@ -166,75 +247,85 @@
- -
-
-
- false
-
-
- Ymax
-
-
+
-
+
+
-
+
+
+ false
+
+
+ Ymin
+
+
+
+ -
+
+
+ false
+
+
+
+ 75
+ 0
+
+
+
+
+ 75
+ 16777215
+
+
+
+ lower limit of Y axis
+
+
+ 0.000000000000000
+
+
+
+ -
+
+
+ false
+
+
+ Ymax
+
+
+
+ -
+
+
+ false
+
+
+
+ 75
+ 16777215
+
+
+
+ upper limit of Y axis
+
+
+ 1000.000000000000000
+
+
+ 1000.000000000000000
+
+
+
+
- -
-
-
- false
-
-
-
- 75
- 16777215
-
-
-
- upper limit of Y axis
-
-
- 1000.000000000000000
-
-
- 1000.000000000000000
-
-
-
- -
-
-
- false
-
-
- Ymin
-
-
-
- -
-
-
- false
-
-
-
- 75
- 16777215
-
-
-
- lower limit of Y axis
-
-
- 0.000000000000000
-
-
-
- -
+
-
Select Range Preset:
- -
+
-
diff --git a/src/plotmanager.cpp b/src/plotmanager.cpp
--- a/src/plotmanager.cpp
+++ b/src/plotmanager.cpp
@@ -17,6 +17,9 @@
along with serialplot. If not, see .
*/
+#include
+#include
+#include
#include
#include "qwt_symbol.h"
@@ -33,13 +36,18 @@ PlotManager::PlotManager(QWidget* plotAr
unzoomAction("&Unzoom", this),
darkBackgroundAction("&Dark Background", this),
showLegendAction("&Legend", this),
- showMultiAction("Multi &Plot", this)
+ showMultiAction("Multi &Plot", this),
+ setSymbolsAction("Symbols", this)
{
_autoScaled = true;
_yMin = 0;
_yMax = 1;
+ _xAxisAsIndex = true;
isDemoShown = false;
_infoModel = infoModel;
+ _numOfSamples = 1;
+ showSymbols = Plot::ShowSymbolsAuto;
+ emptyPlot = NULL;
// initalize layout and single widget
isMulti = false;
@@ -54,6 +62,7 @@ PlotManager::PlotManager(QWidget* plotAr
darkBackgroundAction.setToolTip("Enable Dark Plot Background");
showLegendAction.setToolTip("Display the Legend on Plot");
showMultiAction.setToolTip("Display All Channels Separately");
+ setSymbolsAction.setToolTip("Show/Hide symbols");
showGridAction.setShortcut(QKeySequence("G"));
showMinorGridAction.setShortcut(QKeySequence("M"));
@@ -72,6 +81,37 @@ PlotManager::PlotManager(QWidget* plotAr
showMinorGridAction.setEnabled(false);
+ // setup symbols menu
+ setSymbolsAutoAct = setSymbolsMenu.addAction("Show When Zoomed");
+ setSymbolsAutoAct->setCheckable(true);
+ setSymbolsAutoAct->setChecked(true);
+ connect(setSymbolsAutoAct, SELECT::OVERLOAD_OF(&QAction::triggered),
+ [this](bool checked)
+ {
+ if (checked) setSymbols(Plot::ShowSymbolsAuto);
+ });
+ setSymbolsShowAct = setSymbolsMenu.addAction("Always Show");
+ setSymbolsShowAct->setCheckable(true);
+ connect(setSymbolsShowAct, SELECT::OVERLOAD_OF(&QAction::triggered),
+ [this](bool checked)
+ {
+ if (checked) setSymbols(Plot::ShowSymbolsShow);
+ });
+ setSymbolsHideAct = setSymbolsMenu.addAction("Always Hide");
+ setSymbolsHideAct->setCheckable(true);
+ connect(setSymbolsHideAct, SELECT::OVERLOAD_OF(&QAction::triggered),
+ [this](bool checked)
+ {
+ if (checked) setSymbols(Plot::ShowSymbolsHide);
+ });
+ setSymbolsAction.setMenu(&setSymbolsMenu);
+
+ // add symbol actions to same group so that they appear as radio buttons
+ auto group = new QActionGroup(this);
+ group->addAction(setSymbolsAutoAct);
+ group->addAction(setSymbolsShowAct);
+ group->addAction(setSymbolsHideAct);
+
connect(&showGridAction, SELECT::OVERLOAD_OF(&QAction::triggered),
this, &PlotManager::showGrid);
connect(&showGridAction, SELECT::OVERLOAD_OF(&QAction::triggered),
@@ -118,6 +158,7 @@ PlotManager::~PlotManager()
}
if (scrollArea != NULL) delete scrollArea;
+ if (emptyPlot != NULL) delete emptyPlot;
}
void PlotManager::onChannelInfoChanged(const QModelIndex &topLeft,
@@ -151,6 +192,8 @@ void PlotManager::onChannelInfoChanged(c
}
}
+ checkNoVisChannels();
+
// replot single widget
if (!isMulti)
{
@@ -160,6 +203,20 @@ void PlotManager::onChannelInfoChanged(c
}
}
+void PlotManager::checkNoVisChannels()
+{
+ // if all channels are hidden show indicator
+ bool allhidden = std::none_of(curves.cbegin(), curves.cend(),
+ [](QwtPlotCurve* c) {return c->isVisible();});
+
+ plotWidgets[0]->showNoChannel(allhidden);
+ if (isMulti)
+ {
+ plotWidgets[0]->showNoChannel(allhidden);
+ plotWidgets[0]->setVisible(true);
+ }
+}
+
void PlotManager::setMulti(bool enabled)
{
if (enabled == isMulti) return;
@@ -186,7 +243,9 @@ void PlotManager::setMulti(bool enabled)
// add new widgets and attach
for (auto curve : curves)
{
- curve->attach(addPlotWidget());
+ auto plot = addPlotWidget();
+ plot->setVisible(curve->isVisible());
+ curve->attach(plot);
}
}
else
@@ -200,6 +259,8 @@ void PlotManager::setMulti(bool enabled)
curve->attach(plot);
}
}
+
+ checkNoVisChannels();
}
void PlotManager::setupLayout(bool multiPlot)
@@ -251,7 +312,18 @@ Plot* PlotManager::addPlotWidget()
plot->showMinorGrid(showMinorGridAction.isChecked());
plot->showLegend(showLegendAction.isChecked());
plot->showDemoIndicator(isDemoShown);
- plot->setAxis(_autoScaled, _yMin, _yMax);
+ plot->setYAxis(_autoScaled, _yMin, _yMax);
+ plot->setNumOfSamples(_numOfSamples);
+ plot->setSymbols(showSymbols);
+
+ if (_xAxisAsIndex)
+ {
+ plot->setXAxis(0, _numOfSamples);
+ }
+ else
+ {
+ plot->setXAxis(_xMin, _xMax);
+ }
return plot;
}
@@ -259,7 +331,9 @@ Plot* PlotManager::addPlotWidget()
void PlotManager::addCurve(QString title, FrameBuffer* buffer)
{
auto curve = new QwtPlotCurve(title);
- curve->setSamples(new FrameBufferSeries(buffer));
+ auto series = new FrameBufferSeries(buffer);
+ series->setXAxis(_xAxisAsIndex, _xMin, _xMax);
+ curve->setSamples(series);
_addCurve(curve);
}
@@ -352,6 +426,7 @@ QList PlotManager::menuActions
actions << &darkBackgroundAction;
actions << &showLegendAction;
actions << &showMultiAction;
+ actions << &setSymbolsAction;
return actions;
}
@@ -404,17 +479,51 @@ void PlotManager::darkBackground(bool en
}
}
-void PlotManager::setAxis(bool autoScaled, double yAxisMin, double yAxisMax)
+void PlotManager::setSymbols(Plot::ShowSymbols shown)
+{
+ showSymbols = shown;
+ for (auto plot : plotWidgets)
+ {
+ plot->setSymbols(shown);
+ }
+}
+
+void PlotManager::setYAxis(bool autoScaled, double yAxisMin, double yAxisMax)
{
_autoScaled = autoScaled;
_yMin = yAxisMin;
_yMax = yAxisMax;
for (auto plot : plotWidgets)
{
- plot->setAxis(autoScaled, yAxisMin, yAxisMax);
+ plot->setYAxis(autoScaled, yAxisMin, yAxisMax);
}
}
+void PlotManager::setXAxis(bool asIndex, double xMin, double xMax)
+{
+ _xAxisAsIndex = asIndex;
+ _xMin = xMin;
+ _xMax = xMax;
+ for (auto curve : curves)
+ {
+ // TODO: what happens when addCurve(QVector) is used?
+ FrameBufferSeries* series = static_cast(curve->data());
+ series->setXAxis(asIndex, xMin, xMax);
+ }
+ for (auto plot : plotWidgets)
+ {
+ if (asIndex)
+ {
+ plot->setXAxis(0, _numOfSamples);
+ }
+ else
+ {
+ plot->setXAxis(xMin, xMax);
+ }
+ }
+ replot();
+}
+
void PlotManager::flashSnapshotOverlay()
{
for (auto plot : plotWidgets)
@@ -423,11 +532,54 @@ void PlotManager::flashSnapshotOverlay()
}
}
-void PlotManager::onNumOfSamplesChanged(unsigned value)
+void PlotManager::setNumOfSamples(unsigned value)
{
+ _numOfSamples = value;
for (auto plot : plotWidgets)
{
- plot->onNumOfSamplesChanged(value);
+ plot->setNumOfSamples(value);
+ if (_xAxisAsIndex) plot->setXAxis(0, value);
+ }
+}
+
+PlotViewSettings PlotManager::viewSettings() const
+{
+ return PlotViewSettings(
+ {
+ showGridAction.isChecked(),
+ showMinorGridAction.isChecked(),
+ darkBackgroundAction.isChecked(),
+ showLegendAction.isChecked(),
+ showMultiAction.isChecked(),
+ showSymbols
+ });
+}
+
+void PlotManager::setViewSettings(const PlotViewSettings& settings)
+{
+ showGridAction.setChecked(settings.showGrid);
+ showGrid(settings.showGrid);
+ showMinorGridAction.setChecked(settings.showMinorGrid);
+ showMinorGrid(settings.showMinorGrid);
+ darkBackgroundAction.setChecked(settings.darkBackground);
+ darkBackground(settings.darkBackground);
+ showLegendAction.setChecked(settings.showLegend);
+ showLegend(settings.showLegend);
+ showMultiAction.setChecked(settings.showMulti);
+ setMulti(settings.showMulti);
+
+ setSymbols(settings.showSymbols);
+ if (showSymbols == Plot::ShowSymbolsAuto)
+ {
+ setSymbolsAutoAct->setChecked(true);
+ }
+ else if (showSymbols == Plot::ShowSymbolsShow)
+ {
+ setSymbolsShowAct->setChecked(true);
+ }
+ else
+ {
+ setSymbolsHideAct->setChecked(true);
}
}
@@ -439,6 +591,22 @@ void PlotManager::saveSettings(QSettings
settings->setValue(SG_Plot_MinorGrid, showMinorGridAction.isChecked());
settings->setValue(SG_Plot_Legend, showLegendAction.isChecked());
settings->setValue(SG_Plot_MultiPlot, showMultiAction.isChecked());
+
+ QString showSymbolsStr;
+ if (showSymbols == Plot::ShowSymbolsAuto)
+ {
+ showSymbolsStr = "auto";
+ }
+ else if (showSymbols == Plot::ShowSymbolsShow)
+ {
+ showSymbolsStr = "show";
+ }
+ else
+ {
+ showSymbolsStr = "hide";
+ }
+ settings->setValue(SG_Plot_Symbols, showSymbolsStr);
+
settings->endGroup();
}
@@ -461,5 +629,27 @@ void PlotManager::loadSettings(QSettings
showMultiAction.setChecked(
settings->value(SG_Plot_MultiPlot, showMultiAction.isChecked()).toBool());
setMulti(showMultiAction.isChecked());
+
+ 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
+ {
+ qCritical() << "Invalid symbol setting:" << showSymbolsStr;
+ }
+
settings->endGroup();
}
diff --git a/src/plotmanager.h b/src/plotmanager.h
--- a/src/plotmanager.h
+++ b/src/plotmanager.h
@@ -26,12 +26,24 @@
#include
#include
#include
+#include
+#include
#include
#include "plot.h"
#include "framebufferseries.h"
#include "channelinfomodel.h"
+struct PlotViewSettings
+{
+ bool showGrid;
+ bool showMinorGrid;
+ bool darkBackground;
+ bool showLegend;
+ bool showMulti;
+ Plot::ShowSymbols showSymbols;
+};
+
class PlotManager : public QObject
{
Q_OBJECT
@@ -52,6 +64,10 @@ public:
unsigned numOfCurves();
/// Returns the list of actions to be inserted into the `View` menu
QList menuActions();
+ /// Returns current status of menu actions
+ PlotViewSettings viewSettings() const;
+ /// Set the current state of view
+ void setViewSettings(const PlotViewSettings& settings);
/// Stores plot settings into a `QSettings`.
void saveSettings(QSettings* settings);
/// Loads plot settings from a `QSettings`.
@@ -65,11 +81,13 @@ public slots:
/// Enable display of a "DEMO" label on each plot
void showDemoIndicator(bool show = true);
/// Set the Y axis
- void setAxis(bool autoScaled, double yMin = 0, double yMax = 1);
+ void setYAxis(bool autoScaled, double yMin = 0, double yMax = 1);
+ /// Set the X axis
+ void setXAxis(bool asIndex, double xMin = 0 , double xMax = 1);
/// Display an animation for snapshot
void flashSnapshotOverlay();
/// Should be called to update zoom base
- void onNumOfSamplesChanged(unsigned value);
+ void setNumOfSamples(unsigned value);
private:
bool isMulti;
@@ -78,11 +96,17 @@ private:
QScrollArea* scrollArea;
QList curves;
QList plotWidgets;
+ Plot* emptyPlot; ///< for displaying when all channels are hidden
ChannelInfoModel* _infoModel;
bool isDemoShown;
bool _autoScaled;
double _yMin;
double _yMax;
+ bool _xAxisAsIndex;
+ double _xMin;
+ double _xMax;
+ unsigned _numOfSamples;
+ Plot::ShowSymbols showSymbols;
// menu actions
QAction showGridAction;
@@ -91,6 +115,11 @@ private:
QAction darkBackgroundAction;
QAction showLegendAction;
QAction showMultiAction;
+ QAction setSymbolsAction;
+ QMenu setSymbolsMenu;
+ QAction* setSymbolsAutoAct;
+ QAction* setSymbolsShowAct;
+ QAction* setSymbolsHideAct;
void setupLayout(bool multiPlot);
/// Inserts a new plot widget to the current layout.
@@ -99,6 +128,9 @@ private:
Plot* plotWidget(unsigned curveIndex);
/// Common part of overloaded `addCurve` functions
void _addCurve(QwtPlotCurve* curve);
+ void setSymbols(Plot::ShowSymbols shown);
+ /// Check and make sure "no visible channels" text is shown
+ void checkNoVisChannels();
private slots:
void showGrid(bool show = true);
diff --git a/src/recordpanel.cpp b/src/recordpanel.cpp
--- a/src/recordpanel.cpp
+++ b/src/recordpanel.cpp
@@ -60,6 +60,14 @@ RecordPanel::RecordPanel(DataRecorder* r
{
_recorder->disableBuffering = enabled;
});
+
+ connect(ui->cbWindowsLE, &QCheckBox::toggled,
+ [this](bool enabled)
+ {
+ _recorder->windowsLE = enabled;
+ });
+
+ connect(&recordAction, &QAction::toggled, ui->cbWindowsLE, &QWidget::setDisabled);
}
RecordPanel::~RecordPanel()
diff --git a/src/recordpanel.ui b/src/recordpanel.ui
--- a/src/recordpanel.ui
+++ b/src/recordpanel.ui
@@ -6,7 +6,7 @@
0
0
- 532
+ 627
261
@@ -44,53 +44,93 @@
-
-
-
- Increments file name automatically everytime a new recording starts
-
-
- Auto increment file name
-
-
- true
-
-
-
- -
-
-
- Continue recording to file even when plotting is paused
-
-
- Record while paused
-
-
- true
-
-
-
- -
-
-
- Stop recording when port closed
-
-
- true
-
-
-
- -
-
-
- Channel names are written to the first line of record file
-
-
- Write header line
-
-
- true
-
-
+
+
-
+
+
+ Channel names are written to the first line of record file
+
+
+ Write header line
+
+
+ true
+
+
+
+ -
+
+
+ Continue recording to file even when plotting is paused
+
+
+ Record while paused
+
+
+ true
+
+
+
+ -
+
+
+ Do not buffer when writing to file. Check this if you are using other software to open the file during recording.
+
+
+ Disable buffering
+
+
+
+ -
+
+
+ Stop recording when port closed
+
+
+ true
+
+
+
+ -
+
+
+ Increments file name automatically everytime a new recording starts
+
+
+ Auto increment file name
+
+
+ true
+
+
+
+ -
+
+
+ Use CR+LF as line endings. Some windows software may not show lines correctly otherwise. Can't be changed during recording.
+
+
+ Windows Style Line Endings
+
+
+ false
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 1
+ 20
+
+
+
+
+
-
@@ -133,16 +173,6 @@
-
-
-
- Do not buffer when writing to file. Check this if you are using other software to open the file during recording.
-
-
- Disable buffering
-
-
-
- -
Qt::Vertical
diff --git a/src/scalepicker.cpp b/src/scalepicker.cpp
--- a/src/scalepicker.cpp
+++ b/src/scalepicker.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2015 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -23,6 +23,7 @@
#include
#include
#include
+#include
#include
#include "scalepicker.h"
@@ -107,7 +108,7 @@ bool ScalePicker::eventFilter(QObject* o
{
for (auto sp : snapPoints)
{
- if (fabs(posPx-sp) <= SNAP_DISTANCE)
+ if (std::abs(posPx-sp) <= SNAP_DISTANCE)
{
posPx = sp;
break;
@@ -132,13 +133,8 @@ bool ScalePicker::eventFilter(QObject* o
if (!started && pressed && (fabs(posPx-firstPosPx) > MIN_PICK_SIZE))
{
started = true;
- emit pickStarted(pos);
}
- else if (started)
- {
- pickerOverlay->updateOverlay();
- emit picking(firstPos, pos);
- }
+ pickerOverlay->updateOverlay();
scaleOverlay->updateOverlay();
}
else // event->type() == QEvent::MouseButtonRelease
@@ -148,14 +144,20 @@ bool ScalePicker::eventFilter(QObject* o
{
// finalize
started = false;
- emit picked(firstPos, pos);
+ if (firstPos != pos) // ignore 0 width zoom
+ {
+ emit picked(firstPos, pos);
+ }
}
+ pickerOverlay->updateOverlay();
+ scaleOverlay->updateOverlay();
}
return true;
}
else if (event->type() == QEvent::Leave)
{
scaleOverlay->updateOverlay();
+ pickerOverlay->updateOverlay();
return true;
}
else
@@ -164,54 +166,222 @@ bool ScalePicker::eventFilter(QObject* o
}
}
+const int TEXT_MARGIN = 4;
+
void ScalePicker::drawPlotOverlay(QPainter* painter)
{
+ const double FILL_ALPHA = 0.2;
+
+ painter->save();
+ painter->setPen(_pen);
+
if (started)
{
- painter->save();
- painter->setPen(_pen);
+ QColor color = _pen.color();
+ color.setAlphaF(FILL_ALPHA);
+ painter->setBrush(color);
QRect rect;
+ QwtText text = trackerText();
+ auto tSize = text.textSize(painter->font());
+
if (_scaleWidget->alignment() == QwtScaleDraw::BottomScale ||
_scaleWidget->alignment() == QwtScaleDraw::TopScale)
{
- int height = painter->device()->height();
- rect = QRect(posCanvasPx(firstPosPx), 0, currentPosPx-firstPosPx, height);
+ int canvasHeight = painter->device()->height();
+ int pickWidth = currentPosPx-firstPosPx;
+ rect = QRect(posCanvasPx(firstPosPx), 0, pickWidth, canvasHeight);
}
else // vertical
{
- int width = painter->device()->width();
- rect = QRect(0, posCanvasPx(firstPosPx), width, currentPosPx-firstPosPx);
+ int canvasWidth = painter->device()->width();
+ int pickHeight = currentPosPx-firstPosPx;
+ rect = QRect(0, posCanvasPx(firstPosPx), canvasWidth, pickHeight);
}
painter->drawRect(rect);
- painter->restore();
+ text.draw(painter, pickTrackerTextRect(painter, rect, tSize));
+ }
+ else if (_scaleWidget->underMouse())
+ {
+ // draw tracker text centered on cursor
+ QwtText text = trackerText();
+ auto tsize = text.textSize(painter->font());
+ text.draw(painter, trackerTextRect(painter, currentPosPx, tsize));
+ }
+ painter->restore();
+}
+
+QwtText ScalePicker::trackerText() const
+{
+ double pos;
+ // use stored value if snapped to restore precision
+ if (snapPointMap.contains(currentPosPx))
+ {
+ pos = snapPointMap[currentPosPx];
+ }
+ else
+ {
+ pos = position(currentPosPx);
+ }
+
+ return QwtText(QString("%1").arg(pos));
+}
+
+QRectF ScalePicker::trackerTextRect(QPainter* painter, int posPx, QSizeF textSize) const
+{
+ int canvasPosPx = posCanvasPx(posPx);
+ QPointF topLeft;
+
+ if (_scaleWidget->alignment() == QwtScaleDraw::BottomScale ||
+ _scaleWidget->alignment() == QwtScaleDraw::TopScale)
+ {
+ int left = canvasPosPx - textSize.width() / 2;
+ int canvasWidth = painter->device()->width();
+ left = std::max(TEXT_MARGIN, left);
+ left = std::min(double(left), canvasWidth - textSize.width() - TEXT_MARGIN);
+ int top = 0;
+ if (_scaleWidget->alignment() == QwtScaleDraw::BottomScale)
+ {
+ top = painter->device()->height() - textSize.height();
+ }
+ topLeft = QPointF(left, top);
+ }
+ else // left/right scales
+ {
+ int top = canvasPosPx-textSize.height() / 2;
+ int canvasHeight = painter->device()->height();
+ top = std::max(0, top);
+ top = std::min(double(top), canvasHeight - textSize.height());
+ int left = TEXT_MARGIN;
+ if (_scaleWidget->alignment() == QwtScaleDraw::RightScale)
+ {
+ left = painter->device()->width() - textSize.width();
+ }
+ topLeft = QPointF(left, top);
}
+ return QRectF(topLeft, textSize);
+}
+
+QRectF ScalePicker::pickTrackerTextRect(QPainter* painter, QRect pickRect, QSizeF textSize) const
+{
+ qreal left = 0;
+ int pickLength = currentPosPx - firstPosPx;
+ QPointF topLeft;
+
+ if (_scaleWidget->alignment() == QwtScaleDraw::BottomScale ||
+ _scaleWidget->alignment() == QwtScaleDraw::TopScale)
+ {
+ int canvasWidth = painter->device()->width();
+
+ if (pickLength > 0)
+ {
+ left = pickRect.right() + TEXT_MARGIN;
+ }
+ else
+ {
+ left = pickRect.right() - (textSize.width() + TEXT_MARGIN);
+ }
+
+ // make sure text is not off the canvas
+ if (left < TEXT_MARGIN)
+ {
+ left = std::max(0, pickRect.right()) + TEXT_MARGIN;
+ }
+ else if (left + textSize.width() + TEXT_MARGIN > canvasWidth)
+ {
+ left = std::min(pickRect.right(), canvasWidth) - (textSize.width() + TEXT_MARGIN);
+ }
+
+ if (_scaleWidget->alignment() == QwtScaleDraw::BottomScale)
+ {
+ int canvasHeight = painter->device()->height();
+ topLeft = QPointF(left, canvasHeight - textSize.height());
+ }
+ else // top scale
+ {
+ topLeft = QPointF(left, 0);
+ }
+ }
+ else // left/right scale
+ {
+ int canvasHeight = painter->device()->height();
+
+ int top = 0;
+ if (pickLength > 0)
+ {
+ top = pickRect.bottom();
+ }
+ else
+ {
+ top = pickRect.bottom() - textSize.height();
+ }
+
+ // make sure text is not off the canvas
+ if (top < 0)
+ {
+ top = std::max(0, pickRect.bottom());
+ }
+ else if (top + textSize.height() > canvasHeight)
+ {
+ top = std::min(canvasHeight, pickRect.bottom()) - textSize.height();
+ }
+
+ if (_scaleWidget->alignment() == QwtScaleDraw::LeftScale)
+ {
+ topLeft = QPointF(TEXT_MARGIN, top);
+ }
+ else // right scale
+ {
+ int canvasWidth = painter->device()->width();
+ topLeft = QPointF(canvasWidth - textSize.width() - TEXT_MARGIN, top);
+ }
+ }
+ return QRectF(topLeft, textSize);
}
void ScalePicker::drawScaleOverlay(QPainter* painter)
{
painter->save();
- painter->setPen(_pen);
- if (_scaleWidget->alignment() == QwtScaleDraw::BottomScale ||
- _scaleWidget->alignment() == QwtScaleDraw::TopScale)
- {
- int height = painter->device()->height();
- if (started) painter->drawLine(firstPosPx, 0, firstPosPx, height);
- if (started || _scaleWidget->underMouse())
- {
- painter->drawLine(currentPosPx, 0, currentPosPx, height);
- }
- }
- else // vertical
+ // rotate & adjust coordinate system for vertical drawing
+ if (_scaleWidget->alignment() == QwtScaleDraw::LeftScale ||
+ _scaleWidget->alignment() == QwtScaleDraw::RightScale) // vertical
{
int width = painter->device()->width();
- if (started) painter->drawLine(0, firstPosPx, width, firstPosPx);
- if (started || _scaleWidget->underMouse())
+ painter->rotate(90);
+ painter->translate(0, -width);
+ }
+
+ // draw the indicators
+ if (started) drawTriangle(painter, firstPosPx);
+ if (started || _scaleWidget->underMouse())
+ {
+ drawTriangle(painter, currentPosPx);
+ }
+
+ painter->restore();
+}
+
+void ScalePicker::drawTriangle(QPainter* painter, int position)
+{
+ const double tan60 = 1.732;
+ const double trsize = 10;
+ const int TRIANGLE_NUM_POINTS = 3;
+ const int MARGIN = 2;
+ const QPointF points[TRIANGLE_NUM_POINTS] =
{
- painter->drawLine(0, currentPosPx, width, currentPosPx);
- }
- }
+ {0, 0},
+ {-trsize/tan60 , trsize},
+ {trsize/tan60 , trsize}
+ };
+
+ painter->save();
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(_scaleWidget->palette().windowText());
+ painter->setRenderHint(QPainter::Antialiasing);
+
+ painter->translate(position, MARGIN);
+ painter->drawPolygon(points, TRIANGLE_NUM_POINTS);
painter->restore();
}
@@ -222,7 +392,7 @@ void ScalePicker::setPen(QPen pen)
}
// convert the position of the click to the plot coordinates
-double ScalePicker::position(double posPx)
+double ScalePicker::position(double posPx) const
{
return _scaleWidget->scaleDraw()->scaleMap().invTransform(posPx);
}
@@ -248,7 +418,7 @@ int ScalePicker::positionPx(QMouseEvent*
* when drawing the tracker lines. This function maps scale widgets
* pixel coordinate to canvas' coordinate.
*/
-double ScalePicker::posCanvasPx(double pos)
+double ScalePicker::posCanvasPx(double pos) const
{
// assumption: scale.width < canvas.width && scale.x > canvas.x
if (_scaleWidget->alignment() == QwtScaleDraw::BottomScale ||
@@ -270,9 +440,12 @@ void ScalePicker::updateSnapPoints()
_scaleWidget->scaleDraw()->scaleDiv().ticks(QwtScaleDiv::MinorTick);
snapPoints.clear();
+ snapPointMap.clear();
for(auto t : allTicks)
{
// `round` is used because `allTicks` is double but `snapPoints` is int
- snapPoints << round(_scaleWidget->scaleDraw()->scaleMap().transform(t));
+ int p = round(_scaleWidget->scaleDraw()->scaleMap().transform(t));
+ snapPoints << p;
+ snapPointMap[p] = t;
}
}
diff --git a/src/scalepicker.h b/src/scalepicker.h
--- a/src/scalepicker.h
+++ b/src/scalepicker.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2015 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -25,6 +25,7 @@
#include
#include
#include
+#include
#include
#include
@@ -41,8 +42,6 @@ public:
void setPen(QPen pen);
signals:
- void pickStarted(double pos);
- void picking(double firstPos, double lastPos);
void picked(double firstPos, double lastPos);
private:
@@ -58,10 +57,18 @@ private:
double firstPosPx; // pixel coordinates
double currentPosPx; // current position in pixel coordinates
QList snapPoints;
+ /// used to restore precision of snappoints that is lost due to rounding
+ QMap snapPointMap;
- double position(double); // returns the axis mouse position relative to plot coordinates
+ double position(double) const; // returns the axis mouse position relative to plot coordinates
int positionPx(QMouseEvent*); // returns the axis mouse position in pixels
- double posCanvasPx(double pos); // returns the given position in canvas coordinates
+ double posCanvasPx(double pos) const; // returns the given position in canvas coordinates
+ void drawTriangle(QPainter* painter, int position);
+ QwtText trackerText() const;
+ /// Returns tracker text position
+ QRectF trackerTextRect(QPainter* painter, int posPx, QSizeF textSize) const;
+ /// Returns the text position for tracker text shown during picking
+ QRectF pickTrackerTextRect(QPainter* painter, QRect pickRect, QSizeF textSize) const;
private slots:
void updateSnapPoints();
diff --git a/src/setting_defines.h b/src/setting_defines.h
--- a/src/setting_defines.h
+++ b/src/setting_defines.h
@@ -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_UpdateCheck[] = "UpdateCheck";
// mainwindow setting keys
const char SG_MainWindow_Size[] = "size";
@@ -57,6 +58,8 @@ const char SG_Binary_Endianness[] = "end
// ascii reader keys
const char SG_ASCII_NumOfChannels[] = "numOfChannels";
+const char SG_ASCII_Delimiter[] = "delimiter";
+const char SG_ASCII_CustomDelimiter[] = "customDelimiter";
// framed reader keys
const char SG_CustomFrame_NumOfChannels[] = "numOfChannels";
@@ -76,6 +79,9 @@ const char SG_Channels_Visible[] = "visi
// plot settings keys
const char SG_Plot_NumOfSamples[] = "numOfSamples";
+const char SG_Plot_IndexAsX[] = "indexAsX";
+const char SG_Plot_XMax[] = "xMax";
+const char SG_Plot_XMin[] = "xMin";
const char SG_Plot_AutoScale[] = "autoScale";
const char SG_Plot_YMax[] = "yMax";
const char SG_Plot_YMin[] = "yMin";
@@ -84,6 +90,7 @@ const char SG_Plot_Grid[] = "grid";
const char SG_Plot_MinorGrid[] = "minorGrid";
const char SG_Plot_Legend[] = "legend";
const char SG_Plot_MultiPlot[] = "multiPlot";
+const char SG_Plot_Symbols[] = "symbols";
// command setting keys
const char SG_Commands_Command[] = "command";
@@ -99,4 +106,8 @@ const char SG_Record_Header[]
const char SG_Record_Separator[] = "separator";
const char SG_Record_DisableBuffering[] = "disableBuffering";
+// update check settings keys
+const char SG_UpdateCheck_Periodic[] = "periodicCheck";
+const char SG_UpdateCheck_LastCheck[] = "lastCheck";
+
#endif // SETTING_DEFINES_H
diff --git a/src/snapshot.cpp b/src/snapshot.cpp
--- a/src/snapshot.cpp
+++ b/src/snapshot.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2015 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -21,17 +21,18 @@
#include
#include
+#include "mainwindow.h"
#include "snapshot.h"
#include "snapshotview.h"
-Snapshot::Snapshot(QMainWindow* parent, QString name, ChannelInfoModel infoModel) :
+Snapshot::Snapshot(MainWindow* parent, QString name, ChannelInfoModel infoModel, bool saved) :
QObject(parent),
cInfoModel(infoModel),
_showAction(this),
_deleteAction("&Delete", this)
{
_name = name;
- _saved = false;
+ _saved = saved;
view = NULL;
mainWindow = parent;
diff --git a/src/snapshot.h b/src/snapshot.h
--- a/src/snapshot.h
+++ b/src/snapshot.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2015 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -21,7 +21,6 @@
#define SNAPSHOT_H
#include
-#include
#include
#include
#include
@@ -30,13 +29,14 @@
#include "channelinfomodel.h"
class SnapshotView;
+class MainWindow;
class Snapshot : public QObject
{
Q_OBJECT
public:
- Snapshot(QMainWindow* parent, QString name, ChannelInfoModel infoModel);
+ Snapshot(MainWindow* parent, QString name, ChannelInfoModel infoModel, bool saved = false);
~Snapshot();
QVector> data;
@@ -61,7 +61,7 @@ private:
ChannelInfoModel cInfoModel;
QAction _showAction;
QAction _deleteAction;
- QMainWindow* mainWindow;
+ MainWindow* mainWindow;
SnapshotView* view;
bool _saved;
diff --git a/src/snapshotmanager.cpp b/src/snapshotmanager.cpp
--- a/src/snapshotmanager.cpp
+++ b/src/snapshotmanager.cpp
@@ -24,11 +24,13 @@
#include
#include
#include
+#include
#include
+#include "mainwindow.h"
#include "snapshotmanager.h"
-SnapshotManager::SnapshotManager(QMainWindow* mainWindow,
+SnapshotManager::SnapshotManager(MainWindow* mainWindow,
ChannelManager* channelMan) :
_menu("&Snapshots"),
_takeSnapshotAction("&Take Snapshot", this),
@@ -40,6 +42,7 @@ SnapshotManager::SnapshotManager(QMainWi
_takeSnapshotAction.setToolTip("Take a snapshot of current plot");
_takeSnapshotAction.setShortcut(QKeySequence("F5"));
+ _takeSnapshotAction.setIcon(QIcon::fromTheme("camera"));
loadSnapshotAction.setToolTip("Load snapshots from CSV files");
clearAction.setToolTip("Delete all snapshots");
connect(&_takeSnapshotAction, SIGNAL(triggered(bool)),
@@ -191,7 +194,8 @@ void SnapshotManager::loadSnapshotFromFi
ChannelInfoModel channelInfo(channelNames);
auto snapshot = new Snapshot(
- _mainWindow, QFileInfo(fileName).baseName(), ChannelInfoModel(channelNames));
+ _mainWindow, QFileInfo(fileName).baseName(),
+ ChannelInfoModel(channelNames), true);
snapshot->data = data;
addSnapshot(snapshot, false);
diff --git a/src/snapshotmanager.h b/src/snapshotmanager.h
--- a/src/snapshotmanager.h
+++ b/src/snapshotmanager.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2015 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -28,12 +28,14 @@
#include "channelmanager.h"
#include "snapshot.h"
+class MainWindow;
+
class SnapshotManager : public QObject
{
Q_OBJECT
public:
- SnapshotManager(QMainWindow* mainWindow, ChannelManager* channelMan);
+ SnapshotManager(MainWindow* mainWindow, ChannelManager* channelMan);
~SnapshotManager();
QMenu* menu();
@@ -46,7 +48,7 @@ public:
bool isAllSaved(); ///< returns `true` if all snapshots are saved to a file
private:
- QMainWindow* _mainWindow;
+ MainWindow* _mainWindow;
ChannelManager* _channelMan;
QList snapshots;
diff --git a/src/snapshotview.cpp b/src/snapshotview.cpp
--- a/src/snapshotview.cpp
+++ b/src/snapshotview.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2015 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -20,7 +20,7 @@
#include "snapshotview.h"
#include "ui_snapshotview.h"
-SnapshotView::SnapshotView(QWidget *parent, Snapshot* snapshot) :
+SnapshotView::SnapshotView(MainWindow* parent, Snapshot* snapshot) :
QMainWindow(parent),
ui(new Ui::SnapshotView),
renameDialog(this)
@@ -29,17 +29,20 @@ SnapshotView::SnapshotView(QWidget *pare
ui->setupUi(this);
- plotMan = new PlotManager(ui->plotArea, snapshot->infoModel());
+ plotMan = new PlotManager(ui->plotArea, snapshot->infoModel(), this);
+ plotMan->setViewSettings(parent->viewSettings());
ui->menuSnapshot->insertAction(ui->actionClose, snapshot->deleteAction());
this->setWindowTitle(snapshot->displayName());
// initialize curves
unsigned numOfChannels = snapshot->data.size();
+ unsigned numOfSamples = snapshot->data[0].size();
for (unsigned ci = 0; ci < numOfChannels; ci++)
{
plotMan->addCurve(snapshot->channelName(ci), snapshot->data[ci]);
}
+ plotMan->setNumOfSamples(numOfSamples);
renameDialog.setWindowTitle("Rename Snapshot");
renameDialog.setLabelText("Enter new name:");
@@ -62,6 +65,7 @@ SnapshotView::~SnapshotView()
{
delete curve;
}
+ delete plotMan;
delete ui;
}
diff --git a/src/snapshotview.h b/src/snapshotview.h
--- a/src/snapshotview.h
+++ b/src/snapshotview.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2015 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -29,6 +29,7 @@
#include
#include
+#include "mainwindow.h"
#include "plotmanager.h"
#include "snapshot.h"
@@ -41,7 +42,7 @@ class SnapshotView : public QMainWindow
Q_OBJECT
public:
- explicit SnapshotView(QWidget *parent, Snapshot* snapshot);
+ explicit SnapshotView(MainWindow* parent, Snapshot* snapshot);
~SnapshotView();
signals:
diff --git a/src/updatecheckdialog.cpp b/src/updatecheckdialog.cpp
new file mode 100644
--- /dev/null
+++ b/src/updatecheckdialog.cpp
@@ -0,0 +1,105 @@
+/*
+ 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 "setting_defines.h"
+#include "updatecheckdialog.h"
+#include "ui_updatecheckdialog.h"
+
+UpdateCheckDialog::UpdateCheckDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::UpdateCheckDialog)
+{
+ ui->setupUi(this);
+
+ // by default start from yesterday, so that we check at first run
+ lastCheck = QDate::currentDate().addDays(-1);
+
+ connect(&updateChecker, &UpdateChecker::checkFailed,
+ [this](QString errorMessage)
+ {
+ lastCheck = QDate::currentDate();
+ ui->label->setText(QString("Update check failed.\n") + errorMessage);
+ });
+
+ connect(&updateChecker, &UpdateChecker::checkFinished,
+ [this](bool found, QString newVersion, QString downloadUrl)
+ {
+ QString text;
+ if (!found)
+ {
+ text = "There is no update yet.";
+ }
+ else
+ {
+ show();
+#ifdef UPDATE_TYPE_PKGMAN
+ text = QString("There is a new version: %1. "
+ "Use your package manager to update"
+ " or click to download.")\
+ .arg(newVersion).arg(downloadUrl);
+#else
+ text = QString("Found update to version %1. Click to download.")\
+ .arg(newVersion).arg(downloadUrl);
+#endif
+ }
+
+ lastCheck = QDate::currentDate();
+ ui->label->setText(text);
+ });
+}
+
+UpdateCheckDialog::~UpdateCheckDialog()
+{
+ delete ui;
+}
+
+void UpdateCheckDialog::showEvent(QShowEvent *event)
+{
+ updateChecker.checkUpdate();
+ ui->label->setText("Checking update...");
+}
+
+void UpdateCheckDialog::closeEvent(QShowEvent *event)
+{
+ if (updateChecker.isChecking()) updateChecker.cancelCheck();
+}
+
+void UpdateCheckDialog::saveSettings(QSettings* settings)
+{
+ settings->beginGroup(SettingGroup_UpdateCheck);
+ settings->setValue(SG_UpdateCheck_Periodic, ui->cbPeriodic->isChecked());
+ settings->setValue(SG_UpdateCheck_LastCheck, lastCheck.toString(Qt::ISODate));
+ settings->endGroup();
+}
+
+void UpdateCheckDialog::loadSettings(QSettings* settings)
+{
+ settings->beginGroup(SettingGroup_UpdateCheck);
+ ui->cbPeriodic->setChecked(settings->value(SG_UpdateCheck_Periodic,
+ ui->cbPeriodic->isChecked()).toBool());
+ auto lastCheckS = settings->value(SG_UpdateCheck_LastCheck, lastCheck.toString(Qt::ISODate)).toString();
+ lastCheck = QDate::fromString(lastCheckS, Qt::ISODate);
+ settings->endGroup();
+
+ // start the periodic update if required
+ if (ui->cbPeriodic->isChecked() && lastCheck < QDate::currentDate())
+ {
+ updateChecker.checkUpdate();
+ }
+}
diff --git a/src/updatecheckdialog.h b/src/updatecheckdialog.h
new file mode 100644
--- /dev/null
+++ b/src/updatecheckdialog.h
@@ -0,0 +1,54 @@
+/*
+ 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 .
+*/
+
+#ifndef UPDATECHECKDIALOG_H
+#define UPDATECHECKDIALOG_H
+
+#include
+#include
+#include
+#include "updatechecker.h"
+
+namespace Ui {
+class UpdateCheckDialog;
+}
+
+class UpdateCheckDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit UpdateCheckDialog(QWidget *parent = 0);
+ ~UpdateCheckDialog();
+
+ /// Stores update settings into a `QSettings`.
+ void saveSettings(QSettings* settings);
+ /// Loads update settings from a `QSettings`.
+ void loadSettings(QSettings* settings);
+
+private:
+ Ui::UpdateCheckDialog *ui;
+ UpdateChecker updateChecker;
+ QDate lastCheck;
+
+ void showEvent(QShowEvent *event);
+ void closeEvent(QShowEvent *event);
+};
+
+#endif // UPDATECHECKDIALOG_H
diff --git a/src/updatecheckdialog.ui b/src/updatecheckdialog.ui
new file mode 100644
--- /dev/null
+++ b/src/updatecheckdialog.ui
@@ -0,0 +1,71 @@
+
+
+ UpdateCheckDialog
+
+
+
+ 0
+ 0
+ 400
+ 148
+
+
+
+ Check Update
+
+
+
-
+
+
+ Checking update...
+
+
+ true
+
+
+
+ -
+
+
+ Updates will be checked only once a day at first start of the application
+
+
+ Check updates periodically
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Close
+
+
+
+
+
+
+
+
+ buttonBox
+ clicked(QAbstractButton*)
+ UpdateCheckDialog
+ close()
+
+
+ 199
+ 125
+
+
+ 199
+ 73
+
+
+
+
+
diff --git a/src/updatechecker.cpp b/src/updatechecker.cpp
new file mode 100644
--- /dev/null
+++ b/src/updatechecker.cpp
@@ -0,0 +1,222 @@
+/*
+ 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
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "updatechecker.h"
+
+// This link returns the list of downloads in JSON format. Note that we only use
+// the first page because results are sorted new to old.
+const char BB_DOWNLOADS_URL[] = "https://api.bitbucket.org/2.0/repositories/hyozd/serialplot/downloads?fields=values.name,values.links.self.href";
+
+UpdateChecker::UpdateChecker(QObject *parent) :
+ QObject(parent), nam(this)
+{
+ activeReply = NULL;
+
+ connect(&nam, &QNetworkAccessManager::finished,
+ this, &UpdateChecker::onReqFinished);
+}
+
+bool UpdateChecker::isChecking() const
+{
+ return activeReply != NULL && !activeReply->isFinished();
+}
+
+void UpdateChecker::checkUpdate()
+{
+ if (isChecking()) return;
+
+ auto req = QNetworkRequest(QUrl(BB_DOWNLOADS_URL));
+ activeReply = nam.get(req);
+}
+
+void UpdateChecker::cancelCheck()
+{
+ if (activeReply != NULL) activeReply->abort();
+}
+
+void UpdateChecker::onReqFinished(QNetworkReply* reply)
+{
+ if (reply->error() != QNetworkReply::NoError)
+ {
+ emit checkFailed(QString("Network error: ") + reply->errorString());
+ }
+ else
+ {
+ QJsonParseError error;
+ auto data = QJsonDocument::fromJson(reply->readAll(), &error);
+ if (error.error != QJsonParseError::NoError)
+ {
+ emit checkFailed(QString("JSon parsing error: ") + error.errorString());
+ }
+ else
+ {
+ QList files;
+ if (!parseData(data, files))
+ {
+ // TODO: emit detailed data contents for logging
+ emit checkFailed("Data parsing error.");
+ }
+ else
+ {
+ FileInfo updateFile;
+ if (findUpdate(files, updateFile))
+ {
+ emit checkFinished(
+ true, updateFile.version.toString(), updateFile.link);
+ }
+ else
+ {
+ emit checkFinished(false, "", "");
+ }
+ }
+ }
+ }
+ reply->deleteLater();
+ activeReply = NULL;
+}
+
+bool UpdateChecker::parseData(const QJsonDocument& data, QList& files) const
+{
+ /* Data is expected to be in this form:
+
+ {
+ "values": [
+ {
+ "name": "serialplot-0.9.1-x86_64.AppImage",
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/repositories/hyOzd/serialplot/downloads/serialplot-0.9.1-x86_64.AppImage"
+ }
+ }
+ }, ... ]
+ }
+ */
+
+ if (!data.isObject()) return false;
+
+ auto values = data.object()["values"];
+ if (values == QJsonValue::Undefined || !values.isArray()) return false;
+
+ for (auto value : values.toArray())
+ {
+ if (!value.isObject()) return false;
+
+ auto name = value.toObject().value("name");
+ if (name.isUndefined() || !name.isString())
+ return false;
+
+ auto links = value.toObject().value("links");
+ if (links.isUndefined() || !links.isObject())
+ return false;
+
+ auto self = links.toObject().value("self");
+ if (self.isUndefined() || !self.isObject())
+ return false;
+
+ auto href = self.toObject().value("href");
+ if (href.isUndefined() || !href.isString())
+ return false;
+
+ FileInfo finfo;
+ finfo.name = name.toString();
+ finfo.link = href.toString();
+ finfo.hasVersion = VersionNumber::extract(name.toString(), finfo.version);
+
+ if (finfo.name.contains("amd64") ||
+ finfo.name.contains("x86_64") ||
+ finfo.name.contains("win64"))
+ {
+ finfo.arch = FileArch::amd64;
+ }
+ else if (finfo.name.contains("win32") ||
+ finfo.name.contains("i386"))
+ {
+ finfo.arch = FileArch::i386;
+ }
+ else
+ {
+ finfo.arch = FileArch::unknown;
+ }
+
+ files += finfo;
+ }
+
+ return true;
+}
+
+bool UpdateChecker::findUpdate(const QList& files, FileInfo& foundFile) const
+{
+ QList fflist;
+
+ // filter the file list according to extension and version number
+ for (int i = 0; i < files.length(); i++)
+ {
+ // file type to look
+#if defined(Q_OS_WIN)
+ const char ext[] = ".exe";
+#else // of course linux
+ const char ext[] = ".appimage";
+#endif
+
+ // file architecture to look
+#if defined(Q_PROCESSOR_X86_64)
+ const FileArch arch = FileArch::amd64;
+#elif defined(Q_PROCESSOR_X86_32)
+ const FileArch arch = FileArch::i386;
+#elif defined(Q_PROCESSOR_ARM)
+ const FileArch arch = FileArch::arm;
+#else
+ #error Unknown architecture for update file detection.
+#endif
+
+ // filter the file list
+ auto file = files[i];
+ if (file.name.contains(ext, Qt::CaseInsensitive) &&
+ file.arch == arch &&
+ file.hasVersion && file.version > CurrentVersion)
+ {
+ fflist += file;
+ }
+ }
+
+ // sort and find most up to date file
+ if (!fflist.empty())
+ {
+ std::sort(fflist.begin(), fflist.end(),
+ [](const FileInfo& a, const FileInfo& b)
+ {
+ return a.version > b.version;
+ });
+
+ foundFile = fflist[0];
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
diff --git a/src/updatechecker.h b/src/updatechecker.h
new file mode 100644
--- /dev/null
+++ b/src/updatechecker.h
@@ -0,0 +1,77 @@
+/*
+ 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 .
+*/
+
+#ifndef UPDATECHECKER_H
+#define UPDATECHECKER_H
+
+#include
+#include
+#include
+#include
+
+#include "versionnumber.h"
+
+class UpdateChecker : public QObject
+{
+ Q_OBJECT
+public:
+ explicit UpdateChecker(QObject *parent = 0);
+
+ bool isChecking() const;
+
+signals:
+ void checkFinished(bool found, QString newVersion, QString downloadUrl);
+ void checkFailed(QString errorMessage);
+
+public slots:
+ void checkUpdate();
+ void cancelCheck();
+
+private:
+ enum class FileArch
+ {
+ unknown,
+ i386,
+ amd64,
+ arm
+ };
+
+ struct FileInfo
+ {
+ QString name;
+ QString link;
+ bool hasVersion;
+ VersionNumber version;
+ FileArch arch;
+ };
+
+ QNetworkAccessManager nam;
+ QNetworkReply* activeReply;
+
+ /// Parses json and creates a list of files
+ bool parseData(const QJsonDocument& data, QList& files) const;
+ /// Finds the update file in the file list. Returns `-1` if no new version
+ /// is found.
+ bool findUpdate(const QList& files, FileInfo& foundFile) const;
+
+private slots:
+ void onReqFinished(QNetworkReply* reply);
+};
+
+#endif // UPDATECHECKER_H
diff --git a/src/version.h b/src/version.h
new file mode 100644
--- /dev/null
+++ b/src/version.h
@@ -0,0 +1,32 @@
+/*
+ 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 .
+*/
+
+#ifndef VERSION_H
+#define VERSION_H
+
+#ifndef VERSION_STRING
+#warning VERSION_STRING not defined!
+#define VERSION_STRING "0.0.0"
+#endif
+
+#ifndef VERSION_REVISION
+#define VERSION_REVISION ""
+#endif
+
+#endif // VERSION_H
diff --git a/src/version.h.in b/src/version.h.in
--- a/src/version.h.in
+++ b/src/version.h.in
@@ -1,5 +1,5 @@
/*
- Copyright © 2015 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -20,9 +20,6 @@
#ifndef VERSION_H
#define VERSION_H
-#define MAJOR_VERSION @MAJOR_VERSION@
-#define MINOR_VERSION @MINOR_VERSION@
-#define PATCH_VERSION @PATCH_VERSION@
#define VERSION_STRING "@VERSION_STRING@"
#define VERSION_REVISION "@VERSION_REVISION@"
diff --git a/src/versionnumber.cpp b/src/versionnumber.cpp
new file mode 100644
--- /dev/null
+++ b/src/versionnumber.cpp
@@ -0,0 +1,103 @@
+/*
+ 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
+
+#include "versionnumber.h"
+
+VersionNumber::VersionNumber(unsigned mj, unsigned mn, unsigned pt)
+{
+ major = mj;
+ minor = mn;
+ patch = pt;
+}
+
+QString VersionNumber::toString() const
+{
+ return QString("%1.%2.%3").arg(major).arg(minor).arg(patch);
+}
+
+bool VersionNumber::extract(const QString& str, VersionNumber& number)
+{
+ QRegularExpression regexp("(?:[-_vV \\t]|^)(?\\d+)"
+ "(?:\\.(?\\d+))(?:\\.(?\\d+))?[-_ \\t]?");
+ auto match = regexp.match(str, 0, QRegularExpression::PartialPreferCompleteMatch);
+
+ if (!(match.hasMatch() || match.hasPartialMatch())) return false;
+
+ number.major = match.captured("major").toUInt();
+
+ auto zeroIfNull = [](QString str) -> unsigned
+ {
+ if (str.isNull()) return 0;
+ return str.toUInt();
+ };
+
+ number.minor = zeroIfNull(match.captured("minor"));
+ number.patch = zeroIfNull(match.captured("patch"));
+
+ return true;
+}
+
+bool operator==(const VersionNumber& lhs, const VersionNumber& rhs)
+{
+ return lhs.major == rhs.major &&
+ lhs.minor == rhs.minor &&
+ lhs.patch == rhs.patch;
+}
+
+bool operator<(const VersionNumber& lhs, const VersionNumber& rhs)
+{
+ if (lhs.major < rhs.major)
+ {
+ return true;
+ }
+ else if (lhs.major == rhs.major)
+ {
+ if (lhs.minor < rhs.minor)
+ {
+ return true;
+ }
+ else if (lhs.minor == rhs.minor)
+ {
+ if (lhs.patch < rhs.patch) return true;
+ }
+ }
+ return false;
+}
+
+bool operator>(const VersionNumber& lhs, const VersionNumber& rhs)
+{
+ if (lhs.major > rhs.major)
+ {
+ return true;
+ }
+ else if (lhs.major == rhs.major)
+ {
+ if (lhs.minor > rhs.minor)
+ {
+ return true;
+ }
+ else if (lhs.minor == rhs.minor)
+ {
+ if (lhs.patch > rhs.patch) return true;
+ }
+ }
+ return false;
+}
diff --git a/src/versionnumber.h b/src/versionnumber.h
new file mode 100644
--- /dev/null
+++ b/src/versionnumber.h
@@ -0,0 +1,46 @@
+/*
+ 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 .
+*/
+
+#ifndef VERSIONNUMBER_H
+#define VERSIONNUMBER_H
+
+#include
+
+struct VersionNumber
+{
+ unsigned major = 0;
+ unsigned minor = 0;
+ unsigned patch = 0;
+
+ VersionNumber(unsigned mj=0, unsigned mn=0, unsigned pt=0);
+
+ /// Convert version number to string.
+ QString toString() const;
+
+ /// Extracts the version number from given string.
+ static bool extract(const QString& str, VersionNumber& number);
+};
+
+bool operator==(const VersionNumber& lhs, const VersionNumber& rhs);
+bool operator<(const VersionNumber& lhs, const VersionNumber& rhs);
+bool operator>(const VersionNumber& lhs, const VersionNumber& rhs);
+
+const VersionNumber CurrentVersion(VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
+
+#endif // VERSIONNUMBER_H
diff --git a/src/zoomer.cpp b/src/zoomer.cpp
--- a/src/zoomer.cpp
+++ b/src/zoomer.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -21,9 +21,13 @@
#include
#include
+#include
+
Zoomer::Zoomer(QWidget* widget, bool doReplot) :
ScrollZoomer(widget)
{
+ is_panning = false;
+
// set corner widget between the scrollbars with default background color
auto cornerWidget = new QWidget();
auto bgColor = cornerWidget->palette().color(QPalette::Window).name();
@@ -52,3 +56,88 @@ void Zoomer::zoom( const QRectF & rect)
ScrollZoomer::zoom(rect);
}
+
+QwtText Zoomer::trackerTextF(const QPointF& pos) const
+{
+ QwtText b = ScrollZoomer::trackerTextF(pos);
+
+ const QPolygon pa = selection();
+ if (pa.count() < 2)
+ {
+ return b;
+ }
+
+ const QRectF rect = invTransform(QRect(pa.first(), pa.last()).normalized());
+
+ QString sizeText = QString(" [%1, %2]").\
+ arg(rect.width(), 0, 'g', 4).\
+ arg(rect.height(), 0, 'g', 4);
+
+ b.setText(b.text() + sizeText);
+
+ return b;
+}
+
+void Zoomer::drawRubberBand(QPainter* painter) const
+{
+ const double FILL_ALPHA = 0.2;
+
+ QColor color = painter->pen().color();
+ color.setAlphaF(FILL_ALPHA);
+ painter->setBrush(color);
+
+ ScrollZoomer::drawRubberBand(painter);
+}
+
+QRegion Zoomer::rubberBandMask() const
+{
+ const QPolygon pa = selection();
+ if (pa.count() < 2)
+ {
+ return QRegion();
+ }
+ const QRect r = QRect(pa.first(), pa.last()).normalized().adjusted(0, 0, 1, 1);
+ return QRegion(r);
+}
+
+void Zoomer::widgetMousePressEvent(QMouseEvent* mouseEvent)
+{
+ if (mouseEvent->modifiers() & Qt::ControlModifier)
+ {
+ is_panning = true;
+ parentWidget()->setCursor(Qt::ClosedHandCursor);
+ pan_point = invTransform(mouseEvent->pos());
+ }
+ else
+ {
+ ScrollZoomer::widgetMousePressEvent(mouseEvent);
+ }
+}
+
+void Zoomer::widgetMouseMoveEvent(QMouseEvent* mouseEvent)
+{
+ if (is_panning)
+ {
+ auto cur_point = invTransform(mouseEvent->pos());
+ auto delta = cur_point - pan_point;
+ moveBy(-delta.x(), -delta.y());
+ pan_point = invTransform(mouseEvent->pos());
+ }
+ else
+ {
+ ScrollZoomer::widgetMouseMoveEvent(mouseEvent);
+ }
+}
+
+void Zoomer::widgetMouseReleaseEvent(QMouseEvent* mouseEvent)
+{
+ if (is_panning)
+ {
+ is_panning = false;
+ parentWidget()->setCursor(Qt::CrossCursor);
+ }
+ else
+ {
+ ScrollZoomer::widgetMouseReleaseEvent(mouseEvent);
+ }
+}
diff --git a/src/zoomer.h b/src/zoomer.h
--- a/src/zoomer.h
+++ b/src/zoomer.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -33,6 +33,24 @@ public:
signals:
void unzoomed();
+
+protected:
+ /// Re-implemented to display selection size in the tracker text.
+ QwtText trackerTextF(const QPointF &pos) const;
+ /// Re-implemented for alpha background
+ void drawRubberBand(QPainter* painter) const;
+ /// Re-implemented for alpha background (masking is basically disabled)
+ QRegion rubberBandMask() const;
+ /// Overloaded for panning
+ void widgetMousePressEvent(QMouseEvent* mouseEvent);
+ /// Overloaded for panning
+ void widgetMouseReleaseEvent(QMouseEvent* mouseEvent);
+ /// Overloaded for panning
+ void widgetMouseMoveEvent(QMouseEvent* mouseEvent);
+
+private:
+ bool is_panning;
+ QPointF pan_point;
};
#endif // ZOOMER_H