diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -9,3 +9,4 @@ b4d0a38444d31872633e474d89ffc15cd0fe42f0
27b0354ca2c5ea7b3870156417ce7e04e799bbf7 v0.7.1
fd5f1eb480ec372b49df58b497458de05c30057c v0.8.0
9c9a11cd15fd094e2b2b65dc51805fd8fd1d2460 v0.8.1
+4cf9a1ee1f107a38e03dbe17c4f2882c43d827c9 v0.9.0
diff --git a/CMakeLists.txt b/CMakeLists.txt
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,5 @@
#
-# Copyright © 2015-2016 Hasan Yavuz Özderya
+# Copyright © 2017 Hasan Yavuz Özderya
#
# This file is part of serialplot.
#
@@ -38,24 +38,28 @@ 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)
+# 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 ()
+
# includes
-include_directories("./src" ${QWT_INCLUDE_DIR})
+include_directories("./src"
+ ${QWT_INCLUDE_DIR}
+ ${QTCOLORWIDGETS_INCLUDE_DIRS}
+ )
+
+# flags
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${QTCOLORWIDGETS_FLAGS}")
# wrap UI and resource files
qt5_wrap_ui(UI_FILES
@@ -67,6 +71,7 @@ qt5_wrap_ui(UI_FILES
src/commandwidget.ui
src/dataformatpanel.ui
src/plotcontrolpanel.ui
+ src/recordpanel.ui
src/numberformatbox.ui
src/endiannessbox.ui
src/binarystreamreadersettings.ui
@@ -102,9 +107,12 @@ add_executable(${PROGRAM_NAME} WIN32
src/commandedit.cpp
src/dataformatpanel.cpp
src/plotcontrolpanel.cpp
+ src/recordpanel.cpp
+ src/datarecorder.cpp
src/tooltipfilter.cpp
src/sneakylineedit.cpp
src/channelmanager.cpp
+ src/channelinfomodel.cpp
src/framebufferseries.cpp
src/numberformatbox.cpp
src/endiannessbox.cpp
@@ -124,12 +132,19 @@ add_executable(${PROGRAM_NAME} WIN32
)
# Use the Widgets module from Qt 5.
-target_link_libraries(${PROGRAM_NAME} ${QWT_LIBRARY})
-qt5_use_modules(${PROGRAM_NAME} Widgets SerialPort Svg)
+target_link_libraries(${PROGRAM_NAME}
+ ${QWT_LIBRARY}
+ ${QTCOLORWIDGETS_LIBRARIES}
+ )
+qt5_use_modules(${PROGRAM_NAME} Widgets SerialPort)
if (BUILD_QWT)
add_dependencies(${PROGRAM_NAME} QWT)
-endif (BUILD_QWT)
+endif ()
+
+if (BUILD_QTCOLORWIDGETS)
+ add_dependencies(${PROGRAM_NAME} QCW)
+endif ()
# set compiler flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
@@ -146,12 +161,6 @@ 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)
@@ -171,9 +180,7 @@ if (NOT VERSION_REVISION)
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_REVISION=\\\"${VERSION_REVISION}\\\" ")
# add make run target
add_custom_target(run
diff --git a/cmake/modules/BuildQColorWidgets.cmake b/cmake/modules/BuildQColorWidgets.cmake
new file mode 100644
--- /dev/null
+++ b/cmake/modules/BuildQColorWidgets.cmake
@@ -0,0 +1,36 @@
+#
+# Copyright © 2017 Hasan Yavuz Özderya
+#
+# This file is part of serialplot.
+#
+# serialplot is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# serialplot is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with serialplot. If not, see .
+#
+
+include(ExternalProject)
+
+ExternalProject_Add(QCW
+ PREFIX qcw
+ GIT_REPOSITORY https://github.com/mbasaglia/Qt-Color-Widgets
+ 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(QTCOLORWIDGETS_FLAGS "-D QTCOLORWIDGETS_STATICALLY_LINKED")
+set(QTCOLORWIDGETS_LIBRARY ${binary_dir}/libColorWidgets-qt5.a)
+set(QTCOLORWIDGETS_INCLUDE_DIR ${source_dir}/include)
+
+set(QTCOLORWIDGETS_LIBRARIES ${QTCOLORWIDGETS_LIBRARY})
+set(QTCOLORWIDGETS_INCLUDE_DIRS ${QTCOLORWIDGETS_INCLUDE_DIR})
diff --git a/cmake/modules/BuildQwt.cmake b/cmake/modules/BuildQwt.cmake
--- a/cmake/modules/BuildQwt.cmake
+++ b/cmake/modules/BuildQwt.cmake
@@ -25,8 +25,11 @@ ExternalProject_Add(QWT
# disable QwtDesigner plugin and enable static build
PATCH_COMMAND sed -i -r -e "s/QWT_CONFIG\\s*\\+=\\s*QwtDesigner/#&/"
-e "s/QWT_CONFIG\\s*\\+=\\s*QwtDll/#&/"
+ -e "s/QWT_CONFIG\\s*\\+=\\s*QwtSvg/#&/"
+ -e "s/QWT_CONFIG\\s*\\+=\\s*QwtOpenGL/#&/"
-e "s|QWT_INSTALL_PREFIX\\s*=.*|QWT_INSTALL_PREFIX = |"
- /qwtconfig.pri
+ /qwtconfig.pri
+ UPDATE_COMMAND ""
CONFIGURE_COMMAND qmake /qwt.pro
)
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/qt_5_2_moc_creation_namespace_fix.diff b/cmake/modules/qt_5_2_moc_creation_namespace_fix.diff
new file mode 100644
--- /dev/null
+++ b/cmake/modules/qt_5_2_moc_creation_namespace_fix.diff
@@ -0,0 +1,39 @@
+diff --git a/include/color_dialog.hpp b/include/color_dialog.hpp
+index 5c7653d..895215c 100644
+--- a/include/color_dialog.hpp
++++ b/include/color_dialog.hpp
+@@ -30,6 +30,8 @@
+
+ class QAbstractButton;
+
++using namespace color_widgets;
++
+ namespace color_widgets {
+
+ class QCP_EXPORT ColorDialog : public QDialog
+diff --git a/include/color_list_widget.hpp b/include/color_list_widget.hpp
+index 282bea5..7d8e0c5 100644
+--- a/include/color_list_widget.hpp
++++ b/include/color_list_widget.hpp
+@@ -25,6 +25,8 @@
+ #include "abstract_widget_list.hpp"
+ #include "color_wheel.hpp"
+
++using namespace color_widgets;
++
+ namespace color_widgets {
+
+ class QCP_EXPORT ColorListWidget : public AbstractWidgetList
+diff --git a/include/color_selector.hpp b/include/color_selector.hpp
+index db817d5..48b374d 100644
+--- a/include/color_selector.hpp
++++ b/include/color_selector.hpp
+@@ -25,6 +25,8 @@
+ #include "color_preview.hpp"
+ #include "color_wheel.hpp"
+
++using namespace color_widgets;
++
+ namespace color_widgets {
+
+ /**
diff --git a/serialplot.pro b/serialplot.pro
--- a/serialplot.pro
+++ b/serialplot.pro
@@ -67,7 +67,8 @@ SOURCES += \
src/demoreader.cpp \
src/framedreader.cpp \
src/plotmanager.cpp \
- src/numberformat.cpp
+ src/numberformat.cpp \
+ src/recordpanel.cpp
HEADERS += \
src/mainwindow.h \
@@ -106,7 +107,8 @@ HEADERS += \
src/framedreader.h \
src/plotmanager.h \
src/setting_defines.h \
- src/numberformat.h
+ src/numberformat.h \
+ src/recordpanel.h
FORMS += \
src/mainwindow.ui \
@@ -121,7 +123,8 @@ FORMS += \
src/endiannessbox.ui \
src/framedreadersettings.ui \
src/binarystreamreadersettings.ui \
- src/asciireadersettings.ui
+ src/asciireadersettings.ui \
+ src/recordpanel.ui
INCLUDEPATH += qmake/ src/
diff --git a/src/abstractreader.cpp b/src/abstractreader.cpp
--- a/src/abstractreader.cpp
+++ b/src/abstractreader.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -19,11 +19,14 @@
#include "abstractreader.h"
-AbstractReader::AbstractReader(QIODevice* device, ChannelManager* channelMan, QObject *parent) :
+AbstractReader::AbstractReader(QIODevice* device, ChannelManager* channelMan,
+ DataRecorder* recorder, QObject* parent) :
QObject(parent)
{
_device = device;
_channelMan = channelMan;
+ _recorder = recorder;
+ recording = false;
// initialize sps counter
sampleCount = 0;
@@ -44,3 +47,13 @@ void AbstractReader::spsTimerTimeout()
}
sampleCount = 0;
}
+
+void AbstractReader::addData(double* samples, unsigned length)
+{
+ _channelMan->addData(samples, length);
+ if (recording)
+ {
+ _recorder->addData(samples, length, numOfChannels());
+ }
+ sampleCount += length;
+}
diff --git a/src/abstractreader.h b/src/abstractreader.h
--- a/src/abstractreader.h
+++ b/src/abstractreader.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -26,6 +26,7 @@
#include
#include "channelmanager.h"
+#include "datarecorder.h"
/**
* All reader classes must inherit this class.
@@ -34,7 +35,10 @@ class AbstractReader : public QObject
{
Q_OBJECT
public:
- explicit AbstractReader(QIODevice* device, ChannelManager* channelMan, QObject *parent = 0);
+ explicit AbstractReader(QIODevice* device, ChannelManager* channelMan,
+ DataRecorder* recorder, QObject* parent = 0);
+
+ bool recording; /// is recording started
/**
* Returns a widget to be shown in data format panel when reader
@@ -54,11 +58,18 @@ public:
/// 'disabled'.
virtual void enable(bool enabled = true) = 0;
+ /**
+ * @brief Starts sending data to recorder.
+ *
+ * @note recorder must have been started!
+ */
+ void startRecording();
+
+ /// Stops recording.
+ void stopRecording();
+
signals:
void numOfChannelsChanged(unsigned);
- // TODO: this must be signaled by 'channel man' for better abstraction
- void dataAdded(); ///< emitted when data added to channel man.
- // TODO: this should be a part of 'channel man'
void samplesPerSecondChanged(unsigned);
public slots:
@@ -72,14 +83,18 @@ public slots:
protected:
QIODevice* _device;
- ChannelManager* _channelMan;
- /// Implementing class should simply increase this count as samples are read
- unsigned sampleCount;
+ /// Should be called with read data
+ void addData(double* samples, unsigned length);
private:
const int SPS_UPDATE_TIMEOUT = 1; // second
+
+ unsigned sampleCount;
unsigned samplesPerSecond;
+
+ ChannelManager* _channelMan;
+ DataRecorder* _recorder;
QTimer spsTimer;
private slots:
diff --git a/src/asciireader.cpp b/src/asciireader.cpp
--- a/src/asciireader.cpp
+++ b/src/asciireader.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -24,12 +24,12 @@
/// If set to this value number of channels is determined from input
#define NUMOFCHANNELS_AUTO (0)
-AsciiReader::AsciiReader(QIODevice* device, ChannelManager* channelMan, QObject *parent) :
- AbstractReader(device, channelMan, parent)
+AsciiReader::AsciiReader(QIODevice* device, ChannelManager* channelMan,
+ DataRecorder* recorder, QObject* parent) :
+ AbstractReader(device, channelMan, recorder, parent)
{
paused = false;
discardFirstLine = true;
- sampleCount = 0;
_numOfChannels = _settingsWidget.numOfChannels();
autoNumOfChannels = (_numOfChannels == NUMOFCHANNELS_AUTO);
@@ -139,24 +139,27 @@ void AsciiReader::onDataReady()
{
numReadChannels = separatedValues.length();
qWarning() << "Incoming data is missing data for some channels!";
+ qWarning() << "Read line: " << line;
}
// parse read line
+ double* channelSamples = new double[_numOfChannels]();
for (unsigned ci = 0; ci < numReadChannels; ci++)
{
bool ok;
- double channelSample = separatedValues[ci].toDouble(&ok);
- if (ok)
- {
- _channelMan->addChannelData(ci, &channelSample, 1);
- sampleCount++;
- }
- else
+ channelSamples[ci] = separatedValues[ci].toDouble(&ok);
+ if (!ok)
{
qWarning() << "Data parsing error for channel: " << ci;
+ qWarning() << "Read line: " << line;
+ channelSamples[ci] = 0;
}
}
- emit dataAdded();
+
+ // 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
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -30,7 +30,8 @@ class AsciiReader : public AbstractReade
Q_OBJECT
public:
- explicit AsciiReader(QIODevice* device, ChannelManager* channelMan, QObject *parent = 0);
+ explicit AsciiReader(QIODevice* device, ChannelManager* channelMan,
+ DataRecorder* recorder, QObject *parent = 0);
QWidget* settingsWidget();
unsigned numOfChannels();
void enable(bool enabled = true);
diff --git a/src/binarystreamreader.cpp b/src/binarystreamreader.cpp
--- a/src/binarystreamreader.cpp
+++ b/src/binarystreamreader.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -23,13 +23,13 @@
#include "binarystreamreader.h"
#include "floatswap.h"
-BinaryStreamReader::BinaryStreamReader(QIODevice* device, ChannelManager* channelMan, QObject *parent) :
- AbstractReader(device, channelMan, parent)
+BinaryStreamReader::BinaryStreamReader(QIODevice* device, ChannelManager* channelMan,
+ DataRecorder* recorder, QObject* parent) :
+ AbstractReader(device, channelMan, recorder, parent)
{
paused = false;
skipByteRequested = false;
skipSampleRequested = false;
- sampleCount = 0;
_numOfChannels = _settingsWidget.numOfChannels();
connect(&_settingsWidget, &BinaryStreamReaderSettings::numOfChannelsChanged,
@@ -171,15 +171,9 @@ void BinaryStreamReader::onDataReady()
}
}
- for (unsigned int ci = 0; ci < _numOfChannels; ci++)
- {
- addChannelData(ci,
- channelSamples + ci*numOfPackagesToRead,
- numOfPackagesToRead);
- }
- emit dataAdded();
+ addData(channelSamples, numOfPackagesToRead*_numOfChannels);
- delete channelSamples;
+ delete[] channelSamples;
}
template double BinaryStreamReader::readSampleAs()
@@ -200,13 +194,6 @@ template double BinaryStream
return double(data);
}
-void BinaryStreamReader::addChannelData(unsigned int channel,
- double* data, unsigned size)
-{
- _channelMan->addChannelData(channel, data, size);
- sampleCount += size;
-}
-
void BinaryStreamReader::saveSettings(QSettings* settings)
{
_settingsWidget.saveSettings(settings);
diff --git a/src/binarystreamreader.h b/src/binarystreamreader.h
--- a/src/binarystreamreader.h
+++ b/src/binarystreamreader.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -34,7 +34,8 @@ class BinaryStreamReader : public Abstra
{
Q_OBJECT
public:
- explicit BinaryStreamReader(QIODevice* device, ChannelManager* channelMan, QObject *parent = 0);
+ explicit BinaryStreamReader(QIODevice* device, ChannelManager* channelMan,
+ DataRecorder* recorder, QObject *parent = 0);
QWidget* settingsWidget();
unsigned numOfChannels();
void enable(bool enabled = true);
@@ -65,9 +66,6 @@ private:
*/
template double readSampleAs();
- // `data` contains i th channels data
- void addChannelData(unsigned int channel, double* data, unsigned size);
-
private slots:
void onNumberFormatChanged(NumberFormat numberFormat);
void onNumOfChannelsChanged(unsigned value);
diff --git a/src/channelinfomodel.cpp b/src/channelinfomodel.cpp
new file mode 100644
--- /dev/null
+++ b/src/channelinfomodel.cpp
@@ -0,0 +1,390 @@
+/*
+ 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 "channelinfomodel.h"
+#include "setting_defines.h"
+
+#define NUMOF_COLORS (32)
+
+const QColor colors[NUMOF_COLORS] =
+{
+ QColor("#ff0056"),
+ QColor("#7e2dd2"),
+ QColor("#00ae7e"),
+ QColor("#fe8900"),
+ QColor("#ff937e"),
+ QColor("#6a826c"),
+ QColor("#ff029d"),
+ QColor("#00b917"),
+ QColor("#7a4782"),
+ QColor("#85a900"),
+ QColor("#a42400"),
+ QColor("#683d3b"),
+ QColor("#bdc6ff"),
+ QColor("#263400"),
+ QColor("#bdd393"),
+ QColor("#d5ff00"),
+ QColor("#9e008e"),
+ QColor("#001544"),
+ QColor("#c28c9f"),
+ QColor("#ff74a3"),
+ QColor("#01d0ff"),
+ QColor("#004754"),
+ QColor("#e56ffe"),
+ QColor("#788231"),
+ QColor("#0e4ca1"),
+ QColor("#91d0cb"),
+ QColor("#be9970"),
+ QColor("#968ae8"),
+ QColor("#bb8800"),
+ QColor("#43002c"),
+ QColor("#deff74"),
+ QColor("#00ffc6")
+};
+
+ChannelInfoModel::ChannelInfoModel(unsigned numberOfChannels, QObject* parent) :
+ QAbstractTableModel(parent)
+{
+ _numOfChannels = 0;
+ setNumOfChannels(numberOfChannels);
+}
+
+ChannelInfoModel::ChannelInfoModel(const ChannelInfoModel& other) :
+ ChannelInfoModel(other.rowCount(), other.parent())
+{
+ for (int i = 0; i < other.rowCount(); i++)
+ {
+ setData(index(i, COLUMN_NAME),
+ other.data(other.index(i, COLUMN_NAME), Qt::EditRole),
+ Qt::EditRole);
+ setData(index(i, COLUMN_NAME),
+ other.data(other.index(i, COLUMN_NAME), Qt::ForegroundRole),
+ Qt::ForegroundRole);
+ setData(index(i, COLUMN_VISIBILITY),
+ other.data(other.index(i, COLUMN_VISIBILITY), Qt::CheckStateRole),
+ Qt::CheckStateRole);
+ }
+}
+
+ChannelInfoModel::ChannelInfoModel(const QStringList& channelNames) :
+ ChannelInfoModel(channelNames.length(), NULL)
+{
+ for (int i = 0; i < channelNames.length(); i++)
+ {
+ setData(index(i, COLUMN_NAME), channelNames[i], Qt::EditRole);
+ }
+}
+
+ChannelInfoModel::ChannelInfo::ChannelInfo(unsigned index)
+{
+ name = tr("Channel %1").arg(index + 1);
+ visibility = true;
+ color = colors[index % NUMOF_COLORS];
+}
+
+QString ChannelInfoModel::name(unsigned i) const
+{
+ return infos[i].name;
+}
+
+QColor ChannelInfoModel::color(unsigned i) const
+{
+ return infos[i].color;
+}
+
+bool ChannelInfoModel::isVisible(unsigned i) const
+{
+ return infos[i].visibility;
+}
+
+QStringList ChannelInfoModel::channelNames() const
+{
+ QStringList r;
+ for (unsigned ci = 0; ci < _numOfChannels; ci++)
+ {
+ r << name(ci);
+ }
+ return r;
+}
+
+int ChannelInfoModel::rowCount(const QModelIndex &parent) const
+{
+ return _numOfChannels;
+}
+
+int ChannelInfoModel::columnCount(const QModelIndex & parent) const
+{
+ return COLUMN_COUNT;
+}
+
+Qt::ItemFlags ChannelInfoModel::flags(const QModelIndex &index) const
+{
+ if (index.column() == COLUMN_NAME)
+ {
+ return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren | Qt::ItemIsSelectable;
+ }
+ else if (index.column() == COLUMN_VISIBILITY)
+ {
+ return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren | Qt::ItemIsSelectable;
+ }
+
+ return Qt::NoItemFlags;
+}
+
+QVariant ChannelInfoModel::data(const QModelIndex &index, int role) const
+{
+ // check index
+ if (index.row() >= (int) _numOfChannels || index.row() < 0)
+ {
+ return QVariant();
+ }
+
+ // get color
+ if (role == Qt::ForegroundRole)
+ {
+ return infos[index.row()].color;
+ }
+
+ // get name
+ if (index.column() == COLUMN_NAME)
+ {
+ if (role == Qt::DisplayRole || role == Qt::EditRole)
+ {
+ return QVariant(infos[index.row()].name);
+ }
+ } // get visibility
+ else if (index.column() == COLUMN_VISIBILITY)
+ {
+ if (role == Qt::CheckStateRole)
+ {
+ bool visible = infos[index.row()].visibility;
+ return visible ? Qt::Checked : Qt::Unchecked;
+ }
+ }
+
+ return QVariant();
+}
+
+QVariant ChannelInfoModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation == Qt::Horizontal)
+ {
+ if (role == Qt::DisplayRole)
+ {
+ if (section == COLUMN_NAME)
+ {
+ return tr("Channel");
+ }
+ else if (section == COLUMN_VISIBILITY)
+ {
+ return tr("Visible");
+ }
+ }
+ }
+ else // vertical
+ {
+ if (section < (int) _numOfChannels && role == Qt::DisplayRole)
+ {
+ return QString::number(section + 1);
+ }
+ }
+
+ return QVariant();
+}
+
+bool ChannelInfoModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ // check index
+ if (index.row() >= (int) _numOfChannels || index.row() < 0)
+ {
+ return false;
+ }
+
+ // set color
+ if (role == Qt::ForegroundRole)
+ {
+ infos[index.row()].color = value.value();
+ emit dataChanged(index, index, QVector({Qt::ForegroundRole}));
+ return true;
+ }
+
+ // set name
+ if (index.column() == COLUMN_NAME)
+ {
+ if (role == Qt::DisplayRole || role == Qt::EditRole)
+ {
+ infos[index.row()].name = value.toString();
+ emit dataChanged(index, index, QVector({role}));
+ return true;
+ }
+ } // set visibility
+ else if (index.column() == COLUMN_VISIBILITY)
+ {
+ if (role == Qt::CheckStateRole)
+ {
+ bool checked = value.toInt() == Qt::Checked;
+ infos[index.row()].visibility = checked;
+ emit dataChanged(index, index, QVector({role}));
+ return true;
+ }
+ }
+
+ // invalid index/role
+ return false;
+}
+
+void ChannelInfoModel::setNumOfChannels(unsigned number)
+{
+ if (number == _numOfChannels) return;
+
+ bool isInserting = number > _numOfChannels;
+ if (isInserting)
+ {
+ beginInsertRows(QModelIndex(), _numOfChannels, number-1);
+ }
+ else
+ {
+ beginRemoveRows(QModelIndex(), number, _numOfChannels-1);
+ }
+
+ // we create channel info but never remove channel info to
+ // remember user entered info
+ if ((int) number > infos.length())
+ {
+ for (unsigned ci = _numOfChannels; ci < number; ci++)
+ {
+ infos.append(ChannelInfo(ci));
+ }
+ }
+
+ // make sure newly available channels are visible, we don't
+ // remember visibility option intentionally so that user doesn't
+ // get confused
+ if (number > _numOfChannels)
+ {
+ for (unsigned ci = _numOfChannels; ci < number; ci++)
+ {
+ infos[ci].visibility = true;
+ }
+ }
+
+ _numOfChannels = number;
+
+ if (isInserting)
+ {
+ endInsertRows();
+ }
+ else
+ {
+ endRemoveRows();
+ }
+}
+
+void ChannelInfoModel::resetInfos()
+{
+ beginResetModel();
+ for (unsigned ci = 0; (int) ci < infos.length(); ci++)
+ {
+ infos[ci] = ChannelInfo(ci);
+ }
+ endResetModel();
+}
+
+void ChannelInfoModel::resetNames()
+{
+ beginResetModel();
+ for (unsigned ci = 0; (int) ci < infos.length(); ci++)
+ {
+ infos[ci].name = ChannelInfo(ci).name;
+ }
+ endResetModel();
+}
+
+void ChannelInfoModel::resetColors()
+{
+ beginResetModel();
+ for (unsigned ci = 0; (int) ci < infos.length(); ci++)
+ {
+ infos[ci].color = ChannelInfo(ci).color;
+ }
+ endResetModel();
+}
+
+void ChannelInfoModel::resetVisibility()
+{
+ beginResetModel();
+ for (unsigned ci = 0; (int) ci < infos.length(); ci++)
+ {
+ infos[ci].visibility = true;
+ }
+ endResetModel();
+}
+
+void ChannelInfoModel::saveSettings(QSettings* settings)
+{
+ settings->beginGroup(SettingGroup_Channels);
+ settings->beginWriteArray(SG_Channels_Channel);
+
+ // save all channel information regardless of current number of channels
+ for (unsigned ci = 0; (int) ci < infos.length(); ci++)
+ {
+ settings->setArrayIndex(ci);
+ settings->setValue(SG_Channels_Name, infos[ci].name);
+ settings->setValue(SG_Channels_Color, infos[ci].color);
+ settings->setValue(SG_Channels_Visible, infos[ci].visibility);
+ }
+
+ settings->endArray();
+ settings->endGroup();
+}
+
+void ChannelInfoModel::loadSettings(QSettings* settings)
+{
+ settings->beginGroup(SettingGroup_Channels);
+ unsigned size = settings->beginReadArray(SG_Channels_Channel);
+
+ for (unsigned ci = 0; ci < size; ci++)
+ {
+ settings->setArrayIndex(ci);
+
+ ChannelInfo chanInfo(ci);
+ chanInfo.name = settings->value(SG_Channels_Name, chanInfo.name).toString();
+ chanInfo.color = settings->value(SG_Channels_Color, chanInfo.color).value();
+ chanInfo.visibility = settings->value(SG_Channels_Visible, true).toBool();
+
+ if ((int) ci < infos.size())
+ {
+ infos[ci] = chanInfo;
+
+ if (ci < _numOfChannels)
+ {
+ auto roles = QVector({
+ Qt::DisplayRole, Qt::EditRole, Qt::ForegroundRole, Qt::CheckStateRole});
+ emit dataChanged(index(ci, 0), index(ci, COLUMN_COUNT-1), roles);
+ }
+ }
+ else
+ {
+ infos.append(chanInfo);
+ }
+ }
+
+ settings->endArray();
+ settings->endGroup();
+}
diff --git a/src/channelinfomodel.h b/src/channelinfomodel.h
new file mode 100644
--- /dev/null
+++ b/src/channelinfomodel.h
@@ -0,0 +1,93 @@
+/*
+ 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 CHANNELINFOMODEL_H
+#define CHANNELINFOMODEL_H
+
+#include
+#include
+#include
+#include
+
+class ChannelInfoModel : public QAbstractTableModel
+{
+ Q_OBJECT
+
+public:
+ enum ChannelInfoColumn
+ {
+ COLUMN_NAME = 0,
+ COLUMN_VISIBILITY,
+ COLUMN_COUNT
+ };
+
+ explicit ChannelInfoModel(unsigned numberOfChannels, QObject *parent = 0);
+ ChannelInfoModel(const ChannelInfoModel& other);
+ explicit ChannelInfoModel(const QStringList& channelNames);
+
+ QString name (unsigned i) const;
+ QColor color (unsigned i) const;
+ bool isVisible(unsigned i) const;
+ /// Returns a list of channel names
+ QStringList channelNames() const;
+
+ // implemented from QAbstractItemModel
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
+ Qt::ItemFlags flags(const QModelIndex &index) const;
+ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+
+ void setNumOfChannels(unsigned number);
+ /// Stores all channel info into a `QSettings`
+ void saveSettings(QSettings* settings);
+ /// Loads all channel info from a `QSettings`.
+ void loadSettings(QSettings* settings);
+
+public slots:
+ /// reset all channel info (names, color etc.)
+ void resetInfos();
+ /// reset all channel names
+ void resetNames();
+ /// reset all channel colors
+ void resetColors();
+ /// reset visibility
+ void resetVisibility();
+
+private:
+ struct ChannelInfo
+ {
+ explicit ChannelInfo(unsigned index);
+
+ QString name;
+ bool visibility;
+ QColor color;
+ };
+
+ unsigned _numOfChannels; ///< @note this is not necessarily the length of `infos`
+
+ /**
+ * Channel info is added here but never removed so that we can
+ * remember user entered info (names, colors etc.).
+ */
+ QList infos;
+};
+
+#endif // CHANNELINFOMODEL_H
diff --git a/src/channelmanager.cpp b/src/channelmanager.cpp
--- a/src/channelmanager.cpp
+++ b/src/channelmanager.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -17,30 +17,28 @@
along with serialplot. If not, see .
*/
-#include
#include
+#include
+
#include "channelmanager.h"
#include "setting_defines.h"
ChannelManager::ChannelManager(unsigned numberOfChannels, unsigned numberOfSamples, QObject *parent) :
- QObject(parent)
+ QObject(parent),
+ _infoModel(numberOfChannels)
{
_numOfChannels = numberOfChannels;
_numOfSamples = numberOfSamples;
-
- QStringList channelNamesList;
+ _paused = false;
for (unsigned int i = 0; i < numberOfChannels; i++)
{
channelBuffers.append(new FrameBuffer(numberOfSamples));
- channelNamesList << QString("Channel %1").arg(i+1);
}
- _channelNames.setStringList(channelNamesList);
-
- connect(&_channelNames, &QStringListModel::dataChanged,
- this, &ChannelManager::onChannelNameDataChange);
+ connect(&_infoModel, &ChannelInfoModel::dataChanged,
+ this, &ChannelManager::onChannelInfoChanged);
}
ChannelManager::~ChannelManager()
@@ -71,7 +69,6 @@ void ChannelManager::setNumOfChannels(un
for (unsigned int i = 0; i < number - oldNum; i++)
{
channelBuffers.append(new FrameBuffer(_numOfSamples));
- addChannelName(QString("Channel %1").arg(oldNum+i+1));
}
}
else if(number < oldNum)
@@ -80,10 +77,12 @@ void ChannelManager::setNumOfChannels(un
for (unsigned int i = oldNum-1; i > number-1; i--)
{
delete channelBuffers.takeLast();
- _channelNames.removeRow(i);
}
}
+ _numOfChannels = number;
+ _infoModel.setNumOfChannels(number);
+
emit numOfChannelsChanged(number);
}
@@ -99,74 +98,96 @@ void ChannelManager::setNumOfSamples(uns
emit numOfSamplesChanged(number);
}
+void ChannelManager::pause(bool paused)
+{
+ _paused = paused;
+}
+
FrameBuffer* ChannelManager::channelBuffer(unsigned channel)
{
return channelBuffers[channel];
}
-QStringListModel* ChannelManager::channelNames()
+ChannelInfoModel* ChannelManager::infoModel()
{
- return &_channelNames;
+ return &_infoModel;
}
QString ChannelManager::channelName(unsigned channel)
{
- return _channelNames.data(_channelNames.index(channel, 0), Qt::DisplayRole).toString();
+ return _infoModel.data(_infoModel.index(channel, ChannelInfoModel::COLUMN_NAME),
+ Qt::DisplayRole).toString();
}
-void ChannelManager::setChannelName(unsigned channel, QString name)
+QStringList ChannelManager::channelNames()
{
- _channelNames.setData(_channelNames.index(channel, 0), QVariant(name), Qt::DisplayRole);
+ QStringList list;
+ for (unsigned ci = 0; ci < _numOfChannels; ci++)
+ {
+ list << channelName(ci);
+ }
+ return list;
}
-void ChannelManager::addChannelName(QString name)
+void ChannelManager::onChannelInfoChanged(const QModelIndex & topLeft,
+ const QModelIndex & bottomRight,
+ const QVector & roles)
{
- _channelNames.insertRow(_channelNames.rowCount());
- setChannelName(_channelNames.rowCount()-1, name);
-}
-
-void ChannelManager::onChannelNameDataChange(const QModelIndex & topLeft,
- const QModelIndex & bottomRight,
- const QVector & roles)
-{
- Q_UNUSED(roles);
int start = topLeft.row();
int end = bottomRight.row();
+ int col = topLeft.column();
- // TODO: maybe check `roles` parameter, can't think of a reason for current use case
- for (int i = start; i <= end; i++)
+ for (int ci = start; ci <= end; ci++)
{
- emit channelNameChanged(i, channelName(i));
+ for (auto role : roles)
+ {
+ switch (role)
+ {
+ case Qt::EditRole:
+ if (col == ChannelInfoModel::COLUMN_NAME)
+ {
+ emit channelNameChanged(ci, channelName(ci));
+ }
+ break;
+ case Qt::ForegroundRole:
+ if (col == ChannelInfoModel::COLUMN_NAME)
+ {
+ // TODO: emit channel color changed
+ }
+ break;
+ case Qt::CheckStateRole:
+ if (col == ChannelInfoModel::COLUMN_VISIBILITY)
+ {
+ // TODO: emit visibility
+ }
+ break;
+ }
+ }
+ // emit channelNameChanged(i, channelName(i));
}
}
-void ChannelManager::addChannelData(unsigned channel, double* data, unsigned size)
+void ChannelManager::addData(double* data, unsigned size)
{
- channelBuffer(channel)->addSamples(data, size);
+ Q_ASSERT(size % _numOfChannels == 0);
+
+ if (_paused) return;
+
+ int n = size / _numOfChannels;
+ for (unsigned ci = 0; ci < _numOfChannels; ci++)
+ {
+ channelBuffers[ci]->addSamples(&data[ci*n], n);
+ }
+
+ emit dataAdded();
}
void ChannelManager::saveSettings(QSettings* settings)
{
- settings->beginGroup(SettingGroup_Channels);
- settings->beginWriteArray(SG_Channels_Channel);
- for (unsigned i = 0; i < numOfChannels(); i++)
- {
- settings->setArrayIndex(i);
- settings->setValue(SG_Channels_Name, channelName(i));
- }
- settings->endArray();
- settings->endGroup();
+ _infoModel.saveSettings(settings);
}
void ChannelManager::loadSettings(QSettings* settings)
{
- settings->beginGroup(SettingGroup_Channels);
- settings->beginReadArray(SG_Channels_Channel);
- for (unsigned i = 0; i < numOfChannels(); i++)
- {
- settings->setArrayIndex(i);
- setChannelName(i, settings->value(SG_Channels_Name, channelName(i)).toString());
- }
- settings->endArray();
- settings->endGroup();
+ _infoModel.loadSettings(settings);
}
diff --git a/src/channelmanager.h b/src/channelmanager.h
--- a/src/channelmanager.h
+++ b/src/channelmanager.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -21,12 +21,13 @@
#define CHANNELMANAGER_H
#include
-#include
+#include
#include
#include
#include
#include "framebuffer.h"
+#include "channelinfomodel.h"
class ChannelManager : public QObject
{
@@ -38,36 +39,55 @@ public:
unsigned numOfChannels();
unsigned numOfSamples();
FrameBuffer* channelBuffer(unsigned channel);
- QStringListModel* channelNames();
QString channelName(unsigned channel);
/// Stores channel names into a `QSettings`
void saveSettings(QSettings* settings);
/// Loads channel names from a `QSettings`.
void loadSettings(QSettings* settings);
+ /// Returns a model that manages channel information (name, color etc)
+ ChannelInfoModel* infoModel();
+ /// Returns a list of channel names
+ QStringList channelNames();
signals:
void numOfChannelsChanged(unsigned value);
void numOfSamplesChanged(unsigned value);
void channelNameChanged(unsigned channel, QString name);
+ void dataAdded(); ///< emitted when data added to channel man.
public slots:
void setNumOfChannels(unsigned number);
void setNumOfSamples(unsigned number);
- void setChannelName(unsigned channel, QString name);
- void addChannelData(unsigned channel, double* data, unsigned size);
+ /**
+ * Add data for all channels.
+ *
+ * All channels data is provided in a single array which contains equal
+ * number of samples for all channels. Structure is as shown below:
+ *
+ * [CH0_SMP0, CH0_SMP1 ... CH0_SMPN, CH1_SMP0, CH1_SMP1, ... , CHN_SMPN]
+ *
+ * @param data samples for all channels
+ * @param size size of `data`, must be multiple of `numOfChannels`
+ */
+ void addData(double* data, unsigned size);
+
+ /// When paused `addData` does nothing.
+ void pause(bool paused);
private:
unsigned _numOfChannels;
unsigned _numOfSamples;
+ bool _paused;
QList channelBuffers;
- QStringListModel _channelNames;
+ // QStringListModel _channelNames;
+ ChannelInfoModel _infoModel;
void addChannelName(QString name); ///< appends a new channel name at the end of list
private slots:
- void onChannelNameDataChange(const QModelIndex & topLeft,
- const QModelIndex & bottomRight,
- const QVector & roles = QVector ());
+ void onChannelInfoChanged(const QModelIndex & topLeft,
+ const QModelIndex & bottomRight,
+ const QVector & roles = QVector ());
};
#endif // CHANNELMANAGER_H
diff --git a/src/dataformatpanel.cpp b/src/dataformatpanel.cpp
--- a/src/dataformatpanel.cpp
+++ b/src/dataformatpanel.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -31,26 +31,27 @@
DataFormatPanel::DataFormatPanel(QSerialPort* port,
ChannelManager* channelMan,
+ DataRecorder* recorder,
QWidget *parent) :
QWidget(parent),
ui(new Ui::DataFormatPanel),
- bsReader(port, channelMan, this),
- asciiReader(port, channelMan, this),
- framedReader(port, channelMan, this),
- demoReader(port, channelMan, this)
+ bsReader(port, channelMan, recorder, this),
+ asciiReader(port, channelMan, recorder, this),
+ framedReader(port, channelMan, recorder, this),
+ demoReader(port, channelMan, recorder, this)
{
ui->setupUi(this);
serialPort = port;
_channelMan = channelMan;
paused = false;
+ demoEnabled = false;
// initalize default reader
currentReader = &bsReader;
bsReader.enable();
ui->rbBinary->setChecked(true);
ui->horizontalLayout->addWidget(bsReader.settingsWidget(), 1);
- connect(&bsReader, SIGNAL(dataAdded()), this, SIGNAL(dataAdded()));
connect(&bsReader, SIGNAL(numOfChannelsChanged(unsigned)),
this, SIGNAL(numOfChannelsChanged(unsigned)));
connect(&bsReader, SIGNAL(samplesPerSecondChanged(unsigned)),
@@ -99,8 +100,7 @@ void DataFormatPanel::enableDemo(bool en
if (enabled)
{
demoReader.enable();
- connect(&demoReader, &DemoReader::dataAdded,
- this, &DataFormatPanel::dataAdded);
+ demoReader.recording = currentReader->recording;
connect(&demoReader, &DemoReader::samplesPerSecondChanged,
this, &DataFormatPanel::samplesPerSecondChanged);
}
@@ -109,12 +109,19 @@ void DataFormatPanel::enableDemo(bool en
demoReader.enable(false);
disconnect(&demoReader, 0, this, 0);
}
+ demoEnabled = enabled;
}
-void DataFormatPanel::addChannelData(unsigned int channel,
- double* data, unsigned size)
+void DataFormatPanel::startRecording()
{
- _channelMan->addChannelData(channel, data, size);
+ currentReader->recording = true;
+ if (demoEnabled) demoReader.recording = true;
+}
+
+void DataFormatPanel::stopRecording()
+{
+ currentReader->recording = false;
+ if (demoEnabled) demoReader.recording = false;
}
void DataFormatPanel::selectReader(AbstractReader* reader)
@@ -124,7 +131,6 @@ void DataFormatPanel::selectReader(Abstr
// re-connect signals
disconnect(currentReader, 0, this, 0);
- connect(reader, SIGNAL(dataAdded()), this, SIGNAL(dataAdded()));
connect(reader, SIGNAL(numOfChannelsChanged(unsigned)),
this, SIGNAL(numOfChannelsChanged(unsigned)));
connect(reader, SIGNAL(samplesPerSecondChanged(unsigned)),
@@ -142,8 +148,8 @@ void DataFormatPanel::selectReader(Abstr
emit numOfChannelsChanged(reader->numOfChannels());
}
- // pause
reader->pause(paused);
+ reader->recording = currentReader->recording;
currentReader = reader;
}
diff --git a/src/dataformatpanel.h b/src/dataformatpanel.h
--- a/src/dataformatpanel.h
+++ b/src/dataformatpanel.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -34,6 +34,7 @@
#include "asciireader.h"
#include "demoreader.h"
#include "framedreader.h"
+#include "datarecorder.h"
namespace Ui {
class DataFormatPanel;
@@ -46,7 +47,8 @@ class DataFormatPanel : public QWidget
public:
explicit DataFormatPanel(QSerialPort* port,
ChannelManager* channelMan,
- QWidget *parent = 0);
+ DataRecorder* recorder,
+ QWidget* parent = 0);
~DataFormatPanel();
/// Returns currently selected number of channels
@@ -60,10 +62,19 @@ public slots:
void pause(bool);
void enableDemo(bool); // demo shouldn't be enabled when port is open
+ /**
+ * @brief Starts sending data to recorder.
+ *
+ * @note recorder must have been started!
+ */
+ void startRecording();
+
+ /// Stops recording.
+ void stopRecording();
+
signals:
void numOfChannelsChanged(unsigned);
void samplesPerSecondChanged(unsigned);
- void dataAdded();
private:
Ui::DataFormatPanel *ui;
@@ -81,10 +92,8 @@ private:
bool paused;
+ bool demoEnabled;
DemoReader demoReader;
-
- // `data` contains i th channels data
- void addChannelData(unsigned int channel, double* data, unsigned size);
};
#endif // DATAFORMATPANEL_H
diff --git a/src/datarecorder.cpp b/src/datarecorder.cpp
new file mode 100644
--- /dev/null
+++ b/src/datarecorder.cpp
@@ -0,0 +1,95 @@
+/*
+ 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 "datarecorder.h"
+
+#include
+
+DataRecorder::DataRecorder(QObject *parent) :
+ QObject(parent),
+ fileStream(&file)
+{
+ lastNumChannels = 0;
+ disableBuffering = false;
+ windowsLE = false;
+}
+
+bool DataRecorder::startRecording(QString fileName, QString separator, QStringList channelNames)
+{
+ Q_ASSERT(!file.isOpen());
+ _sep = separator;
+
+ // open file
+ file.setFileName(fileName);
+ if (!file.open(QIODevice::WriteOnly))
+ {
+ qCritical() << "Opening file " << fileName
+ << " for recording failed with error: " << file.error();
+ return false;
+ }
+
+ // write header line
+ if (!channelNames.isEmpty())
+ {
+ fileStream << channelNames.join(_sep);
+ fileStream << le();
+ lastNumChannels = channelNames.length();
+ }
+ return true;
+}
+
+void DataRecorder::addData(double* data, unsigned length, unsigned numOfChannels)
+{
+ Q_ASSERT(length > 0);
+ Q_ASSERT(length % numOfChannels == 0);
+
+ if (lastNumChannels != 0 && numOfChannels != lastNumChannels)
+ {
+ qWarning() << "Number of channels changed from " << lastNumChannels
+ << " to " << numOfChannels <<
+ " during recording, CSV file is corrupted but no data will be lost.";
+ }
+ lastNumChannels = numOfChannels;
+
+ unsigned numOfSamples = length / numOfChannels; // per channel
+ for (unsigned int i = 0; i < numOfSamples; i++)
+ {
+ for (unsigned ci = 0; ci < numOfChannels; ci++)
+ {
+ fileStream << data[ci * numOfSamples + i];
+ if (ci != numOfChannels-1) fileStream << _sep;
+ }
+ fileStream << le();
+ }
+
+ if (disableBuffering) fileStream.flush();
+}
+
+void DataRecorder::stopRecording()
+{
+ Q_ASSERT(file.isOpen());
+
+ file.close();
+ lastNumChannels = 0;
+}
+
+const char* DataRecorder::le() const
+{
+ return windowsLE ? "\r\n" : "\n";
+}
diff --git a/src/datarecorder.h b/src/datarecorder.h
new file mode 100644
--- /dev/null
+++ b/src/datarecorder.h
@@ -0,0 +1,88 @@
+/*
+ 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 DATARECORDER_H
+#define DATARECORDER_H
+
+#include
+#include
+#include
+
+class DataRecorder : public QObject
+{
+ Q_OBJECT
+public:
+ explicit DataRecorder(QObject *parent = 0);
+
+ /// Disables file buffering
+ 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.
+ *
+ * @param fileName name of the recording file
+ * @param separator column separator
+ * @param channelNames names of the channels for header line, if empty no header line is written
+ * @return false if file operation fails (read only etc.)
+ */
+ bool startRecording(QString fileName, QString separator, QStringList channelNames);
+
+ /**
+ * @brief Adds data to a channel.
+ *
+ * Multiple rows of data can be added at a time. Each channels
+ * data should be ordered consecutively in the `data` array:
+ *
+ * [CH0_SMP0, CH0_SMP1 ... CH0_SMPN, CH1_SMP0, CH1_SMP1, ... , CHN_SMPN]
+ *
+ * If `numOfChannels` changes during recording, no data will be
+ * lost (ie. it will be written to the file) but this will produce
+ * an invalid CSV file. An error message will be written to the
+ * console.
+ *
+ * @param data samples array
+ * @param length number of samples in `data`, must be multiple of `numOfChannels`
+ * @param numOfChannels how many channels samples this data carries
+ */
+ void addData(double* data, unsigned length, unsigned numOfChannels);
+
+ /// Stops recording, closes file.
+ void stopRecording();
+
+private:
+ unsigned lastNumChannels; ///< used for error message only
+ QFile file;
+ QTextStream fileStream;
+ QString _sep;
+
+ /// Returns the selected line ending.
+ const char* le() const;
+};
+
+#endif // DATARECORDER_H
diff --git a/src/demoreader.cpp b/src/demoreader.cpp
--- a/src/demoreader.cpp
+++ b/src/demoreader.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -25,8 +25,9 @@
#define M_PI 3.14159265358979323846
#endif
-DemoReader::DemoReader(QIODevice* device, ChannelManager* channelMan, QObject *parent) :
- AbstractReader(device, channelMan, parent)
+DemoReader::DemoReader(QIODevice* device, ChannelManager* channelMan,
+ DataRecorder* recorder, QObject* parent) :
+ AbstractReader(device, channelMan, recorder, parent)
{
paused = false;
_numOfChannels = 1;
@@ -76,13 +77,13 @@ void DemoReader::demoTimerTimeout()
if (!paused)
{
+ double* samples = new double[_numOfChannels];
for (unsigned ci = 0; ci < _numOfChannels; ci++)
{
// we are calculating the fourier components of square wave
- double value = 4*sin(2*M_PI*double((ci+1)*count)/period)/((2*(ci+1))*M_PI);
- _channelMan->addChannelData(ci, &value, 1);
- sampleCount++;
+ samples[ci] = 4*sin(2*M_PI*double((ci+1)*count)/period)/((2*(ci+1))*M_PI);
}
- emit dataAdded();
+ addData(samples, _numOfChannels);
+ delete[] samples;
}
}
diff --git a/src/demoreader.h b/src/demoreader.h
--- a/src/demoreader.h
+++ b/src/demoreader.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -38,7 +38,8 @@ class DemoReader : public AbstractReader
Q_OBJECT
public:
- explicit DemoReader(QIODevice* device, ChannelManager* channelMan, QObject *parent = 0);
+ explicit DemoReader(QIODevice* device, ChannelManager* channelMan,
+ DataRecorder* recorder, QObject* parent = 0);
/// Demo reader is an exception so this function returns NULL
QWidget* settingsWidget();
diff --git a/src/framebuffer.cpp b/src/framebuffer.cpp
--- a/src/framebuffer.cpp
+++ b/src/framebuffer.cpp
@@ -30,7 +30,7 @@ FrameBuffer::FrameBuffer(size_t size)
FrameBuffer::~FrameBuffer()
{
- delete data;
+ delete[] data;
}
void FrameBuffer::resize(size_t size)
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.
@@ -21,9 +21,19 @@
FrameBufferSeries::FrameBufferSeries(FrameBuffer* buffer)
{
+ xAsIndex = true;
+ _xmin = 0;
+ _xmax = 1;
_buffer = buffer;
}
+void FrameBufferSeries::setXAxis(bool asIndex, double xmin, double xmax)
+{
+ xAsIndex = asIndex;
+ _xmin = xmin;
+ _xmax = xmax;
+}
+
size_t FrameBufferSeries::size() const
{
return _buffer->size();
@@ -31,10 +41,27 @@ size_t FrameBufferSeries::size() const
QPointF FrameBufferSeries::sample(size_t i) const
{
- return QPointF(i, _buffer->sample(i));
+ if (xAsIndex)
+ {
+ return QPointF(i, _buffer->sample(i));
+ }
+ else
+ {
+ return QPointF(i * (_xmax - _xmin) / 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;
+ }
}
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,6 +37,9 @@ 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;
@@ -44,6 +47,9 @@ public:
private:
FrameBuffer* _buffer;
+ bool xAsIndex;
+ double _xmin;
+ double _xmax;
};
#endif // FRAMEBUFFERSERIES_H
diff --git a/src/framedreader.cpp b/src/framedreader.cpp
--- a/src/framedreader.cpp
+++ b/src/framedreader.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -23,8 +23,9 @@
#include "framedreader.h"
-FramedReader::FramedReader(QIODevice* device, ChannelManager* channelMan, QObject *parent) :
- AbstractReader(device, channelMan, parent)
+FramedReader::FramedReader(QIODevice* device, ChannelManager* channelMan,
+ DataRecorder* recorder, QObject* parent) :
+ AbstractReader(device, channelMan, recorder, parent)
{
paused = false;
@@ -310,22 +311,14 @@ void FramedReader::readFrameDataAndCheck
if (!checksumEnabled || checksumPassed)
{
// commit data
- for (unsigned int ci = 0; ci < _numOfChannels; ci++)
- {
- _channelMan->addChannelData(
- ci,
- channelSamples + ci*numOfPackagesToRead,
- numOfPackagesToRead);
- sampleCount += numOfPackagesToRead;
- }
- emit dataAdded();
+ addData(channelSamples, numOfPackagesToRead*_numOfChannels);
}
else
{
qCritical() << "Checksum failed! Received:" << rChecksum << "Calculated:" << calcChecksum;
}
- delete channelSamples;
+ delete[] channelSamples;
}
template double FramedReader::readSampleAs()
diff --git a/src/framedreader.h b/src/framedreader.h
--- a/src/framedreader.h
+++ b/src/framedreader.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -33,7 +33,8 @@ class FramedReader : public AbstractRead
Q_OBJECT
public:
- explicit FramedReader(QIODevice* device, ChannelManager* channelMan, QObject *parent = 0);
+ explicit FramedReader(QIODevice* device, ChannelManager* channelMan,
+ DataRecorder* recorder, QObject *parent = 0);
QWidget* settingsWidget();
unsigned numOfChannels();
void enable(bool enabled = true);
@@ -83,8 +84,6 @@ private:
/// reads payload portion of the frame, calculates checksum and commits data
/// @note should be called only if there are enough bytes on device
void readFrameDataAndCheck();
- // `data` contains i th channels data
- void addChannelData(unsigned int channel, double* data, unsigned size);
private slots:
void onDataReady();
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/main.cpp b/src/main.cpp
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2015 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -24,7 +24,6 @@
#include "tooltipfilter.h"
#include "version.h"
-
MainWindow* pMainWindow;
void messageHandler(QtMsgType type, const QMessageLogContext &context,
@@ -50,5 +49,6 @@ int main(int argc, char *argv[])
qDebug() << "Revision" << VERSION_REVISION;
w.show();
+
return a.exec();
}
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -62,19 +62,22 @@ MainWindow::MainWindow(QWidget *parent)
channelMan(1, 1, this),
snapshotMan(this, &channelMan),
commandPanel(&serialPort),
- dataFormatPanel(&serialPort, &channelMan)
+ dataFormatPanel(&serialPort, &channelMan, &recorder),
+ recordPanel(&recorder, &channelMan)
{
ui->setupUi(this);
- plotMan = new PlotManager(ui->plotArea);
+ plotMan = new PlotManager(ui->plotArea, channelMan.infoModel());
ui->tabWidget->insertTab(0, &portControl, "Port");
ui->tabWidget->insertTab(1, &dataFormatPanel, "Data Format");
ui->tabWidget->insertTab(2, &plotControlPanel, "Plot");
ui->tabWidget->insertTab(3, &commandPanel, "Commands");
+ ui->tabWidget->insertTab(4, &recordPanel, "Record");
ui->tabWidget->setCurrentIndex(0);
auto tbPortControl = portControl.toolBar();
addToolBar(tbPortControl);
+ addToolBar(recordPanel.toolbar());
ui->plotToolBar->addAction(snapshotMan.takeSnapshotAction());
ui->menuBar->insertMenu(ui->menuHelp->menuAction(), snapshotMan.menu());
@@ -83,6 +86,7 @@ MainWindow::MainWindow(QWidget *parent)
connect(&commandPanel, &CommandPanel::focusRequested, [this]()
{
this->ui->tabWidget->setCurrentWidget(&commandPanel);
+ this->ui->tabWidget->showTabs();
});
tbPortControl->setObjectName("tbPortControl");
@@ -130,14 +134,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);
- 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()));
@@ -150,11 +158,42 @@ MainWindow::MainWindow(QWidget *parent)
this, SLOT(onPortError(QSerialPort::SerialPortError)));
// init data format and reader
- QObject::connect(&dataFormatPanel, &DataFormatPanel::dataAdded,
+ QObject::connect(&channelMan, &ChannelManager::dataAdded,
plotMan, &PlotManager::replot);
QObject::connect(ui->actionPause, &QAction::triggered,
- &dataFormatPanel, &DataFormatPanel::pause);
+ &channelMan, &ChannelManager::pause);
+
+ QObject::connect(&recordPanel, &RecordPanel::recordStarted,
+ &dataFormatPanel, &DataFormatPanel::startRecording);
+
+ QObject::connect(&recordPanel, &RecordPanel::recordStopped,
+ &dataFormatPanel, &DataFormatPanel::stopRecording);
+
+ QObject::connect(ui->actionPause, &QAction::triggered,
+ [this](bool enabled)
+ {
+ if (enabled && !recordPanel.recordPaused())
+ {
+ dataFormatPanel.pause(true);
+ }
+ else
+ {
+ dataFormatPanel.pause(false);
+ }
+ });
+
+ QObject::connect(&recordPanel, &RecordPanel::recordPausedChanged,
+ [this](bool enabled)
+ {
+ if (ui->actionPause->isChecked() && enabled)
+ {
+ dataFormatPanel.pause(false);
+ }
+ });
+
+ connect(&serialPort, &QIODevice::aboutToClose,
+ &recordPanel, &RecordPanel::onPortClose);
// init data arrays and plot
numOfSamples = plotControlPanel.numOfSamples();
@@ -169,10 +208,7 @@ MainWindow::MainWindow(QWidget *parent)
connect(&channelMan, &ChannelManager::numOfChannelsChanged,
this, &MainWindow::onNumOfChannelsChanged);
- connect(&channelMan, &ChannelManager::channelNameChanged,
- this, &MainWindow::onChannelNameChanged);
-
- plotControlPanel.setChannelNamesModel(channelMan.channelNames());
+ plotControlPanel.setChannelInfoModel(channelMan.infoModel());
// init curve list
for (unsigned int i = 0; i < numOfChannels; i++)
@@ -180,9 +216,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->onNumOfSamplesChanged(numOfSamples);
// Init sps (sample per second) counter
spsLabel.setText("0sps");
@@ -215,15 +254,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();
@@ -237,6 +273,7 @@ MainWindow::~MainWindow()
void MainWindow::closeEvent(QCloseEvent * event)
{
+ // save snapshots
if (!snapshotMan.isAllSaved())
{
auto clickedButton = QMessageBox::warning(
@@ -250,6 +287,41 @@ void MainWindow::closeEvent(QCloseEvent
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);
}
@@ -369,17 +441,6 @@ void MainWindow::onNumOfChannelsChanged(
plotMan->replot();
}
-void MainWindow::onChannelNameChanged(unsigned channel, QString name)
-{
- // This slot is triggered also when a new channel is added, in
- // this case curve list doesn't contain said channel. No worries,
- // since `onNumOfChannelsChanged` slot will update curve list.
- if (channel < plotMan->numOfCurves()) // check if channel exists in curve list
- {
- plotMan->setTitle(channel, name);
- }
-}
-
void MainWindow::onSpsChanged(unsigned sps)
{
spsLabel.setText(QString::number(sps) + "sps");
@@ -463,6 +524,11 @@ void MainWindow::messageHandler(QtMsgTyp
{
ui->statusBar->showMessage(msg, 5000);
}
+
+ if (type == QtFatalMsg)
+ {
+ __builtin_trap();
+ }
}
void MainWindow::saveAllSettings(QSettings* settings)
@@ -474,6 +540,7 @@ void MainWindow::saveAllSettings(QSettin
plotControlPanel.saveSettings(settings);
plotMan->saveSettings(settings);
commandPanel.saveSettings(settings);
+ recordPanel.saveSettings(settings);
}
void MainWindow::loadAllSettings(QSettings* settings)
@@ -485,6 +552,7 @@ void MainWindow::loadAllSettings(QSettin
plotControlPanel.loadSettings(settings);
plotMan->loadSettings(settings);
commandPanel.loadSettings(settings);
+ recordPanel.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
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -38,11 +38,13 @@
#include "commandpanel.h"
#include "dataformatpanel.h"
#include "plotcontrolpanel.h"
+#include "recordpanel.h"
#include "ui_about_dialog.h"
#include "framebuffer.h"
#include "channelmanager.h"
#include "snapshotmanager.h"
#include "plotmanager.h"
+#include "datarecorder.h"
namespace Ui {
class MainWindow;
@@ -74,10 +76,12 @@ private:
ChannelManager channelMan;
PlotManager* plotMan;
SnapshotManager snapshotMan;
+ DataRecorder recorder; // operated by `recordPanel`
QLabel spsLabel;
CommandPanel commandPanel;
DataFormatPanel dataFormatPanel;
+ RecordPanel recordPanel;
PlotControlPanel plotControlPanel;
bool isDemoRunning();
@@ -99,7 +103,6 @@ private slots:
void onNumOfSamplesChanged(int value);
void onNumOfChannelsChanged(unsigned value);
- void onChannelNameChanged(unsigned channel, QString name);
void clearPlot();
void onSpsChanged(unsigned sps);
diff --git a/src/mainwindow.ui b/src/mainwindow.ui
--- a/src/mainwindow.ui
+++ b/src/mainwindow.ui
@@ -141,6 +141,9 @@
false
+
+
+
Pause
@@ -152,6 +155,9 @@
+
+
+
Clear
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,7 @@ Plot::Plot(QWidget* parent) :
{
isAutoScaled = true;
symbolSize = 0;
+ numOfSamples = 1;
QObject::connect(&zoomer, &Zoomer::unzoomed, this, &Plot::unzoomed);
@@ -78,7 +79,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 +93,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 +125,13 @@ void Plot::resetAxes()
setAxisScale(QwtPlot::yLeft, yMin, yMax);
}
+ zoomer.setZoomBase();
+
replot();
}
void Plot::unzoomed()
{
- setAxisAutoScale(QwtPlot::xBottom);
resetAxes();
onXScaleChanged();
}
@@ -229,7 +252,10 @@ void Plot::onXScaleChanged()
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)
{
@@ -273,8 +299,6 @@ void Plot::resizeEvent(QResizeEvent * ev
void Plot::onNumOfSamplesChanged(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.
@@ -37,28 +37,14 @@ class Plot : public QwtPlot
{
Q_OBJECT
+ friend class PlotManager;
+
public:
Plot(QWidget* parent = 0);
~Plot();
static QColor makeColor(unsigned int channelIndex);
-private:
- bool isAutoScaled;
- double yMin, yMax;
- int symbolSize;
- Zoomer zoomer;
- ScaleZoomer sZoomer;
- QwtPlotGrid grid;
- PlotSnapshotOverlay* snapshotOverlay;
- QwtPlotLegendItem legend;
- QwtPlotTextLabel demoIndicator;
-
- /// update the display of symbols depending on `symbolSize`
- void updateSymbols();
- void resetAxes();
- void resizeEvent(QResizeEvent * event);
-
public slots:
void showGrid(bool show = true);
void showMinorGrid(bool show = true);
@@ -66,7 +52,8 @@ public slots:
void showDemoIndicator(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);
/**
* Displays an animation for snapshot.
@@ -77,6 +64,26 @@ public slots:
void onNumOfSamplesChanged(unsigned value);
+protected:
+ /// update the display of symbols depending on `symbolSize`
+ void updateSymbols();
+
+private:
+ bool isAutoScaled;
+ double yMin, yMax;
+ double _xMin, _xMax;
+ unsigned numOfSamples;
+ int symbolSize;
+ Zoomer zoomer;
+ ScaleZoomer sZoomer;
+ QwtPlotGrid grid;
+ PlotSnapshotOverlay* snapshotOverlay;
+ QwtPlotLegendItem legend;
+ QwtPlotTextLabel demoIndicator;
+
+ void resetAxes();
+ void resizeEvent(QResizeEvent * event);
+
private slots:
void unzoomed();
void onXScaleChanged();
diff --git a/src/plotcontrolpanel.cpp b/src/plotcontrolpanel.cpp
--- a/src/plotcontrolpanel.cpp
+++ b/src/plotcontrolpanel.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -23,6 +23,7 @@
#include
+#include "color_selector.hpp"
#include "plotcontrolpanel.h"
#include "ui_plotcontrolpanel.h"
#include "setting_defines.h"
@@ -41,7 +42,12 @@ Q_DECLARE_METATYPE(Range);
PlotControlPanel::PlotControlPanel(QWidget *parent) :
QWidget(parent),
- ui(new Ui::PlotControlPanel)
+ ui(new Ui::PlotControlPanel),
+ resetAct(tr("Reset"), this),
+ resetNamesAct(tr("Reset Names"), this),
+ resetColorsAct(tr("Reset Colors"), this),
+ showAllAct(tr("Show All"), this),
+ resetMenu(tr("Reset Menu"), this)
{
ui->setupUi(this);
@@ -55,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)));
@@ -68,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
{
@@ -92,6 +113,18 @@ PlotControlPanel::PlotControlPanel(QWidg
QObject::connect(ui->cbRangePresets, SIGNAL(activated(int)),
this, SLOT(onRangeSelected()));
+
+ // color selector starts disabled until a channel is selected
+ ui->colorSelector->setColor(QColor(0,0,0,0));
+ ui->colorSelector->setDisplayMode(color_widgets::ColorPreview::AllAlpha);
+ ui->colorSelector->setDisabled(true);
+
+ // reset button
+ resetMenu.addAction(&resetNamesAct);
+ resetMenu.addAction(&resetColorsAct);
+ resetMenu.addAction(&showAllAct);
+ resetAct.setMenu(&resetMenu);
+ ui->tbReset->setDefaultAction(&resetAct);
}
PlotControlPanel::~PlotControlPanel()
@@ -163,7 +196,7 @@ void PlotControlPanel::onAutoScaleChecke
ui->spYmin->setEnabled(false);
ui->spYmax->setEnabled(false);
- emit scaleChanged(true); // autoscale
+ emit yScaleChanged(true); // autoscale
}
else
{
@@ -172,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();
@@ -204,15 +255,116 @@ void PlotControlPanel::onRangeSelected()
ui->cbAutoScale->setChecked(false);
}
-void PlotControlPanel::setChannelNamesModel(QAbstractItemModel * model)
+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->lvChannelNames->setModel(model);
+ ui->tvChannelInfo->setModel(model);
+
+ // channel color selector
+ connect(ui->tvChannelInfo->selectionModel(), &QItemSelectionModel::currentRowChanged,
+ [this](const QModelIndex ¤t, const QModelIndex &previous)
+ {
+ // TODO: duplicate with below lambda
+ QColor color(0,0,0,0); // transparent
+
+ if (current.isValid())
+ {
+ ui->colorSelector->setEnabled(true);
+ auto model = ui->tvChannelInfo->model();
+ color = model->data(current, Qt::ForegroundRole).value();
+ }
+ else
+ {
+ ui->colorSelector->setDisabled(true);
+ }
+
+ // temporarily block signals because `setColor` emits `colorChanged`
+ bool wasBlocked = ui->colorSelector->blockSignals(true);
+ ui->colorSelector->setColor(color);
+ ui->colorSelector->blockSignals(wasBlocked);
+ });
+
+ connect(ui->tvChannelInfo->selectionModel(), &QItemSelectionModel::selectionChanged,
+ [this](const QItemSelection & selected, const QItemSelection & deselected)
+ {
+ if (!selected.length())
+ {
+ ui->colorSelector->setDisabled(true);
+
+ // temporarily block signals because `setColor` emits `colorChanged`
+ bool wasBlocked = ui->colorSelector->blockSignals(true);
+ ui->colorSelector->setColor(QColor(0,0,0,0));
+ ui->colorSelector->blockSignals(wasBlocked);
+ }
+ });
+
+ connect(ui->colorSelector, &color_widgets::ColorSelector::colorChanged,
+ [this](QColor color)
+ {
+ auto index = ui->tvChannelInfo->selectionModel()->currentIndex();
+ ui->tvChannelInfo->model()->setData(index, color, Qt::ForegroundRole);
+ });
+
+ connect(model, &QAbstractItemModel::dataChanged,
+ [this](const QModelIndex & topLeft, const QModelIndex & bottomRight, const QVector & roles = QVector ())
+ {
+ auto current = ui->tvChannelInfo->selectionModel()->currentIndex();
+
+ // no current selection
+ if (!current.isValid()) return;
+
+ auto mod = ui->tvChannelInfo->model();
+ QColor color = mod->data(current, Qt::ForegroundRole).value();
+
+ // temporarily block signals because `setColor` emits `colorChanged`
+ bool wasBlocked = ui->colorSelector->blockSignals(true);
+ ui->colorSelector->setColor(color);
+ ui->colorSelector->blockSignals(wasBlocked);
+ });
+
+ // reset actions
+ connect(&resetAct, &QAction::triggered, model, &ChannelInfoModel::resetInfos);
+ connect(&resetNamesAct, &QAction::triggered, model, &ChannelInfoModel::resetNames);
+ connect(&resetColorsAct, &QAction::triggered, model, &ChannelInfoModel::resetColors);
+ connect(&showAllAct, &QAction::triggered, model, &ChannelInfoModel::resetVisibility);
}
void PlotControlPanel::saveSettings(QSettings* settings)
{
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());
@@ -224,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
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -21,8 +21,11 @@
#define PLOTCONTROLPANEL_H
#include
-#include
#include
+#include
+#include
+
+#include "channelinfomodel.h"
namespace Ui {
class PlotControlPanel;
@@ -37,11 +40,14 @@ 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 setChannelNamesModel(QAbstractItemModel * model);
+ void setChannelInfoModel(ChannelInfoModel* model);
/// Stores plot settings into a `QSettings`
void saveSettings(QSettings* settings);
@@ -50,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;
@@ -60,6 +67,9 @@ private:
/// User can disable this setting in the checkbox
bool warnNumOfSamples;
+ QAction resetAct, resetNamesAct, resetColorsAct, showAllAct;
+ QMenu resetMenu;
+
/// Show a confirmation dialog before setting #samples to a big value
bool askNSConfirmation(int value);
@@ -68,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
@@ -15,28 +15,108 @@
-
-
-
-
-
-
- font-weight: bold;
-
-
- Channel Names:
-
-
-
- -
-
-
-
- 16777215
- 170
-
-
-
-
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 3
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 300
+ 170
+
+
+
+ QAbstractItemView::SingleSelection
+
+
+ QAbstractItemView::SelectRows
+
+
+
+ -
+
+
+ QLayout::SetMaximumSize
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 20
+ 20
+
+
+
+
+ 20
+ 20
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 1
+ 20
+
+
+
+
+ -
+
+
+ Reset
+
+
+ QToolButton::MenuButtonPopup
+
+
+ Qt::NoArrow
+
+
+
+
+
+
+
-
@@ -48,7 +128,7 @@
-
- QFormLayout::AllNonFixedFieldsGrow
+ QFormLayout::FieldsStayAtSizeHint
-
@@ -77,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
@@ -86,81 +247,115 @@
- -
-
-
- 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:
- -
+
-
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::MinimumExpanding
+
+
+
+ 1
+ 20
+
+
+
+
+
+
+ color_widgets::ColorSelector
+ QWidget
+
+ 1
+
+
diff --git a/src/plotmanager.cpp b/src/plotmanager.cpp
--- a/src/plotmanager.cpp
+++ b/src/plotmanager.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -17,14 +17,14 @@
along with serialplot. If not, see .
*/
-#include
+#include "qwt_symbol.h"
#include "plot.h"
#include "plotmanager.h"
#include "utils.h"
#include "setting_defines.h"
-PlotManager::PlotManager(QWidget* plotArea, QObject *parent) :
+PlotManager::PlotManager(QWidget* plotArea, ChannelInfoModel* infoModel, QObject *parent) :
QObject(parent),
_plotArea(plotArea),
showGridAction("&Grid", this),
@@ -38,6 +38,8 @@ PlotManager::PlotManager(QWidget* plotAr
_yMin = 0;
_yMax = 1;
isDemoShown = false;
+ _infoModel = infoModel;
+ _numOfSamples = 1;
// initalize layout and single widget
isMulti = false;
@@ -85,6 +87,21 @@ PlotManager::PlotManager(QWidget* plotAr
this, &PlotManager::showLegend);
connect(&showMultiAction, SELECT::OVERLOAD_OF(&QAction::triggered),
this, &PlotManager::setMulti);
+
+ // connect to channel info model
+ if (_infoModel != NULL) // TODO: remove when snapshots have infomodel
+ {
+ connect(_infoModel, &QAbstractItemModel::dataChanged,
+ this, &PlotManager::onChannelInfoChanged);
+
+ connect(_infoModel, &QAbstractItemModel::modelReset,
+ [this]()
+ {
+ onChannelInfoChanged(_infoModel->index(0, 0), // start
+ _infoModel->index(_infoModel->rowCount()-1, 0), // end
+ {}); // roles ignored
+ });
+ }
}
PlotManager::~PlotManager()
@@ -103,6 +120,46 @@ PlotManager::~PlotManager()
if (scrollArea != NULL) delete scrollArea;
}
+void PlotManager::onChannelInfoChanged(const QModelIndex &topLeft,
+ const QModelIndex &bottomRight,
+ const QVector &roles)
+{
+ int start = topLeft.row();
+ int end = bottomRight.row();
+
+ for (int ci = start; ci <= end; ci++)
+ {
+ QString name = topLeft.sibling(ci, ChannelInfoModel::COLUMN_NAME).data(Qt::EditRole).toString();
+ QColor color = topLeft.sibling(ci, ChannelInfoModel::COLUMN_NAME).data(Qt::ForegroundRole).value();
+ bool visible = topLeft.sibling(ci, ChannelInfoModel::COLUMN_VISIBILITY).data(Qt::CheckStateRole).toBool();
+
+ curves[ci]->setTitle(name);
+ curves[ci]->setPen(color);
+ curves[ci]->setVisible(visible);
+ curves[ci]->setItemAttribute(QwtPlotItem::Legend, visible);
+
+ // replot only updated widgets
+ if (isMulti)
+ {
+ plotWidgets[ci]->updateSymbols(); // required for color change
+ plotWidgets[ci]->updateLegend(curves[ci]);
+ plotWidgets[ci]->setVisible(visible);
+ if (visible)
+ {
+ plotWidgets[ci]->replot();
+ }
+ }
+ }
+
+ // replot single widget
+ if (!isMulti)
+ {
+ plotWidgets[0]->updateSymbols();
+ plotWidgets[0]->updateLegend();
+ replot();
+ }
+}
+
void PlotManager::setMulti(bool enabled)
{
if (enabled == isMulti) return;
@@ -194,7 +251,16 @@ Plot* PlotManager::addPlotWidget()
plot->showMinorGrid(showMinorGridAction.isChecked());
plot->showLegend(showLegendAction.isChecked());
plot->showDemoIndicator(isDemoShown);
- plot->setAxis(_autoScaled, _yMin, _yMax);
+ plot->setYAxis(_autoScaled, _yMin, _yMax);
+
+ if (_xAxisAsIndex)
+ {
+ plot->setXAxis(0, _numOfSamples);
+ }
+ else
+ {
+ plot->setXAxis(_xMin, _xMax);
+ }
return plot;
}
@@ -202,7 +268,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);
}
@@ -219,7 +287,8 @@ void PlotManager::_addCurve(QwtPlotCurve
curves.append(curve);
unsigned index = curves.size()-1;
- curve->setPen(Plot::makeColor(index));
+ auto color = _infoModel->color(index);
+ curve->setPen(color);
// create the plot for the curve if we are on multi display
Plot* plot;
@@ -346,17 +415,42 @@ void PlotManager::darkBackground(bool en
}
}
-void PlotManager::setAxis(bool autoScaled, double yAxisMin, double yAxisMax)
+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)
@@ -367,9 +461,11 @@ void PlotManager::flashSnapshotOverlay()
void PlotManager::onNumOfSamplesChanged(unsigned value)
{
+ _numOfSamples = value;
for (auto plot : plotWidgets)
{
plot->onNumOfSamplesChanged(value);
+ if (_xAxisAsIndex) plot->setXAxis(0, value);
}
}
diff --git a/src/plotmanager.h b/src/plotmanager.h
--- a/src/plotmanager.h
+++ b/src/plotmanager.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -30,13 +30,14 @@
#include
#include "plot.h"
#include "framebufferseries.h"
+#include "channelinfomodel.h"
class PlotManager : public QObject
{
Q_OBJECT
public:
- explicit PlotManager(QWidget* plotArea, QObject *parent = 0);
+ explicit PlotManager(QWidget* plotArea, ChannelInfoModel* infoModel = NULL, QObject *parent = 0);
~PlotManager();
/// Add a new curve with title and buffer. A color is
/// automatically chosen for curve.
@@ -64,7 +65,9 @@ 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
@@ -77,10 +80,15 @@ private:
QScrollArea* scrollArea;
QList curves;
QList plotWidgets;
+ ChannelInfoModel* _infoModel;
bool isDemoShown;
bool _autoScaled;
double _yMin;
double _yMax;
+ bool _xAxisAsIndex;
+ double _xMin;
+ double _xMax;
+ unsigned _numOfSamples;
// menu actions
QAction showGridAction;
@@ -104,6 +112,10 @@ private slots:
void showLegend(bool show = true);
void unzoom();
void darkBackground(bool enabled = true);
+
+ void onChannelInfoChanged(const QModelIndex & topLeft,
+ const QModelIndex & bottomRight,
+ const QVector & roles = QVector ());
};
#endif // PLOTMANAGER_H
diff --git a/src/portcontrol.ui b/src/portcontrol.ui
--- a/src/portcontrol.ui
+++ b/src/portcontrol.ui
@@ -317,6 +317,24 @@
+
+ cbPortList
+ pbReloadPorts
+ pbOpenPort
+ cbBaudRate
+ rbNoParity
+ rbOddParity
+ rbEvenParity
+ rb8Bits
+ rb7Bits
+ rb6Bits
+ rb5Bits
+ rb1StopBit
+ rb2StopBit
+ rbNoFlowControl
+ rbHardwareControl
+ rbSoftwareControl
+
diff --git a/src/recordpanel.cpp b/src/recordpanel.cpp
new file mode 100644
--- /dev/null
+++ b/src/recordpanel.cpp
@@ -0,0 +1,294 @@
+/*
+ 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 "recordpanel.h"
+#include "ui_recordpanel.h"
+#include "setting_defines.h"
+
+RecordPanel::RecordPanel(DataRecorder* recorder, ChannelManager* channelMan, QWidget *parent) :
+ QWidget(parent),
+ ui(new Ui::RecordPanel),
+ recordToolBar(tr("Record Toolbar")),
+ recordAction(QIcon::fromTheme("media-record"), tr("Record"), this)
+{
+ overwriteSelected = false;
+ _recorder = recorder;
+ _channelMan = channelMan;
+
+ ui->setupUi(this);
+
+ recordToolBar.setObjectName("tbRecord");
+
+ recordAction.setCheckable(true);
+ recordToolBar.addAction(&recordAction);
+ ui->pbRecord->setDefaultAction(&recordAction);
+
+ connect(ui->pbBrowse, &QPushButton::clicked,
+ this, &RecordPanel::selectFile);
+ connect(&recordAction, &QAction::triggered,
+ this, &RecordPanel::onRecord);
+
+ connect(ui->cbRecordPaused, SIGNAL(toggled(bool)),
+ this, SIGNAL(recordPausedChanged(bool)));
+
+ connect(ui->cbDisableBuffering, &QCheckBox::toggled,
+ [this](bool enabled)
+ {
+ _recorder->disableBuffering = enabled;
+ });
+
+ connect(ui->cbWindowsLE, &QCheckBox::toggled,
+ [this](bool enabled)
+ {
+ _recorder->windowsLE = enabled;
+ });
+
+ connect(&recordAction, &QAction::toggled, ui->cbWindowsLE, &QWidget::setDisabled);
+}
+
+RecordPanel::~RecordPanel()
+{
+ delete ui;
+}
+
+QToolBar* RecordPanel::toolbar()
+{
+ return &recordToolBar;
+}
+
+bool RecordPanel::isRecording()
+{
+ return recordAction.isChecked();
+}
+
+bool RecordPanel::recordPaused()
+{
+ return ui->cbRecordPaused->isChecked();
+}
+
+bool RecordPanel::selectFile()
+{
+ QString fileName = QFileDialog::getSaveFileName(
+ parentWidget(), tr("Select recording file"));
+
+ if (fileName.isEmpty())
+ {
+ return false;
+ }
+ else
+ {
+ selectedFile = fileName;
+ ui->lbFileName->setText(selectedFile);
+ overwriteSelected = QFile::exists(fileName);
+ return true;
+ }
+}
+
+void RecordPanel::onRecord(bool start)
+{
+ if (!start)
+ {
+ stopRecording();
+ return;
+ }
+
+ bool canceled = false;
+ if (ui->leSeparator->text().isEmpty())
+ {
+ QMessageBox::critical(this, "Error",
+ "Column separator cannot be empty! Please select a separator.");
+ ui->leSeparator->setFocus(Qt::OtherFocusReason);
+ canceled = true;
+ }
+
+ // check file name
+ if (!canceled && selectedFile.isEmpty() && !selectFile())
+ {
+ canceled = true;
+ }
+
+ if (!canceled && !overwriteSelected && QFile::exists(selectedFile))
+ {
+ if (ui->cbAutoIncrement->isChecked())
+ {
+ // TODO: should we increment even if user selected to replace?
+ canceled = !incrementFileName();
+ }
+ else
+ {
+ canceled = !confirmOverwrite(selectedFile);
+ }
+ }
+
+ if (canceled)
+ {
+ recordAction.setChecked(false);
+ }
+ else
+ {
+ overwriteSelected = false;
+ startRecording();
+ }
+}
+
+bool RecordPanel::incrementFileName(void)
+{
+ QFileInfo fileInfo(selectedFile);
+
+ QString base = fileInfo.completeBaseName();
+ QRegularExpression regex("(.*?)(\\d+)(?!.*\\d)(.*)");
+ auto match = regex.match(base);
+
+ if (match.hasMatch())
+ {
+ bool ok;
+ int fileNum = match.captured(2).toInt(&ok);
+ base = match.captured(1) + QString::number(fileNum + 1) + match.captured(3);
+ }
+ else
+ {
+ base += "_1";
+ }
+
+ QString suffix = fileInfo.suffix();;
+ if (!suffix.isEmpty())
+ {
+ suffix = "." + suffix;
+ }
+
+ QString autoFileName = fileInfo.path() + "/" + base + suffix;
+
+ // check if auto generated file name exists, ask user another name
+ if (QFile::exists(autoFileName))
+ {
+ if (!confirmOverwrite(autoFileName))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ selectedFile = autoFileName;
+ }
+
+ ui->lbFileName->setText(selectedFile);
+ return true;
+}
+
+bool RecordPanel::confirmOverwrite(QString fileName)
+{
+ // prepare message box
+ QMessageBox mb(parentWidget());
+ mb.setWindowTitle(tr("File Already Exists"));
+ mb.setIcon(QMessageBox::Warning);
+ mb.setText(tr("File (%1) already exists. How to continue?").arg(fileName));
+
+ auto bCancel = mb.addButton(QMessageBox::Cancel);
+ auto bOverwrite = mb.addButton(tr("Overwrite"), QMessageBox::DestructiveRole);
+ mb.addButton(tr("Select Another File"), QMessageBox::YesRole);
+
+ mb.setEscapeButton(bCancel);
+
+ // show message box
+ mb.exec();
+
+ if (mb.clickedButton() == bCancel)
+ {
+ return false;
+ }
+ else if (mb.clickedButton() == bOverwrite)
+ {
+ selectedFile = fileName;
+ return true;
+ }
+ else // select button
+ {
+ return selectFile();
+ }
+}
+
+void RecordPanel::startRecording(void)
+{
+ QStringList channelNames;
+ if (ui->cbHeader->isChecked())
+ {
+ channelNames = _channelMan->infoModel()->channelNames();
+ }
+ _recorder->startRecording(selectedFile, getSeparator(), channelNames);
+ emit recordStarted();
+}
+
+void RecordPanel::stopRecording(void)
+{
+ emit recordStopped();
+ _recorder->stopRecording();
+}
+
+void RecordPanel::onPortClose()
+{
+ if (recordAction.isChecked() && ui->cbStopOnClose->isChecked())
+ {
+ stopRecording();
+ recordAction.setChecked(false);
+ }
+}
+
+QString RecordPanel::getSeparator() const
+{
+ QString sep = ui->leSeparator->text();
+ sep.replace("\\t", "\t");
+ return sep;
+}
+
+void RecordPanel::saveSettings(QSettings* settings)
+{
+ settings->beginGroup(SettingGroup_Record);
+ settings->setValue(SG_Record_AutoIncrement, ui->cbAutoIncrement->isChecked());
+ settings->setValue(SG_Record_RecordPaused, ui->cbRecordPaused->isChecked());
+ settings->setValue(SG_Record_StopOnClose, ui->cbStopOnClose->isChecked());
+ settings->setValue(SG_Record_Header, ui->cbHeader->isChecked());
+ settings->setValue(SG_Record_DisableBuffering, ui->cbDisableBuffering->isChecked());
+ settings->setValue(SG_Record_Separator, ui->leSeparator->text());
+ settings->endGroup();
+}
+
+void RecordPanel::loadSettings(QSettings* settings)
+{
+ settings->beginGroup(SettingGroup_Record);
+ ui->cbAutoIncrement->setChecked(
+ settings->value(SG_Record_AutoIncrement, ui->cbAutoIncrement->isChecked()).toBool());
+ ui->cbRecordPaused->setChecked(
+ settings->value(SG_Record_RecordPaused, ui->cbRecordPaused->isChecked()).toBool());
+ ui->cbStopOnClose->setChecked(
+ settings->value(SG_Record_StopOnClose, ui->cbStopOnClose->isChecked()).toBool());
+ ui->cbHeader->setChecked(
+ settings->value(SG_Record_Header, ui->cbHeader->isChecked()).toBool());
+ ui->cbDisableBuffering->setChecked(
+ settings->value(SG_Record_DisableBuffering, ui->cbDisableBuffering->isChecked()).toBool());
+ ui->leSeparator->setText(settings->value(SG_Record_Separator, ui->leSeparator->text()).toString());
+ settings->endGroup();
+}
diff --git a/src/recordpanel.h b/src/recordpanel.h
new file mode 100644
--- /dev/null
+++ b/src/recordpanel.h
@@ -0,0 +1,117 @@
+/*
+ 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 RECORDPANEL_H
+#define RECORDPANEL_H
+
+#include
+#include
+#include
+#include
+
+#include "datarecorder.h"
+#include "channelmanager.h"
+
+namespace Ui {
+class RecordPanel;
+}
+
+class RecordPanel : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit RecordPanel(DataRecorder* recorder, ChannelManager* channelMan,
+ QWidget* parent = 0);
+ ~RecordPanel();
+
+ QToolBar* toolbar();
+
+ bool isRecording();
+ bool recordPaused();
+
+ /// Stores settings into a `QSettings`
+ void saveSettings(QSettings* settings);
+ /// Loads settings from a `QSettings`.
+ void loadSettings(QSettings* settings);
+
+signals:
+ void recordStarted();
+ void recordStopped();
+ void recordPausedChanged(bool enabled);
+
+public slots:
+ /// Must be called when port is closed
+ void onPortClose();
+
+private:
+ Ui::RecordPanel *ui;
+ QToolBar recordToolBar;
+ QAction recordAction;
+ QString selectedFile;
+ bool overwriteSelected;
+ DataRecorder* _recorder;
+ ChannelManager* _channelMan;
+
+ /**
+ * @brief Increments the file name.
+ *
+ * If file name doesn't have a number at the end of it, a number is appended
+ * with underscore starting from 1.
+ *
+ * @return false if user cancels
+ */
+ bool incrementFileName(void);
+
+ /**
+ * @brief Used to ask user confirmation if auto generated file
+ * name exists.
+ *
+ * If user confirms overwrite, `selectedFile` is set to
+ * `fileName`. User is also given option to select file and is
+ * shown a file select dialog in this case.
+ *
+ * @param fileName auto generated file name.
+ * @return false if user cancels
+ */
+ bool confirmOverwrite(QString fileName);
+
+ void startRecording(void);
+ void stopRecording(void);
+
+ /// Returns separator text from ui. "\t" is converted to TAB
+ /// character.
+ QString getSeparator() const;
+
+private slots:
+ /**
+ * @brief Opens up the file select dialog
+ *
+ * If you cancel the selection operation, currently selected file is not
+ * changed.
+ *
+ * @return true if file selected, false if user cancels
+ */
+ bool selectFile();
+
+ void onRecord(bool start);
+
+};
+
+#endif // RECORDPANEL_H
diff --git a/src/recordpanel.ui b/src/recordpanel.ui
new file mode 100644
--- /dev/null
+++ b/src/recordpanel.ui
@@ -0,0 +1,227 @@
+
+
+ RecordPanel
+
+
+
+ 0
+ 0
+ 627
+ 261
+
+
+
+ Form
+
+
+ -
+
+
-
+
+
-
+
+
+ Select record file
+
+
+ Browse
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Select file...
+
+
+
+
+
+ -
+
+
-
+
+
+ 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
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Column Separator:
+
+
+
+ -
+
+
+
+ 30
+ 16777215
+
+
+
+ For TAB character enter \t
+
+
+ ,
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 1
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 85
+ 50
+
+
+
+ Start/Stop Recording
+
+
+ Record
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 1
+
+
+
+
+
+
+
+
+
+
+
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
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -29,6 +29,7 @@ const char SettingGroup_CustomFrame[] =
const char SettingGroup_Channels[] = "Channels";
const char SettingGroup_Plot[] = "Plot";
const char SettingGroup_Commands[] = "Commands";
+const char SettingGroup_Record[] = "Record";
// mainwindow setting keys
const char SG_MainWindow_Size[] = "size";
@@ -70,9 +71,14 @@ const char SG_CustomFrame_DebugMode[] =
// channel manager keys
const char SG_Channels_Channel[] = "channel";
const char SG_Channels_Name[] = "name";
+const char SG_Channels_Color[] = "color";
+const char SG_Channels_Visible[] = "visible";
// 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";
@@ -88,4 +94,12 @@ const char SG_Commands_Name[] = "name";
const char SG_Commands_Type[] = "type";
const char SG_Commands_Data[] = "data";
+// record panel settings keys
+const char SG_Record_AutoIncrement[] = "autoIncrement";
+const char SG_Record_RecordPaused[] = "recordPaused";
+const char SG_Record_StopOnClose[] = "stopOnClose";
+const char SG_Record_Header[] = "header";
+const char SG_Record_Separator[] = "separator";
+const char SG_Record_DisableBuffering[] = "disableBuffering";
+
#endif // SETTING_DEFINES_H
diff --git a/src/snapshot.cpp b/src/snapshot.cpp
--- a/src/snapshot.cpp
+++ b/src/snapshot.cpp
@@ -24,8 +24,9 @@
#include "snapshot.h"
#include "snapshotview.h"
-Snapshot::Snapshot(QMainWindow* parent, QString name) :
+Snapshot::Snapshot(QMainWindow* parent, QString name, ChannelInfoModel infoModel) :
QObject(parent),
+ cInfoModel(infoModel),
_showAction(this),
_deleteAction("&Delete", this)
{
@@ -106,14 +107,14 @@ void Snapshot::setName(QString name)
emit nameChanged(this);
}
-void Snapshot::setChannelNames(QStringList names)
+ChannelInfoModel* Snapshot::infoModel()
{
- _channelNames = names;
+ return &cInfoModel;
}
QString Snapshot::channelName(unsigned channel)
{
- return _channelNames[channel];
+ return cInfoModel.name(channel);
}
void Snapshot::save(QString fileName)
diff --git a/src/snapshot.h b/src/snapshot.h
--- a/src/snapshot.h
+++ b/src/snapshot.h
@@ -27,6 +27,8 @@
#include
#include
+#include "channelinfomodel.h"
+
class SnapshotView;
class Snapshot : public QObject
@@ -34,7 +36,7 @@ class Snapshot : public QObject
Q_OBJECT
public:
- Snapshot(QMainWindow* parent, QString name);
+ Snapshot(QMainWindow* parent, QString name, ChannelInfoModel infoModel);
~Snapshot();
QVector> data;
@@ -43,8 +45,8 @@ public:
QString name();
QString displayName(); ///< `name()` plus '*' if snapshot is not saved
+ ChannelInfoModel* infoModel();
void setName(QString name);
- void setChannelNames(QStringList names); // must be called when setting data!
QString channelName(unsigned channel);
void save(QString fileName); ///< save snapshot data as CSV
@@ -56,7 +58,7 @@ signals:
private:
QString _name;
- QStringList _channelNames;
+ ChannelInfoModel cInfoModel;
QAction _showAction;
QAction _deleteAction;
QMainWindow* mainWindow;
diff --git a/src/snapshotmanager.cpp b/src/snapshotmanager.cpp
--- a/src/snapshotmanager.cpp
+++ b/src/snapshotmanager.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2015 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -24,6 +24,7 @@
#include
#include
#include
+#include
#include
#include "snapshotmanager.h"
@@ -40,6 +41,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)),
@@ -63,7 +65,7 @@ SnapshotManager::~SnapshotManager()
Snapshot* SnapshotManager::makeSnapshot()
{
QString name = QTime::currentTime().toString("'Snapshot ['HH:mm:ss']'");
- auto snapshot = new Snapshot(_mainWindow, name);
+ auto snapshot = new Snapshot(_mainWindow, name, *(_channelMan->infoModel()));
unsigned numOfChannels = _channelMan->numOfChannels();
unsigned numOfSamples = _channelMan->numOfSamples();
@@ -76,7 +78,6 @@ Snapshot* SnapshotManager::makeSnapshot(
snapshot->data[ci][i] = QPointF(i, _channelMan->channelBuffer(ci)->sample(i));
}
}
- snapshot->setChannelNames(_channelMan->channelNames()->stringList());
return snapshot;
}
@@ -189,9 +190,11 @@ void SnapshotManager::loadSnapshotFromFi
lineNum++;
}
- auto snapshot = new Snapshot(_mainWindow, QFileInfo(fileName).baseName());
+ ChannelInfoModel channelInfo(channelNames);
+
+ auto snapshot = new Snapshot(
+ _mainWindow, QFileInfo(fileName).baseName(), ChannelInfoModel(channelNames));
snapshot->data = data;
- snapshot->setChannelNames(channelNames);
addSnapshot(snapshot, false);
}
diff --git a/src/snapshotview.cpp b/src/snapshotview.cpp
--- a/src/snapshotview.cpp
+++ b/src/snapshotview.cpp
@@ -29,7 +29,7 @@ SnapshotView::SnapshotView(QWidget *pare
ui->setupUi(this);
- plotMan = new PlotManager(ui->plotArea);
+ plotMan = new PlotManager(ui->plotArea, snapshot->infoModel());
ui->menuSnapshot->insertAction(ui->actionClose, snapshot->deleteAction());
this->setWindowTitle(snapshot->displayName());
@@ -46,7 +46,7 @@ SnapshotView::SnapshotView(QWidget *pare
connect(ui->actionRename, &QAction::triggered,
this, &SnapshotView::showRenameDialog);
- connect(ui->actionExport, &QAction::triggered,
+ connect(ui->actionSave, &QAction::triggered,
this, &SnapshotView::save);
// add 'View' menu items
diff --git a/src/snapshotview.ui b/src/snapshotview.ui
--- a/src/snapshotview.ui
+++ b/src/snapshotview.ui
@@ -26,7 +26,7 @@
0
0
544
- 20
+ 25
-
+
- &Export CSV
+ &Save as CSV
Save snapshot as CSV file
diff --git a/src/version.h b/src/version.h
new file mode 100644
--- /dev/null
+++ b/src/version.h
@@ -0,0 +1,31 @@
+/*
+ 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
+#define VERSION_STRING "0.9.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/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