PythonQwt 0.16.2__tar.gz → 0.16.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pythonqwt-0.16.2/PythonQwt.egg-info → pythonqwt-0.16.3}/PKG-INFO +1 -1
- {pythonqwt-0.16.2 → pythonqwt-0.16.3/PythonQwt.egg-info}/PKG-INFO +1 -1
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/PythonQwt.egg-info/SOURCES.txt +7 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/reference/index.rst +3 -2
- pythonqwt-0.16.3/doc/reference/qtdesigner.rst +50 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/pyproject.toml +1 -1
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/__init__.py +1 -1
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/null_paintdevice.py +1 -1
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/plot.py +21 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/plot_canvas.py +4 -1
- pythonqwt-0.16.3/qwt/qtdesigner.py +211 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/scale_draw.py +1 -1
- pythonqwt-0.16.3/qwt/tests/test_issue109_backingstore.py +68 -0
- pythonqwt-0.16.3/qwt/tests/test_issue33_sidebyside.py +260 -0
- pythonqwt-0.16.3/qwt/tests/test_issue88_mousetracking.py +70 -0
- pythonqwt-0.16.3/qwt/tests/test_qtdesigner.py +55 -0
- pythonqwt-0.16.3/qwt/tests/test_qtdesigner.ui +31 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/utils.py +2 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/LICENSE +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/MANIFEST.in +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/PythonQwt-tests.desktop +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/PythonQwt.egg-info/dependency_links.txt +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/PythonQwt.egg-info/entry_points.txt +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/PythonQwt.egg-info/requires.txt +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/PythonQwt.egg-info/top_level.txt +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/README.md +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/_static/PythonQwt_logo.png +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/_static/QwtPlot_example.png +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/_static/panorama.png +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/_static/symbol_path_example.png +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/conf.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/bodedemo.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/cartesian.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/cpudemo.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/curvebenchmark1.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/curvebenchmark2.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/curvedemo1.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/curvedemo2.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/data.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/errorbar.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/eventfilter.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/image.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/index.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/logcurve.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/mapdemo.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/multidemo.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/simple.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/vertical.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/index.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/installation.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/issue93_optimization_summary.md +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/overview.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/plot_example.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/reference/graphic.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/reference/interval.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/reference/plot.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/reference/plot_directpainter.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/reference/plot_layout.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/reference/plot_series.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/reference/scale.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/reference/symbol.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/reference/text.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/reference/toqimage.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/reference/transform.rst +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/symbol_path_example.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/_math.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/color_map.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/column_symbol.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/dyngrid_layout.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/graphic.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/interval.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/legend.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/painter.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/painter_command.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/plot_curve.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/plot_directpainter.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/plot_grid.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/plot_layout.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/plot_marker.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/plot_renderer.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/plot_series.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/qthelpers.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/scale_div.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/scale_engine.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/scale_map.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/scale_widget.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/symbol.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/__init__.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/comparative_benchmarks.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/conftest.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/PythonQwt.svg +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/bodedemo.png +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/cartesian.png +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/cpudemo.png +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/curvebenchmark1.png +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/curvebenchmark2.png +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/curvedemo1.png +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/curvedemo2.png +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/data.png +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/errorbar.png +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/eventfilter.png +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/image.png +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/loadtest.png +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/logcurve.png +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/mapdemo.png +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/multidemo.png +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/simple.png +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/stylesheet.png +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/symbol.svg +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/symbols.png +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/testlauncher.png +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/vertical.png +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_backingstore.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_bodedemo.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_cartesian.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_cpudemo.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_curvebenchmark1.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_curvebenchmark2.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_curvedemo1.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_curvedemo2.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_data.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_errorbar.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_eventfilter.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_highdpi.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_image.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_issue107_gdi_leak.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_loadtest.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_logcurve.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_mapdemo.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_multidemo.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_relativemargin.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_simple.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_stylesheet.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_symbols.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_vertical.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/text.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/toqimage.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/transform.py +0 -0
- {pythonqwt-0.16.2 → pythonqwt-0.16.3}/setup.cfg +0 -0
|
@@ -44,6 +44,7 @@ doc/reference/plot.rst
|
|
|
44
44
|
doc/reference/plot_directpainter.rst
|
|
45
45
|
doc/reference/plot_layout.rst
|
|
46
46
|
doc/reference/plot_series.rst
|
|
47
|
+
doc/reference/qtdesigner.rst
|
|
47
48
|
doc/reference/scale.rst
|
|
48
49
|
doc/reference/symbol.rst
|
|
49
50
|
doc/reference/text.rst
|
|
@@ -69,6 +70,7 @@ qwt/plot_layout.py
|
|
|
69
70
|
qwt/plot_marker.py
|
|
70
71
|
qwt/plot_renderer.py
|
|
71
72
|
qwt/plot_series.py
|
|
73
|
+
qwt/qtdesigner.py
|
|
72
74
|
qwt/qthelpers.py
|
|
73
75
|
qwt/scale_div.py
|
|
74
76
|
qwt/scale_draw.py
|
|
@@ -96,10 +98,15 @@ qwt/tests/test_eventfilter.py
|
|
|
96
98
|
qwt/tests/test_highdpi.py
|
|
97
99
|
qwt/tests/test_image.py
|
|
98
100
|
qwt/tests/test_issue107_gdi_leak.py
|
|
101
|
+
qwt/tests/test_issue109_backingstore.py
|
|
102
|
+
qwt/tests/test_issue33_sidebyside.py
|
|
103
|
+
qwt/tests/test_issue88_mousetracking.py
|
|
99
104
|
qwt/tests/test_loadtest.py
|
|
100
105
|
qwt/tests/test_logcurve.py
|
|
101
106
|
qwt/tests/test_mapdemo.py
|
|
102
107
|
qwt/tests/test_multidemo.py
|
|
108
|
+
qwt/tests/test_qtdesigner.py
|
|
109
|
+
qwt/tests/test_qtdesigner.ui
|
|
103
110
|
qwt/tests/test_relativemargin.py
|
|
104
111
|
qwt/tests/test_simple.py
|
|
105
112
|
qwt/tests/test_stylesheet.py
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Qt Designer
|
|
2
|
+
===========
|
|
3
|
+
|
|
4
|
+
PythonQwt ships a Qt Designer plugin which makes a 2D plotting widget
|
|
5
|
+
(:class:`qwt.qtdesigner.QwtPlotWidget`, a thin :class:`qwt.QwtPlot` subclass)
|
|
6
|
+
available directly inside Qt Designer's widget box, in the "PythonQwt" group.
|
|
7
|
+
|
|
8
|
+
Installing the plugin
|
|
9
|
+
---------------------
|
|
10
|
+
|
|
11
|
+
The plugin file ``qwtplugin.py`` lives in the ``qtdesigner`` directory at the
|
|
12
|
+
root of the PythonQwt source tree. To make it available in Qt Designer, add
|
|
13
|
+
that directory to the ``PYQTDESIGNERPATH`` environment variable before
|
|
14
|
+
starting Qt Designer:
|
|
15
|
+
|
|
16
|
+
.. code-block:: bash
|
|
17
|
+
|
|
18
|
+
# Linux/macOS
|
|
19
|
+
export PYQTDESIGNERPATH=<path to PythonQwt>/qtdesigner
|
|
20
|
+
designer
|
|
21
|
+
|
|
22
|
+
.. code-block:: bat
|
|
23
|
+
|
|
24
|
+
rem Windows
|
|
25
|
+
set PYQTDESIGNERPATH=<path to PythonQwt>\qtdesigner
|
|
26
|
+
designer
|
|
27
|
+
|
|
28
|
+
.. note::
|
|
29
|
+
|
|
30
|
+
Qt Designer custom widget plugins require PyQt5 or PyQt6. PySide6 does not
|
|
31
|
+
expose ``QPyDesignerCustomWidgetPlugin``.
|
|
32
|
+
|
|
33
|
+
Loading a ``.ui`` file at runtime
|
|
34
|
+
---------------------------------
|
|
35
|
+
|
|
36
|
+
The :mod:`qwt.qtdesigner` module provides helper functions to load or compile
|
|
37
|
+
``.ui`` files embedding :class:`qwt.qtdesigner.QwtPlotWidget` widgets:
|
|
38
|
+
|
|
39
|
+
.. code-block:: python
|
|
40
|
+
|
|
41
|
+
from qwt.qtdesigner import loadui
|
|
42
|
+
|
|
43
|
+
FormClass = loadui("myform.ui")
|
|
44
|
+
form = FormClass()
|
|
45
|
+
form.plotwidget.setTitle("Loaded from Qt Designer")
|
|
46
|
+
|
|
47
|
+
Reference
|
|
48
|
+
---------
|
|
49
|
+
|
|
50
|
+
.. automodule:: qwt.qtdesigner
|
|
@@ -50,7 +50,7 @@ test = ["pytest", "pytest-xvfb"]
|
|
|
50
50
|
include = ["qwt*"]
|
|
51
51
|
|
|
52
52
|
[tool.setuptools.package-data]
|
|
53
|
-
"*" = ["*.png", "*.svg", "*.mo", "*.cfg", "*.toml"]
|
|
53
|
+
"*" = ["*.png", "*.svg", "*.mo", "*.cfg", "*.toml", "*.ui"]
|
|
54
54
|
|
|
55
55
|
[tool.setuptools.dynamic]
|
|
56
56
|
version = { attr = "qwt.__version__" }
|
|
@@ -63,7 +63,7 @@ from qwt.symbol import QwtSymbol as QSbl # see deprecated section
|
|
|
63
63
|
from qwt.text import QwtText # noqa: F401
|
|
64
64
|
from qwt.toqimage import array_to_qimage as toQImage # noqa: F401
|
|
65
65
|
|
|
66
|
-
__version__ = "0.16.
|
|
66
|
+
__version__ = "0.16.3"
|
|
67
67
|
QWT_VERSION_STR = "6.1.5"
|
|
68
68
|
|
|
69
69
|
|
|
@@ -275,7 +275,7 @@ class QwtNullPaintDevice(QPaintDevice):
|
|
|
275
275
|
elif deviceMetric == QPaintDevice.PdmDevicePixelRatio:
|
|
276
276
|
value = 1
|
|
277
277
|
elif deviceMetric == QPaintDevice.PdmDevicePixelRatioScaled:
|
|
278
|
-
value =
|
|
278
|
+
value = 0x10000
|
|
279
279
|
else:
|
|
280
280
|
value = super(QwtNullPaintDevice, self).metric(deviceMetric)
|
|
281
281
|
return value
|
|
@@ -997,9 +997,30 @@ class QwtPlot(QFrame):
|
|
|
997
997
|
if canvas is not None:
|
|
998
998
|
canvas.setParent(self)
|
|
999
999
|
canvas.installEventFilter(self)
|
|
1000
|
+
# Mouse move events occur over the canvas widget, not the plot frame
|
|
1001
|
+
# itself, so the canvas must share the plot's mouse-tracking state
|
|
1002
|
+
# for `QwtPlot.setMouseTracking` to behave as expected.
|
|
1003
|
+
canvas.setMouseTracking(self.hasMouseTracking())
|
|
1000
1004
|
if self.isVisible():
|
|
1001
1005
|
canvas.show()
|
|
1002
1006
|
|
|
1007
|
+
def setMouseTracking(self, enable):
|
|
1008
|
+
"""
|
|
1009
|
+
Enable or disable mouse tracking for the plot.
|
|
1010
|
+
|
|
1011
|
+
The plot's drawing area is occupied by the canvas widget, so mouse move
|
|
1012
|
+
events over the plot are received by the canvas rather than the plot
|
|
1013
|
+
itself. This override propagates the mouse-tracking state to the canvas
|
|
1014
|
+
so that enabling tracking on the plot delivers mouse move events even
|
|
1015
|
+
when no mouse button is pressed (see Issue #88).
|
|
1016
|
+
|
|
1017
|
+
:param bool enable: True to enable mouse tracking, False to disable it
|
|
1018
|
+
"""
|
|
1019
|
+
super(QwtPlot, self).setMouseTracking(enable)
|
|
1020
|
+
canvas = self.__data.canvas
|
|
1021
|
+
if canvas is not None:
|
|
1022
|
+
canvas.setMouseTracking(enable)
|
|
1023
|
+
|
|
1003
1024
|
def event(self, event):
|
|
1004
1025
|
if event.type() == QEvent.LayoutRequest:
|
|
1005
1026
|
self.updateLayout()
|
|
@@ -591,7 +591,6 @@ class QwtPlotCanvas(QFrame):
|
|
|
591
591
|
if (
|
|
592
592
|
self.testPaintAttribute(self.BackingStore)
|
|
593
593
|
and self.__data.backingStore is not None
|
|
594
|
-
and not self.__data.backingStore.isNull()
|
|
595
594
|
):
|
|
596
595
|
bs = self.__data.backingStore
|
|
597
596
|
pixelRatio = bs.devicePixelRatio()
|
|
@@ -615,6 +614,10 @@ class QwtPlotCanvas(QFrame):
|
|
|
615
614
|
if self.frameWidth() > 0:
|
|
616
615
|
self.drawBorder(p)
|
|
617
616
|
p.end()
|
|
617
|
+
# Store the regenerated pixmap back: the C++ original uses a
|
|
618
|
+
# reference into the stored pixmap, whereas the rebinding above
|
|
619
|
+
# only updates the local variable.
|
|
620
|
+
self.__data.backingStore = bs
|
|
618
621
|
painter.drawPixmap(0, 0, self.__data.backingStore)
|
|
619
622
|
else:
|
|
620
623
|
if self.testAttribute(Qt.WA_StyledBackground):
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the terms of the Qwt License
|
|
4
|
+
# Copyright (c) 2015 Pierre Raybaut, for the Qt Designer plugin helpers
|
|
5
|
+
# (see LICENSE file for more details)
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
Qt Designer
|
|
9
|
+
-----------
|
|
10
|
+
|
|
11
|
+
The :mod:`qwt.qtdesigner` module provides helpers to integrate :mod:`qwt`
|
|
12
|
+
widgets into Qt Designer:
|
|
13
|
+
|
|
14
|
+
* :py:class:`QwtPlotWidget`
|
|
15
|
+
* :py:func:`loadui`
|
|
16
|
+
* :py:func:`compileui`
|
|
17
|
+
* :py:func:`create_qtdesigner_plugin`
|
|
18
|
+
|
|
19
|
+
:class:`QwtPlotWidget` is a thin :class:`qwt.QwtPlot` subclass exposing the
|
|
20
|
+
standard Qt widget constructor (``__init__(self, parent=None)``). It is the
|
|
21
|
+
widget promoted by the Qt Designer plugin, so that the code generated by
|
|
22
|
+
``uic`` (e.g. ``QwtPlotWidget(parent=...)`` with PyQt6) instantiates it
|
|
23
|
+
correctly. :class:`qwt.QwtPlot` itself keeps its historical constructor.
|
|
24
|
+
|
|
25
|
+
.. note::
|
|
26
|
+
|
|
27
|
+
Qt Designer custom widget plugins rely on
|
|
28
|
+
:class:`QtDesigner.QPyDesignerCustomWidgetPlugin`, which is only available
|
|
29
|
+
with PyQt5/PyQt6. PySide6 does not expose this class.
|
|
30
|
+
|
|
31
|
+
.. autoclass:: QwtPlotWidget
|
|
32
|
+
|
|
33
|
+
.. autofunction:: loadui
|
|
34
|
+
|
|
35
|
+
.. autofunction:: compileui
|
|
36
|
+
|
|
37
|
+
.. autofunction:: create_qtdesigner_plugin
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from __future__ import annotations
|
|
41
|
+
|
|
42
|
+
import io
|
|
43
|
+
|
|
44
|
+
from qtpy import QtGui as QG
|
|
45
|
+
from qtpy import uic
|
|
46
|
+
|
|
47
|
+
from qwt.plot import QwtPlot
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class QwtPlotWidget(QwtPlot):
|
|
51
|
+
""":class:`qwt.QwtPlot` widget with the standard Qt constructor.
|
|
52
|
+
|
|
53
|
+
This subclass is meant to be promoted in Qt Designer: unlike
|
|
54
|
+
:class:`qwt.QwtPlot` (whose constructor accepts the historical
|
|
55
|
+
``QwtPlot([title], [parent])`` overloads), it exposes the conventional
|
|
56
|
+
``__init__(self, parent=None)`` signature expected by the code generated
|
|
57
|
+
by ``uic`` (notably PyQt6, which uses ``QwtPlotWidget(parent=...)``).
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
parent: Parent widget
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(self, parent=None):
|
|
64
|
+
super().__init__(parent)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def loadui(fname: str, replace_class: str | None = None):
|
|
68
|
+
"""Return Widget or Window class from a Qt Designer ``.ui`` file.
|
|
69
|
+
|
|
70
|
+
When a promoted widget inherits from a class that is not known to the
|
|
71
|
+
``uic`` parser (i.e. not a standard Qt widget), ``loadUiType`` fails to
|
|
72
|
+
resolve the widget hierarchy. Passing the offending base class name as
|
|
73
|
+
``replace_class`` neutralizes it by substituting ``QFrame`` (the base
|
|
74
|
+
class of :class:`qwt.QwtPlot`) before the file is parsed.
|
|
75
|
+
|
|
76
|
+
For a plain :class:`qwt.QwtPlot` widget (which already inherits from
|
|
77
|
+
``QFrame``), no replacement is required and ``replace_class`` can be left
|
|
78
|
+
to ``None``.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
fname: Path to the ``.ui`` file
|
|
82
|
+
replace_class: Base class name to replace by ``QFrame``, or ``None``
|
|
83
|
+
to load the file unchanged
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
The generated form class
|
|
87
|
+
"""
|
|
88
|
+
with open(fname) as f:
|
|
89
|
+
uifile_text = f.read()
|
|
90
|
+
if replace_class is not None:
|
|
91
|
+
uifile_text = uifile_text.replace(replace_class, "QFrame")
|
|
92
|
+
ui, base_class = uic.loadUiType(io.StringIO(uifile_text))
|
|
93
|
+
|
|
94
|
+
class Form(base_class, ui):
|
|
95
|
+
"""Form class generated from the ``.ui`` file"""
|
|
96
|
+
|
|
97
|
+
def __init__(self, parent=None):
|
|
98
|
+
super().__init__(parent)
|
|
99
|
+
self.setupUi(self)
|
|
100
|
+
|
|
101
|
+
return Form
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def compileui(fname: str, replace_class: str | None = None) -> None:
|
|
105
|
+
"""Compile a Qt Designer ``.ui`` file into a Python module.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
fname: Path to the ``.ui`` file
|
|
109
|
+
replace_class: Base class name to replace by ``QFrame``, or ``None``
|
|
110
|
+
to compile the file unchanged
|
|
111
|
+
"""
|
|
112
|
+
with open(fname) as f:
|
|
113
|
+
uifile_text = f.read()
|
|
114
|
+
if replace_class is not None:
|
|
115
|
+
uifile_text = uifile_text.replace(replace_class, "QFrame")
|
|
116
|
+
with open(fname.replace(".ui", "_ui.py"), "w") as pyfile:
|
|
117
|
+
uic.compileUi(io.StringIO(uifile_text), pyfile, pyqt3_wrapper=True)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def create_qtdesigner_plugin(
|
|
121
|
+
group: str,
|
|
122
|
+
module_name: str,
|
|
123
|
+
class_name: str,
|
|
124
|
+
icon: str | QG.QIcon | None = None,
|
|
125
|
+
tooltip: str = "",
|
|
126
|
+
whatsthis: str = "",
|
|
127
|
+
):
|
|
128
|
+
"""Return a custom Qt Designer plugin class.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
group: Name of the group the widget belongs to in Qt Designer
|
|
132
|
+
module_name: Name of the module where the widget class is defined
|
|
133
|
+
class_name: Name of the widget class
|
|
134
|
+
icon: Icon shown in Qt Designer (path to an image file or
|
|
135
|
+
:class:`QtGui.QIcon` instance)
|
|
136
|
+
tooltip: Tool tip shown in Qt Designer
|
|
137
|
+
whatsthis: "What's this" help text shown in Qt Designer
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
A :class:`QtDesigner.QPyDesignerCustomWidgetPlugin` subclass
|
|
141
|
+
|
|
142
|
+
Example::
|
|
143
|
+
|
|
144
|
+
Plugin = create_qtdesigner_plugin(
|
|
145
|
+
group="PythonQwt",
|
|
146
|
+
module_name="qwt.qtdesigner",
|
|
147
|
+
class_name="QwtPlotWidget",
|
|
148
|
+
)
|
|
149
|
+
"""
|
|
150
|
+
from qtpy.QtDesigner import QPyDesignerCustomWidgetPlugin
|
|
151
|
+
|
|
152
|
+
Widget = getattr(__import__(module_name, fromlist=[class_name]), class_name)
|
|
153
|
+
|
|
154
|
+
class CustomWidgetPlugin(QPyDesignerCustomWidgetPlugin):
|
|
155
|
+
"""Qt Designer plugin for the ``%s`` widget""" % class_name
|
|
156
|
+
|
|
157
|
+
def __init__(self, parent=None):
|
|
158
|
+
QPyDesignerCustomWidgetPlugin.__init__(self)
|
|
159
|
+
self.initialized = False
|
|
160
|
+
|
|
161
|
+
def initialize(self, core):
|
|
162
|
+
"""Initialize the plugin"""
|
|
163
|
+
if self.initialized:
|
|
164
|
+
return
|
|
165
|
+
self.initialized = True
|
|
166
|
+
|
|
167
|
+
def isInitialized(self):
|
|
168
|
+
"""Return whether the plugin has been initialized"""
|
|
169
|
+
return self.initialized
|
|
170
|
+
|
|
171
|
+
def createWidget(self, parent):
|
|
172
|
+
"""Return a new instance of the custom widget"""
|
|
173
|
+
return Widget(parent)
|
|
174
|
+
|
|
175
|
+
def name(self):
|
|
176
|
+
"""Return the name of the custom widget class"""
|
|
177
|
+
return class_name
|
|
178
|
+
|
|
179
|
+
def group(self):
|
|
180
|
+
"""Return the name of the group the widget belongs to"""
|
|
181
|
+
return group
|
|
182
|
+
|
|
183
|
+
def icon(self):
|
|
184
|
+
"""Return the icon shown in Qt Designer"""
|
|
185
|
+
if isinstance(icon, QG.QIcon):
|
|
186
|
+
return icon
|
|
187
|
+
elif isinstance(icon, str):
|
|
188
|
+
return QG.QIcon(icon)
|
|
189
|
+
return QG.QIcon()
|
|
190
|
+
|
|
191
|
+
def toolTip(self):
|
|
192
|
+
"""Return the tool tip shown in Qt Designer"""
|
|
193
|
+
return tooltip
|
|
194
|
+
|
|
195
|
+
def whatsThis(self):
|
|
196
|
+
"""Return the "What's this" help text shown in Qt Designer"""
|
|
197
|
+
return whatsthis
|
|
198
|
+
|
|
199
|
+
def isContainer(self):
|
|
200
|
+
"""Return whether the widget is a container"""
|
|
201
|
+
return False
|
|
202
|
+
|
|
203
|
+
def domXml(self):
|
|
204
|
+
"""Return the XML used to define the widget in Qt Designer"""
|
|
205
|
+
return f'<widget class="{class_name}" name="{class_name.lower()}" />\n'
|
|
206
|
+
|
|
207
|
+
def includeFile(self):
|
|
208
|
+
"""Return the module name where the widget class is defined"""
|
|
209
|
+
return module_name
|
|
210
|
+
|
|
211
|
+
return CustomWidgetPlugin
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the terms of the Qwt License
|
|
4
|
+
# (see LICENSE file for more details)
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
Test for issue 109: the ``QwtPlotCanvas.BackingStore`` paint attribute was
|
|
8
|
+
non-functional.
|
|
9
|
+
|
|
10
|
+
After ``invalidateBackingStore()`` reset the buffer to an empty ``QPixmap()``,
|
|
11
|
+
``paintEvent`` skipped the backing-store branch because of an extra
|
|
12
|
+
``isNull()`` guard, so the double-buffering cache was never regenerated. The
|
|
13
|
+
regenerated pixmap was also only bound to a local variable and never stored
|
|
14
|
+
back. This test ensures the cache is refreshed (and reused) as intended.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
from qtpy import QtCore as QC
|
|
19
|
+
from qtpy import QtWidgets as QW
|
|
20
|
+
|
|
21
|
+
from qwt import QwtPlot, QwtPlotCurve
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _make_plot_with_backing_store():
|
|
25
|
+
plot = QwtPlot()
|
|
26
|
+
plot.resize(QC.QSize(400, 300))
|
|
27
|
+
canvas = plot.canvas()
|
|
28
|
+
canvas.setPaintAttribute(canvas.BackingStore, True)
|
|
29
|
+
x = np.linspace(0, 10, 100)
|
|
30
|
+
QwtPlotCurve.make(x, np.sin(x), "Sine", plot)
|
|
31
|
+
return plot, canvas
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_backing_store_is_regenerated_after_replot():
|
|
35
|
+
"""The backing store must be repopulated when painting after a replot."""
|
|
36
|
+
# A live QApplication must exist before constructing any QWidget, otherwise
|
|
37
|
+
# Qt aborts the process. Tests run in a shared interpreter, but no test
|
|
38
|
+
# keeps a persistent Python reference to the application, so the singleton
|
|
39
|
+
# may be garbage-collected between tests (observed on Linux/PyQt5 in CI).
|
|
40
|
+
app = QW.QApplication.instance() or QW.QApplication([])
|
|
41
|
+
|
|
42
|
+
plot, canvas = _make_plot_with_backing_store()
|
|
43
|
+
plot.show()
|
|
44
|
+
app.processEvents() # lay the canvas out and paint it a first time
|
|
45
|
+
|
|
46
|
+
plot.replot() # invalidates the backing store
|
|
47
|
+
|
|
48
|
+
# Force a paint so the backing store branch runs. ``grab()`` synchronously
|
|
49
|
+
# triggers ``paintEvent`` and works headless (QT_QPA_PLATFORM=offscreen),
|
|
50
|
+
# without depending on the event loop delivering a deferred repaint.
|
|
51
|
+
canvas.grab()
|
|
52
|
+
|
|
53
|
+
bs = canvas.backingStore()
|
|
54
|
+
assert bs is not None
|
|
55
|
+
assert not bs.isNull()
|
|
56
|
+
# The regenerated pixmap must match the canvas size (accounting for the
|
|
57
|
+
# device pixel ratio), proving it was actually rebuilt and stored back.
|
|
58
|
+
assert bs.size() == canvas.size() * bs.devicePixelRatio()
|
|
59
|
+
|
|
60
|
+
plot.close()
|
|
61
|
+
plot.deleteLater()
|
|
62
|
+
app.processEvents()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
app = QW.QApplication.instance() or QW.QApplication([])
|
|
67
|
+
test_backing_store_is_regenerated_after_replot()
|
|
68
|
+
print("OK")
|