Skip to content

Commit e3e3ba0

Browse files
Merge pull request #116 from PlotPyStack/fix/115-porting-bugs
fix: porting bugs in rarely used APIs, and release 0.17.0
2 parents 36fffb6 + 645ac55 commit e3e3ba0

18 files changed

Lines changed: 900 additions & 52 deletions

‎CHANGELOG.md‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# PythonQwt Releases
22

3-
## Unreleased
3+
## Version 0.17.0
44

55
### Requirements
66

@@ -9,6 +9,22 @@
99
### Bug fixes
1010

1111
- Fixed [Issue #113](https://github.com/PlotPyStack/PythonQwt/issues/113): after replacing the scale draw of an axis (`QwtPlot.setAxisScaleDraw`), the axis ticks could be misaligned with the grid lines and plot items, until the next scale division change (e.g. after panning or zooming): a replot did not fix it. The new scale draw was laid out before getting the position and length of the previous one, so that the axis reserved space for its end labels that the canvas did not take into account. The new scale draw now takes the geometry of the previous one, and the axis stays aligned with the canvas
12+
- Fixed `QwtGraphic.toPixmap(size)`, `QwtGraphic.toImage()` and `QwtGraphic.toImage(size)`, which raised a `TypeError` (possibly aborting the process, as the painter was still active on the destroyed pixmap or image) instead of rendering the graphic: they now render it like `QwtGraphic.toPixmap()` ([Issue #115](https://github.com/PlotPyStack/PythonQwt/issues/115))
13+
- Fixed `QwtPlotMarker.legendIcon` for markers with a symbol, which raised an `AssertionError` or a `TypeError`: such markers can now be shown in a legend ([Issue #115](https://github.com/PlotPyStack/PythonQwt/issues/115))
14+
- Fixed `QwtPlotGrid.make`, which raised a `TypeError` when only some of the minor pen arguments (`mincolor`, `minwidth`, `minstyle`) were given: each of them is now honored, the missing ones taking their documented default value ([Issue #115](https://github.com/PlotPyStack/PythonQwt/issues/115))
15+
- Fixed `QwtPowerTransform`, which raised a `ValueError` when drawing a curve on an axis with a power scale (curve data are mapped as NumPy arrays), and an `AttributeError` with NumPy < 2.0 (`numpy.pow` does not exist there): it now transforms scalars (with unchanged results) as well as NumPy arrays, with any supported NumPy version ([Issue #115](https://github.com/PlotPyStack/PythonQwt/issues/115))
16+
- Fixed `QwtPlotDirectPainter.setAttribute`, which did not set or clear attributes as requested: enabling `AtomicPainter` twice disabled it, and `FullRepaint` or `CopyBackingStore` could not be disabled anymore once enabled ([Issue #115](https://github.com/PlotPyStack/PythonQwt/issues/115))
17+
- Fixed painting into a `QwtGraphic` with clipping enabled, which raised a `TypeError` on each paint operation (aborting the process with PyQt's default exception hook): clipped paint operations are now recorded, and the bounding rectangle of the graphic is clipped as in Qwt ([Issue #115](https://github.com/PlotPyStack/PythonQwt/issues/115))
18+
- Fixed the `QwtPainterCommand` copy constructor (`QwtPainterCommand(other)`), which raised an `AttributeError` for state commands and a `TypeError` (Qt objects cannot be pickled) for pixmap and image commands: commands of every type can now be copied, the copy being independent of the original ([Issue #115](https://github.com/PlotPyStack/PythonQwt/issues/115))
19+
- Fixed `QwtDynGridLayout` (used by `QwtLegend`), whose `numRows()` returned a float (e.g. `2.5` for 3 items on 2 columns) and which raised a `TypeError` when laying out its items with vertical expansion (`setExpandingDirections(Qt.Vertical)`) ([Issue #115](https://github.com/PlotPyStack/PythonQwt/issues/115))
20+
- Fixed canvas style sheets with rounded borders (e.g. `plot.canvas().setStyleSheet("border: 2px solid black; border-radius: 8px;")`), which raised a `TypeError` on each style change, resize and paint event (aborting the process with PyQt's default exception hook): the rounded border of the style sheet is now taken into account to clip the plot items and to fill the corners with the background of the parent widget, as in Qwt ([Issue #115](https://github.com/PlotPyStack/PythonQwt/issues/115))
21+
- Fixed the image formats listed by the file dialog of `QwtPlotRenderer.exportTo`, which read `*.b'bmp' *.b'png' ...` instead of `*.bmp *.png ...` ([Issue #115](https://github.com/PlotPyStack/PythonQwt/issues/115))
22+
- Fixed `QwtInterval.intersect` (and the `&` operator), which returned an invalid interval for partially overlapping intervals (e.g. `QwtInterval(0, 2) & QwtInterval(1, 3)` instead of `[1, 2]`), and `QwtInterval.intersects`, which gave the opposite result for intervals sharing a bound (e.g. `False` for `[0, 1]` and `[1, 2]`) ([Issue #115](https://github.com/PlotPyStack/PythonQwt/issues/115))
23+
24+
### Other changes
25+
26+
- The source distribution now includes the Qt Designer plugin (`qtdesigner/qwtplugin.py`, see the "Qt Designer" section of the documentation) and this changelog ([Issue #115](https://github.com/PlotPyStack/PythonQwt/issues/115))
27+
- Documentation: the reference of the `qwt.qtdesigner` module, whose docstrings follow the Google style, is now rendered properly (it produced formatting errors), and `QwtDateTimeScaleDraw` and `QwtDateTimeScaleEngine` are now documented ([Issue #115](https://github.com/PlotPyStack/PythonQwt/issues/115))
1228

1329
## Version 0.16.3
1430

‎MANIFEST.in‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
graft doc
2-
include *.desktop
2+
include *.desktop
3+
include CHANGELOG.md
4+
include qtdesigner/*.py

‎doc/conf.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
# Add any Sphinx extension module names here, as strings. They can be extensions
2121
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
22-
extensions = ["sphinx.ext.autodoc"]
22+
extensions = ["sphinx.ext.autodoc", "sphinx.ext.napoleon"]
2323
try:
2424
import sphinx.ext.viewcode # noqa: F401
2525

‎qwt/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
from qwt.text import QwtText # noqa: F401
6464
from qwt.toqimage import array_to_qimage as toQImage # noqa: F401
6565

66-
__version__ = "0.16.3"
66+
__version__ = "0.17.0"
6767
QWT_VERSION_STR = "6.1.5"
6868

6969

‎qwt/dyngrid_layout.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ def setGeometry(self, rect):
159159
if self.isEmpty():
160160
return
161161
self.__data.numColumns = self.columnsForWidth(rect.width())
162-
self.__data.numRows = self.itemCount() / self.__data.numColumns
162+
self.__data.numRows = self.itemCount() // self.__data.numColumns
163163
if self.itemCount() % self.__data.numColumns:
164164
self.__data.numRows += 1
165165
itemGeometries = self.layoutItems(rect, self.__data.numColumns)
@@ -314,7 +314,7 @@ def stretchGrid(self, rect, numColumns, rowHeight, colWidth):
314314
colWidth[col] += space
315315
xDelta -= space
316316
if expandV:
317-
numRows = self.itemCount() / numColumns
317+
numRows = self.itemCount() // numColumns
318318
if self.itemCount() % numColumns:
319319
numRows += 1
320320
yDelta = rect.height() - hmargins - (numRows - 1) * self.spacing()

‎qwt/graphic.py‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
import math
1717

18-
from qtpy.QtCore import QObject, QPointF, QRect, QRectF, QSize, QSizeF, Qt
18+
from qtpy.QtCore import QObject, QPointF, QRectF, QSize, QSizeF, Qt
1919
from qtpy.QtGui import (
2020
QImage,
2121
QPaintEngine,
@@ -603,7 +603,7 @@ def toPixmap(self, *args):
603603
aspectRatioMode = args[-1]
604604
pixmap = QPixmap(size)
605605
pixmap.fill(Qt.transparent)
606-
r = QRect(0, 0, size.width(), size.height())
606+
r = QRectF(0.0, 0.0, size.width(), size.height())
607607
painter = QPainter(pixmap)
608608
self.render(painter, r, aspectRatioMode)
609609
painter.end()
@@ -652,7 +652,7 @@ def toImage(self, *args):
652652
h = math.ceil(sz.height())
653653
image = QImage(w, h, QImage.Format_ARGB32)
654654
image.fill(0)
655-
r = QRect(0, 0, sz.width(), sz.height())
655+
r = QRectF(0.0, 0.0, sz.width(), sz.height())
656656
painter = QPainter(image)
657657
self.render(painter, r, Qt.KeepAspectRatio)
658658
painter.end()
@@ -664,9 +664,10 @@ def toImage(self, *args):
664664
aspectRatioMode = args[-1]
665665
image = QImage(size, QImage.Format_ARGB32_Premultiplied)
666666
image.fill(0)
667-
r = QRect(0, 0, size.width(), size.height())
667+
r = QRectF(0.0, 0.0, size.width(), size.height())
668668
painter = QPainter(image)
669669
self.render(painter, r, aspectRatioMode)
670+
painter.end()
670671
return image
671672

672673
def drawPath(self, path):
@@ -756,7 +757,7 @@ def updateBoundingRect(self, rect):
756757
br = QRectF(rect)
757758
painter = self.paintEngine().painter()
758759
if painter and painter.hasClipping():
759-
cr = painter.clipRegion().boundingRect()
760+
cr = QRectF(painter.clipRegion().boundingRect())
760761
cr = painter.transform().mapRect(cr)
761762
br &= cr
762763
if self.__data.boundingRect.width() < 0:

‎qwt/interval.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ def intersect(self, other):
296296
if i1.borderFlags() & self.ExcludeMinimum:
297297
i1, i2 = i2, i1
298298

299-
if i1.maxValue() < i2.maxValue():
299+
if i1.maxValue() < i2.minValue():
300300
return QwtInterval()
301301

302302
if i1.maxValue() == i2.minValue():
@@ -346,9 +346,9 @@ def intersects(self, other):
346346
if i1.maxValue() > i2.minValue():
347347
return True
348348
elif i1.maxValue() == i2.minValue():
349-
return (
349+
return not (
350350
i1.borderFlags() & self.ExcludeMaximum
351-
and i2.borderFlags() & self.ExcludeMinimum
351+
or i2.borderFlags() & self.ExcludeMinimum
352352
)
353353
return False
354354

‎qwt/painter_command.py‎

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,44 @@
1313
:members:
1414
"""
1515

16-
import copy
17-
18-
from qtpy.QtGui import QPaintEngine, QPainterPath
16+
from qtpy.QtCore import QPointF, QRectF
17+
from qtpy.QtGui import (
18+
QBrush,
19+
QFont,
20+
QImage,
21+
QPaintEngine,
22+
QPainterPath,
23+
QPen,
24+
QPixmap,
25+
QRegion,
26+
QTransform,
27+
)
28+
29+
# Qt value types stored in painter command data, copied with their copy
30+
# constructor (other stored values are immutable: enums, flags, numbers...)
31+
_QT_VALUE_TYPES = (
32+
QBrush,
33+
QFont,
34+
QImage,
35+
QPainterPath,
36+
QPen,
37+
QPixmap,
38+
QPointF,
39+
QRectF,
40+
QRegion,
41+
QTransform,
42+
)
43+
44+
45+
def _copy_data(data):
46+
"""Return a copy of painter command data, like the Qwt C++ copy
47+
constructors of `PixmapData`, `ImageData` and `StateData`"""
48+
result = data.__class__()
49+
for name, value in vars(data).items():
50+
if isinstance(value, _QT_VALUE_TYPES):
51+
value = type(value)(value)
52+
setattr(result, name, value)
53+
return result
1954

2055

2156
def _flag_int(flag):
@@ -215,11 +250,11 @@ def copy(self, other):
215250
if other.__type == self.Path:
216251
self.__path = QPainterPath(other.__path)
217252
elif other.__type == self.Pixmap:
218-
self.__pixmapData = copy.deepcopy(other.__pixmapData)
253+
self.__pixmapData = _copy_data(other.__pixmapData)
219254
elif other.__type == self.Image:
220-
self.__imageData = copy.deepcopy(other.__imageData)
255+
self.__imageData = _copy_data(other.__imageData)
221256
elif other.__type == self.State:
222-
self.__stateData == copy.deepcopy(other.__stateData)
257+
self.__stateData = _copy_data(other.__stateData)
223258

224259
def reset(self):
225260
self.__type = self.Invalid

‎qwt/plot_canvas.py‎

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,17 @@
1616
import weakref
1717
from collections.abc import Sequence
1818

19-
from qtpy.QtCore import QEvent, QObject, QPoint, QPointF, QRect, QRectF, QSize, Qt
19+
from qtpy.QtCore import (
20+
QEvent,
21+
QObject,
22+
QPoint,
23+
QPointF,
24+
QRect,
25+
QRectF,
26+
QSize,
27+
QSizeF,
28+
Qt,
29+
)
2030
from qtpy.QtGui import (
2131
QBrush,
2232
QGradient,
@@ -81,7 +91,7 @@ def drawRects(self, rects, count):
8191
)
8292

8393
def drawPath(self, path):
84-
rect = QRectF(QPointF(0.0, 0.0), self.__size)
94+
rect = QRectF(QPointF(0.0, 0.0), QSizeF(self.__size))
8595
if path.controlPointRect().contains(rect.center()):
8696
self.setCornerRects(path)
8797
self.alignCornerRects(rect)
@@ -188,9 +198,11 @@ def qwtCombinePathList(rect, pathList):
188198
if not pathList:
189199
return QPainterPath()
190200

191-
ordered = [None] * 8
201+
rect = QRectF(rect)
202+
ordered = [QPainterPath() for _i in range(8)]
192203
for subPath in pathList:
193204
index = -1
205+
subPath = QPainterPath(subPath)
194206
br = subPath.controlPointRect()
195207
if br.center().x() < rect.center().x():
196208
if br.center().y() < rect.center().y():
@@ -199,7 +211,7 @@ def qwtCombinePathList(rect, pathList):
199211
else:
200212
index = 0
201213
else:
202-
if abs(br.bottom() - rect.bottom) < abs(br.left() - rect.left()):
214+
if abs(br.bottom() - rect.bottom()) < abs(br.left() - rect.left()):
203215
index = 6
204216
else:
205217
index = 7
@@ -297,7 +309,8 @@ def qwtFillBackground(*args):
297309
else:
298310
clipRegion = widget.contentsRect()
299311
bgWidget = qwtBackgroundWidget(widget.parentWidget())
300-
for rect in fillRects:
312+
for fillRect in fillRects:
313+
rect = QRectF(fillRect).toAlignedRect()
301314
if clipRegion.intersects(rect):
302315
pm = QPixmap(rect.size())
303316
QwtPainter.fillPixmap(

‎qwt/plot_directpainter.py‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,13 @@ def setAttribute(self, attribute, on=True):
119119
120120
:py:meth:`testAttribute()`
121121
"""
122-
if self.testAttribute(attribute) != on:
123-
self.__data.attributes |= attribute
124-
else:
125-
self.__data.attributes &= ~attribute
126-
if attribute == self.AtomicPainter and on:
127-
self.reset()
122+
if bool(self.testAttribute(attribute)) != on:
123+
if on:
124+
self.__data.attributes |= attribute
125+
else:
126+
self.__data.attributes &= ~attribute
127+
if attribute == self.AtomicPainter and on:
128+
self.reset()
128129

129130
def testAttribute(self, attribute):
130131
"""

0 commit comments

Comments
 (0)