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.
Files changed (139) hide show
  1. {pythonqwt-0.16.2/PythonQwt.egg-info → pythonqwt-0.16.3}/PKG-INFO +1 -1
  2. {pythonqwt-0.16.2 → pythonqwt-0.16.3/PythonQwt.egg-info}/PKG-INFO +1 -1
  3. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/PythonQwt.egg-info/SOURCES.txt +7 -0
  4. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/reference/index.rst +3 -2
  5. pythonqwt-0.16.3/doc/reference/qtdesigner.rst +50 -0
  6. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/pyproject.toml +1 -1
  7. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/__init__.py +1 -1
  8. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/null_paintdevice.py +1 -1
  9. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/plot.py +21 -0
  10. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/plot_canvas.py +4 -1
  11. pythonqwt-0.16.3/qwt/qtdesigner.py +211 -0
  12. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/scale_draw.py +1 -1
  13. pythonqwt-0.16.3/qwt/tests/test_issue109_backingstore.py +68 -0
  14. pythonqwt-0.16.3/qwt/tests/test_issue33_sidebyside.py +260 -0
  15. pythonqwt-0.16.3/qwt/tests/test_issue88_mousetracking.py +70 -0
  16. pythonqwt-0.16.3/qwt/tests/test_qtdesigner.py +55 -0
  17. pythonqwt-0.16.3/qwt/tests/test_qtdesigner.ui +31 -0
  18. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/utils.py +2 -0
  19. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/LICENSE +0 -0
  20. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/MANIFEST.in +0 -0
  21. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/PythonQwt-tests.desktop +0 -0
  22. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/PythonQwt.egg-info/dependency_links.txt +0 -0
  23. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/PythonQwt.egg-info/entry_points.txt +0 -0
  24. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/PythonQwt.egg-info/requires.txt +0 -0
  25. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/PythonQwt.egg-info/top_level.txt +0 -0
  26. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/README.md +0 -0
  27. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/_static/PythonQwt_logo.png +0 -0
  28. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/_static/QwtPlot_example.png +0 -0
  29. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/_static/panorama.png +0 -0
  30. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/_static/symbol_path_example.png +0 -0
  31. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/conf.py +0 -0
  32. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/bodedemo.rst +0 -0
  33. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/cartesian.rst +0 -0
  34. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/cpudemo.rst +0 -0
  35. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/curvebenchmark1.rst +0 -0
  36. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/curvebenchmark2.rst +0 -0
  37. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/curvedemo1.rst +0 -0
  38. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/curvedemo2.rst +0 -0
  39. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/data.rst +0 -0
  40. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/errorbar.rst +0 -0
  41. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/eventfilter.rst +0 -0
  42. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/image.rst +0 -0
  43. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/index.rst +0 -0
  44. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/logcurve.rst +0 -0
  45. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/mapdemo.rst +0 -0
  46. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/multidemo.rst +0 -0
  47. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/simple.rst +0 -0
  48. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/examples/vertical.rst +0 -0
  49. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/index.rst +0 -0
  50. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/installation.rst +0 -0
  51. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/issue93_optimization_summary.md +0 -0
  52. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/overview.rst +0 -0
  53. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/plot_example.py +0 -0
  54. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/reference/graphic.rst +0 -0
  55. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/reference/interval.rst +0 -0
  56. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/reference/plot.rst +0 -0
  57. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/reference/plot_directpainter.rst +0 -0
  58. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/reference/plot_layout.rst +0 -0
  59. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/reference/plot_series.rst +0 -0
  60. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/reference/scale.rst +0 -0
  61. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/reference/symbol.rst +0 -0
  62. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/reference/text.rst +0 -0
  63. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/reference/toqimage.rst +0 -0
  64. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/reference/transform.rst +0 -0
  65. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/doc/symbol_path_example.py +0 -0
  66. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/_math.py +0 -0
  67. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/color_map.py +0 -0
  68. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/column_symbol.py +0 -0
  69. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/dyngrid_layout.py +0 -0
  70. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/graphic.py +0 -0
  71. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/interval.py +0 -0
  72. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/legend.py +0 -0
  73. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/painter.py +0 -0
  74. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/painter_command.py +0 -0
  75. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/plot_curve.py +0 -0
  76. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/plot_directpainter.py +0 -0
  77. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/plot_grid.py +0 -0
  78. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/plot_layout.py +0 -0
  79. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/plot_marker.py +0 -0
  80. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/plot_renderer.py +0 -0
  81. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/plot_series.py +0 -0
  82. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/qthelpers.py +0 -0
  83. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/scale_div.py +0 -0
  84. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/scale_engine.py +0 -0
  85. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/scale_map.py +0 -0
  86. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/scale_widget.py +0 -0
  87. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/symbol.py +0 -0
  88. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/__init__.py +0 -0
  89. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/comparative_benchmarks.py +0 -0
  90. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/conftest.py +0 -0
  91. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/PythonQwt.svg +0 -0
  92. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/bodedemo.png +0 -0
  93. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/cartesian.png +0 -0
  94. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/cpudemo.png +0 -0
  95. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/curvebenchmark1.png +0 -0
  96. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/curvebenchmark2.png +0 -0
  97. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/curvedemo1.png +0 -0
  98. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/curvedemo2.png +0 -0
  99. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/data.png +0 -0
  100. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/errorbar.png +0 -0
  101. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/eventfilter.png +0 -0
  102. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/image.png +0 -0
  103. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/loadtest.png +0 -0
  104. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/logcurve.png +0 -0
  105. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/mapdemo.png +0 -0
  106. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/multidemo.png +0 -0
  107. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/simple.png +0 -0
  108. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/stylesheet.png +0 -0
  109. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/symbol.svg +0 -0
  110. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/symbols.png +0 -0
  111. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/testlauncher.png +0 -0
  112. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/data/vertical.png +0 -0
  113. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_backingstore.py +0 -0
  114. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_bodedemo.py +0 -0
  115. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_cartesian.py +0 -0
  116. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_cpudemo.py +0 -0
  117. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_curvebenchmark1.py +0 -0
  118. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_curvebenchmark2.py +0 -0
  119. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_curvedemo1.py +0 -0
  120. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_curvedemo2.py +0 -0
  121. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_data.py +0 -0
  122. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_errorbar.py +0 -0
  123. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_eventfilter.py +0 -0
  124. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_highdpi.py +0 -0
  125. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_image.py +0 -0
  126. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_issue107_gdi_leak.py +0 -0
  127. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_loadtest.py +0 -0
  128. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_logcurve.py +0 -0
  129. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_mapdemo.py +0 -0
  130. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_multidemo.py +0 -0
  131. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_relativemargin.py +0 -0
  132. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_simple.py +0 -0
  133. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_stylesheet.py +0 -0
  134. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_symbols.py +0 -0
  135. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/tests/test_vertical.py +0 -0
  136. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/text.py +0 -0
  137. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/toqimage.py +0 -0
  138. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/qwt/transform.py +0 -0
  139. {pythonqwt-0.16.2 → pythonqwt-0.16.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PythonQwt
3
- Version: 0.16.2
3
+ Version: 0.16.3
4
4
  Summary: Qt plotting widgets for Python
5
5
  Author-email: Pierre Raybaut <pierre.raybaut@gmail.com>
6
6
  License: PythonQwt License Agreement
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PythonQwt
3
- Version: 0.16.2
3
+ Version: 0.16.3
4
4
  Summary: Qt plotting widgets for Python
5
5
  Author-email: Pierre Raybaut <pierre.raybaut@gmail.com>
6
6
  License: PythonQwt License Agreement
@@ -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
@@ -5,18 +5,19 @@ Public API:
5
5
 
6
6
  .. toctree::
7
7
  :maxdepth: 2
8
-
8
+
9
9
  plot
10
10
  scale
11
11
  symbol
12
12
  text
13
13
  toqimage
14
+ qtdesigner
14
15
 
15
16
  Private API:
16
17
 
17
18
  .. toctree::
18
19
  :maxdepth: 2
19
-
20
+
20
21
  graphic
21
22
  interval
22
23
  plot_directpainter
@@ -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.2"
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 = 1
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
@@ -607,7 +607,7 @@ class QwtScaleDraw(QwtAbstractScaleDraw):
607
607
  * start: Start border distance
608
608
  * end: End border distance
609
609
  """
610
- start, end = 0, 1.0
610
+ start, end = 0, 1
611
611
 
612
612
  if not self.hasComponent(QwtAbstractScaleDraw.Labels):
613
613
  return start, end
@@ -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")