- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
-
- # Custom Mini Canvas Preview, a custom Qt widget
- # Copyright (C) 2011-2020 Filipe Coelho <falktx@falktx.com>
- #
- # This program is free software; you can redistribute it and/or
- # modify it under the terms of the GNU General Public License as
- # published by the Free Software Foundation; either version 2 of
- # the License, or any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # For a full copy of the GNU General Public License see the doc/GPL.txt file.
-
- # ---------------------------------------------------------------------------------------------------------------------
- # Imports (Global)
-
- from math import floor, ceil
-
- from PyQt5.QtCore import pyqtSignal, Qt, QRectF, QTimer, QEvent, QPoint
- from PyQt5.QtGui import QBrush, QColor, QCursor, QPainter, QPainterPath, QPen, QPixmap
- from PyQt5.QtWidgets import QFrame, QGraphicsScene
-
- # ---------------------------------------------------------------------------------------------------------------------
- # Antialiasing settings
-
- from patchcanvas import options, ANTIALIASING_FULL
-
- # ---------------------------------------------------------------------------------------------------------------------
- # Widget Class
-
- class CanvasPreviewFrame(QFrame):
- miniCanvasMoved = pyqtSignal(float, float)
-
- # x = 2
- # y = 2
- # w = width - 4
- # h = height - 3
- # bounds -1 px
-
- _kRectX = 0
- _kRectY = 1
- _kRectWidth = 2
- _kRectHeight = 3
-
- _kCursorName = 0
- _kCursorZoom = 1
- _kCursorZoomIn = 2
- _kCursorZoomOut = 3
- _kCursorZoomCount = 4
-
- _MOUSE_MODE_NONE = 0
- _MOUSE_MODE_MOVE = 1
- _MOUSE_MODE_SCALE = 2
-
- _RUBBERBAND_BLENDING_DEFAULT = 0
- _RUBBERBAND_BLENDING_PLUS = 1
- _RUBBERBAND_BLENDING_DIFF = 2
-
- # -----------------------------------------------------------------------------------------------------------------
-
- def __init__(self, parent):
- QFrame.__init__(self, parent)
-
- #self.setMinimumWidth(210)
- #self.setMinimumHeight(162)
- #self.setMaximumHeight(162)
-
- self.fRealParent = None
- self.fInternalWidth = 210-6 # -4 for width + -1px*2 bounds
- self.fInternalHeight = 162-5 # -3 for height + -1px*2 bounds
- self.fInternalRatio = self.fInternalWidth / self.fInternalHeight
-
- self.fScene = None
- self.fRenderSource = QRectF(0.0, 0.0, 0.0, 0.0)
- self.fRenderTarget = QRectF(0.0, 0.0, 0.0, 0.0)
- self.fUseCustomPaint = False
- self.fFrameWidth = 0.0
-
- self.fInitialX = 0.0
- self.fInitialY = 0.0
- self.fScale = 1.0
-
- self.fViewBg = QColor(0, 0, 0)
- self.fViewBrush = QBrush(QColor(75, 75, 255, 30))
- self.fViewPen = QPen(Qt.blue, 1)
- self.fViewRect = [3.0, 3.0, 10.0, 10.0]
-
- self.fMouseMode = self._MOUSE_MODE_NONE
- self.fMouseLeftDown = False
- self.fMouseRightDown = False
- self.fMouseInitialZoomPos = None
-
- self.fRubberBandBlending = self._RUBBERBAND_BLENDING_DEFAULT
-
- self.fZoomCursors = [None for _ in range(self._kCursorZoomCount)]
- self.fZoomCursors[self._kCursorName] = "black"
- self.fZoomCursors[self._kCursorZoom] = QCursor(QPixmap(":/cursors/zoom_black.png"), 8, 7)
- self.fZoomCursors[self._kCursorZoomIn] = QCursor(QPixmap(":/cursors/zoom-in_black.png"), 8, 7)
- self.fZoomCursors[self._kCursorZoomOut] = QCursor(QPixmap(":/cursors/zoom-out_black.png"), 8, 7)
-
- def init(self, scene: QGraphicsScene, realWidth: float, realHeight: float, useCustomPaint: bool = False):
- self.fScene = scene
- self.fRenderSource = QRectF(0.0, 0.0, realWidth, realHeight)
- self.fInternalRatio = realWidth / realHeight
- self._updateStyle()
-
- if self.fUseCustomPaint != useCustomPaint:
- self.fUseCustomPaint = useCustomPaint
- self.repaint()
-
- def setRealParent(self, parent):
- self.fRealParent = parent
-
- # -----------------------------------------------------------------------------------------------------------------
-
- def setViewPosX(self, xp: float):
- self.fViewRect[self._kRectX] = xp * (self.fInternalWidth - self.fViewRect[self._kRectWidth]/self.fScale)
- self.update()
-
- def setViewPosY(self, yp: float):
- self.fViewRect[self._kRectY] = yp * (self.fInternalHeight - self.fViewRect[self._kRectHeight]/self.fScale)
- self.update()
-
- def setViewScale(self, scale: float):
- self.fScale = scale
-
- if self.fRealParent is not None:
- QTimer.singleShot(0, self.fRealParent.slot_miniCanvasCheckAll)
-
- def setViewSize(self, width: float, height: float):
- self.fViewRect[self._kRectWidth] = width * self.fInternalWidth
- self.fViewRect[self._kRectHeight] = height * self.fInternalHeight
- self.update()
-
- def setViewTheme(self, bgColor, brushColor, penColor):
- bg_black = bgColor.blackF()
- brush_black = brushColor.blackF()
- r0, g0, b0, _ = bgColor.getRgb()
- r1, g1, b1, _ = brushColor.getRgb()
-
- if brush_black < bg_black:
- self.fRubberBandBlending = self._RUBBERBAND_BLENDING_PLUS
- brushColor = QColor(r1-r0, g1-g0, b1-b0, 40)
- elif bg_black < brush_black:
- self.fRubberBandBlending = self._RUBBERBAND_BLENDING_DIFF
- brushColor = QColor(r0-r1, g0-g1, b0-b1, 40)
- else:
- bgColor.setAlpha(40)
- self.fRubberBandBlending = self._RUBBERBAND_BLENDING_DEFAULT
-
- penColor.setAlpha(100)
- self.fViewBg = bgColor
- self.fViewBrush = QBrush(brushColor)
- self.fViewPen = QPen(penColor, 1)
-
- cursorName = "black" if bg_black < 0.5 else "white"
- if self.fZoomCursors[self._kCursorName] != cursorName:
- prefix = ":/cursors/zoom"
- self.fZoomCursors[self._kCursorName] = cursorName
- self.fZoomCursors[self._kCursorZoom] = QCursor(QPixmap("{}_{}.png".format(prefix, cursorName)), 8, 7)
- self.fZoomCursors[self._kCursorZoomIn] = QCursor(QPixmap("{}-in_{}.png".format(prefix, cursorName)), 8, 7)
- self.fZoomCursors[self._kCursorZoomOut] = QCursor(QPixmap("{}-out_{}.png".format(prefix, cursorName)), 8, 7)
-
- # -----------------------------------------------------------------------------------------------------------------
-
- def changeEvent(self, event):
- if event.type() in (QEvent.StyleChange, QEvent.PaletteChange):
- self._updateStyle()
- QFrame.changeEvent(self, event)
-
- def mousePressEvent(self, event):
- if event.button() == Qt.LeftButton:
- event.accept()
- self.fMouseLeftDown = True
- self._updateMouseMode(event)
- return
- if event.button() == Qt.RightButton:
- event.accept()
- self.fMouseRightDown = True
- self._updateMouseMode(event)
- return
- if event.button() == Qt.MidButton:
- event.accept()
- self.fMouseLeftDown = True
- self.fMouseRightDown = True
- self._updateMouseMode(event)
- return
- QFrame.mouseMoveEvent(self, event)
-
- def mouseMoveEvent(self, event):
- if self.fMouseMode == self._MOUSE_MODE_MOVE:
- event.accept()
- self._moveViewRect(event.x(), event.y())
- return
- if self.fMouseMode == self._MOUSE_MODE_SCALE:
- event.accept()
- self._scaleViewRect(event.globalY())
- return
- QFrame.mouseMoveEvent(self, event)
-
- def mouseReleaseEvent(self, event):
- if event.button() == Qt.LeftButton:
- event.accept()
- self.fMouseLeftDown = False
- self._updateMouseMode()
- return
- if event.button() == Qt.RightButton:
- event.accept()
- self.fMouseRightDown = False
- self._updateMouseMode(event)
- return
- if event.button() == Qt.MidButton:
- event.accept()
- self.fMouseLeftDown = event.buttons() & Qt.LeftButton
- self.fMouseRightDown = event.buttons() & Qt.RightButton
- self._updateMouseMode(event)
- return
- QFrame.mouseReleaseEvent(self, event)
-
- def wheelEvent(self, event):
- if self.fScene is None:
- QFrame.wheelEvent(self, event)
- return
-
- event.accept()
- self.fScene.zoom_wheel(event.angleDelta().y())
-
- def paintEvent(self, event):
- if self.fScene is None:
- QFrame.paintEvent(self, event)
- return
-
- painter = QPainter(self)
- painter.setRenderHint(QPainter.Antialiasing, bool(options.antialiasing == ANTIALIASING_FULL))
-
- # Brightness-aware out-of-canvas shading
- bg_color = self.fViewBg
- bg_black = bg_color.black()
- bg_shade = -12 if bg_black < 127 else 12
- r,g,b,_ = bg_color.getRgb()
- bg_color = QColor(r+bg_shade, g+bg_shade, b+bg_shade)
-
- frameWidth = self.fFrameWidth
- if self.fUseCustomPaint:
- # Inner shadow
- color = QColor.fromHsv(40, 0, 255-max(210, bg_color.black(), bg_black))
- painter.setBrush(Qt.transparent)
- painter.setPen(color)
- painter.drawRect(QRectF(0.5, 0.5, self.width()-1, self.height()-1))
-
- # Background
- painter.setBrush(bg_color)
- painter.setPen(bg_color)
- painter.drawRect(QRectF(1.5, 1.5, self.width()-3, self.height()-3))
- else:
- use_rounding = int(frameWidth > 1)
-
- rounding = 0.5 * use_rounding
- painter.setBrush(bg_color)
- painter.setPen(bg_color)
- painter.drawRoundedRect(QRectF(0.5+frameWidth,
- 0.5+frameWidth,
- self.width()-1-frameWidth*2,
- self.height()-1-frameWidth*2), rounding, rounding)
-
- clipPath = QPainterPath()
- rounding = 1.0 * use_rounding
- clipPath.addRoundedRect(QRectF(frameWidth,
- frameWidth,
- self.width()-frameWidth*2,
- self.height()-frameWidth*2), rounding, rounding)
- painter.setClipPath(clipPath)
-
- self.fScene.render(painter, self.fRenderTarget, self.fRenderSource, Qt.KeepAspectRatio)
-
- # Allow cursor frame to look joined with minicanvas frame
- painter.setClipping(False)
-
- width = self.fViewRect[self._kRectWidth]/self.fScale
- height = self.fViewRect[self._kRectHeight]/self.fScale
-
- # cursor
- lineHinting = self.fViewPen.widthF() / 2
- x = self.fViewRect[self._kRectX]+self.fInitialX
- y = self.fViewRect[self._kRectY]+self.fInitialY
- scr_x = floor(x)
- scr_y = floor(y)
- rect = QRectF(
- scr_x+lineHinting,
- scr_y+lineHinting,
- ceil(width+x-scr_x)-lineHinting*2,
- ceil(height+y-scr_y)-lineHinting*2 )
-
- if self.fRubberBandBlending == self._RUBBERBAND_BLENDING_PLUS:
- painter.setCompositionMode(QPainter.CompositionMode_Plus)
- elif self.fRubberBandBlending == self._RUBBERBAND_BLENDING_DIFF:
- painter.setCompositionMode(QPainter.CompositionMode_Difference)
-
- painter.setBrush(self.fViewBrush)
- painter.setPen(Qt.NoPen)
- painter.drawRect(rect)
-
- painter.setCompositionMode(QPainter.CompositionMode_SourceOver)
- painter.setBrush(Qt.NoBrush)
- painter.setPen(self.fViewPen)
- painter.drawRect(rect)
-
- if self.fUseCustomPaint:
- event.accept()
- else:
- QFrame.paintEvent(self, event)
-
- def resizeEvent(self, event):
- size = event.size()
- width = size.width()
- height = size.height()
- extRatio = (width - self.fFrameWidth * 2) / (height - self.fFrameWidth * 2)
- if extRatio >= self.fInternalRatio:
- self.fInternalHeight = floor(height - self.fFrameWidth * 2)
- self.fInternalWidth = floor(self.fInternalHeight * self.fInternalRatio)
- self.fInitialX = floor(float(width - self.fInternalWidth) / 2.0)
- self.fInitialY = self.fFrameWidth
- else:
- self.fInternalWidth = floor(width - self.fFrameWidth * 2)
- self.fInternalHeight = floor(self.fInternalWidth / self.fInternalRatio)
- self.fInitialX = self.fFrameWidth
- self.fInitialY = floor(float(height - self.fInternalHeight) / 2.0)
-
- self.fRenderTarget = QRectF(self.fInitialX,
- self.fInitialY,
- float(self.fInternalWidth),
- float(self.fInternalHeight))
-
- if self.fRealParent is not None:
- QTimer.singleShot(0, self.fRealParent.slot_miniCanvasCheckAll)
-
- QFrame.resizeEvent(self, event)
-
- # -----------------------------------------------------------------------------------------------------------------
-
- def _moveViewRect(self, x: float, y: float):
- if self.fScene is None:
- return
-
- x -= self.fInitialX
- y -= self.fInitialY
-
- fixPos = False
- rCentX = self.fViewRect[self._kRectWidth] / self.fScale / 2
- rCentY = self.fViewRect[self._kRectHeight] / self.fScale / 2
- if x < rCentX:
- x = rCentX
- fixPos = True
- elif x > self.fInternalWidth - rCentX:
- x = self.fInternalWidth - rCentX
- fixPos = True
-
- if y < rCentY:
- y = rCentY
- fixPos = True
- elif y > self.fInternalHeight - rCentY:
- y = self.fInternalHeight - rCentY
- fixPos = True
-
- if fixPos:
- globalPos = self.mapToGlobal(QPoint(self.fInitialX + x, self.fInitialY + y))
- self.cursor().setPos(globalPos)
-
- x = self.fRenderSource.width() * x / self.fInternalWidth
- y = self.fRenderSource.height() * y / self.fInternalHeight
- self.fScene.m_view.centerOn(x, y)
-
- def _scaleViewRect(self, globalY: int):
- if self.fScene is None:
- return
-
- dy = self.fMouseInitialZoomPos.y() - globalY
- if dy != 0:
- self.setCursor(self.fZoomCursors[self._kCursorZoomIn if dy > 0 else self._kCursorZoomOut])
- self.fScene.zoom_wheel(dy)
-
- self.cursor().setPos(self.fMouseInitialZoomPos)
-
- def _updateMouseMode(self, event = None):
- if self.fMouseLeftDown and self.fMouseRightDown:
- self.fMouseInitialZoomPos = event.globalPos()
- self.setCursor(self.fZoomCursors[self._kCursorZoom])
- self.fMouseMode = self._MOUSE_MODE_SCALE
-
- elif self.fMouseLeftDown:
- self.setCursor(QCursor(Qt.SizeAllCursor))
- if self.fMouseMode == self._MOUSE_MODE_NONE:
- self._moveViewRect(event.x(), event.y())
- self.fMouseMode = self._MOUSE_MODE_MOVE
-
- else:
- self.unsetCursor()
- self.fMouseMode = self._MOUSE_MODE_NONE
-
- def _updateStyle(self):
- self.fFrameWidth = 1 if self.fUseCustomPaint else self.frameWidth()
-
- # ---------------------------------------------------------------------------------------------------------------------
-
- if __name__ == '__main__':
- # pylint: disable=unused-import
- # pylint: disable=ungrouped-imports
- import sys
- import resources_rc
- from PyQt5.QtWidgets import QApplication
- # pylint: enable=unused-import
- # pylint: enable=ungrouped-imports
-
- app = QApplication(sys.argv)
-
- gui = CanvasPreviewFrame(None)
- gui.show()
-
- sys.exit(app.exec_())
|