##// END OF EJS Templates
PySide2 bindings + some GUI clean...
jeandet -
r1478:6e3f56cd8c8b
parent child
Show More
@@ -1,79 +1,79
1 1 find_package(PythonLibs 3 REQUIRED)
2 2 find_package(PythonInterp 3 REQUIRED)
3 3 find_package(PySide2 REQUIRED)
4 4 find_package(Shiboken2 REQUIRED)
5 5 include(PythonInfo)
6 6 find_python_site_packages(PYTHON_SITE_PACKAGES)
7 7
8 8 set(BINDINGS_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
9 9 set(BINDINGS_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}")
10 10
11 11 configure_file("${BINDINGS_SRC_DIR}/bindings.xml" "${BINDINGS_BUILD_DIR}/bindings.xml" COPYONLY)
12 12 configure_file("${BINDINGS_SRC_DIR}/main.py" "${BINDINGS_BUILD_DIR}/main.py" COPYONLY)
13 13
14 14 execute_process(COMMAND "${PYTHON_EXECUTABLE}" "${BINDINGS_SRC_DIR}/src_list.py" cmake "${BINDINGS_BUILD_DIR}" OUTPUT_VARIABLE BINDINGS_SOURCE)
15 15
16 16 set_property(SOURCE ${BINDINGS_SOURCE} PROPERTY SKIP_AUTOGEN ON)
17 17
18 18 list(APPEND BINDINGS_INCLUDE_DIRS
19 19 ${PYTHON_INCLUDE_DIRS}
20 20 ${Qt5Core_INCLUDE_DIRS}
21 21 ${Qt5Widgets_INCLUDE_DIRS}
22 22 ${Qt5Gui_INCLUDE_DIRS}
23 23 ${CMAKE_CURRENT_SOURCE_DIR}/../../gui/include
24 24 ${CMAKE_CURRENT_SOURCE_DIR}/../../core/include
25 25 ${CMAKE_CURRENT_SOURCE_DIR}/../../core/external/TimeSeries/include
26 26 )
27 27 list(REMOVE_DUPLICATES BINDINGS_INCLUDE_DIRS)
28 28 foreach(DIR ${BINDINGS_INCLUDE_DIRS})
29 29 list(APPEND BINDINGS_INCLUDE_DIRS_ARGS "-I${DIR}")
30 30 endforeach()
31 31
32 32 set(SHIBOKEN_OPTIONS --generator-set=shiboken
33 33 --enable-parent-ctor-heuristic
34 34 --enable-return-value-heuristic
35 35 --use-isnull-as-nb_nonzero
36 36 --avoid-protected-hack
37 37 --enable-pyside-extensions
38 38 -std=c++17)
39 39 add_custom_command(
40 40 OUTPUT ${BINDINGS_SOURCE}
41 41 COMMAND Shiboken2::shiboken2 ${SHIBOKEN_OPTIONS}
42 42 ${BINDINGS_INCLUDE_DIRS_ARGS}
43 43 --typesystem-paths=${PYSIDE_TYPESYSTEMS}
44 44 --output-directory=${CMAKE_CURRENT_BINARY_DIR}
45 45 ${CMAKE_CURRENT_SOURCE_DIR}/bindings.h ${CMAKE_CURRENT_SOURCE_DIR}/bindings.xml
46 46
47 47 DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/bindings.xml"
48 48 IMPLICIT_DEPENDS CXX "${CMAKE_CURRENT_SOURCE_DIR}/bindings.h"
49 49 WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
50 50 COMMENT "Generating Python bindings with shiboken2")
51 51
52 52 include_directories(
53 53 ${PYSIDE_INCLUDE_DIR}/QtCore
54 54 ${PYSIDE_INCLUDE_DIR}/QtGui
55 55 ${PYSIDE_INCLUDE_DIR}/QtWidgets)
56 56
57 57 include_directories(
58 58 ${PYTHON_SITE_PACKAGES}/numpy/core/include/
59 59 ${PYTHON_INCLUDE_DIRS}
60 60 ${SHIBOKEN_INCLUDE_DIR}
61 61 ${PYSIDE_INCLUDE_DIR}
62 62 ${PYSIDE_INCLUDE_DIR}/QtCore
63 63 ${PYSIDE_INCLUDE_DIR}/QtGui
64 64 ${PYSIDE_INCLUDE_DIR}/QtWidgets)
65 65
66 add_library(SciQLopBindings MODULE ${BINDINGS_SOURCE} numpy_wrappers.h)
66 add_library(SciQLopBindings MODULE ${BINDINGS_SOURCE} numpy_wrappers.h PyDataProvider.h)
67 67 set_target_properties(
68 68 SciQLopBindings
69 69 PROPERTIES
70 70 PREFIX ""
71 71 OUTPUT_NAME "SciQLopBindings"
72 72 )
73 73 target_link_libraries(SciQLopBindings sciqlopapp)
74 74 target_link_libraries(SciQLopBindings Shiboken2::libshiboken)
75 75 target_link_libraries(SciQLopBindings PySide2::pyside2)
76 76
77 77 add_executable(debug_sciqlop_app main.cpp )
78 78 find_package (Python3 COMPONENTS Development)
79 79 target_link_libraries(debug_sciqlop_app PRIVATE Python3::Python)
@@ -1,16 +1,89
1 1 #pragma once
2 #include <Data/DataProviderParameters.h>
2 3 #include <Data/IDataProvider.h>
4 #include <DataSource/DataSourceController.h>
5 #include <DataSource/DataSourceItem.h>
6 #include <DataSource/DataSourceItemAction.h>
7 #include <QPair>
8 #include <SqpApplication.h>
9 // must be included last because of Python/Qt definition of slots
10 #include "numpy_wrappers.h"
11
12 struct Product
13 {
14 QString path;
15 std::vector<std::string> components;
16 QMap<QString, QString> metadata;
17 Product() = default;
18 explicit Product(const QString& path, const std::vector<std::string>& components,
19 const QMap<QString, QString>& metadata)
20 : path { path }, components { components }, metadata { metadata }
21 {
22 }
23 virtual ~Product() = default;
24 };
3 25
4 26 class PyDataProvider : public IDataProvider
5 27 {
6 28 public:
7 PyDataProvider() {}
29 PyDataProvider()
30 {
31 auto& dataSourceController = sqpApp->dataSourceController();
32 dataSourceController.registerProvider(this);
33 }
34
35 virtual ~PyDataProvider() {}
8 36
9 virtual TimeSeries::ITimeSerie getData(const std::string& key, double start_time, double stop_time)
10 {}
37 virtual QPair<NpArray, NpArray> getData(
38 const std::string& key, double start_time, double stop_time)
39 {
40 (void)key, (void)start_time, (void)stop_time;
41 return {};
42 }
11 43
12 virtual TimeSeries::ITimeSerie* getData(const DataProviderParameters& parameters)
44 virtual TimeSeries::ITimeSerie* getData(const DataProviderParameters& parameters) override
13 45 {
46 if (parameters.m_Data.contains("name"))
47 {
48 auto data = getData(parameters.m_Data["name"].toString().toStdString(),
49 parameters.m_Range.m_TStart, parameters.m_Range.m_TEnd);
50 // TODO add shape/type switch
51 return new ScalarTimeSerie { data.first.to_std_vect(), data.second.to_std_vect() };
52 }
14 53 return nullptr;
15 54 }
55
56
57 inline void register_products(const QVector<Product*>& products)
58 {
59 auto& dataSourceController = sqpApp->dataSourceController();
60 auto id = this->id();
61 auto data_source_name = this->name();
62 std::for_each(std::cbegin(products), std::cend(products),
63 [&id, &dataSourceController](const Product* product) {
64 dataSourceController.setDataSourceItem(id, product->path, product->metadata);
65 });
66 }
16 67 };
68
69
70 struct Providers
71 {
72 Providers() = default;
73 virtual ~Providers() = default;
74 inline void register_provider(PyDataProvider* provider)
75 {
76 auto& dataSourceController = sqpApp->dataSourceController();
77 dataSourceController.setDataProvider(
78 provider->id(), std::unique_ptr<IDataProvider>(provider));
79 }
80 };
81
82
83 inline ScalarTimeSerie test_PyDataProvider(PyDataProvider& prov)
84 {
85 auto v = prov.getData("", 0., 0.);
86 ScalarTimeSerie s;
87 s.set_data(v.first.to_std_vect(), v.second.to_std_vect());
88 return s;
89 }
@@ -1,72 +1,91
1 1 <?xml version="1.0"?>
2 2 <typesystem package="SciQLopBindings">
3 3 <load-typesystem name="typesystem_core.xml" generate="no" />
4 4 <load-typesystem name="typesystem_gui.xml" generate="no" />
5 5 <load-typesystem name="typesystem_widgets.xml" generate="no" />
6 6 <primitive-type name="std::string"/>
7 7 <primitive-type name="std::size_t"/>
8 8 <container-type name="std::vector" type="vector">
9 9 <include file-name="vector" location="global"/>
10 10 <conversion-rule>
11 11 <native-to-target>
12 12 %INTYPE::size_type vectorSize = %in.size();
13 13 PyObject* %out = PyList_New((int) vectorSize);
14 14 for (%INTYPE::size_type idx = 0; idx &lt; vectorSize; ++idx) {
15 15 %INTYPE_0 cppItem(%in[idx]);
16 16 PyList_SET_ITEM(%out, idx, %CONVERTTOPYTHON[%INTYPE_0](cppItem));
17 17 }
18 18 return %out;
19 19 </native-to-target>
20 20 <target-to-native>
21 21 <add-conversion type="PySequence">
22 22 Shiboken::AutoDecRef seq(PySequence_Fast(%in, 0));
23 23 int vectorSize = PySequence_Fast_GET_SIZE(seq.object());
24 24 %out.reserve(vectorSize);
25 25 for (int idx = 0; idx &lt; vectorSize; ++idx ) {
26 26 PyObject* pyItem = PySequence_Fast_GET_ITEM(seq.object(), idx);
27 27 %OUTTYPE_0 cppItem = %CONVERTTOCPP[%OUTTYPE_0](pyItem);
28 28 %out.push_back(cppItem);
29 29 }
30 30 </add-conversion>
31 31 </target-to-native>
32 32 </conversion-rule>
33 33 </container-type>
34 <object-type name="PyDataProvider" />
35 <object-type name="Product" />
34 36 <object-type name="MainWindow" />
35 37 <object-type name="SqpApplication">
36 38 <modify-function signature="SqpApplication(int&amp;,char**)" access="private"/>
37 39 </object-type>
38 <object-type name="PyDataProvider" />
39 <function signature="SqpApplication_ctor()"/>
40 <object-type name="Providers">
41 <modify-function signature="register_provider(PyDataProvider*)">
42 <modify-argument index="1">
43 <define-ownership owner="c++" />
44 </modify-argument>
45 </modify-function>
46 </object-type>
47 <function signature="SqpApplication_ctor()" return-type="SqpApplication*"/>
48 <add-function signature="SqpApplication_ctor(PySequence)" return-type="SqpApplication*">
49 <inject-code class="target">
50 static int argc;
51 static char **argv;
52 Shiboken::listToArgcArgv(%1, &amp;argc, &amp;argv, "PySideApp");
53 auto retval = new SqpApplication(argc,argv);
54 %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](retval);
55 </inject-code>
56 </add-function>
40 57 <function signature="init_resources()"/>
41 58 <primitive-type name="NpArray" target-lang-api-name="PyObject">
42 59 <include file-name="numpy_wrappers.h" location="local"/>
43 60 <conversion-rule>
44 61 <native-to-target>
45 return %in.py_object();
62 auto result = %in.py_object();
63 return result;
46 64 </native-to-target>
47 65 <target-to-native>
48 66 <add-conversion type="PyObject" check="NpArray::isNpArray(%in)">
49 67 %out = %OUTTYPE(%in);
50 68 </add-conversion>
51 69 </target-to-native>
52 70 </conversion-rule>
53 71 </primitive-type>
54 72 <function signature="load_plugins(const SqpApplication&amp;)"/>
55 73 <object-type name="ScalarTimeSerie">
56 74 <add-function signature="ScalarTimeSerie(NpArray&amp;,NpArray&amp;)" return-type="ScalarTimeSerie">
57 75 <inject-code class="target">
58 76 %BEGIN_ALLOW_THREADS
59 77 %0 = new ScalarTimeSerieWrapper();
60 78 %0.set_data(%1.to_std_vect(),%2.to_std_vect());
61 79 %END_ALLOW_THREADS
62 80 </inject-code>
63 81 </add-function>
64 82 <add-function signature="size()" return-type="int" access="public" static="no">
65 83 <inject-code class="target">
66 84 %RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME();
67 85 %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
68 86 </inject-code>
69 87 </add-function>
70 88 </object-type>
89 <function signature="test_PyDataProvider(PyDataProvider&amp;)"/>
71 90 <function signature="test_np_array(NpArray&amp;)"/>
72 91 </typesystem>
@@ -1,26 +1,39
1 1 #include <fstream>
2 2 #include <iostream>
3 3 #define PY_SSIZE_T_CLEAN
4 4 #define Py_DEBUG
5 5 #include <Python.h>
6 6
7 wchar_t** decode_argv(int argc, char** argv)
8 {
9 wchar_t** _argv = static_cast<wchar_t**>(PyMem_Malloc(sizeof(wchar_t*) * argc));
10 for (int i = 0; i < argc; i++)
11 {
12 wchar_t* arg = Py_DecodeLocale(argv[i], NULL);
13 _argv[i] = arg;
14 }
15 return _argv;
16 }
17
7 18 int main(int argc, char** argv)
8 19 {
9 20 wchar_t* program = Py_DecodeLocale(argv[0], NULL);
10 21 if (program == NULL)
11 22 {
12 23 fprintf(stderr, "Fatal error: cannot decode argv[0]\n");
13 24 exit(1);
14 25 }
26
15 27 Py_SetProgramName(program); /* optional but recommended */
16 28 Py_Initialize();
29 PySys_SetArgv(argc, decode_argv(argc, argv));
17 30 std::ifstream t(argv[1]);
18 31 std::string str((std::istreambuf_iterator<char>(t)), std::istreambuf_iterator<char>());
19 32 PyRun_SimpleString(str.data());
20 33 if (Py_FinalizeEx() < 0)
21 34 {
22 35 exit(120);
23 36 }
24 37 PyMem_RawFree(program);
25 38 return 0;
26 39 }
@@ -1,102 +1,103
1 1 # This Python file uses the following encoding: utf-8
2 2 import os
3 3 print(os.getcwd())
4 4 import sys
5 5 from PySide2.QtWidgets import QApplication, QMainWindow, QDockWidget
6 6 from PySide2.QtCore import QSize, Qt
7 7 from PySide2 import QtGui
8 8 import os
9 sys.path.append(os.getcwd())
9 10 from SciQLopBindings import SqpApplication, MainWindow, init_resources, load_plugins, SqpApplication_ctor
10 11 from qtconsole.rich_ipython_widget import RichJupyterWidget
11 12 from qtconsole.inprocess import QtInProcessKernelManager
12 13
13 14
14 15 class IPythonWidget(RichJupyterWidget):
15 16 """Live IPython console widget.
16 17
17 18 .. image:: img/IPythonWidget.png
18 19
19 20 :param custom_banner: Custom welcome message to be printed at the top of
20 21 the console.
21 22 """
22 23
23 24 def __init__(self, parent=None, custom_banner=None, *args, **kwargs):
24 25 if parent is not None:
25 26 kwargs["parent"] = parent
26 27 super(IPythonWidget, self).__init__(*args, **kwargs)
27 28 if custom_banner is not None:
28 29 self.banner = custom_banner
29 30 self.setWindowTitle(self.banner)
30 31 self.kernel_manager = kernel_manager = QtInProcessKernelManager()
31 32 kernel_manager.start_kernel()
32 33 self.kernel_client = kernel_client = self._kernel_manager.client()
33 34 kernel_client.start_channels()
34 35
35 36 def stop():
36 37 kernel_client.stop_channels()
37 38 kernel_manager.shutdown_kernel()
38 39 self.exit_requested.connect(stop)
39 40
40 41 def sizeHint(self):
41 42 """Return a reasonable default size for usage in :class:`PlotWindow`"""
42 43 return QSize(500, 300)
43 44
44 45 def pushVariables(self, variable_dict):
45 46 """ Given a dictionary containing name / value pairs, push those
46 47 variables to the IPython console widget.
47 48
48 49 :param variable_dict: Dictionary of variables to be pushed to the
49 50 console's interactive namespace (```{variable_name: object, …}```)
50 51 """
51 52 self.kernel_manager.kernel.shell.push(variable_dict)
52 53
53 54
54 55 class IPythonDockWidget(QDockWidget):
55 56 """Dock Widget including a :class:`IPythonWidget` inside
56 57 a vertical layout.
57 58
58 59 .. image:: img/IPythonDockWidget.png
59 60
60 61 :param available_vars: Dictionary of variables to be pushed to the
61 62 console's interactive namespace: ``{"variable_name": object, …}``
62 63 :param custom_banner: Custom welcome message to be printed at the top of
63 64 the console
64 65 :param title: Dock widget title
65 66 :param parent: Parent :class:`qt.QMainWindow` containing this
66 67 :class:`qt.QDockWidget`
67 68 """
68 69 def __init__(self, parent=None, available_vars=None, custom_banner=None,
69 70 title="Console"):
70 71 super(IPythonDockWidget, self).__init__(title, parent)
71 72
72 73 self.ipyconsole = IPythonWidget(custom_banner=custom_banner)
73 74
74 75 self.layout().setContentsMargins(0, 0, 0, 0)
75 76 self.setWidget(self.ipyconsole)
76 77
77 78 if available_vars is not None:
78 79 self.ipyconsole.pushVariables(available_vars)
79 80 self.ipyconsole.pushVariables({"blah":self})
80 81
81 82 def showEvent(self, event):
82 83 """Make sure this widget is raised when it is shown
83 84 (when it is first created as a tab in PlotWindow or when it is shown
84 85 again after hiding).
85 86 """
86 87 self.raise_()
87 88
88 89 def print_process_id():
89 90 print ('Process ID is:', os.getpid())
90 91
91 92
92 93 if __name__ == "__main__":
93 94 init_resources()
94 app = SqpApplication_ctor()
95 app = SqpApplication_ctor(sys.argv)
95 96 QtGui.qApp = app
96 97 load_plugins(app)
97 98 main_window = MainWindow()
98 99 term = IPythonDockWidget(available_vars={"app":app, "main_window":main_window}, custom_banner="SciQLop IPython Console ")
99 100 main_window.addDockWidget(Qt.BottomDockWidgetArea, term)
100 101 main_window.show()
101 102 sys.exit(app.exec_())
102 103
@@ -1,178 +1,179
1 1 #ifndef NUMPY_WRAPPERS_H
2 2 #define NUMPY_WRAPPERS_H
3 3 #include <Data/ScalarTimeSerie.h>
4 4 #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
5 5 #if defined(slots) && (defined(__GNUC__) || defined(_MSC_VER) || defined(__clang__))
6 6 #pragma push_macro("slots")
7 7 #undef slots
8 8 extern "C"
9 9 {
10 10 /*
11 11 * Python 2 uses the "register" keyword, which is deprecated in C++ 11
12 12 * and forbidden in C++17.
13 13 */
14 14 #if defined(__clang__)
15 15 #pragma clang diagnostic push
16 16 #pragma clang diagnostic ignored "-Wdeprecated-register"
17 17 #endif
18 18
19 19 #include <Python.h>
20 20 #include <numpy/arrayobject.h>
21 21
22 22 #if defined(__clang__)
23 23 #pragma clang diagnostic pop
24 24 #endif
25 25 }
26 26 #else
27 27 #include <Python.h>
28 28 #include <numpy/arrayobject.h>
29 29 #endif
30 30 #include <assert.h>
31 31
32 #include <map>
33
32 34 inline int init_numpy()
33 35 {
34 36 import_array(); // PyError if not successful
35 37 return 0;
36 38 }
37 39 const static int numpy_initialized = init_numpy();
38 40 template <typename dest_type = PyObject>
39 41 struct PyObjectWrapper
40 42 {
41 43 private:
42 PyObject* _py_obj;
43
44 PyObject* _py_obj = nullptr;
44 45 void inc_refcount()
45 46 {
46 if (_py_obj)
47 Py_IncRef(_py_obj);
47 Py_XINCREF(_py_obj);
48 48 }
49 49 void dec_refcount()
50 50 {
51 if (_py_obj)
52 Py_DecRef(_py_obj);
51 Py_XDECREF(_py_obj);
52 _py_obj = nullptr;
53 53 }
54 54
55 55 public:
56 56 PyObjectWrapper() : _py_obj { nullptr } {}
57 PyObjectWrapper(const PyObjectWrapper& other) : _py_obj { other._py_obj } { inc_refcount(); };
58 PyObjectWrapper(PyObjectWrapper&& other) : _py_obj { other._py_obj }
57 PyObjectWrapper(const PyObjectWrapper& other) : _py_obj { other._py_obj } { inc_refcount(); }
58 PyObjectWrapper(PyObjectWrapper&& other) : _py_obj { other._py_obj } { inc_refcount(); }
59 explicit PyObjectWrapper(PyObject* obj) : _py_obj { obj }
59 60 {
60 other._py_obj = nullptr;
61 inc_refcount();
61 62 }
62 PyObjectWrapper(PyObject* obj) : _py_obj { obj } { inc_refcount(); }
63 63 ~PyObjectWrapper() { dec_refcount(); }
64 64 PyObjectWrapper& operator=(PyObjectWrapper&& other)
65 65 {
66 dec_refcount();
66 67 this->_py_obj = other._py_obj;
67 other._py_obj = nullptr;
68 inc_refcount();
68 69 return *this;
69 70 }
70 71 PyObjectWrapper& operator=(const PyObjectWrapper& other)
71 72 {
72 73 dec_refcount();
73 74 this->_py_obj = other._py_obj;
74 75 inc_refcount();
75 76 return *this;
76 77 }
77 78
78 79 PyObject* py_object() { return _py_obj; }
79 80 inline dest_type* get() { return reinterpret_cast<dest_type*>(_py_obj); }
80 81 inline bool is_null() { return _py_obj == nullptr; }
81 82 };
82 83
83 84 struct NpArray
84 85 {
85 86 private:
86 87 PyObjectWrapper<PyArrayObject> _py_obj;
87 88 NpArray(NpArray& other) = delete;
88 89 NpArray(const NpArray& other) = delete;
89 90 NpArray(const NpArray&& other) = delete;
90 91
91 92 public:
92 93 static bool isNpArray(PyObject* obj)
93 94 {
94 return obj && PyArray_Check(reinterpret_cast<PyArrayObject*>(obj))
95 && PyArray_IS_C_CONTIGUOUS(reinterpret_cast<PyArrayObject*>(obj));
95 auto arr = reinterpret_cast<PyArrayObject*>(obj);
96 auto is_c_aray = obj && PyArray_Check(arr) && PyArray_ISCARRAY(arr);
97 return is_c_aray;
96 98 }
97 99 NpArray() : _py_obj { nullptr } {}
98 NpArray(NpArray&& other) : _py_obj { std::move(other._py_obj) } {}
100 NpArray(NpArray&& other) : _py_obj { other._py_obj } {}
99 101 explicit NpArray(PyObject* obj) : _py_obj { obj }
100 102 {
101 std::cout << "NpArray ctor" << std::endl;
102 103 assert(isNpArray(obj));
103 104 assert(PyArray_ISFLOAT(_py_obj.get()));
104 105 }
105 106
106 107 NpArray& operator=(const NpArray& other)
107 108 {
108 109 this->_py_obj = other._py_obj;
109 110 return *this;
110 111 }
111 112
112 113 NpArray& operator=(NpArray&& other)
113 114 {
114 this->_py_obj = std::move(other._py_obj);
115 this->_py_obj = other._py_obj;
115 116 return *this;
116 117 }
117 118
118 119 std::vector<std::size_t> shape()
119 120 {
120 121 std::vector<std::size_t> shape;
121 122 if (!_py_obj.is_null())
122 123 {
123 124 if (int ndim = PyArray_NDIM(_py_obj.get()); ndim > 0)
124 125 {
125 126 if (ndim < 10)
126 127 {
127 128 shape.resize(ndim);
128 129 std::copy_n(PyArray_SHAPE(_py_obj.get()), ndim, std::begin(shape));
129 130 }
130 131 }
131 132 }
132 133 return shape;
133 134 }
134 135
135 136 std::size_t flat_size()
136 137 {
137 138 auto s = this->shape();
138 139 return std::accumulate(std::cbegin(s), std::cend(s), 0);
139 140 }
140 141
141 142 double data(std::size_t pos)
142 143 {
143 144 if (!_py_obj.is_null())
144 145 {
145 146 return reinterpret_cast<double*>(PyArray_DATA(_py_obj.get()))[pos];
146 147 }
147 148 return nan("NAN");
148 149 }
149 150
150 151 std::vector<double> to_std_vect()
151 152 {
152 153 auto sz = flat_size();
153 154 std::vector<double> v(sz);
154 155 auto d_ptr = reinterpret_cast<double*>(PyArray_DATA(_py_obj.get()));
155 156 std::copy(d_ptr, d_ptr + sz, std::begin(v));
156 157 return v;
157 158 }
158 159
159 160 PyObject* py_object() { return _py_obj.py_object(); }
160 161 };
161 162
162 163 inline int test_np_array(NpArray& arr)
163 164 {
164 165 auto shape = arr.shape();
165 166 std::cout << "len(shape)=" << shape.size() << std::endl;
166 167 std::for_each(std::cbegin(shape), std::cend(shape), [](auto sz) {
167 168 static int i = 0;
168 169 std::cout << "shape[" << i++ << "]=" << sz << std::endl;
169 170 });
170 171 auto flatsize = std::accumulate(std::cbegin(shape), std::cend(shape), 0);
171 172 for (auto i = 0; i < flatsize; i++)
172 173 {
173 174 std::cout << "data[" << i << "]=" << arr.data(i) << std::endl;
174 175 }
175 176 return 1;
176 177 }
177 178
178 179 #endif //#ifndef NUMPY_WRAPPERS_H
@@ -1,37 +1,37
1 1 #ifndef TOOLBAR_H
2 2 #define TOOLBAR_H
3 3
4 4 #include <Data/DateTimeRange.h>
5 5 #include <QAction>
6 6 #include <QActionGroup>
7 7 #include <QObject>
8 8 #include <QToolBar>
9 9 #include <QWidget>
10 10 #include <TimeWidget/TimeWidget.h>
11 11
12 // @TODO remove this, shouldn't need to include SqpApplication to get PlotsInteractionMode
12 // TODO remove this, shouldn't need to include SqpApplication to get PlotsInteractionMode
13 13 #include <SqpApplication.h>
14 14
15 15 class ToolBar : public QToolBar
16 16 {
17 17 Q_OBJECT
18 18 public:
19 19 explicit ToolBar(QWidget* parent = nullptr);
20 20
21 21 QAction* timeRange;
22 22 QAction* pointerMode;
23 23 QAction* zoomMode;
24 24 QAction* organizationMode;
25 25 QAction* zonesMode;
26 26 QAction* cursorsActn;
27 27 QAction* cataloguesActn;
28 28
29 29 TimeWidgetAction* timeWidget;
30 30 signals:
31 31 void setPlotsInteractionMode(SqpApplication::PlotsInteractionMode);
32 32 void setPlotsCursorMode(SqpApplication::PlotsCursorMode);
33 33 void timeUpdated(DateTimeRange time);
34 34 void showCataloguesBrowser();
35 35 };
36 36
37 37 #endif // TOOLBAR_H
@@ -1,281 +1,208
1 1 /*------------------------------------------------------------------------------
2 2 -- This file is a part of the SciQLop Software
3 3 -- Copyright (C) 2017, Plasma Physics Laboratory - CNRS
4 4 --
5 5 -- This program is free software; you can redistribute it and/or modify
6 6 -- it under the terms of the GNU General Public License as published by
7 7 -- the Free Software Foundation; either version 2 of the License, or
8 8 -- (at your option) any later version.
9 9 --
10 10 -- This program is distributed in the hope that it will be useful,
11 11 -- but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 -- GNU General Public License for more details.
14 14 --
15 15 -- You should have received a copy of the GNU General Public License
16 16 -- along with this program; if not, write to the Free Software
17 17 -- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 18 -------------------------------------------------------------------------------*/
19 19 /*-- Author : Alexis Jeandet
20 20 -- Mail : alexis.jeandet@member.fsf.org
21 21 ----------------------------------------------------------------------------*/
22 22 #include "MainWindow.h"
23 23 #include "ui_MainWindow.h"
24 24
25 25 #include <Catalogue/CatalogueController.h>
26 26 #include <Catalogue2/browser.h>
27 27 #include <DataSource/DataSourceController.h>
28 #include <DataSource/DataSourceItem.h>
28 29 #include <DataSource/DataSourceWidget.h>
29 30 #include <Settings/SqpSettingsDialog.h>
30 31 #include <Settings/SqpSettingsGeneralWidget.h>
31 32 #include <SidePane/SqpSidePane.h>
32 33 #include <SqpApplication.h>
33 34 #include <Time/TimeController.h>
34 35 #include <TimeWidget/TimeWidget.h>
35 36
36 37 #include "toolbar.h"
37 38
38 39 #include <QAction>
39 40 #include <QCloseEvent>
40 41 #include <QDate>
41 42 #include <QDir>
42 43 #include <QFileDialog>
43 44 #include <QMessageBox>
44 45 #include <QToolBar>
45 46 #include <QToolButton>
46 47 #include <memory.h>
47 48
48 49
49 50 Q_LOGGING_CATEGORY(LOG_MainWindow, "MainWindow")
50 51
51 namespace
52 {
53 const auto LEFTMAININSPECTORWIDGETSPLITTERINDEX = 0;
54 const auto LEFTINSPECTORSIDEPANESPLITTERINDEX = 1;
55 const auto VIEWPLITTERINDEX = 2;
56 const auto RIGHTINSPECTORSIDEPANESPLITTERINDEX = 3;
57 const auto RIGHTMAININSPECTORWIDGETSPLITTERINDEX = 4;
58 }
59
60 52 class MainWindow::MainWindowPrivate
61 53 {
62 54 public:
63 55 explicit MainWindowPrivate(MainWindow* mainWindow)
64 : m_LastOpenLeftInspectorSize {}
65 , m_LastOpenRightInspectorSize {}
66 , m_GeneralSettingsWidget { new SqpSettingsGeneralWidget { mainWindow } }
56 : m_GeneralSettingsWidget { new SqpSettingsGeneralWidget { mainWindow } }
67 57 , m_SettingsDialog { new SqpSettingsDialog { mainWindow } }
68 58 , m_CatalogExplorer { new CataloguesBrowser { mainWindow } }
69 59 {
70 60 }
71 61
72 QSize m_LastOpenLeftInspectorSize;
73 QSize m_LastOpenRightInspectorSize;
74 62 /// General settings widget. MainWindow has the ownership
75 63 SqpSettingsGeneralWidget* m_GeneralSettingsWidget;
76 64 /// Settings dialog. MainWindow has the ownership
77 65 SqpSettingsDialog* m_SettingsDialog;
78 66 /// Catalogue dialog. MainWindow has the ownership
79 67 CataloguesBrowser* m_CatalogExplorer;
80 68
81 69 bool checkDataToSave(QWidget* parentWidget);
82 70 };
83 71
84 72 MainWindow::MainWindow(QWidget* parent)
85 73 : QMainWindow { parent }
86 74 , m_Ui { new Ui::MainWindow }
87 75 , impl { spimpl::make_unique_impl<MainWindowPrivate>(this) }
88 76 {
89 77 m_Ui->setupUi(this);
90 78 setWindowTitle(QString("SciQLop v%1").arg(SCIQLOP_VERSION));
91 79
92 m_Ui->splitter->setCollapsible(LEFTINSPECTORSIDEPANESPLITTERINDEX, false);
93 m_Ui->splitter->setCollapsible(RIGHTINSPECTORSIDEPANESPLITTERINDEX, false);
94
95 // impl->m_CatalogExplorer->setVisualizationWidget(m_Ui->view);
96
97
98 auto spacerLeftTop = new QWidget {};
99 spacerLeftTop->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
100
101 auto spacerLeftBottom = new QWidget {};
102 spacerLeftBottom->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
103
104
105 auto spacerRightTop = new QWidget {};
106 spacerRightTop->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
107
108 auto spacerRightBottom = new QWidget {};
109 spacerRightBottom->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
110
111
112 auto openInspector = [this](bool checked, bool right, auto action) {
113 action->setIcon(
114 QIcon { (checked ^ right) ? ":/icones/next.png" : ":/icones/previous.png" });
115
116 auto& lastInspectorSize
117 = right ? impl->m_LastOpenRightInspectorSize : impl->m_LastOpenLeftInspectorSize;
118
119 auto nextInspectorSize = right ? m_Ui->rightMainInspectorWidget->size()
120 : m_Ui->leftMainInspectorWidget->size();
121
122 // Update of the last opened geometry
123 if (checked)
124 {
125 lastInspectorSize = nextInspectorSize;
126 }
127
128 auto startSize = lastInspectorSize;
129 auto endSize = startSize;
130 endSize.setWidth(0);
131
132 auto splitterInspectorIndex
133 = right ? RIGHTMAININSPECTORWIDGETSPLITTERINDEX : LEFTMAININSPECTORWIDGETSPLITTERINDEX;
134
135 auto currentSizes = m_Ui->splitter->sizes();
136 if (checked)
137 {
138 // adjust sizes individually here, e.g.
139 currentSizes[splitterInspectorIndex] -= lastInspectorSize.width();
140 currentSizes[VIEWPLITTERINDEX] += lastInspectorSize.width();
141 m_Ui->splitter->setSizes(currentSizes);
142 }
143 else
144 {
145 // adjust sizes individually here, e.g.
146 currentSizes[splitterInspectorIndex] += lastInspectorSize.width();
147 currentSizes[VIEWPLITTERINDEX] -= lastInspectorSize.width();
148 m_Ui->splitter->setSizes(currentSizes);
149 }
150 };
151
152
153 80 // //////////////// //
154 81 // Menu and Toolbar //
155 82 // //////////////// //
156 83 this->menuBar()->addAction(tr("File"));
157 84 auto toolsMenu = this->menuBar()->addMenu(tr("Tools"));
158 85 toolsMenu->addAction(tr("Settings..."), [this]() {
159 86 // Loads settings
160 87 impl->m_SettingsDialog->loadSettings();
161 88
162 89 // Open settings dialog and save settings if the dialog is accepted
163 90 if (impl->m_SettingsDialog->exec() == QDialog::Accepted)
164 91 {
165 92 impl->m_SettingsDialog->saveSettings();
166 93 }
167 94 });
168 95 auto mainToolBar = new ToolBar(this);
169 96 this->addToolBar(mainToolBar);
170 97 connect(mainToolBar, &ToolBar::setPlotsInteractionMode, sqpApp,
171 98 &SqpApplication::setPlotsInteractionMode);
172 99 connect(mainToolBar, &ToolBar::setPlotsCursorMode, sqpApp, &SqpApplication::setPlotsCursorMode);
173 100 connect(mainToolBar, &ToolBar::showCataloguesBrowser,
174 101 [this]() { impl->m_CatalogExplorer->show(); });
175 102
176 103 // //////// //
177 104 // Settings //
178 105 // //////// //
179 106
180 107 // Registers "general settings" widget to the settings dialog
181 108 impl->m_SettingsDialog->registerWidget(
182 109 QStringLiteral("General"), impl->m_GeneralSettingsWidget);
183 110
184 111 // /////////// //
185 112 // Connections //
186 113 // /////////// //
187 114
188 115 // Widgets / controllers connections
189 116
190 117 // DataSource
191 connect(&sqpApp->dataSourceController(), SIGNAL(dataSourceItemSet(DataSourceItem*)),
192 m_Ui->dataSourceWidget, SLOT(addDataSource(DataSourceItem*)));
118 connect(&sqpApp->dataSourceController(), &DataSourceController::dataSourceItemSet,
119 m_Ui->dataSourceWidget, &DataSourceWidget::addDataSource);
193 120
194 121 // Time
195 122 // connect(timeWidget, SIGNAL(timeUpdated(DateTimeRange)), &sqpApp->timeController(),
196 123 // SLOT(onTimeToUpdate(DateTimeRange)));
197 124 connect(mainToolBar, &ToolBar::timeUpdated, &sqpApp->timeController(),
198 125 &TimeController::setDateTimeRange);
199 126
200 127 // Widgets / widgets connections
201 128
202 129 // For the following connections, we use DirectConnection to allow each widget that can
203 130 // potentially attach a menu to the variable's menu to do so before this menu is displayed.
204 131 // The order of connections is also important, since it determines the order in which each
205 132 // widget will attach its menu
206 133 connect(m_Ui->variableInspectorWidget,
207 134 SIGNAL(tableMenuAboutToBeDisplayed(QMenu*, const QVector<std::shared_ptr<Variable>>&)),
208 135 m_Ui->view, SLOT(attachVariableMenu(QMenu*, const QVector<std::shared_ptr<Variable>>&)),
209 136 Qt::DirectConnection);
210 137 }
211 138
212 139 MainWindow::~MainWindow() {}
213 140
214 141 void MainWindow::changeEvent(QEvent* e)
215 142 {
216 143 QMainWindow::changeEvent(e);
217 144 switch (e->type())
218 145 {
219 146 case QEvent::LanguageChange:
220 147 m_Ui->retranslateUi(this);
221 148 break;
222 149 default:
223 150 break;
224 151 }
225 152 }
226 153
227 154 void MainWindow::closeEvent(QCloseEvent* event)
228 155 {
229 156 if (!impl->checkDataToSave(this))
230 157 {
231 158 event->ignore();
232 159 }
233 160 else
234 161 {
235 162 event->accept();
236 163 }
237 164 }
238 165
239 166 void MainWindow::keyPressEvent(QKeyEvent* event)
240 167 {
241 168 switch (event->key())
242 169 {
243 170 case Qt::Key_F11:
244 171 if (this->isFullScreen())
245 172 {
246 173 this->showNormal();
247 174 }
248 175 else
249 176 {
250 177 this->showFullScreen();
251 178 }
252 179 break;
253 180 default:
254 181 break;
255 182 }
256 183 }
257 184
258 185 bool MainWindow::MainWindowPrivate::checkDataToSave(QWidget* parentWidget)
259 186 {
260 187 // auto hasChanges = sqpApp->catalogueController().hasChanges();
261 188 // if (hasChanges)
262 189 // {
263 190 // // There are some unsaved changes
264 191 // switch (QMessageBox::question(parentWidget, tr("Save changes"),
265 192 // tr("The catalogue controller has unsaved changes.\nDo you want to save them ?"),
266 193 // QMessageBox::SaveAll | QMessageBox::Discard | QMessageBox::Cancel,
267 194 // QMessageBox::SaveAll))
268 195 // {
269 196 // case QMessageBox::SaveAll:
270 197 // sqpApp->catalogueController().saveAll();
271 198 // break;
272 199 // case QMessageBox::Discard:
273 200 // break;
274 201 // case QMessageBox::Cancel:
275 202 // default:
276 203 // return false;
277 204 // }
278 205 // }
279 206
280 207 return true;
281 208 }
@@ -1,177 +1,110
1 1 <?xml version="1.0" encoding="UTF-8"?>
2 2 <ui version="4.0">
3 3 <class>MainWindow</class>
4 4 <widget class="QMainWindow" name="MainWindow">
5 5 <property name="geometry">
6 6 <rect>
7 7 <x>0</x>
8 8 <y>0</y>
9 9 <width>800</width>
10 10 <height>600</height>
11 11 </rect>
12 12 </property>
13 13 <property name="windowTitle">
14 14 <string>SciQlop</string>
15 15 </property>
16 16 <property name="dockNestingEnabled">
17 17 <bool>true</bool>
18 18 </property>
19 <widget class="QWidget" name="centralWidget">
19 <widget class="VisualizationWidget" name="view">
20 20 <property name="enabled">
21 21 <bool>true</bool>
22 22 </property>
23 23 <property name="sizePolicy">
24 24 <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
25 25 <horstretch>0</horstretch>
26 26 <verstretch>0</verstretch>
27 27 </sizepolicy>
28 28 </property>
29 29 <property name="maximumSize">
30 30 <size>
31 31 <width>16777215</width>
32 32 <height>16777215</height>
33 33 </size>
34 34 </property>
35 <layout class="QHBoxLayout" name="horizontalLayout">
36 <property name="spacing">
37 <number>0</number>
38 </property>
39 <property name="leftMargin">
40 <number>0</number>
41 </property>
42 <property name="topMargin">
43 <number>0</number>
44 </property>
45 <property name="rightMargin">
46 <number>0</number>
47 </property>
48 <property name="bottomMargin">
49 <number>0</number>
50 </property>
51 <item>
52 <widget class="QSplitter" name="splitter">
53 <property name="orientation">
54 <enum>Qt::Horizontal</enum>
55 </property>
56 <widget class="QWidget" name="leftMainInspectorWidget" native="true">
57 <layout class="QVBoxLayout" name="verticalLayout">
58 <property name="spacing">
59 <number>0</number>
60 </property>
61 <property name="leftMargin">
62 <number>0</number>
63 </property>
64 <property name="topMargin">
65 <number>0</number>
66 </property>
67 <property name="rightMargin">
68 <number>0</number>
69 </property>
70 <property name="bottomMargin">
71 <number>0</number>
72 </property>
73 <item>
74 <widget class="DataSourceWidget" name="dataSourceWidget" native="true"/>
75 </item>
76 <item>
77 <widget class="QWidget" name="dateTimeWidget" native="true"/>
78 </item>
79 <item>
80 <widget class="VariableInspectorWidget" name="variableInspectorWidget" native="true"/>
81 </item>
82 </layout>
83 </widget>
84 <widget class="VisualizationWidget" name="view" native="true">
85 <property name="sizePolicy">
86 <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
87 <horstretch>0</horstretch>
88 <verstretch>0</verstretch>
89 </sizepolicy>
90 </property>
91 </widget>
92 <widget class="QWidget" name="rightMainInspectorWidget" native="true">
93 <layout class="QVBoxLayout" name="verticalLayout_3">
94 <property name="spacing">
95 <number>0</number>
96 </property>
97 <property name="leftMargin">
98 <number>0</number>
99 </property>
100 <property name="topMargin">
101 <number>0</number>
102 </property>
103 <property name="rightMargin">
104 <number>0</number>
105 </property>
106 <property name="bottomMargin">
107 <number>0</number>
108 </property>
109 <item>
110 <widget class="QWidget" name="commonPropertyInspectorWidget" native="true"/>
111 </item>
112 <item>
113 <widget class="QTreeWidget" name="catalogWidget">
114 <column>
115 <property name="text">
116 <string notr="true">Name</string>
117 </property>
118 </column>
119 </widget>
120 </item>
121 </layout>
122 </widget>
123 </widget>
124 </item>
125 </layout>
126 35 </widget>
127 36 <widget class="QMenuBar" name="menuBar">
128 37 <property name="geometry">
129 38 <rect>
130 39 <x>0</x>
131 40 <y>0</y>
132 41 <width>800</width>
133 <height>27</height>
42 <height>24</height>
134 43 </rect>
135 44 </property>
136 45 <property name="sizePolicy">
137 46 <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
138 47 <horstretch>0</horstretch>
139 48 <verstretch>0</verstretch>
140 49 </sizepolicy>
141 50 </property>
142 51 <property name="minimumSize">
143 52 <size>
144 53 <width>0</width>
145 54 <height>0</height>
146 55 </size>
147 56 </property>
148 57 <property name="nativeMenuBar">
149 58 <bool>true</bool>
150 59 </property>
151 60 </widget>
152 61 <widget class="QStatusBar" name="statusBar"/>
62 <widget class="QDockWidget" name="dockWidget">
63 <property name="features">
64 <set>QDockWidget::DockWidgetMovable</set>
65 </property>
66 <property name="windowTitle">
67 <string>Products</string>
68 </property>
69 <attribute name="dockWidgetArea">
70 <number>1</number>
71 </attribute>
72 <widget class="DataSourceWidget" name="dataSourceWidget"/>
73 </widget>
74 <widget class="QDockWidget" name="dockWidget_2">
75 <property name="features">
76 <set>QDockWidget::DockWidgetMovable</set>
77 </property>
78 <property name="windowTitle">
79 <string>Variables</string>
80 </property>
81 <attribute name="dockWidgetArea">
82 <number>1</number>
83 </attribute>
84 <widget class="VariableInspectorWidget" name="variableInspectorWidget"/>
85 </widget>
153 86 </widget>
154 87 <layoutdefault spacing="6" margin="11"/>
155 88 <customwidgets>
156 89 <customwidget>
157 90 <class>VisualizationWidget</class>
158 91 <extends>QWidget</extends>
159 92 <header location="global">Visualization/VisualizationWidget.h</header>
160 93 <container>1</container>
161 94 </customwidget>
162 95 <customwidget>
163 96 <class>DataSourceWidget</class>
164 97 <extends>QWidget</extends>
165 98 <header location="global">DataSource/DataSourceWidget.h</header>
166 99 <container>1</container>
167 100 </customwidget>
168 101 <customwidget>
169 102 <class>VariableInspectorWidget</class>
170 103 <extends>QWidget</extends>
171 104 <header location="global">Variable/VariableInspectorWidget.h</header>
172 105 <container>1</container>
173 106 </customwidget>
174 107 </customwidgets>
175 108 <resources/>
176 109 <connections/>
177 110 </ui>
@@ -1,1 +1,1
1 Subproject commit c4e273750e74336d1d125c02989c922f2032e489
1 Subproject commit 9ff5f48e3d71ce2d3a8b2156f778f3f9be3ea389
@@ -1,225 +1,225
1 1 #include <DataSource/DataSourceItem.h>
2 2 #include <DataSource/DataSourceItemAction.h>
3 3 #include <DataSource/DataSourceTreeWidgetItem.h>
4 4
5 5 #include <QAction>
6 6
7 7 Q_LOGGING_CATEGORY(LOG_DataSourceTreeWidgetItem, "DataSourceTreeWidgetItem")
8 8
9 9 namespace {
10 10
11 11 // Column indexes
12 12 const auto NAME_COLUMN = 0;
13 13
14 14 /**
15 15 * Generates the full name of an item.
16 16 *
17 17 * The full name of an item is its name possibly suffixed by the name of its plugin, in case there
18 18 * are items of the same name in its relatives
19 19 * @param item the item for which to generate the complete name
20 20 * @return the complete name of the item
21 21 */
22 22 QString completeName(const DataSourceItem &item)
23 23 {
24 24 auto name = item.name();
25 25
26 26 if (item.type() == DataSourceItemType::NODE) {
27 27 return name;
28 28 }
29 29
30 30 auto parentItem = item.parentItem();
31 31 if (!parentItem) {
32 32 return name;
33 33 }
34 34
35 35 // Finds in item's relatives items that have the same name
36 36 bool foundSameName = false;
37 37 for (auto i = 0, count = parentItem->childCount(); i < count && !foundSameName; ++i) {
38 38 auto child = parentItem->child(i);
39 39 foundSameName = child != &item
40 40 && QString::compare(child->name(), item.name(), Qt::CaseInsensitive) == 0;
41 41 }
42 42
43 43 // If the name of the item is not unique, it is completed by the plugin suffix
44 44 return foundSameName
45 45 ? QString{"%1 (%2)"}.arg(name, item.data(DataSourceItem::PLUGIN_DATA_KEY).toString())
46 46 : name;
47 47 }
48 48
49 49 QIcon itemIcon(const DataSourceItem *dataSource)
50 50 {
51 51 if (dataSource) {
52 52 auto dataSourceType = dataSource->type();
53 53 switch (dataSourceType) {
54 54 case DataSourceItemType::NODE: {
55 55 return dataSource->isRoot() ? QIcon{":/icones/dataSourceRoot.png"}
56 56 : QIcon{":/icones/dataSourceNode.png"};
57 57 }
58 58 case DataSourceItemType::PRODUCT:
59 59 return QIcon{":/icones/dataSourceProduct.png"};
60 60 case DataSourceItemType::COMPONENT:
61 61 return QIcon{":/icones/dataSourceComponent.png"};
62 62 default:
63 63 // No action
64 64 break;
65 65 }
66 66
67 67 qCWarning(LOG_DataSourceTreeWidgetItem())
68 68 << QObject::tr("Can't set data source icon : unknown data source type");
69 69 }
70 70 else {
71 71 qCCritical(LOG_DataSourceTreeWidgetItem())
72 72 << QObject::tr("Can't set data source icon : the data source is null");
73 73 }
74 74
75 75 // Default cases
76 76 return QIcon{};
77 77 }
78 78
79 79 /// @return the tooltip text for a variant. The text depends on whether the data is a simple variant
80 80 /// or a list of variants
81 81 QString tooltipValue(const QVariant &variant) noexcept
82 82 {
83 83 // If the variant is a list of variants, the text of the tooltip is of the form: {val1, val2,
84 84 // ...}
85 85 if (variant.canConvert<QVariantList>()) {
86 86 auto valueString = QStringLiteral("{");
87 87
88 88 auto variantList = variant.value<QVariantList>();
89 89 for (auto it = variantList.cbegin(), end = variantList.cend(); it != end; ++it) {
90 90 valueString.append(it->toString());
91 91
92 92 if (std::distance(it, end) != 1) {
93 93 valueString.append(", ");
94 94 }
95 95 }
96 96
97 97 valueString.append(QStringLiteral("}"));
98 98
99 99 return valueString;
100 100 }
101 101 else {
102 102 return variant.toString();
103 103 }
104 104 }
105 105
106 106 QString itemTooltip(const DataSourceItem *dataSource) noexcept
107 107 {
108 108 // The tooltip displays all item's data
109 109 if (dataSource) {
110 110 auto result = QString{};
111 111
112 112 const auto &data = dataSource->data();
113 113 for (auto it = data.cbegin(), end = data.cend(); it != end; ++it) {
114 114 result.append(QString{"<b>%1:</b> %2<br/>"}.arg(it.key(), tooltipValue(it.value())));
115 115 }
116 116
117 117 return result;
118 118 }
119 119 else {
120 120 qCCritical(LOG_DataSourceTreeWidgetItem())
121 121 << QObject::tr("Can't set data source tooltip : the data source is null");
122 122
123 123 return QString{};
124 124 }
125 125 }
126 126
127 127 } // namespace
128 128
129 129 struct DataSourceTreeWidgetItem::DataSourceTreeWidgetItemPrivate {
130 130 explicit DataSourceTreeWidgetItemPrivate(const DataSourceItem *data)
131 131 : m_Data{data}, m_Name{completeName(*m_Data)}
132 132 {
133 133 }
134 134
135 135 /// Model used to retrieve data source information
136 136 const DataSourceItem *m_Data;
137 137 /// Name displayed
138 138 QString m_Name;
139 139 /// Actions associated to the item. The parent of the item (QTreeWidget) takes the ownership of
140 140 /// the actions
141 QList<QAction *> m_Actions;
141 QList<QAction *> m_Actions; //TODO check if no memory leak here
142 142 };
143 143
144 144 DataSourceTreeWidgetItem::DataSourceTreeWidgetItem(const DataSourceItem *data, int type)
145 145 : DataSourceTreeWidgetItem{nullptr, data, type}
146 146 {
147 147 }
148 148
149 149 DataSourceTreeWidgetItem::DataSourceTreeWidgetItem(QTreeWidget *parent, const DataSourceItem *data,
150 150 int type)
151 151 : QTreeWidgetItem{parent, type},
152 152 impl{spimpl::make_unique_impl<DataSourceTreeWidgetItemPrivate>(data)}
153 153 {
154 154 // Sets the icon and the tooltip depending on the data source
155 155 setIcon(0, itemIcon(impl->m_Data));
156 156 setToolTip(0, itemTooltip(impl->m_Data));
157 157
158 158 // Generates tree actions based on the item actions
159 159 auto createTreeAction = [this, &parent](const auto &itemAction) {
160 160 auto treeAction = new QAction{itemAction->name(), parent};
161 161
162 162 // Executes item action when tree action is triggered
163 163 QObject::connect(treeAction, &QAction::triggered, itemAction,
164 164 &DataSourceItemAction::execute);
165 165
166 166 return treeAction;
167 167 };
168 168
169 169 auto itemActions = impl->m_Data->actions();
170 170 std::transform(std::cbegin(itemActions), std::cend(itemActions),
171 171 std::back_inserter(impl->m_Actions), createTreeAction);
172 172
173 173 // Sets the flags of the items
174 174 auto flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
175 175 if (data->type() == DataSourceItemType::COMPONENT
176 176 || data->type() == DataSourceItemType::PRODUCT) {
177 177 flags |= Qt::ItemIsDragEnabled;
178 178 }
179 179
180 180 setFlags(flags);
181 181 }
182 182
183 183 const DataSourceItem *DataSourceTreeWidgetItem::data() const
184 184 {
185 185 return impl->m_Data;
186 186 }
187 187
188 188 QVariant DataSourceTreeWidgetItem::data(int column, int role) const
189 189 {
190 190 if (role == Qt::DisplayRole) {
191 191 if (impl->m_Data) {
192 192 switch (column) {
193 193 case NAME_COLUMN:
194 194 return impl->m_Name;
195 195 default:
196 196 // No action
197 197 break;
198 198 }
199 199
200 200 qCWarning(LOG_DataSourceTreeWidgetItem())
201 201 << QObject::tr("Can't get data (unknown column %1)").arg(column);
202 202 }
203 203 else {
204 204 qCCritical(LOG_DataSourceTreeWidgetItem()) << QObject::tr("Can't get data (null item)");
205 205 }
206 206
207 207 return QVariant{};
208 208 }
209 209 else {
210 210 return QTreeWidgetItem::data(column, role);
211 211 }
212 212 }
213 213
214 214 void DataSourceTreeWidgetItem::setData(int column, int role, const QVariant &value)
215 215 {
216 216 // Data can't be changed by edition
217 217 if (role != Qt::EditRole) {
218 218 QTreeWidgetItem::setData(column, role, value);
219 219 }
220 220 }
221 221
222 222 QList<QAction *> DataSourceTreeWidgetItem::actions() const noexcept
223 223 {
224 224 return impl->m_Actions;
225 225 }
@@ -1,123 +1,124
1 1 #include <DataSource/DataSourceWidget.h>
2 2
3 3 #include <ui_DataSourceWidget.h>
4 4
5 5 #include <DataSource/DataSourceItem.h>
6 6 #include <DataSource/DataSourceTreeWidgetHelper.h>
7 7 #include <DataSource/DataSourceTreeWidgetItem.h>
8 8
9 9 #include <QMenu>
10 10
11 namespace {
11 namespace
12 {
12 13
13 14 /// Number of columns displayed in the tree
14 15 const auto TREE_NB_COLUMNS = 1;
15 16
16 17 /// Header labels for the tree
17 const auto TREE_HEADER_LABELS = QStringList{QObject::tr("Name")};
18 const auto TREE_HEADER_LABELS = QStringList { QObject::tr("Name") };
18 19
19 20 /**
20 21 * Creates the item associated to a data source
21 22 * @param dataSource the data source for which to create the item
22 23 * @return the new item
23 24 */
24 DataSourceTreeWidgetItem *createTreeWidgetItem(DataSourceItem *dataSource)
25 DataSourceTreeWidgetItem* createTreeWidgetItem(DataSourceItem* dataSource)
25 26 {
26 27 // Creates item for the data source
27 auto item = new DataSourceTreeWidgetItem{dataSource};
28
28 auto item = new DataSourceTreeWidgetItem { dataSource };
29 29 // Generates items for the children of the data source
30 for (auto i = 0; i < dataSource->childCount(); ++i) {
31 item->addChild(createTreeWidgetItem(dataSource->child(i)));
32 }
33
30 std::for_each(dataSource->cbegin(), dataSource->cend(),
31 [&item](const std::unique_ptr<DataSourceItem>& child) {
32 item->addChild(createTreeWidgetItem(child.get()));
33 });
34 34 return item;
35 35 }
36 36
37 37 } // namespace
38 38
39 DataSourceWidget::DataSourceWidget(QWidget *parent)
40 : QWidget{parent},
41 ui{new Ui::DataSourceWidget},
42 m_Root{
43 std::make_unique<DataSourceItem>(DataSourceItemType::NODE, QStringLiteral("Sources"))}
39 DataSourceWidget::DataSourceWidget(QWidget* parent)
40 : QWidget { parent }
41 , ui { new Ui::DataSourceWidget }
42 , m_Root { std::make_unique<DataSourceItem>(
43 DataSourceItemType::NODE, QStringLiteral("Sources")) }
44 44 {
45 45 ui->setupUi(this);
46 46
47 47 // Set tree properties
48 48 ui->treeWidget->setColumnCount(TREE_NB_COLUMNS);
49 49 ui->treeWidget->setHeaderLabels(TREE_HEADER_LABELS);
50 50 ui->treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
51 51
52 52 // Connection to show a menu when right clicking on the tree
53 53 connect(ui->treeWidget, &QTreeWidget::customContextMenuRequested, this,
54 &DataSourceWidget::onTreeMenuRequested);
54 &DataSourceWidget::onTreeMenuRequested);
55 55
56 56 // Connection to filter tree
57 57 connect(ui->filterLineEdit, &QLineEdit::textChanged, this, &DataSourceWidget::filterChanged);
58 58
59 59 // First init
60 60 updateTreeWidget();
61 61 }
62 62
63 63 DataSourceWidget::~DataSourceWidget() noexcept
64 64 {
65 65 delete ui;
66 66 }
67 67
68 void DataSourceWidget::addDataSource(DataSourceItem *dataSource) noexcept
68 void DataSourceWidget::addDataSource(DataSourceItem* dataSource) noexcept
69 69 {
70 70 // Merges the data source (without taking its root)
71 if (dataSource) {
72 for (auto i = 0, count = dataSource->childCount(); i < count; ++i) {
73 m_Root->merge(*dataSource->child(i));
74 }
75
71 if (dataSource)
72 {
73 std::for_each(std::cbegin(*dataSource), std::cend(*dataSource),
74 [this](const auto& child) { this->m_Root->merge(*child.get()); });
76 75 updateTreeWidget();
77 76 }
78 77 }
79 78
80 79 void DataSourceWidget::updateTreeWidget() noexcept
81 80 {
82 81 ui->treeWidget->clear();
83 82
84 83 auto rootItem = createTreeWidgetItem(m_Root.get());
85 84 ui->treeWidget->addTopLevelItem(rootItem);
86 85 rootItem->setExpanded(true);
87 86
88 87 // Sorts tree
89 88 ui->treeWidget->setSortingEnabled(true);
90 89 ui->treeWidget->sortByColumn(0, Qt::AscendingOrder);
91 90 }
92 91
93 void DataSourceWidget::filterChanged(const QString &text) noexcept
92 void DataSourceWidget::filterChanged(const QString& text) noexcept
94 93 {
95 auto validateItem = [&text](const DataSourceTreeWidgetItem &item) {
96 auto regExp = QRegExp{text, Qt::CaseInsensitive, QRegExp::Wildcard};
94 auto validateItem = [&text](const DataSourceTreeWidgetItem& item) {
95 auto regExp = QRegExp { text, Qt::CaseInsensitive, QRegExp::Wildcard };
97 96
98 97 // An item is valid if any of its metadata validates the text filter
99 98 auto itemMetadata = item.data()->data();
100 99 auto itemMetadataEnd = itemMetadata.cend();
101 100 auto acceptFilter
102 = [&regExp](const auto &variant) { return variant.toString().contains(regExp); };
101 = [&regExp](const auto& variant) { return variant.toString().contains(regExp); };
103 102
104 103 return std::find_if(itemMetadata.cbegin(), itemMetadataEnd, acceptFilter)
105 != itemMetadataEnd;
104 != itemMetadataEnd;
106 105 };
107 106
108 107 // Applies filter on tree widget
109 108 DataSourceTreeWidgetHelper::filter(*ui->treeWidget, validateItem);
110 109 }
111 110
112 void DataSourceWidget::onTreeMenuRequested(const QPoint &pos) noexcept
111 void DataSourceWidget::onTreeMenuRequested(const QPoint& pos) noexcept
113 112 {
114 113 // Retrieves the selected item in the tree, and build the menu from its actions
115 if (auto selectedItem = dynamic_cast<DataSourceTreeWidgetItem *>(ui->treeWidget->itemAt(pos))) {
116 QMenu treeMenu{};
114 if (auto selectedItem = dynamic_cast<DataSourceTreeWidgetItem*>(ui->treeWidget->itemAt(pos)))
115 {
116 QMenu treeMenu {};
117 117 treeMenu.addActions(selectedItem->actions());
118 118
119 if (!treeMenu.isEmpty()) {
119 if (!treeMenu.isEmpty())
120 {
120 121 treeMenu.exec(QCursor::pos());
121 122 }
122 123 }
123 124 }
@@ -1,1577 +1,1577
1 1 #include "Visualization/VisualizationGraphWidget.h"
2 2 #include "Visualization/IVisualizationWidgetVisitor.h"
3 3 #include "Visualization/VisualizationCursorItem.h"
4 4 #include "Visualization/VisualizationDefs.h"
5 5 #include "Visualization/VisualizationGraphHelper.h"
6 6 #include "Visualization/VisualizationGraphRenderingDelegate.h"
7 7 #include "Visualization/VisualizationMultiZoneSelectionDialog.h"
8 8 #include "Visualization/VisualizationSelectionZoneItem.h"
9 9 #include "Visualization/VisualizationSelectionZoneManager.h"
10 10 #include "Visualization/VisualizationWidget.h"
11 11 #include "Visualization/VisualizationZoneWidget.h"
12 12 #include "ui_VisualizationGraphWidget.h"
13 13
14 14 #include <Actions/ActionsGuiController.h>
15 15 #include <Actions/FilteringAction.h>
16 16 #include <Common/MimeTypesDef.h>
17 17 #include <Common/containers.h>
18 18 #include <Data/DateTimeRangeHelper.h>
19 19 #include <DragAndDrop/DragDropGuiController.h>
20 20 #include <Settings/SqpSettingsDefs.h>
21 21 #include <SqpApplication.h>
22 22 #include <Time/TimeController.h>
23 23 #include <Variable/Variable2.h>
24 24 #include <Variable/VariableController2.h>
25 25
26 26 #include <unordered_map>
27 27
28 28 Q_LOGGING_CATEGORY(LOG_VisualizationGraphWidget, "VisualizationGraphWidget")
29 29
30 30 namespace
31 31 {
32 32
33 33 /// Key pressed to enable drag&drop in all modes
34 34 const auto DRAG_DROP_MODIFIER = Qt::AltModifier;
35 35
36 36 /// Key pressed to enable zoom on horizontal axis
37 37 const auto HORIZONTAL_ZOOM_MODIFIER = Qt::ControlModifier;
38 38
39 39 /// Key pressed to enable zoom on vertical axis
40 40 const auto VERTICAL_ZOOM_MODIFIER = Qt::ShiftModifier;
41 41
42 42 /// Speed of a step of a wheel event for a pan, in percentage of the axis range
43 43 const auto PAN_SPEED = 5;
44 44
45 45 /// Key pressed to enable a calibration pan
46 46 const auto VERTICAL_PAN_MODIFIER = Qt::AltModifier;
47 47
48 48 /// Key pressed to enable multi selection of selection zones
49 49 const auto MULTI_ZONE_SELECTION_MODIFIER = Qt::ControlModifier;
50 50
51 51 /// Minimum size for the zoom box, in percentage of the axis range
52 52 const auto ZOOM_BOX_MIN_SIZE = 0.8;
53 53
54 54 /// Format of the dates appearing in the label of a cursor
55 55 const auto CURSOR_LABELS_DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd\nhh:mm:ss:zzz");
56 56
57 57 } // namespace
58 58
59 59 struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate
60 60 {
61 61
62 62 explicit VisualizationGraphWidgetPrivate(const QString& name)
63 63 : m_Name { name }
64 64 , m_Flags { GraphFlag::EnableAll }
65 65 , m_IsCalibration { false }
66 66 , m_RenderingDelegate { nullptr }
67 67 {
68 68 m_plot = new QCustomPlot();
69 69 // Necessary for all platform since Qt::AA_EnableHighDpiScaling is enable.
70 70 m_plot->setPlottingHint(QCP::phFastPolylines, true);
71 71 }
72 72
73 73 void updateData(
74 74 PlottablesMap& plottables, std::shared_ptr<Variable2> variable, const DateTimeRange& range)
75 75 {
76 76 VisualizationGraphHelper::updateData(plottables, variable, range);
77 77
78 78 // Prevents that data has changed to update rendering
79 79 m_RenderingDelegate->onPlotUpdated();
80 80 }
81 81
82 82 QString m_Name;
83 83 // 1 variable -> n qcpplot
84 84 std::map<std::shared_ptr<Variable2>, PlottablesMap> m_VariableToPlotMultiMap;
85 85 GraphFlags m_Flags;
86 86 bool m_IsCalibration;
87 87 QCustomPlot* m_plot;
88 88 QPoint m_lastMousePos;
89 89 QCPRange m_lastXRange;
90 90 QCPRange m_lastYRange;
91 91 /// Delegate used to attach rendering features to the plot
92 92 std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate;
93 93
94 94 QCPItemRect* m_DrawingZoomRect = nullptr;
95 95 QStack<QPair<QCPRange, QCPRange>> m_ZoomStack;
96 96
97 97 std::unique_ptr<VisualizationCursorItem> m_HorizontalCursor = nullptr;
98 98 std::unique_ptr<VisualizationCursorItem> m_VerticalCursor = nullptr;
99 99
100 100 VisualizationSelectionZoneItem* m_DrawingZone = nullptr;
101 101 VisualizationSelectionZoneItem* m_HoveredZone = nullptr;
102 102 QVector<VisualizationSelectionZoneItem*> m_SelectionZones;
103 103
104 104 bool m_HasMovedMouse = false; // Indicates if the mouse moved in a releaseMouse even
105 105
106 106 bool m_VariableAutoRangeOnInit = true;
107 107
108 108 inline void enterPlotDrag(const QPoint& position)
109 109 {
110 110 m_lastMousePos = m_plot->mapFromParent(position);
111 111 m_lastXRange = m_plot->xAxis->range();
112 112 m_lastYRange = m_plot->yAxis->range();
113 113 }
114 114
115 115 inline bool isDrawingZoomRect() { return m_DrawingZoomRect != nullptr; }
116 116 void updateZoomRect(const QPoint& newPos)
117 117 {
118 118 QPointF pos { m_plot->xAxis->pixelToCoord(newPos.x()),
119 119 m_plot->yAxis->pixelToCoord(newPos.y()) };
120 120 m_DrawingZoomRect->bottomRight->setCoords(pos);
121 121 m_plot->replot(QCustomPlot::rpQueuedReplot);
122 122 }
123 123
124 124 void applyZoomRect()
125 125 {
126 126 auto axisX = m_plot->axisRect()->axis(QCPAxis::atBottom);
127 127 auto axisY = m_plot->axisRect()->axis(QCPAxis::atLeft);
128 128
129 129 auto newAxisXRange = QCPRange { m_DrawingZoomRect->topLeft->coords().x(),
130 130 m_DrawingZoomRect->bottomRight->coords().x() };
131 131
132 132 auto newAxisYRange = QCPRange { m_DrawingZoomRect->topLeft->coords().y(),
133 133 m_DrawingZoomRect->bottomRight->coords().y() };
134 134
135 135 removeDrawingRect();
136 136
137 137 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
138 138 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0))
139 139 {
140 140 m_ZoomStack.push(qMakePair(axisX->range(), axisY->range()));
141 141 axisX->setRange(newAxisXRange);
142 142 axisY->setRange(newAxisYRange);
143 143
144 144 m_plot->replot(QCustomPlot::rpQueuedReplot);
145 145 }
146 146 }
147 147
148 148 inline bool isDrawingZoneRect() { return m_DrawingZone != nullptr; }
149 149 void updateZoneRect(const QPoint& newPos)
150 150 {
151 151 m_DrawingZone->setEnd(m_plot->xAxis->pixelToCoord(newPos.x()));
152 152 m_plot->replot(QCustomPlot::rpQueuedReplot);
153 153 }
154 154
155 155 void startDrawingRect(const QPoint& pos)
156 156 {
157 157 removeDrawingRect();
158 158
159 159 auto axisPos = posToAxisPos(pos);
160 160
161 161 m_DrawingZoomRect = new QCPItemRect { m_plot };
162 162 QPen p;
163 163 p.setWidth(2);
164 164 m_DrawingZoomRect->setPen(p);
165 165
166 166 m_DrawingZoomRect->topLeft->setCoords(axisPos);
167 167 m_DrawingZoomRect->bottomRight->setCoords(axisPos);
168 168 }
169 169
170 170 void removeDrawingRect()
171 171 {
172 172 if (m_DrawingZoomRect)
173 173 {
174 174 m_plot->removeItem(m_DrawingZoomRect); // the item is deleted by QCustomPlot
175 175 m_DrawingZoomRect = nullptr;
176 176 m_plot->replot(QCustomPlot::rpQueuedReplot);
177 177 }
178 178 }
179 179
180 180 void selectZone(const QPoint& pos)
181 181 {
182 182 auto zoneAtPos = selectionZoneAt(pos);
183 183 setSelectionZonesEditionEnabled(
184 184 sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones);
185 185 }
186 186
187 187 void startDrawingZone(const QPoint& pos)
188 188 {
189 189 endDrawingZone();
190 190
191 191 auto axisPos = posToAxisPos(pos);
192 192
193 193 m_DrawingZone = new VisualizationSelectionZoneItem { m_plot };
194 194 m_DrawingZone->setRange(axisPos.x(), axisPos.x());
195 195 m_DrawingZone->setEditionEnabled(false);
196 196 }
197 197
198 198 void endDrawingZone()
199 199 {
200 200 if (m_DrawingZone)
201 201 {
202 202 auto drawingZoneRange = m_DrawingZone->range();
203 203 if (qAbs(drawingZoneRange.m_TEnd - drawingZoneRange.m_TStart) > 0)
204 204 {
205 205 m_DrawingZone->setEditionEnabled(true);
206 206 addSelectionZone(m_DrawingZone);
207 207 }
208 208 else
209 209 {
210 210 m_plot->removeItem(m_DrawingZone);
211 211 }
212 212
213 213 m_plot->replot(QCustomPlot::rpQueuedReplot);
214 214 m_DrawingZone = nullptr;
215 215 }
216 216 }
217 217
218 218 void moveSelectionZone(const QPoint& destination)
219 219 {
220 220 /*
221 221 * I give up on this for now
222 * @TODO implement this, the difficulty is that selection zones have their own
222 * TODO implement this, the difficulty is that selection zones have their own
223 223 * event handling code which seems to rely on QCP GUI event handling propagation
224 224 * which was a realy bad design choice.
225 225 */
226 226 }
227 227
228 228 void setSelectionZonesEditionEnabled(bool value)
229 229 {
230 230 for (auto s : m_SelectionZones)
231 231 {
232 232 s->setEditionEnabled(value);
233 233 }
234 234 }
235 235
236 236 void addSelectionZone(VisualizationSelectionZoneItem* zone) { m_SelectionZones << zone; }
237 237
238 238 VisualizationSelectionZoneItem* selectionZoneAt(const QPoint& pos) const
239 239 {
240 240 VisualizationSelectionZoneItem* selectionZoneItemUnderCursor = nullptr;
241 241 auto minDistanceToZone = -1;
242 242 for (auto zone : m_SelectionZones)
243 243 {
244 244 auto distanceToZone = zone->selectTest(pos, false);
245 245 if ((minDistanceToZone < 0 || distanceToZone <= minDistanceToZone)
246 246 && distanceToZone >= 0 && distanceToZone < m_plot->selectionTolerance())
247 247 {
248 248 selectionZoneItemUnderCursor = zone;
249 249 }
250 250 }
251 251
252 252 return selectionZoneItemUnderCursor;
253 253 }
254 254
255 255 QVector<VisualizationSelectionZoneItem*> selectionZonesAt(
256 256 const QPoint& pos, const QCustomPlot& plot) const
257 257 {
258 258 QVector<VisualizationSelectionZoneItem*> zones;
259 259 for (auto zone : m_SelectionZones)
260 260 {
261 261 auto distanceToZone = zone->selectTest(pos, false);
262 262 if (distanceToZone >= 0 && distanceToZone < plot.selectionTolerance())
263 263 {
264 264 zones << zone;
265 265 }
266 266 }
267 267
268 268 return zones;
269 269 }
270 270
271 271 void moveSelectionZoneOnTop(VisualizationSelectionZoneItem* zone, QCustomPlot& plot)
272 272 {
273 273 if (!m_SelectionZones.isEmpty() && m_SelectionZones.last() != zone)
274 274 {
275 275 zone->moveToTop();
276 276 m_SelectionZones.removeAll(zone);
277 277 m_SelectionZones.append(zone);
278 278 }
279 279 }
280 280
281 281 QPointF posToAxisPos(const QPoint& pos) const
282 282 {
283 283 auto axisX = m_plot->axisRect()->axis(QCPAxis::atBottom);
284 284 auto axisY = m_plot->axisRect()->axis(QCPAxis::atLeft);
285 285 return QPointF { axisX->pixelToCoord(pos.x()), axisY->pixelToCoord(pos.y()) };
286 286 }
287 287
288 288 bool pointIsInAxisRect(const QPointF& axisPoint, QCustomPlot& plot) const
289 289 {
290 290 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
291 291 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
292 292 return axisX->range().contains(axisPoint.x()) && axisY->range().contains(axisPoint.y());
293 293 }
294 294
295 295 inline QCPRange _pixDistanceToRange(double pos1, double pos2, QCPAxis* axis)
296 296 {
297 297 if (axis->scaleType() == QCPAxis::stLinear)
298 298 {
299 299 auto diff = axis->pixelToCoord(pos1) - axis->pixelToCoord(pos2);
300 300 return QCPRange { axis->range().lower + diff, axis->range().upper + diff };
301 301 }
302 302 else
303 303 {
304 304 auto diff = axis->pixelToCoord(pos1) / axis->pixelToCoord(pos2);
305 305 return QCPRange { axis->range().lower * diff, axis->range().upper * diff };
306 306 }
307 307 }
308 308
309 309 void setRange(const DateTimeRange& newRange, bool updateVar = true)
310 310 {
311 311 this->m_plot->xAxis->setRange(newRange.m_TStart, newRange.m_TEnd);
312 312 if (updateVar)
313 313 {
314 314 for (auto it = m_VariableToPlotMultiMap.begin(), end = m_VariableToPlotMultiMap.end();
315 315 it != end; it = m_VariableToPlotMultiMap.upper_bound(it->first))
316 316 {
317 317 sqpApp->variableController().asyncChangeRange(it->first, newRange);
318 318 }
319 319 }
320 320 m_plot->replot(QCustomPlot::rpQueuedReplot);
321 321 }
322 322
323 323 void setRange(const QCPRange& newRange)
324 324 {
325 325 auto graphRange = DateTimeRange { newRange.lower, newRange.upper };
326 326 setRange(graphRange);
327 327 }
328 328
329 329 void rescaleY() { m_plot->yAxis->rescale(true); }
330 330
331 331 std::tuple<double, double> moveGraph(const QPoint& destination)
332 332 {
333 333 auto currentPos = m_plot->mapFromParent(destination);
334 334 auto xAxis = m_plot->axisRect()->rangeDragAxis(Qt::Horizontal);
335 335 auto yAxis = m_plot->axisRect()->rangeDragAxis(Qt::Vertical);
336 336 auto oldXRange = xAxis->range();
337 337 auto oldYRange = yAxis->range();
338 338 double dx = xAxis->pixelToCoord(m_lastMousePos.x()) - xAxis->pixelToCoord(currentPos.x());
339 339 xAxis->setRange(m_lastXRange.lower + dx, m_lastXRange.upper + dx);
340 340 if (yAxis->scaleType() == QCPAxis::stLinear)
341 341 {
342 342 double dy
343 343 = yAxis->pixelToCoord(m_lastMousePos.y()) - yAxis->pixelToCoord(currentPos.y());
344 344 yAxis->setRange(m_lastYRange.lower + dy, m_lastYRange.upper + dy);
345 345 }
346 346 else
347 347 {
348 348 double dy
349 349 = yAxis->pixelToCoord(m_lastMousePos.y()) / yAxis->pixelToCoord(currentPos.y());
350 350 yAxis->setRange(m_lastYRange.lower * dy, m_lastYRange.upper * dy);
351 351 }
352 352 auto newXRange = xAxis->range();
353 353 auto newYRange = yAxis->range();
354 354 setRange(xAxis->range());
355 355 // m_lastMousePos = currentPos;
356 356 return { newXRange.lower - oldXRange.lower, newYRange.lower - oldYRange.lower };
357 357 }
358 358
359 359 void zoom(double factor, int center, Qt::Orientation orientation)
360 360 {
361 361 QCPAxis* axis = m_plot->axisRect()->rangeZoomAxis(orientation);
362 362 axis->scaleRange(factor, axis->pixelToCoord(center));
363 363 if (orientation == Qt::Horizontal)
364 364 setRange(axis->range());
365 365 m_plot->replot(QCustomPlot::rpQueuedReplot);
366 366 }
367 367
368 368 void transform(const DateTimeRangeTransformation& tranformation)
369 369 {
370 370 auto graphRange = m_plot->xAxis->range();
371 371 DateTimeRange range { graphRange.lower, graphRange.upper };
372 372 range = range.transform(tranformation);
373 373 setRange(range);
374 374 m_plot->replot(QCustomPlot::rpQueuedReplot);
375 375 }
376 376
377 377 void move(double dx, double dy)
378 378 {
379 379 auto xAxis = m_plot->axisRect()->rangeDragAxis(Qt::Horizontal);
380 380 auto yAxis = m_plot->axisRect()->rangeDragAxis(Qt::Vertical);
381 381 xAxis->setRange(QCPRange(xAxis->range().lower + dx, xAxis->range().upper + dx));
382 382 yAxis->setRange(QCPRange(yAxis->range().lower + dy, yAxis->range().upper + dy));
383 383 setRange(xAxis->range());
384 384 m_plot->replot(QCustomPlot::rpQueuedReplot);
385 385 }
386 386
387 387 void move(double factor, Qt::Orientation orientation)
388 388 {
389 389 auto oldRange = m_plot->xAxis->range();
390 390 QCPAxis* axis = m_plot->axisRect()->rangeDragAxis(orientation);
391 391 if (m_plot->xAxis->scaleType() == QCPAxis::stLinear)
392 392 {
393 393 double rg = (axis->range().upper - axis->range().lower) * (factor / 10);
394 394 axis->setRange(axis->range().lower + (rg), axis->range().upper + (rg));
395 395 }
396 396 else if (m_plot->xAxis->scaleType() == QCPAxis::stLogarithmic)
397 397 {
398 398 int start = 0, stop = 0;
399 399 double diff = 0.;
400 400 if (factor > 0.0)
401 401 {
402 402 stop = m_plot->width() * factor / 10;
403 403 start = 2 * m_plot->width() * factor / 10;
404 404 }
405 405 if (factor < 0.0)
406 406 {
407 407 factor *= -1.0;
408 408 start = m_plot->width() * factor / 10;
409 409 stop = 2 * m_plot->width() * factor / 10;
410 410 }
411 411 diff = axis->pixelToCoord(start) / axis->pixelToCoord(stop);
412 412 axis->setRange(m_plot->axisRect()->rangeDragAxis(orientation)->range().lower * diff,
413 413 m_plot->axisRect()->rangeDragAxis(orientation)->range().upper * diff);
414 414 }
415 415 if (orientation == Qt::Horizontal)
416 416 setRange(axis->range());
417 417 m_plot->replot(QCustomPlot::rpQueuedReplot);
418 418 }
419 419 };
420 420
421 421 VisualizationGraphWidget::VisualizationGraphWidget(const QString& name, QWidget* parent)
422 422 : VisualizationDragWidget { parent }
423 423 , ui { new Ui::VisualizationGraphWidget }
424 424 , impl { spimpl::make_unique_impl<VisualizationGraphWidgetPrivate>(name) }
425 425 {
426 426 ui->setupUi(this);
427 427 this->layout()->addWidget(impl->m_plot);
428 428 // 'Close' options : widget is deleted when closed
429 429 setAttribute(Qt::WA_DeleteOnClose);
430 430
431 431 // The delegate must be initialized after the ui as it uses the plot
432 432 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this);
433 433
434 434 // Init the cursors
435 435 impl->m_HorizontalCursor = std::make_unique<VisualizationCursorItem>(&plot());
436 436 impl->m_HorizontalCursor->setOrientation(Qt::Horizontal);
437 437 impl->m_VerticalCursor = std::make_unique<VisualizationCursorItem>(&plot());
438 438 impl->m_VerticalCursor->setOrientation(Qt::Vertical);
439 439
440 440 this->setFocusPolicy(Qt::WheelFocus);
441 441 this->setMouseTracking(true);
442 442 impl->m_plot->setAttribute(Qt::WA_TransparentForMouseEvents);
443 443 impl->m_plot->setContextMenuPolicy(Qt::CustomContextMenu);
444 444 impl->m_plot->setParent(this);
445 445
446 446 connect(&sqpApp->variableController(), &VariableController2::variableDeleted, this,
447 447 &VisualizationGraphWidget::variableDeleted);
448 448 }
449 449
450 450
451 451 VisualizationGraphWidget::~VisualizationGraphWidget()
452 452 {
453 453 delete ui;
454 454 }
455 455
456 456 VisualizationZoneWidget* VisualizationGraphWidget::parentZoneWidget() const noexcept
457 457 {
458 458 auto parent = parentWidget();
459 459 while (parent != nullptr && !qobject_cast<VisualizationZoneWidget*>(parent))
460 460 {
461 461 parent = parent->parentWidget();
462 462 }
463 463
464 464 return qobject_cast<VisualizationZoneWidget*>(parent);
465 465 }
466 466
467 467 VisualizationWidget* VisualizationGraphWidget::parentVisualizationWidget() const
468 468 {
469 469 auto parent = parentWidget();
470 470 while (parent != nullptr && !qobject_cast<VisualizationWidget*>(parent))
471 471 {
472 472 parent = parent->parentWidget();
473 473 }
474 474
475 475 return qobject_cast<VisualizationWidget*>(parent);
476 476 }
477 477
478 478 void VisualizationGraphWidget::setFlags(GraphFlags flags)
479 479 {
480 480 impl->m_Flags = std::move(flags);
481 481 }
482 482
483 483 void VisualizationGraphWidget::addVariable(std::shared_ptr<Variable2> variable, DateTimeRange range)
484 484 {
485 485 // Uses delegate to create the qcpplot components according to the variable
486 486 auto createdPlottables = VisualizationGraphHelper::create(variable, *impl->m_plot);
487 487
488 488 // Sets graph properties
489 489 impl->m_RenderingDelegate->setGraphProperties(*variable, createdPlottables);
490 490
491 491 impl->m_VariableToPlotMultiMap.insert({ variable, std::move(createdPlottables) });
492 492
493 493 setGraphRange(range);
494 494 // If the variable already has its data loaded, load its units and its range in the graph
495 495 if (variable->data() != nullptr)
496 496 {
497 497 impl->m_RenderingDelegate->setAxesUnits(*variable);
498 498 }
499 499 else
500 500 {
501 501 auto context = new QObject { this };
502 502 connect(
503 503 variable.get(), &Variable2::updated, context, [this, variable, context, range](QUuid) {
504 504 this->impl->m_RenderingDelegate->setAxesUnits(*variable);
505 505 this->impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
506 506 delete context;
507 507 });
508 508 }
509 //@TODO this is bad! when variable is moved to another graph it still fires
509 //TODO this is bad! when variable is moved to another graph it still fires
510 510 // even if this has been deleted
511 511 connect(variable.get(), &Variable2::updated, this, &VisualizationGraphWidget::variableUpdated);
512 512 this->onUpdateVarDisplaying(variable, range); // My bullshit
513 513 emit variableAdded(variable);
514 514 }
515 515
516 516 void VisualizationGraphWidget::removeVariable(std::shared_ptr<Variable2> variable) noexcept
517 517 {
518 518 // Each component associated to the variable :
519 519 // - is removed from qcpplot (which deletes it)
520 520 // - is no longer referenced in the map
521 521 auto variableIt = impl->m_VariableToPlotMultiMap.find(variable);
522 522 if (variableIt != impl->m_VariableToPlotMultiMap.cend())
523 523 {
524 524 emit variableAboutToBeRemoved(variable);
525 525
526 526 auto& plottablesMap = variableIt->second;
527 527
528 528 for (auto plottableIt = plottablesMap.cbegin(), plottableEnd = plottablesMap.cend();
529 529 plottableIt != plottableEnd;)
530 530 {
531 531 impl->m_plot->removePlottable(plottableIt->second);
532 532 plottableIt = plottablesMap.erase(plottableIt);
533 533 }
534 534
535 535 impl->m_VariableToPlotMultiMap.erase(variableIt);
536 536 }
537 537
538 538 // Updates graph
539 539 impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
540 540 }
541 541
542 542 std::vector<std::shared_ptr<Variable2>> VisualizationGraphWidget::variables() const
543 543 {
544 544 auto variables = std::vector<std::shared_ptr<Variable2>> {};
545 545 for (auto it = std::cbegin(impl->m_VariableToPlotMultiMap);
546 546 it != std::cend(impl->m_VariableToPlotMultiMap); ++it)
547 547 {
548 548 variables.push_back(it->first);
549 549 }
550 550
551 551 return variables;
552 552 }
553 553
554 554 void VisualizationGraphWidget::setYRange(std::shared_ptr<Variable2> variable)
555 555 {
556 556 if (!variable)
557 557 {
558 558 qCCritical(LOG_VisualizationGraphWidget()) << "Can't set y-axis range: variable is null";
559 559 return;
560 560 }
561 561
562 562 VisualizationGraphHelper::setYAxisRange(variable, *impl->m_plot);
563 563 }
564 564
565 565 DateTimeRange VisualizationGraphWidget::graphRange() const noexcept
566 566 {
567 567 auto graphRange = impl->m_plot->xAxis->range();
568 568 return DateTimeRange { graphRange.lower, graphRange.upper };
569 569 }
570 570
571 571 void VisualizationGraphWidget::setGraphRange(
572 572 const DateTimeRange& range, bool updateVar, bool forward)
573 573 {
574 574 impl->setRange(range, updateVar);
575 575 if (forward)
576 576 {
577 577 emit this->setrange_sig(this->graphRange(), true, false);
578 578 }
579 579 }
580 580
581 581 void VisualizationGraphWidget::setAutoRangeOnVariableInitialization(bool value)
582 582 {
583 583 impl->m_VariableAutoRangeOnInit = value;
584 584 }
585 585
586 586 QVector<DateTimeRange> VisualizationGraphWidget::selectionZoneRanges() const
587 587 {
588 588 QVector<DateTimeRange> ranges;
589 589 for (auto zone : impl->m_SelectionZones)
590 590 {
591 591 ranges << zone->range();
592 592 }
593 593
594 594 return ranges;
595 595 }
596 596
597 597 void VisualizationGraphWidget::addSelectionZones(const QVector<DateTimeRange>& ranges)
598 598 {
599 599 for (const auto& range : ranges)
600 600 {
601 601 // note: ownership is transfered to QCustomPlot
602 602 auto zone = new VisualizationSelectionZoneItem(&plot());
603 603 zone->setRange(range.m_TStart, range.m_TEnd);
604 604 impl->addSelectionZone(zone);
605 605 }
606 606
607 607 plot().replot(QCustomPlot::rpQueuedReplot);
608 608 }
609 609
610 610 VisualizationSelectionZoneItem* VisualizationGraphWidget::addSelectionZone(
611 611 const QString& name, const DateTimeRange& range)
612 612 {
613 613 // note: ownership is transfered to QCustomPlot
614 614 auto zone = new VisualizationSelectionZoneItem(&plot());
615 615 zone->setName(name);
616 616 zone->setRange(range.m_TStart, range.m_TEnd);
617 617 impl->addSelectionZone(zone);
618 618
619 619 plot().replot(QCustomPlot::rpQueuedReplot);
620 620
621 621 return zone;
622 622 }
623 623
624 624 void VisualizationGraphWidget::removeSelectionZone(VisualizationSelectionZoneItem* selectionZone)
625 625 {
626 626 parentVisualizationWidget()->selectionZoneManager().setSelected(selectionZone, false);
627 627
628 628 if (impl->m_HoveredZone == selectionZone)
629 629 {
630 630 impl->m_HoveredZone = nullptr;
631 631 setCursor(Qt::ArrowCursor);
632 632 }
633 633
634 634 impl->m_SelectionZones.removeAll(selectionZone);
635 635 plot().removeItem(selectionZone);
636 636 plot().replot(QCustomPlot::rpQueuedReplot);
637 637 }
638 638
639 639 void VisualizationGraphWidget::undoZoom()
640 640 {
641 641 auto zoom = impl->m_ZoomStack.pop();
642 642 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
643 643 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
644 644
645 645 axisX->setRange(zoom.first);
646 646 axisY->setRange(zoom.second);
647 647
648 648 plot().replot(QCustomPlot::rpQueuedReplot);
649 649 }
650 650
651 651 void VisualizationGraphWidget::zoom(
652 652 double factor, int center, Qt::Orientation orientation, bool forward)
653 653 {
654 654 impl->zoom(factor, center, orientation);
655 655 if (forward && orientation == Qt::Horizontal)
656 656 emit this->setrange_sig(this->graphRange(), true, false);
657 657 }
658 658
659 659 void VisualizationGraphWidget::move(double factor, Qt::Orientation orientation, bool forward)
660 660 {
661 661 impl->move(factor, orientation);
662 662 if (forward)
663 663 emit this->setrange_sig(this->graphRange(), true, false);
664 664 }
665 665
666 666 void VisualizationGraphWidget::move(double dx, double dy, bool forward)
667 667 {
668 668 impl->move(dx, dy);
669 669 if (forward)
670 670 emit this->setrange_sig(this->graphRange(), true, false);
671 671 }
672 672
673 673 void VisualizationGraphWidget::transform(
674 674 const DateTimeRangeTransformation& tranformation, bool forward)
675 675 {
676 676 impl->transform(tranformation);
677 677 if (forward)
678 678 emit this->setrange_sig(this->graphRange(), true, false);
679 679 }
680 680
681 681 void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor* visitor)
682 682 {
683 683 if (visitor)
684 684 {
685 685 visitor->visit(this);
686 686 }
687 687 else
688 688 {
689 689 qCCritical(LOG_VisualizationGraphWidget())
690 690 << tr("Can't visit widget : the visitor is null");
691 691 }
692 692 }
693 693
694 694 bool VisualizationGraphWidget::canDrop(Variable2& variable) const
695 695 {
696 696 auto isSpectrogram
697 697 = [](auto& variable) { return variable.type() == DataSeriesType::SPECTROGRAM; };
698 698
699 699 // - A spectrogram series can't be dropped on graph with existing plottables
700 700 // - No data series can be dropped on graph with existing spectrogram series
701 701 return isSpectrogram(variable)
702 702 ? impl->m_VariableToPlotMultiMap.empty()
703 703 : std::none_of(impl->m_VariableToPlotMultiMap.cbegin(),
704 704 impl->m_VariableToPlotMultiMap.cend(),
705 705 [isSpectrogram](const auto& entry) { return isSpectrogram(*entry.first); });
706 706 }
707 707
708 708 bool VisualizationGraphWidget::contains(Variable2& variable) const
709 709 {
710 710 // Finds the variable among the keys of the map
711 711 auto variablePtr = &variable;
712 712 auto findVariable
713 713 = [variablePtr](const auto& entry) { return variablePtr == entry.first.get(); };
714 714
715 715 auto end = impl->m_VariableToPlotMultiMap.cend();
716 716 auto it = std::find_if(impl->m_VariableToPlotMultiMap.cbegin(), end, findVariable);
717 717 return it != end;
718 718 }
719 719
720 720 QString VisualizationGraphWidget::name() const
721 721 {
722 722 return impl->m_Name;
723 723 }
724 724
725 725 QMimeData* VisualizationGraphWidget::mimeData(const QPoint& position) const
726 726 {
727 727 auto mimeData = new QMimeData;
728 728
729 729 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(position);
730 730 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
731 731 && selectionZoneItemUnderCursor)
732 732 {
733 733 mimeData->setData(MIME_TYPE_TIME_RANGE,
734 734 TimeController::mimeDataForTimeRange(selectionZoneItemUnderCursor->range()));
735 735 mimeData->setData(MIME_TYPE_SELECTION_ZONE,
736 736 TimeController::mimeDataForTimeRange(selectionZoneItemUnderCursor->range()));
737 737 }
738 738 else
739 739 {
740 740 mimeData->setData(MIME_TYPE_GRAPH, QByteArray {});
741 741
742 742 auto timeRangeData = TimeController::mimeDataForTimeRange(graphRange());
743 743 mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData);
744 744 }
745 745
746 746 return mimeData;
747 747 }
748 748
749 749 QPixmap VisualizationGraphWidget::customDragPixmap(const QPoint& dragPosition)
750 750 {
751 751 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(dragPosition);
752 752 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
753 753 && selectionZoneItemUnderCursor)
754 754 {
755 755
756 756 auto zoneTopLeft = selectionZoneItemUnderCursor->topLeft->pixelPosition();
757 757 auto zoneBottomRight = selectionZoneItemUnderCursor->bottomRight->pixelPosition();
758 758
759 759 auto zoneSize = QSizeF { qAbs(zoneBottomRight.x() - zoneTopLeft.x()),
760 760 qAbs(zoneBottomRight.y() - zoneTopLeft.y()) }
761 761 .toSize();
762 762
763 763 auto pixmap = QPixmap(zoneSize);
764 764 render(&pixmap, QPoint(), QRegion { QRect { zoneTopLeft.toPoint(), zoneSize } });
765 765
766 766 return pixmap;
767 767 }
768 768
769 769 return QPixmap();
770 770 }
771 771
772 772 bool VisualizationGraphWidget::isDragAllowed() const
773 773 {
774 774 return true;
775 775 }
776 776
777 777 void VisualizationGraphWidget::highlightForMerge(bool highlighted)
778 778 {
779 779 if (highlighted)
780 780 {
781 781 plot().setBackground(QBrush(QColor("#BBD5EE")));
782 782 }
783 783 else
784 784 {
785 785 plot().setBackground(QBrush(Qt::white));
786 786 }
787 787
788 788 plot().update();
789 789 }
790 790
791 791 void VisualizationGraphWidget::addVerticalCursor(double time)
792 792 {
793 793 impl->m_VerticalCursor->setPosition(time);
794 794 impl->m_VerticalCursor->setVisible(true);
795 795
796 796 auto text
797 797 = DateUtils::dateTime(time).toString(CURSOR_LABELS_DATETIME_FORMAT).replace(' ', '\n');
798 798 impl->m_VerticalCursor->setLabelText(text);
799 799 }
800 800
801 801 void VisualizationGraphWidget::addVerticalCursorAtViewportPosition(double position)
802 802 {
803 803 impl->m_VerticalCursor->setAbsolutePosition(position);
804 804 impl->m_VerticalCursor->setVisible(true);
805 805
806 806 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
807 807 auto text
808 808 = DateUtils::dateTime(axis->pixelToCoord(position)).toString(CURSOR_LABELS_DATETIME_FORMAT);
809 809 impl->m_VerticalCursor->setLabelText(text);
810 810 }
811 811
812 812 void VisualizationGraphWidget::removeVerticalCursor()
813 813 {
814 814 impl->m_VerticalCursor->setVisible(false);
815 815 plot().replot(QCustomPlot::rpQueuedReplot);
816 816 }
817 817
818 818 void VisualizationGraphWidget::addHorizontalCursor(double value)
819 819 {
820 820 impl->m_HorizontalCursor->setPosition(value);
821 821 impl->m_HorizontalCursor->setVisible(true);
822 822 impl->m_HorizontalCursor->setLabelText(QString::number(value));
823 823 }
824 824
825 825 void VisualizationGraphWidget::addHorizontalCursorAtViewportPosition(double position)
826 826 {
827 827 impl->m_HorizontalCursor->setAbsolutePosition(position);
828 828 impl->m_HorizontalCursor->setVisible(true);
829 829
830 830 auto axis = plot().axisRect()->axis(QCPAxis::atLeft);
831 831 impl->m_HorizontalCursor->setLabelText(QString::number(axis->pixelToCoord(position)));
832 832 }
833 833
834 834 void VisualizationGraphWidget::removeHorizontalCursor()
835 835 {
836 836 impl->m_HorizontalCursor->setVisible(false);
837 837 plot().replot(QCustomPlot::rpQueuedReplot);
838 838 }
839 839
840 840 void VisualizationGraphWidget::closeEvent(QCloseEvent* event)
841 841 {
842 842 Q_UNUSED(event);
843 843
844 844 for (auto i : impl->m_SelectionZones)
845 845 {
846 846 parentVisualizationWidget()->selectionZoneManager().setSelected(i, false);
847 847 }
848 848
849 849 // Prevents that all variables will be removed from graph when it will be closed
850 850 for (auto& variableEntry : impl->m_VariableToPlotMultiMap)
851 851 {
852 852 emit variableAboutToBeRemoved(variableEntry.first);
853 853 }
854 854 }
855 855
856 856 void VisualizationGraphWidget::enterEvent(QEvent* event)
857 857 {
858 858 Q_UNUSED(event);
859 859 impl->m_RenderingDelegate->showGraphOverlay(true);
860 860 }
861 861
862 862 void VisualizationGraphWidget::leaveEvent(QEvent* event)
863 863 {
864 864 Q_UNUSED(event);
865 865 impl->m_RenderingDelegate->showGraphOverlay(false);
866 866
867 867 if (auto parentZone = parentZoneWidget())
868 868 {
869 869 parentZone->notifyMouseLeaveGraph(this);
870 870 }
871 871 else
872 872 {
873 873 qCWarning(LOG_VisualizationGraphWidget()) << "leaveEvent: No parent zone widget";
874 874 }
875 875
876 876 if (impl->m_HoveredZone)
877 877 {
878 878 impl->m_HoveredZone->setHovered(false);
879 879 impl->m_HoveredZone = nullptr;
880 880 }
881 881 }
882 882
883 883 void VisualizationGraphWidget::wheelEvent(QWheelEvent* event)
884 884 {
885 885 double factor;
886 886 double wheelSteps = event->delta() / 120.0; // a single step delta is +/-120 usually
887 887 if (event->modifiers() == Qt::ControlModifier)
888 888 {
889 889 if (event->orientation() == Qt::Vertical) // mRangeZoom.testFlag(Qt::Vertical))
890 890 {
891 891 setCursor(Qt::SizeVerCursor);
892 892 factor = pow(impl->m_plot->axisRect()->rangeZoomFactor(Qt::Vertical), wheelSteps);
893 893 zoom(factor, event->pos().y(), Qt::Vertical);
894 894 }
895 895 }
896 896 else if (event->modifiers() == Qt::ShiftModifier)
897 897 {
898 898 if (event->orientation() == Qt::Vertical) // mRangeZoom.testFlag(Qt::Vertical))
899 899 {
900 900 setCursor(Qt::SizeHorCursor);
901 901 factor = pow(impl->m_plot->axisRect()->rangeZoomFactor(Qt::Horizontal), wheelSteps);
902 902 zoom(factor, event->pos().x(), Qt::Horizontal);
903 903 }
904 904 }
905 905 else
906 906 {
907 907 move(wheelSteps, Qt::Horizontal);
908 908 }
909 909 event->accept();
910 910 }
911 911
912 912
913 913 void VisualizationGraphWidget::mouseMoveEvent(QMouseEvent* event)
914 914 {
915 915 if (impl->isDrawingZoomRect())
916 916 {
917 917 impl->updateZoomRect(event->pos());
918 918 }
919 919 else if (impl->isDrawingZoneRect())
920 920 {
921 921 impl->updateZoneRect(event->pos());
922 922 }
923 923 else if (event->buttons() == Qt::LeftButton)
924 924 {
925 925 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::None)
926 926 {
927 927 auto [dx, dy] = impl->moveGraph(event->pos());
928 928 emit this->setrange_sig(this->graphRange(), true, false);
929 929 }
930 930 else if (sqpApp->plotsInteractionMode()
931 931 == SqpApplication::PlotsInteractionMode::SelectionZones)
932 932 {
933 933 auto posInPlot = this->impl->m_plot->mapFromParent(event->pos());
934 934 if (auto item = impl->m_plot->itemAt(posInPlot))
935 935 {
936 936 if (qobject_cast<VisualizationSelectionZoneItem*>(item))
937 937 {
938 938 QMouseEvent e { QEvent::MouseMove, posInPlot, event->button(), event->buttons(),
939 939 event->modifiers() };
940 940 sqpApp->sendEvent(this->impl->m_plot, &e);
941 941 this->impl->m_plot->replot(QCustomPlot::rpImmediateRefresh);
942 942 }
943 943 }
944 944 }
945 945 }
946 946 else
947 947 {
948 948 impl->m_RenderingDelegate->updateTooltip(event);
949 949 }
950 950 // event->accept();
951 951 QWidget::mouseMoveEvent(event);
952 952 }
953 953
954 954 void VisualizationGraphWidget::mouseReleaseEvent(QMouseEvent* event)
955 955 {
956 956 if (impl->isDrawingZoomRect())
957 957 {
958 958 auto oldRange = this->graphRange();
959 959 impl->applyZoomRect();
960 960 auto newRange = this->graphRange();
961 961 if (auto tf = DateTimeRangeHelper::computeTransformation(oldRange, newRange))
962 962 emit this->transform_sig(tf.value(), false);
963 963 }
964 964 else if (impl->isDrawingZoneRect())
965 965 {
966 966 impl->endDrawingZone();
967 967 }
968 968 else
969 969 {
970 970 setCursor(Qt::ArrowCursor);
971 971 }
972 972 auto posInPlot = this->impl->m_plot->mapFromParent(event->pos());
973 973 if (auto item = impl->m_plot->itemAt(posInPlot))
974 974 {
975 975 if (qobject_cast<VisualizationSelectionZoneItem*>(item))
976 976 {
977 977 QMouseEvent e { QEvent::MouseButtonRelease, posInPlot, event->button(),
978 978 event->buttons(), event->modifiers() };
979 979 sqpApp->sendEvent(this->impl->m_plot, &e);
980 980 }
981 981 }
982 982 event->accept();
983 983 }
984 984
985 985 void VisualizationGraphWidget::mousePressEvent(QMouseEvent* event)
986 986 {
987 987 if (event->button() == Qt::RightButton)
988 988 {
989 989 onGraphMenuRequested(event->pos());
990 990 }
991 991 else
992 992 {
993 993 auto selectedZone = impl->selectionZoneAt(event->pos());
994 994 switch (sqpApp->plotsInteractionMode())
995 995 {
996 996 case SqpApplication::PlotsInteractionMode::DragAndDrop:
997 997 break;
998 998 case SqpApplication::PlotsInteractionMode::SelectionZones:
999 999 impl->setSelectionZonesEditionEnabled(true);
1000 1000 if ((event->modifiers() == Qt::ControlModifier) && (selectedZone != nullptr))
1001 1001 {
1002 1002 auto alreadySelectedZones
1003 1003 = parentVisualizationWidget()->selectionZoneManager().selectedItems();
1004 1004 selectedZone->setAssociatedEditedZones(alreadySelectedZones);
1005 1005 if (SciQLop::containers::contains(alreadySelectedZones, selectedZone))
1006 1006 {
1007 1007 alreadySelectedZones.removeOne(selectedZone);
1008 1008 }
1009 1009 else
1010 1010 {
1011 1011 alreadySelectedZones.append(selectedZone);
1012 1012 }
1013 1013 parentVisualizationWidget()->selectionZoneManager().select(
1014 1014 alreadySelectedZones);
1015 1015 }
1016 1016 else
1017 1017 {
1018 1018 if (!selectedZone)
1019 1019 {
1020 1020 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1021 1021 impl->startDrawingZone(event->pos());
1022 1022 }
1023 1023 else
1024 1024 {
1025 1025 parentVisualizationWidget()->selectionZoneManager().select(
1026 1026 { selectedZone });
1027 1027 }
1028 1028 }
1029 1029 {
1030 1030 auto posInPlot = this->impl->m_plot->mapFromParent(event->pos());
1031 1031 if (auto item = impl->m_plot->itemAt(posInPlot))
1032 1032 {
1033 1033 if (qobject_cast<VisualizationSelectionZoneItem*>(item))
1034 1034 {
1035 1035 QMouseEvent e { QEvent::MouseButtonPress, posInPlot, event->button(),
1036 1036 event->buttons(), event->modifiers() };
1037 1037 sqpApp->sendEvent(this->impl->m_plot, &e);
1038 1038 }
1039 1039 }
1040 1040 }
1041 1041 break;
1042 1042 case SqpApplication::PlotsInteractionMode::ZoomBox:
1043 1043 impl->startDrawingRect(event->pos());
1044 1044 break;
1045 1045 default:
1046 1046 if (auto item = impl->m_plot->itemAt(event->pos()))
1047 1047 {
1048 1048 emit impl->m_plot->itemClick(item, event);
1049 1049 if (qobject_cast<VisualizationSelectionZoneItem*>(item))
1050 1050 {
1051 1051 setCursor(Qt::ClosedHandCursor);
1052 1052 impl->enterPlotDrag(event->pos());
1053 1053 }
1054 1054 }
1055 1055 else
1056 1056 {
1057 1057 setCursor(Qt::ClosedHandCursor);
1058 1058 impl->enterPlotDrag(event->pos());
1059 1059 }
1060 1060 }
1061 1061 }
1062 1062 // event->accept();
1063 1063 QWidget::mousePressEvent(event);
1064 1064 }
1065 1065
1066 1066 void VisualizationGraphWidget::mouseDoubleClickEvent(QMouseEvent* event)
1067 1067 {
1068 1068 impl->m_RenderingDelegate->onMouseDoubleClick(event);
1069 1069 }
1070 1070
1071 1071 void VisualizationGraphWidget::keyReleaseEvent(QKeyEvent* event)
1072 1072 {
1073 1073 switch (event->key())
1074 1074 {
1075 1075 case Qt::Key_Control:
1076 1076 event->accept();
1077 1077 break;
1078 1078 case Qt::Key_Shift:
1079 1079 event->accept();
1080 1080 break;
1081 1081 default:
1082 1082 QWidget::keyReleaseEvent(event);
1083 1083 break;
1084 1084 }
1085 1085 setCursor(Qt::ArrowCursor);
1086 1086 // event->accept();
1087 1087 }
1088 1088
1089 1089 void VisualizationGraphWidget::keyPressEvent(QKeyEvent* event)
1090 1090 {
1091 1091 switch (event->key())
1092 1092 {
1093 1093 case Qt::Key_Control:
1094 1094 setCursor(Qt::CrossCursor);
1095 1095 break;
1096 1096 case Qt::Key_Shift:
1097 1097 break;
1098 1098 case Qt::Key_M:
1099 1099 impl->rescaleY();
1100 1100 impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
1101 1101 break;
1102 1102 case Qt::Key_Left:
1103 1103 if (event->modifiers() != Qt::ControlModifier)
1104 1104 {
1105 1105 move(-0.1, Qt::Horizontal);
1106 1106 }
1107 1107 else
1108 1108 {
1109 1109 zoom(2, this->width() / 2, Qt::Horizontal);
1110 1110 }
1111 1111 break;
1112 1112 case Qt::Key_Right:
1113 1113 if (event->modifiers() != Qt::ControlModifier)
1114 1114 {
1115 1115 move(0.1, Qt::Horizontal);
1116 1116 }
1117 1117 else
1118 1118 {
1119 1119 zoom(0.5, this->width() / 2, Qt::Horizontal);
1120 1120 }
1121 1121 break;
1122 1122 case Qt::Key_Up:
1123 1123 if (event->modifiers() != Qt::ControlModifier)
1124 1124 {
1125 1125 move(0.1, Qt::Vertical);
1126 1126 }
1127 1127 else
1128 1128 {
1129 1129 zoom(0.5, this->height() / 2, Qt::Vertical);
1130 1130 }
1131 1131 break;
1132 1132 case Qt::Key_Down:
1133 1133 if (event->modifiers() != Qt::ControlModifier)
1134 1134 {
1135 1135 move(-0.1, Qt::Vertical);
1136 1136 }
1137 1137 else
1138 1138 {
1139 1139 zoom(2, this->height() / 2, Qt::Vertical);
1140 1140 }
1141 1141 break;
1142 1142 default:
1143 1143 QWidget::keyPressEvent(event);
1144 1144 break;
1145 1145 }
1146 1146 }
1147 1147
1148 1148 QCustomPlot& VisualizationGraphWidget::plot() const noexcept
1149 1149 {
1150 1150 return *impl->m_plot;
1151 1151 }
1152 1152
1153 1153 void VisualizationGraphWidget::onGraphMenuRequested(const QPoint& pos) noexcept
1154 1154 {
1155 1155 QMenu graphMenu {};
1156 1156
1157 1157 // Iterates on variables (unique keys)
1158 1158 for (auto it = impl->m_VariableToPlotMultiMap.cbegin(),
1159 1159 end = impl->m_VariableToPlotMultiMap.cend();
1160 1160 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first))
1161 1161 {
1162 1162 // 'Remove variable' action
1163 1163 graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()),
1164 1164 [this, var = it->first]() { removeVariable(var); });
1165 1165 }
1166 1166
1167 1167 if (!impl->m_ZoomStack.isEmpty())
1168 1168 {
1169 1169 if (!graphMenu.isEmpty())
1170 1170 {
1171 1171 graphMenu.addSeparator();
1172 1172 }
1173 1173
1174 1174 graphMenu.addAction(tr("Undo Zoom"), [this]() { undoZoom(); });
1175 1175 }
1176 1176
1177 1177 // Selection Zone Actions
1178 1178 auto selectionZoneItem = impl->selectionZoneAt(pos);
1179 1179 if (selectionZoneItem)
1180 1180 {
1181 1181 auto selectedItems = parentVisualizationWidget()->selectionZoneManager().selectedItems();
1182 1182 selectedItems.removeAll(selectionZoneItem);
1183 1183 selectedItems.prepend(selectionZoneItem); // Put the current selection zone first
1184 1184
1185 1185 auto zoneActions = sqpApp->actionsGuiController().selectionZoneActions();
1186 1186 if (!zoneActions.isEmpty() && !graphMenu.isEmpty())
1187 1187 {
1188 1188 graphMenu.addSeparator();
1189 1189 }
1190 1190
1191 1191 QHash<QString, QMenu*> subMenus;
1192 1192 QHash<QString, bool> subMenusEnabled;
1193 1193 QHash<QString, FilteringAction*> filteredMenu;
1194 1194
1195 1195 for (auto zoneAction : zoneActions)
1196 1196 {
1197 1197
1198 1198 auto isEnabled = zoneAction->isEnabled(selectedItems);
1199 1199
1200 1200 auto menu = &graphMenu;
1201 1201 QString menuPath;
1202 1202 for (auto subMenuName : zoneAction->subMenuList())
1203 1203 {
1204 1204 menuPath += '/';
1205 1205 menuPath += subMenuName;
1206 1206
1207 1207 if (!subMenus.contains(menuPath))
1208 1208 {
1209 1209 menu = menu->addMenu(subMenuName);
1210 1210 subMenus[menuPath] = menu;
1211 1211 subMenusEnabled[menuPath] = isEnabled;
1212 1212 }
1213 1213 else
1214 1214 {
1215 1215 menu = subMenus.value(menuPath);
1216 1216 if (isEnabled)
1217 1217 {
1218 1218 // The sub menu is enabled if at least one of its actions is enabled
1219 1219 subMenusEnabled[menuPath] = true;
1220 1220 }
1221 1221 }
1222 1222 }
1223 1223
1224 1224 FilteringAction* filterAction = nullptr;
1225 1225 if (sqpApp->actionsGuiController().isMenuFiltered(zoneAction->subMenuList()))
1226 1226 {
1227 1227 filterAction = filteredMenu.value(menuPath);
1228 1228 if (!filterAction)
1229 1229 {
1230 1230 filterAction = new FilteringAction { this };
1231 1231 filteredMenu[menuPath] = filterAction;
1232 1232 menu->addAction(filterAction);
1233 1233 }
1234 1234 }
1235 1235
1236 1236 auto action = menu->addAction(zoneAction->name());
1237 1237 action->setEnabled(isEnabled);
1238 1238 action->setShortcut(zoneAction->displayedShortcut());
1239 1239 QObject::connect(action, &QAction::triggered,
1240 1240 [zoneAction, selectedItems]() { zoneAction->execute(selectedItems); });
1241 1241
1242 1242 if (filterAction && zoneAction->isFilteringAllowed())
1243 1243 {
1244 1244 filterAction->addActionToFilter(action);
1245 1245 }
1246 1246 }
1247 1247
1248 1248 for (auto it = subMenus.cbegin(); it != subMenus.cend(); ++it)
1249 1249 {
1250 1250 it.value()->setEnabled(subMenusEnabled[it.key()]);
1251 1251 }
1252 1252 }
1253 1253
1254 1254 if (!graphMenu.isEmpty())
1255 1255 {
1256 1256 graphMenu.exec(QCursor::pos());
1257 1257 }
1258 1258 }
1259 1259
1260 1260 void VisualizationGraphWidget::onMouseDoubleClick(QMouseEvent* event) noexcept
1261 1261 {
1262 1262 impl->m_RenderingDelegate->onMouseDoubleClick(event);
1263 1263 }
1264 1264
1265 1265 void VisualizationGraphWidget::onMouseMove(QMouseEvent* event) noexcept
1266 1266 {
1267 1267 // Handles plot rendering when mouse is moving
1268 1268 impl->m_RenderingDelegate->updateTooltip(event);
1269 1269
1270 1270 auto axisPos = impl->posToAxisPos(event->pos());
1271 1271
1272 1272 // Zoom box and zone drawing
1273 1273 if (impl->m_DrawingZoomRect)
1274 1274 {
1275 1275 impl->m_DrawingZoomRect->bottomRight->setCoords(axisPos);
1276 1276 }
1277 1277 else if (impl->m_DrawingZone)
1278 1278 {
1279 1279 impl->m_DrawingZone->setEnd(axisPos.x());
1280 1280 }
1281 1281
1282 1282 // Cursor
1283 1283 if (auto parentZone = parentZoneWidget())
1284 1284 {
1285 1285 if (impl->pointIsInAxisRect(axisPos, plot()))
1286 1286 {
1287 1287 parentZone->notifyMouseMoveInGraph(event->pos(), axisPos, this);
1288 1288 }
1289 1289 else
1290 1290 {
1291 1291 parentZone->notifyMouseLeaveGraph(this);
1292 1292 }
1293 1293 }
1294 1294
1295 1295 // Search for the selection zone under the mouse
1296 1296 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1297 1297 if (selectionZoneItemUnderCursor && !impl->m_DrawingZone
1298 1298 && sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones)
1299 1299 {
1300 1300
1301 1301 // Sets the appropriate cursor shape
1302 1302 auto cursorShape = selectionZoneItemUnderCursor->curshorShapeForPosition(event->pos());
1303 1303 setCursor(cursorShape);
1304 1304
1305 1305 // Manages the hovered zone
1306 1306 if (selectionZoneItemUnderCursor != impl->m_HoveredZone)
1307 1307 {
1308 1308 if (impl->m_HoveredZone)
1309 1309 {
1310 1310 impl->m_HoveredZone->setHovered(false);
1311 1311 }
1312 1312 selectionZoneItemUnderCursor->setHovered(true);
1313 1313 impl->m_HoveredZone = selectionZoneItemUnderCursor;
1314 1314 plot().replot(QCustomPlot::rpQueuedReplot);
1315 1315 }
1316 1316 }
1317 1317 else
1318 1318 {
1319 1319 // There is no zone under the mouse or the interaction mode is not "selection zones"
1320 1320 if (impl->m_HoveredZone)
1321 1321 {
1322 1322 impl->m_HoveredZone->setHovered(false);
1323 1323 impl->m_HoveredZone = nullptr;
1324 1324 }
1325 1325
1326 1326 setCursor(Qt::ArrowCursor);
1327 1327 }
1328 1328
1329 1329 impl->m_HasMovedMouse = true;
1330 1330 VisualizationDragWidget::mouseMoveEvent(event);
1331 1331 }
1332 1332
1333 1333 void VisualizationGraphWidget::onMouseWheel(QWheelEvent* event) noexcept
1334 1334 {
1335 1335 // Processes event only if the wheel occurs on axis rect
1336 1336 if (!dynamic_cast<QCPAxisRect*>(impl->m_plot->layoutElementAt(event->posF())))
1337 1337 {
1338 1338 return;
1339 1339 }
1340 1340
1341 1341 auto value = event->angleDelta().x() + event->angleDelta().y();
1342 1342 if (value != 0)
1343 1343 {
1344 1344
1345 1345 auto direction = value > 0 ? 1.0 : -1.0;
1346 1346 auto isZoomX = event->modifiers().testFlag(HORIZONTAL_ZOOM_MODIFIER);
1347 1347 auto isZoomY = event->modifiers().testFlag(VERTICAL_ZOOM_MODIFIER);
1348 1348 impl->m_IsCalibration = event->modifiers().testFlag(VERTICAL_PAN_MODIFIER);
1349 1349
1350 1350 auto zoomOrientations = QFlags<Qt::Orientation> {};
1351 1351 zoomOrientations.setFlag(Qt::Horizontal, isZoomX);
1352 1352 zoomOrientations.setFlag(Qt::Vertical, isZoomY);
1353 1353
1354 1354 impl->m_plot->axisRect()->setRangeZoom(zoomOrientations);
1355 1355
1356 1356 if (!isZoomX && !isZoomY)
1357 1357 {
1358 1358 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
1359 1359 auto diff = direction * (axis->range().size() * (PAN_SPEED / 100.0));
1360 1360
1361 1361 axis->setRange(axis->range() + diff);
1362 1362
1363 1363 if (plot().noAntialiasingOnDrag())
1364 1364 {
1365 1365 plot().setNotAntialiasedElements(QCP::aeAll);
1366 1366 }
1367 1367
1368 1368 // plot().replot(QCustomPlot::rpQueuedReplot);
1369 1369 }
1370 1370 }
1371 1371 }
1372 1372
1373 1373 void VisualizationGraphWidget::onMousePress(QMouseEvent* event) noexcept
1374 1374 {
1375 1375 auto isDragDropClick = event->modifiers().testFlag(DRAG_DROP_MODIFIER);
1376 1376 auto isSelectionZoneMode
1377 1377 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
1378 1378 auto isLeftClick = event->buttons().testFlag(Qt::LeftButton);
1379 1379
1380 1380 if (!isDragDropClick && isLeftClick)
1381 1381 {
1382 1382 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::ZoomBox)
1383 1383 {
1384 1384 // Starts a zoom box
1385 1385 impl->startDrawingRect(event->pos());
1386 1386 }
1387 1387 else if (isSelectionZoneMode && impl->m_DrawingZone == nullptr)
1388 1388 {
1389 1389 // Starts a new selection zone
1390 1390 auto zoneAtPos = impl->selectionZoneAt(event->pos());
1391 1391 if (!zoneAtPos)
1392 1392 {
1393 1393 impl->startDrawingZone(event->pos());
1394 1394 }
1395 1395 }
1396 1396 }
1397 1397
1398 1398
1399 1399 // Allows zone edition only in selection zone mode without drag&drop
1400 1400 impl->setSelectionZonesEditionEnabled(isSelectionZoneMode && !isDragDropClick);
1401 1401
1402 1402 // Selection / Deselection
1403 1403 if (isSelectionZoneMode)
1404 1404 {
1405 1405 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
1406 1406 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1407 1407
1408 1408
1409 1409 if (selectionZoneItemUnderCursor && !selectionZoneItemUnderCursor->selected()
1410 1410 && !isMultiSelectionClick)
1411 1411 {
1412 1412 parentVisualizationWidget()->selectionZoneManager().select(
1413 1413 { selectionZoneItemUnderCursor });
1414 1414 }
1415 1415 else if (!selectionZoneItemUnderCursor && !isMultiSelectionClick && isLeftClick)
1416 1416 {
1417 1417 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1418 1418 }
1419 1419 else
1420 1420 {
1421 1421 // No selection change
1422 1422 }
1423 1423
1424 1424 if (selectionZoneItemUnderCursor && isLeftClick)
1425 1425 {
1426 1426 selectionZoneItemUnderCursor->setAssociatedEditedZones(
1427 1427 parentVisualizationWidget()->selectionZoneManager().selectedItems());
1428 1428 }
1429 1429 }
1430 1430
1431 1431
1432 1432 impl->m_HasMovedMouse = false;
1433 1433 VisualizationDragWidget::mousePressEvent(event);
1434 1434 }
1435 1435
1436 1436 void VisualizationGraphWidget::onMouseRelease(QMouseEvent* event) noexcept
1437 1437 {
1438 1438 if (impl->m_DrawingZoomRect)
1439 1439 {
1440 1440
1441 1441 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
1442 1442 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
1443 1443
1444 1444 auto newAxisXRange = QCPRange { impl->m_DrawingZoomRect->topLeft->coords().x(),
1445 1445 impl->m_DrawingZoomRect->bottomRight->coords().x() };
1446 1446
1447 1447 auto newAxisYRange = QCPRange { impl->m_DrawingZoomRect->topLeft->coords().y(),
1448 1448 impl->m_DrawingZoomRect->bottomRight->coords().y() };
1449 1449
1450 1450 impl->removeDrawingRect();
1451 1451
1452 1452 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
1453 1453 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0))
1454 1454 {
1455 1455 impl->m_ZoomStack.push(qMakePair(axisX->range(), axisY->range()));
1456 1456 axisX->setRange(newAxisXRange);
1457 1457 axisY->setRange(newAxisYRange);
1458 1458
1459 1459 plot().replot(QCustomPlot::rpQueuedReplot);
1460 1460 }
1461 1461 }
1462 1462
1463 1463 impl->endDrawingZone();
1464 1464
1465 1465 // Selection / Deselection
1466 1466 auto isSelectionZoneMode
1467 1467 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
1468 1468 if (isSelectionZoneMode)
1469 1469 {
1470 1470 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
1471 1471 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1472 1472 if (selectionZoneItemUnderCursor && event->button() == Qt::LeftButton
1473 1473 && !impl->m_HasMovedMouse)
1474 1474 {
1475 1475
1476 1476 auto zonesUnderCursor = impl->selectionZonesAt(event->pos(), plot());
1477 1477 if (zonesUnderCursor.count() > 1)
1478 1478 {
1479 1479 // There are multiple zones under the mouse.
1480 1480 // Performs the selection with a selection dialog.
1481 1481 VisualizationMultiZoneSelectionDialog dialog { this };
1482 1482 dialog.setZones(zonesUnderCursor);
1483 1483 dialog.move(mapToGlobal(event->pos() - QPoint(dialog.width() / 2, 20)));
1484 1484 dialog.activateWindow();
1485 1485 dialog.raise();
1486 1486 if (dialog.exec() == QDialog::Accepted)
1487 1487 {
1488 1488 auto selection = dialog.selectedZones();
1489 1489
1490 1490 if (!isMultiSelectionClick)
1491 1491 {
1492 1492 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1493 1493 }
1494 1494
1495 1495 for (auto it = selection.cbegin(); it != selection.cend(); ++it)
1496 1496 {
1497 1497 auto zone = it.key();
1498 1498 auto isSelected = it.value();
1499 1499 parentVisualizationWidget()->selectionZoneManager().setSelected(
1500 1500 zone, isSelected);
1501 1501
1502 1502 if (isSelected)
1503 1503 {
1504 1504 // Puts the zone on top of the stack so it can be moved or resized
1505 1505 impl->moveSelectionZoneOnTop(zone, plot());
1506 1506 }
1507 1507 }
1508 1508 }
1509 1509 }
1510 1510 else
1511 1511 {
1512 1512 if (!isMultiSelectionClick)
1513 1513 {
1514 1514 parentVisualizationWidget()->selectionZoneManager().select(
1515 1515 { selectionZoneItemUnderCursor });
1516 1516 impl->moveSelectionZoneOnTop(selectionZoneItemUnderCursor, plot());
1517 1517 }
1518 1518 else
1519 1519 {
1520 1520 parentVisualizationWidget()->selectionZoneManager().setSelected(
1521 1521 selectionZoneItemUnderCursor,
1522 1522 !selectionZoneItemUnderCursor->selected()
1523 1523 || event->button() == Qt::RightButton);
1524 1524 }
1525 1525 }
1526 1526 }
1527 1527 else
1528 1528 {
1529 1529 // No selection change
1530 1530 }
1531 1531 }
1532 1532 }
1533 1533
1534 1534 void VisualizationGraphWidget::onDataCacheVariableUpdated()
1535 1535 {
1536 1536 auto graphRange = impl->m_plot->xAxis->range();
1537 1537 auto dateTime = DateTimeRange { graphRange.lower, graphRange.upper };
1538 1538
1539 1539 for (auto& variableEntry : impl->m_VariableToPlotMultiMap)
1540 1540 {
1541 1541 auto variable = variableEntry.first;
1542 1542 qCDebug(LOG_VisualizationGraphWidget())
1543 1543 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S" << variable->range();
1544 1544 qCDebug(LOG_VisualizationGraphWidget())
1545 1545 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime;
1546 1546 if (dateTime.contains(variable->range()) || dateTime.intersect(variable->range()))
1547 1547 {
1548 1548 impl->updateData(variableEntry.second, variable, variable->range());
1549 1549 }
1550 1550 }
1551 1551 }
1552 1552
1553 1553 void VisualizationGraphWidget::onUpdateVarDisplaying(
1554 1554 std::shared_ptr<Variable2> variable, const DateTimeRange& range)
1555 1555 {
1556 1556 auto it = impl->m_VariableToPlotMultiMap.find(variable);
1557 1557 if (it != impl->m_VariableToPlotMultiMap.end())
1558 1558 {
1559 1559 impl->updateData(it->second, variable, range);
1560 1560 }
1561 1561 }
1562 1562
1563 1563 void VisualizationGraphWidget::variableUpdated(QUuid id)
1564 1564 {
1565 1565 for (auto& [var, plotables] : impl->m_VariableToPlotMultiMap)
1566 1566 {
1567 1567 if (var->ID() == id)
1568 1568 {
1569 1569 impl->updateData(plotables, var, this->graphRange());
1570 1570 }
1571 1571 }
1572 1572 }
1573 1573
1574 1574 void VisualizationGraphWidget::variableDeleted(const std::shared_ptr<Variable2>& variable)
1575 1575 {
1576 1576 this->removeVariable(variable);
1577 1577 }
General Comments 0
You need to be logged in to leave comments. Login now