eric6/E5Graphics/E5GraphicsView.py

changeset 6942
2602857055c5
parent 6645
ad476851d7e0
child 7229
53054eb5b15a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/E5Graphics/E5GraphicsView.py	Sun Apr 14 15:09:21 2019 +0200
@@ -0,0 +1,401 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2007 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a canvas view class.
+"""
+
+from __future__ import unicode_literals
+
+import sys
+
+from PyQt5.QtCore import pyqtSignal, QRectF, QSize, QSizeF, Qt
+from PyQt5.QtGui import QBrush, QPainter, QPixmap, QFont, QColor
+from PyQt5.QtWidgets import QGraphicsView
+
+import Preferences
+
+
+class E5GraphicsView(QGraphicsView):
+    """
+    Class implementing a graphics view.
+    
+    @signal zoomValueChanged(int) emitted to signal a change of the zoom value
+    """
+    zoomValueChanged = pyqtSignal(int)
+    
+    ZoomLevels = [
+        1, 3, 5, 7, 9,
+        10, 20, 30, 50, 67, 80, 90,
+        100,
+        110, 120, 133, 150, 170, 200, 240, 300, 400,
+        500, 600, 700, 800, 900, 1000,
+    ]
+    ZoomLevelDefault = 100
+    
+    def __init__(self, scene, parent=None):
+        """
+        Constructor
+        
+        @param scene reference to the scene object (QGraphicsScene)
+        @param parent parent widget (QWidget)
+        """
+        super(E5GraphicsView, self).__init__(scene, parent)
+        self.setObjectName("E5GraphicsView")
+        
+        self.__initialSceneSize = self.scene().sceneRect().size()
+        self.setBackgroundBrush(QBrush(Qt.white))
+        self.setRenderHint(QPainter.Antialiasing, True)
+        self.setDragMode(QGraphicsView.RubberBandDrag)
+        self.setAlignment(Qt.Alignment(Qt.AlignLeft | Qt.AlignTop))
+        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
+        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
+        self.setViewportUpdateMode(QGraphicsView.SmartViewportUpdate)
+        
+        self.setWhatsThis(self.tr(
+            "<b>Graphics View</b>\n"
+            "<p>This graphics view is used to show a diagram. \n"
+            "There are various actions available to manipulate the \n"
+            "shown items.</p>\n"
+            "<ul>\n"
+            "<li>Clicking on an item selects it.</li>\n"
+            "<li>Ctrl-clicking adds an item to the selection.</li>\n"
+            "<li>Ctrl-clicking a selected item deselects it.</li>\n"
+            "<li>Clicking on an empty spot of the canvas resets the selection."
+            "</li>\n"
+            "<li>Dragging the mouse over the canvas spans a rubberband to \n"
+            "select multiple items.</li>\n"
+            "<li>Dragging the mouse over a selected item moves the \n"
+            "whole selection.</li>\n"
+            "</ul>\n"
+        ))
+        
+    def __levelForZoom(self, zoom):
+        """
+        Private method determining the zoom level index given a zoom factor.
+        
+        @param zoom zoom factor (integer)
+        @return index of zoom factor (integer)
+        """
+        try:
+            index = E5GraphicsView.ZoomLevels.index(zoom)
+        except ValueError:
+            for index in range(len(E5GraphicsView.ZoomLevels)):
+                if zoom <= E5GraphicsView.ZoomLevels[index]:
+                    break
+        return index
+    
+    def zoomIn(self):
+        """
+        Public method to zoom in.
+        """
+        index = self.__levelForZoom(self.zoom())
+        if index < len(E5GraphicsView.ZoomLevels) - 1:
+            self.setZoom(E5GraphicsView.ZoomLevels[index + 1])
+        
+    def zoomOut(self):
+        """
+        Public method to zoom out.
+        """
+        index = self.__levelForZoom(self.zoom())
+        if index > 0:
+            self.setZoom(E5GraphicsView.ZoomLevels[index - 1])
+    
+    def zoomReset(self):
+        """
+        Public method to handle the reset the zoom value.
+        """
+        self.setZoom(
+            E5GraphicsView.ZoomLevels[E5GraphicsView.ZoomLevelDefault])
+        
+    def setZoom(self, value):
+        """
+        Public method to set the zoom value in percent.
+        
+        @param value zoom value in percent (integer)
+        """
+        if value != self.zoom():
+            self.resetTransform()
+            factor = value / 100.0
+            self.scale(factor, factor)
+            self.zoomValueChanged.emit(value)
+        
+    def zoom(self):
+        """
+        Public method to get the current zoom factor in percent.
+        
+        @return current zoom factor in percent (integer)
+        """
+        return int(self.transform().m11() * 100.0)
+       
+    def resizeScene(self, amount, isWidth=True):
+        """
+        Public method to resize the scene.
+        
+        @param amount size increment (integer)
+        @param isWidth flag indicating width is to be resized (boolean)
+        """
+        sceneRect = self.scene().sceneRect()
+        width = sceneRect.width()
+        height = sceneRect.height()
+        if isWidth:
+            width += amount
+        else:
+            height += amount
+        rect = self._getDiagramRect(10)
+        if width < rect.width():
+            width = rect.width()
+        if height < rect.height():
+            height = rect.height()
+        
+        self.setSceneSize(width, height)
+        
+    def setSceneSize(self, width, height):
+        """
+        Public method to set the scene size.
+        
+        @param width width for the scene (real)
+        @param height height for the scene (real)
+        """
+        rect = self.scene().sceneRect()
+        rect.setHeight(height)
+        rect.setWidth(width)
+        self.scene().setSceneRect(rect)
+        
+    def autoAdjustSceneSize(self, limit=False):
+        """
+        Public method to adjust the scene size to the diagram size.
+        
+        @param limit flag indicating to limit the scene to the
+            initial size (boolean)
+        """
+        size = self._getDiagramSize(10)
+        if limit:
+            newWidth = max(size.width(), self.__initialSceneSize.width())
+            newHeight = max(size.height(), self.__initialSceneSize.height())
+        else:
+            newWidth = size.width()
+            newHeight = size.height()
+        self.setSceneSize(newWidth, newHeight)
+        
+    def _getDiagramRect(self, border=0):
+        """
+        Protected method to calculate the minimum rectangle fitting the
+        diagram.
+        
+        @param border border width to include in the calculation (integer)
+        @return the minimum rectangle (QRectF)
+        """
+        startx = sys.maxsize
+        starty = sys.maxsize
+        endx = 0
+        endy = 0
+        items = self.filteredItems(list(self.scene().items()))
+        for itm in items:
+            rect = itm.sceneBoundingRect()
+            itmEndX = rect.x() + rect.width()
+            itmEndY = rect.y() + rect.height()
+            itmStartX = rect.x()
+            itmStartY = rect.y()
+            if startx >= itmStartX:
+                startx = itmStartX
+            if starty >= itmStartY:
+                starty = itmStartY
+            if endx <= itmEndX:
+                endx = itmEndX
+            if endy <= itmEndY:
+                endy = itmEndY
+        if border:
+            startx -= border
+            starty -= border
+            endx += border
+            endy += border
+            
+        return QRectF(startx, starty, endx - startx + 1, endy - starty + 1)
+        
+    def _getDiagramSize(self, border=0):
+        """
+        Protected method to calculate the minimum size fitting the diagram.
+        
+        @param border border width to include in the calculation (integer)
+        @return the minimum size (QSizeF)
+        """
+        endx = 0
+        endy = 0
+        items = self.filteredItems(list(self.scene().items()))
+        for itm in items:
+            rect = itm.sceneBoundingRect()
+            itmEndX = rect.x() + rect.width()
+            itmEndY = rect.y() + rect.height()
+            if endx <= itmEndX:
+                endx = itmEndX
+            if endy <= itmEndY:
+                endy = itmEndY
+        if border:
+            endx += border
+            endy += border
+            
+        return QSizeF(endx + 1, endy + 1)
+        
+    def __getDiagram(self, rect, imageFormat="PNG", filename=None):
+        """
+        Private method to retrieve the diagram from the scene fitting it
+        in the minimum rectangle.
+        
+        @param rect minimum rectangle fitting the diagram (QRectF)
+        @param imageFormat format for the image file (string)
+        @param filename name of the file for non pixmaps (string)
+        @return diagram pixmap to receive the diagram (QPixmap)
+        """
+        selectedItems = self.scene().selectedItems()
+        
+        # step 1: deselect all widgets
+        if selectedItems:
+            for item in selectedItems:
+                item.setSelected(False)
+            
+        # step 2: grab the diagram
+        if imageFormat == "PNG":
+            paintDevice = QPixmap(int(rect.width()), int(rect.height()))
+            paintDevice.fill(self.backgroundBrush().color())
+        else:
+            from PyQt5.QtSvg import QSvgGenerator
+            paintDevice = QSvgGenerator()
+            paintDevice.setResolution(100)  # 100 dpi
+            paintDevice.setSize(QSize(int(rect.width()), int(rect.height())))
+            paintDevice.setViewBox(rect)
+            paintDevice.setFileName(filename)
+        painter = QPainter(paintDevice)
+        painter.setRenderHint(QPainter.Antialiasing, True)
+        self.scene().render(painter, QRectF(), rect)
+        
+        # step 3: reselect the widgets
+        if selectedItems:
+            for item in selectedItems:
+                item.setSelected(True)
+        
+        return paintDevice
+        
+    def saveImage(self, filename, imageFormat="PNG"):
+        """
+        Public method to save the scene to a file.
+        
+        @param filename name of the file to write the image to (string)
+        @param imageFormat format for the image file (string)
+        @return flag indicating success (boolean)
+        """
+        rect = self._getDiagramRect(self.border)
+        if imageFormat == "SVG":
+            self.__getDiagram(rect, imageFormat=imageFormat, filename=filename)
+            return True
+        else:
+            pixmap = self.__getDiagram(rect)
+            return pixmap.save(filename, imageFormat)
+        
+    def printDiagram(self, printer, diagramName=""):
+        """
+        Public method to print the diagram.
+        
+        @param printer reference to a ready configured printer object
+            (QPrinter)
+        @param diagramName name of the diagram (string)
+        """
+        painter = QPainter()
+        painter.begin(printer)
+        offsetX = 0
+        offsetY = 0
+        widthX = 0
+        heightY = 0
+        font = QFont("times", 10)
+        painter.setFont(font)
+        fm = painter.fontMetrics()
+        fontHeight = fm.lineSpacing()
+        marginX = printer.pageRect().x() - printer.paperRect().x()
+        marginX = \
+            Preferences.getPrinter("LeftMargin") * int(
+                printer.resolution() / 2.54) - marginX
+        marginY = printer.pageRect().y() - printer.paperRect().y()
+        marginY = \
+            Preferences.getPrinter("TopMargin") * int(
+                printer.resolution() / 2.54) - marginY
+        
+        width = printer.width() - marginX \
+            - Preferences.getPrinter("RightMargin") * int(
+                printer.resolution() / 2.54)
+        height = printer.height() - fontHeight - 4 - marginY \
+            - Preferences.getPrinter("BottomMargin") * int(
+                printer.resolution() / 2.54)
+        
+        border = self.border == 0 and 5 or self.border
+        rect = self._getDiagramRect(border)
+        diagram = self.__getDiagram(rect)
+        
+        finishX = False
+        finishY = False
+        page = 0
+        pageX = 0
+        pageY = 1
+        while not finishX or not finishY:
+            if not finishX:
+                offsetX = pageX * width
+                pageX += 1
+            elif not finishY:
+                offsetY = pageY * height
+                offsetX = 0
+                pageY += 1
+                finishX = False
+                pageX = 1
+            if (width + offsetX) > diagram.width():
+                finishX = True
+                widthX = diagram.width() - offsetX
+            else:
+                widthX = width
+            if diagram.width() < width:
+                widthX = diagram.width()
+                finishX = True
+                offsetX = 0
+            if (height + offsetY) > diagram.height():
+                finishY = True
+                heightY = diagram.height() - offsetY
+            else:
+                heightY = height
+            if diagram.height() < height:
+                finishY = True
+                heightY = diagram.height()
+                offsetY = 0
+            
+            painter.drawPixmap(marginX, marginY, diagram,
+                               offsetX, offsetY, widthX, heightY)
+            # write a foot note
+            s = self.tr("{0}, Page {1}").format(diagramName, page + 1)
+            tc = QColor(50, 50, 50)
+            painter.setPen(tc)
+            painter.drawRect(marginX, marginY, width, height)
+            painter.drawLine(marginX, marginY + height + 2,
+                             marginX + width, marginY + height + 2)
+            painter.setFont(font)
+            painter.drawText(marginX, marginY + height + 4, width,
+                             fontHeight, Qt.AlignRight, s)
+            if not finishX or not finishY:
+                printer.newPage()
+                page += 1
+        
+        painter.end()
+    
+    ###########################################################################
+    ## The methods below should be overridden by subclasses to get special
+    ## behavior.
+    ###########################################################################
+    
+    def filteredItems(self, items):
+        """
+        Public method to filter a list of items.
+        
+        @param items list of items as returned by the scene object
+            (QGraphicsItem)
+        @return list of interesting collision items (QGraphicsItem)
+        """
+        # just return the list unchanged
+        return items

eric ide

mercurial