2 # SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com>
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # ---------------------------------------------------------------------------------------------------------------------
8 from math
import floor
, ceil
10 from qt_compat
import qt_config
13 from PyQt5
.QtCore
import pyqtSignal
, QT_VERSION
, Qt
, QRectF
, QTimer
, QEvent
, QPoint
14 from PyQt5
.QtGui
import QBrush
, QColor
, QCursor
, QPainter
, QPainterPath
, QPen
, QPixmap
15 from PyQt5
.QtWidgets
import QFrame
, QGraphicsScene
17 from PyQt6
.QtCore
import pyqtSignal
, QT_VERSION
, Qt
, QRectF
, QTimer
, QEvent
, QPoint
18 from PyQt6
.QtGui
import QBrush
, QColor
, QCursor
, QPainter
, QPainterPath
, QPen
, QPixmap
19 from PyQt6
.QtWidgets
import QFrame
, QGraphicsScene
21 # ---------------------------------------------------------------------------------------------------------------------
22 # Antialiasing settings
24 from patchcanvas
import options
, ANTIALIASING_FULL
26 # ---------------------------------------------------------------------------------------------------------------------
29 class CanvasPreviewFrame(QFrame
):
30 miniCanvasMoved
= pyqtSignal(float, float)
53 _RUBBERBAND_BLENDING_DEFAULT
= 0
54 _RUBBERBAND_BLENDING_PLUS
= 1
55 _RUBBERBAND_BLENDING_DIFF
= 2
57 # -----------------------------------------------------------------------------------------------------------------
59 def __init__(self
, parent
):
60 QFrame
.__init
__(self
, parent
)
62 #self.setMinimumWidth(210)
63 #self.setMinimumHeight(162)
64 #self.setMaximumHeight(162)
66 self
.fRealParent
= None
67 self
.fInternalWidth
= 210-6 # -4 for width + -1px*2 bounds
68 self
.fInternalHeight
= 162-5 # -3 for height + -1px*2 bounds
69 self
.fInternalRatio
= self
.fInternalWidth
/ self
.fInternalHeight
72 self
.fRenderSource
= QRectF(0.0, 0.0, 0.0, 0.0)
73 self
.fRenderTarget
= QRectF(0.0, 0.0, 0.0, 0.0)
74 self
.fUseCustomPaint
= False
75 self
.fFrameWidth
= 0.0
81 self
.fViewBg
= QColor(0, 0, 0)
82 self
.fViewBrush
= QBrush(QColor(75, 75, 255, 30))
83 self
.fViewPen
= QPen(Qt
.blue
, 1)
84 self
.fViewRect
= [3.0, 3.0, 10.0, 10.0]
86 self
.fMouseMode
= self
._MOUSE
_MODE
_NONE
87 self
.fMouseLeftDown
= False
88 self
.fMouseRightDown
= False
89 self
.fMouseInitialZoomPos
= None
91 self
.fRubberBandBlending
= self
._RUBBERBAND
_BLENDING
_DEFAULT
93 self
.fZoomCursors
= [None for _
in range(self
._kCursorZoomCount
)]
94 self
.fZoomCursors
[self
._kCursorName
] = "black"
95 self
.fZoomCursors
[self
._kCursorZoom
] = QCursor(QPixmap(":/cursors/zoom_black.png"), 8, 7)
96 self
.fZoomCursors
[self
._kCursorZoomIn
] = QCursor(QPixmap(":/cursors/zoom-in_black.png"), 8, 7)
97 self
.fZoomCursors
[self
._kCursorZoomOut
] = QCursor(QPixmap(":/cursors/zoom-out_black.png"), 8, 7)
99 def init(self
, scene
: QGraphicsScene
, realWidth
: float, realHeight
: float, useCustomPaint
: bool = False):
101 self
.fRenderSource
= QRectF(0.0, 0.0, realWidth
, realHeight
)
102 self
.fInternalRatio
= realWidth
/ realHeight
105 if self
.fUseCustomPaint
!= useCustomPaint
:
106 self
.fUseCustomPaint
= useCustomPaint
109 def setRealParent(self
, parent
):
110 self
.fRealParent
= parent
112 # -----------------------------------------------------------------------------------------------------------------
114 def setViewPosX(self
, xp
: float):
115 self
.fViewRect
[self
._kRectX
] = xp
* (self
.fInternalWidth
- self
.fViewRect
[self
._kRectWidth
]/self
.fScale
)
118 def setViewPosY(self
, yp
: float):
119 self
.fViewRect
[self
._kRectY
] = yp
* (self
.fInternalHeight
- self
.fViewRect
[self
._kRectHeight
]/self
.fScale
)
122 def setViewScale(self
, scale
: float):
125 if self
.fRealParent
is not None:
126 QTimer
.singleShot(0, self
.fRealParent
.slot_miniCanvasCheckAll
)
128 def setViewSize(self
, width
: float, height
: float):
129 self
.fViewRect
[self
._kRectWidth
] = width
* self
.fInternalWidth
130 self
.fViewRect
[self
._kRectHeight
] = height
* self
.fInternalHeight
133 def setViewTheme(self
, bgColor
, brushColor
, penColor
):
134 bg_black
= bgColor
.blackF()
135 brush_black
= brushColor
.blackF()
136 r0
, g0
, b0
, _
= bgColor
.getRgb()
137 r1
, g1
, b1
, _
= brushColor
.getRgb()
139 if brush_black
< bg_black
:
140 self
.fRubberBandBlending
= self
._RUBBERBAND
_BLENDING
_PLUS
141 brushColor
= QColor(r1
-r0
, g1
-g0
, b1
-b0
, 40)
142 elif bg_black
< brush_black
:
143 self
.fRubberBandBlending
= self
._RUBBERBAND
_BLENDING
_DIFF
144 brushColor
= QColor(r0
-r1
, g0
-g1
, b0
-b1
, 40)
147 self
.fRubberBandBlending
= self
._RUBBERBAND
_BLENDING
_DEFAULT
149 penColor
.setAlpha(100)
150 self
.fViewBg
= bgColor
151 self
.fViewBrush
= QBrush(brushColor
)
152 self
.fViewPen
= QPen(penColor
, 1)
154 cursorName
= "black" if bg_black
< 0.5 else "white"
155 if self
.fZoomCursors
[self
._kCursorName
] != cursorName
:
156 prefix
= ":/cursors/zoom"
157 self
.fZoomCursors
[self
._kCursorName
] = cursorName
158 self
.fZoomCursors
[self
._kCursorZoom
] = QCursor(QPixmap(f
"{prefix}_{cursorName}.png"), 8, 7)
159 self
.fZoomCursors
[self
._kCursorZoomIn
] = QCursor(QPixmap(f
"{prefix}-in_{cursorName}.png"), 8, 7)
160 self
.fZoomCursors
[self
._kCursorZoomOut
] = QCursor(QPixmap(f
"{prefix}-out_{cursorName}.png"), 8, 7)
162 # -----------------------------------------------------------------------------------------------------------------
164 def changeEvent(self
, event
):
165 if event
.type() in (QEvent
.StyleChange
, QEvent
.PaletteChange
):
167 QFrame
.changeEvent(self
, event
)
169 def mousePressEvent(self
, event
):
170 if event
.button() == Qt
.LeftButton
:
172 self
.fMouseLeftDown
= True
173 self
._updateMouseMode
(event
)
175 if event
.button() == Qt
.RightButton
:
177 self
.fMouseRightDown
= True
178 self
._updateMouseMode
(event
)
180 if event
.button() == Qt
.MiddleButton
:
182 self
.fMouseLeftDown
= True
183 self
.fMouseRightDown
= True
184 self
._updateMouseMode
(event
)
186 QFrame
.mouseMoveEvent(self
, event
)
188 def mouseMoveEvent(self
, event
):
189 if self
.fMouseMode
== self
._MOUSE
_MODE
_MOVE
:
191 if QT_VERSION
>= 0x60000:
192 pos
= event
.position()
198 self
._moveViewRect
(x
, y
)
200 if self
.fMouseMode
== self
._MOUSE
_MODE
_SCALE
:
202 self
._scaleViewRect
(event
.globalY())
204 QFrame
.mouseMoveEvent(self
, event
)
206 def mouseReleaseEvent(self
, event
):
207 if event
.button() == Qt
.LeftButton
:
209 self
.fMouseLeftDown
= False
210 self
._updateMouseMode
(event
)
212 if event
.button() == Qt
.RightButton
:
214 self
.fMouseRightDown
= False
215 self
._updateMouseMode
(event
)
217 if event
.button() == Qt
.MiddleButton
:
219 self
.fMouseLeftDown
= event
.buttons() & Qt
.LeftButton
220 self
.fMouseRightDown
= event
.buttons() & Qt
.RightButton
221 self
._updateMouseMode
(event
)
223 QFrame
.mouseReleaseEvent(self
, event
)
225 def wheelEvent(self
, event
):
226 if self
.fScene
is None:
227 QFrame
.wheelEvent(self
, event
)
231 self
.fScene
.zoom_wheel(event
.angleDelta().y())
233 def paintEvent(self
, event
):
234 if self
.fScene
is None:
235 QFrame
.paintEvent(self
, event
)
238 painter
= QPainter(self
)
239 painter
.setRenderHint(QPainter
.Antialiasing
, bool(options
.antialiasing
== ANTIALIASING_FULL
))
241 # Brightness-aware out-of-canvas shading
242 bg_color
= self
.fViewBg
243 bg_black
= bg_color
.black()
244 bg_shade
= -12 if bg_black
< 127 else 12
245 r
,g
,b
,_
= bg_color
.getRgb()
246 bg_color
= QColor(r
+bg_shade
, g
+bg_shade
, b
+bg_shade
)
248 frameWidth
= self
.fFrameWidth
249 if self
.fUseCustomPaint
:
251 color
= QColor
.fromHsv(40, 0, 255-max(210, bg_color
.black(), bg_black
))
252 painter
.setBrush(Qt
.transparent
)
253 painter
.setPen(color
)
254 painter
.drawRect(QRectF(0.5, 0.5, self
.width()-1, self
.height()-1))
257 painter
.setBrush(bg_color
)
258 painter
.setPen(bg_color
)
259 painter
.drawRect(QRectF(1.5, 1.5, self
.width()-3, self
.height()-3))
261 use_rounding
= int(frameWidth
> 1)
263 rounding
= 0.5 * use_rounding
264 painter
.setBrush(bg_color
)
265 painter
.setPen(bg_color
)
266 painter
.drawRoundedRect(QRectF(0.5+frameWidth
,
268 self
.width()-1-frameWidth
*2,
269 self
.height()-1-frameWidth
*2), rounding
, rounding
)
271 clipPath
= QPainterPath()
272 rounding
= 1.0 * use_rounding
273 clipPath
.addRoundedRect(QRectF(frameWidth
,
275 self
.width()-frameWidth
*2,
276 self
.height()-frameWidth
*2), rounding
, rounding
)
277 painter
.setClipPath(clipPath
)
279 self
.fScene
.render(painter
, self
.fRenderTarget
, self
.fRenderSource
, Qt
.KeepAspectRatio
)
281 # Allow cursor frame to look joined with minicanvas frame
282 painter
.setClipping(False)
284 width
= self
.fViewRect
[self
._kRectWidth
]/self
.fScale
285 height
= self
.fViewRect
[self
._kRectHeight
]/self
.fScale
288 lineHinting
= self
.fViewPen
.widthF() / 2
289 x
= self
.fViewRect
[self
._kRectX
]+self
.fInitialX
290 y
= self
.fViewRect
[self
._kRectY
]+self
.fInitialY
296 ceil(width
+x
-scr_x
)-lineHinting
*2,
297 ceil(height
+y
-scr_y
)-lineHinting
*2 )
299 if self
.fRubberBandBlending
== self
._RUBBERBAND
_BLENDING
_PLUS
:
300 painter
.setCompositionMode(QPainter
.CompositionMode_Plus
)
301 elif self
.fRubberBandBlending
== self
._RUBBERBAND
_BLENDING
_DIFF
:
302 painter
.setCompositionMode(QPainter
.CompositionMode_Difference
)
304 painter
.setBrush(self
.fViewBrush
)
305 painter
.setPen(Qt
.NoPen
)
306 painter
.drawRect(rect
)
308 painter
.setCompositionMode(QPainter
.CompositionMode_SourceOver
)
309 painter
.setBrush(Qt
.NoBrush
)
310 painter
.setPen(self
.fViewPen
)
311 painter
.drawRect(rect
)
313 if self
.fUseCustomPaint
:
316 QFrame
.paintEvent(self
, event
)
318 def resizeEvent(self
, event
):
321 height
= size
.height()
322 extRatio
= (width
- self
.fFrameWidth
* 2) / (height
- self
.fFrameWidth
* 2)
323 if extRatio
>= self
.fInternalRatio
:
324 self
.fInternalHeight
= floor(height
- self
.fFrameWidth
* 2)
325 self
.fInternalWidth
= floor(self
.fInternalHeight
* self
.fInternalRatio
)
326 self
.fInitialX
= floor(float(width
- self
.fInternalWidth
) / 2.0)
327 self
.fInitialY
= self
.fFrameWidth
329 self
.fInternalWidth
= floor(width
- self
.fFrameWidth
* 2)
330 self
.fInternalHeight
= floor(self
.fInternalWidth
/ self
.fInternalRatio
)
331 self
.fInitialX
= self
.fFrameWidth
332 self
.fInitialY
= floor(float(height
- self
.fInternalHeight
) / 2.0)
334 self
.fRenderTarget
= QRectF(self
.fInitialX
,
336 float(self
.fInternalWidth
),
337 float(self
.fInternalHeight
))
339 if self
.fRealParent
is not None:
340 QTimer
.singleShot(0, self
.fRealParent
.slot_miniCanvasCheckAll
)
342 QFrame
.resizeEvent(self
, event
)
344 # -----------------------------------------------------------------------------------------------------------------
346 def _moveViewRect(self
, x
: float, y
: float):
347 if self
.fScene
is None:
354 rCentX
= self
.fViewRect
[self
._kRectWidth
] / self
.fScale
/ 2
355 rCentY
= self
.fViewRect
[self
._kRectHeight
] / self
.fScale
/ 2
359 elif x
> self
.fInternalWidth
- rCentX
:
360 x
= self
.fInternalWidth
- rCentX
366 elif y
> self
.fInternalHeight
- rCentY
:
367 y
= self
.fInternalHeight
- rCentY
371 globalPos
= self
.mapToGlobal(QPoint(int(self
.fInitialX
+ x
), int(self
.fInitialY
+ y
)))
372 self
.cursor().setPos(globalPos
)
374 x
= self
.fRenderSource
.width() * x
/ self
.fInternalWidth
375 y
= self
.fRenderSource
.height() * y
/ self
.fInternalHeight
376 self
.fScene
.m_view
.centerOn(x
, y
)
378 def _scaleViewRect(self
, globalY
: int):
379 if self
.fScene
is None:
382 dy
= self
.fMouseInitialZoomPos
.y() - globalY
384 self
.setCursor(self
.fZoomCursors
[self
._kCursorZoomIn
if dy
> 0 else self
._kCursorZoomOut
])
385 self
.fScene
.zoom_wheel(dy
)
387 self
.cursor().setPos(self
.fMouseInitialZoomPos
)
389 def _updateMouseMode(self
, event
):
390 if self
.fMouseLeftDown
and self
.fMouseRightDown
:
391 self
.fMouseInitialZoomPos
= event
.globalPos()
392 self
.setCursor(self
.fZoomCursors
[self
._kCursorZoom
])
393 self
.fMouseMode
= self
._MOUSE
_MODE
_SCALE
395 elif self
.fMouseLeftDown
:
396 self
.setCursor(QCursor(Qt
.SizeAllCursor
))
397 if self
.fMouseMode
== self
._MOUSE
_MODE
_NONE
:
398 if QT_VERSION
>= 0x60000:
399 pos
= event
.position()
405 self
._moveViewRect
(x
, y
)
406 self
.fMouseMode
= self
._MOUSE
_MODE
_MOVE
410 self
.fMouseMode
= self
._MOUSE
_MODE
_NONE
412 def _updateStyle(self
):
413 self
.fFrameWidth
= 1 if self
.fUseCustomPaint
else self
.frameWidth()
415 # ---------------------------------------------------------------------------------------------------------------------