eric7/EricWidgets/EricMapWidget.py

branch
eric7
changeset 8358
144a6b854f70
parent 8356
68ec9c3d4de5
child 8647
cdbce48aded8
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/EricWidgets/EricMapWidget.py	Sat May 22 19:58:24 2021 +0200
@@ -0,0 +1,352 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2014 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a base class for showing a document map.
+"""
+
+from PyQt6.QtCore import Qt, QSize, QRect, QCoreApplication
+from PyQt6.QtGui import QColor, QBrush, QPainter
+from PyQt6.QtWidgets import QWidget, QAbstractScrollArea
+
+
+class EricMapWidget(QWidget):
+    """
+    Class implementing a base class for showing a document map.
+    """
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent widget (QWidget)
+        """
+        super().__init__(parent)
+        self.setAttribute(Qt.WidgetAttribute.WA_OpaquePaintEvent)
+        
+        self.__width = 14
+        self.__lineBorder = 1
+        self.__lineHeight = 2
+        self.__backgroundColor = QColor("#e7e7e7")
+        self.__setSliderColor()
+        
+        self._master = None
+        self.__enabled = False
+        self.__rightSide = True
+        
+        if parent is not None and isinstance(parent, QAbstractScrollArea):
+            self.setMaster(parent)
+    
+    def __setSliderColor(self):
+        """
+        Private method to set the slider color depending upon the background
+        color.
+        """
+        if self.__backgroundColor.toHsv().value() < 128:
+            # dark background, use white slider
+            self.__sliderColor = Qt.GlobalColor.white
+        else:
+            # light background, use black slider
+            self.__sliderColor = Qt.GlobalColor.black
+    
+    def __updateMasterViewportWidth(self):
+        """
+        Private method to update the master's viewport width.
+        """
+        if self._master:
+            if self.__enabled:
+                width = self.__width
+            else:
+                width = 0
+            if self.__rightSide:
+                self._master.setViewportMargins(0, 0, width, 0)
+            else:
+                self._master.setViewportMargins(width, 0, 0, 0)
+    
+    def setMaster(self, master):
+        """
+        Public method to set the map master widget.
+        
+        @param master map master widget (QAbstractScrollArea)
+        """
+        self._master = master
+        self._master.setVerticalScrollBarPolicy(
+            Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
+        self._master.verticalScrollBar().valueChanged.connect(self.update)
+        self._master.verticalScrollBar().rangeChanged.connect(self.update)
+        self.__updateMasterViewportWidth()
+    
+    def setWidth(self, width):
+        """
+        Public method to set the widget width.
+        
+        @param width widget width (integer)
+        """
+        if width != self.__width:
+            self.__width = max(6, width)    # minimum width 6 pixels
+            self.__updateMasterViewportWidth()
+            self.update()
+    
+    def width(self):
+        """
+        Public method to get the widget's width.
+        
+        @return widget width (integer)
+        """
+        return self.__width
+    
+    def setMapPosition(self, onRight):
+        """
+        Public method to set, whether the map should be shown to the right or
+        left of the master widget.
+        
+        @param onRight flag indicating to show the map on the right side of
+            the master widget
+        @type bool
+        """
+        if onRight != self.__rightSide:
+            self.__rightSide = onRight
+            self.__updateMasterViewportWidth()
+            self.update()
+    
+    def isOnRightSide(self):
+        """
+        Public method to test, if the map is shown on the right side of the
+        master widget.
+        
+        @return flag indicating that the map is to the right of the master
+            widget
+        @rtype bool
+        """
+        return self.__rightSide
+    
+    def setLineDimensions(self, border, height):
+        """
+        Public method to set the line (indicator) dimensions.
+        
+        @param border border width on each side in x-direction (integer)
+        @param height height of the line in pixels (integer)
+        """
+        if border != self.__lineBorder or height != self.__lineHeight:
+            self.__lineBorder = max(1, border)  # min border 1 pixel
+            self.__lineHeight = max(1, height)  # min height 1 pixel
+            self.update()
+    
+    def lineDimensions(self):
+        """
+        Public method to get the line (indicator) dimensions.
+        
+        @return tuple with border width (integer) and line height (integer)
+        """
+        return self.__lineBorder, self.__lineHeight
+    
+    def setEnabled(self, enable):
+        """
+        Public method to set the enabled state.
+        
+        @param enable flag indicating the enabled state (boolean)
+        """
+        if enable != self.__enabled:
+            self.__enabled = enable
+            self.setVisible(enable)
+            self.__updateMasterViewportWidth()
+    
+    def isEnabled(self):
+        """
+        Public method to check the enabled state.
+        
+        @return flag indicating the enabled state (boolean)
+        """
+        return self.__enabled
+    
+    def setBackgroundColor(self, color):
+        """
+        Public method to set the widget background color.
+        
+        @param color color for the background (QColor)
+        """
+        if color != self.__backgroundColor:
+            self.__backgroundColor = color
+            self.__setSliderColor()
+            self.update()
+    
+    def backgroundColor(self):
+        """
+        Public method to get the background color.
+        
+        @return background color (QColor)
+        """
+        return QColor(self.__backgroundColor)
+    
+    def sizeHint(self):
+        """
+        Public method to give an indication about the preferred size.
+        
+        @return preferred size (QSize)
+        """
+        return QSize(self.__width, 0)
+    
+    def paintEvent(self, event):
+        """
+        Protected method to handle a paint event.
+        
+        @param event paint event (QPaintEvent)
+        """
+        # step 1: fill the whole painting area
+        painter = QPainter(self)
+        painter.fillRect(event.rect(), self.__backgroundColor)
+        
+        # step 2: paint the indicators
+        self._paintIt(painter)
+        
+        # step 3: paint the slider
+        if self._master:
+            penColor = self.__sliderColor
+            painter.setPen(penColor)
+            brushColor = Qt.GlobalColor.transparent
+            painter.setBrush(QBrush(brushColor))
+            painter.drawRect(self.__generateSliderRange(
+                self._master.verticalScrollBar()))
+    
+    def _paintIt(self, painter):
+        """
+        Protected method for painting the widget's indicators.
+        
+        Note: This method should be implemented by subclasses.
+        
+        @param painter reference to the painter object (QPainter)
+        """
+        pass
+    
+    def mousePressEvent(self, event):
+        """
+        Protected method to handle a mouse button press.
+        
+        @param event reference to the mouse event (QMouseEvent)
+        """
+        if event.button() == Qt.MouseButton.LeftButton and self._master:
+            vsb = self._master.verticalScrollBar()
+            value = self.position2Value(event.position().toPoint().y() - 1)
+            vsb.setValue(value - 0.5 * vsb.pageStep())  # center on page
+        self.__mousePressPos = None
+    
+    def mouseMoveEvent(self, event):
+        """
+        Protected method to handle a mouse moves.
+        
+        @param event reference to the mouse event (QMouseEvent)
+        """
+        if event.buttons() & Qt.MouseButton.LeftButton and self._master:
+            vsb = self._master.verticalScrollBar()
+            value = self.position2Value(event.position().toPoint().y() - 1)
+            vsb.setValue(value - 0.5 * vsb.pageStep())  # center on page
+    
+    def wheelEvent(self, event):
+        """
+        Protected slot handling mouse wheel events.
+        
+        @param event reference to the wheel event (QWheelEvent)
+        """
+        isVertical = event.angleDelta().x() == 0
+        if (
+            self._master and
+            event.modifiers() == Qt.KeyboardModifier.NoModifier and
+            isVertical
+        ):
+            QCoreApplication.sendEvent(self._master.verticalScrollBar(), event)
+    
+    def calculateGeometry(self):
+        """
+        Public method to recalculate the map widget's geometry.
+        """
+        if self._master:
+            cr = self._master.contentsRect()
+            vsb = self._master.verticalScrollBar()
+            if vsb.isVisible():
+                vsbw = vsb.contentsRect().width()
+            else:
+                vsbw = 0
+            margins = self._master.contentsMargins()
+            if margins.right() > vsbw:
+                vsbw = 0
+            if self.__rightSide:
+                self.setGeometry(
+                    QRect(cr.right() - self.__width - vsbw, cr.top(),
+                          self.__width, cr.height()))
+            else:
+                self.setGeometry(
+                    QRect(0, cr.top(), self.__width, cr.height()))
+            self.update()
+    
+    def scaleFactor(self, slider=False):
+        """
+        Public method to determine the scrollbar's scale factor.
+        
+        @param slider flag indicating to calculate the result for the slider
+            (boolean)
+        @return scale factor (float)
+        """
+        if self._master:
+            delta = 0 if slider else 2
+            vsb = self._master.verticalScrollBar()
+            posHeight = vsb.height() - delta - 1
+            valHeight = vsb.maximum() - vsb.minimum() + vsb.pageStep()
+            return float(posHeight) / valHeight
+        else:
+            return 1.0
+    
+    def value2Position(self, value, slider=False):
+        """
+        Public method to convert a scrollbar value into a position.
+        
+        @param value value to convert (integer)
+        @param slider flag indicating to calculate the result for the slider
+            (boolean)
+        @return position (integer)
+        """
+        if self._master:
+            offset = 0 if slider else 1
+            vsb = self._master.verticalScrollBar()
+            return (value - vsb.minimum()) * self.scaleFactor(slider) + offset
+        else:
+            return value
+    
+    def position2Value(self, position, slider=False):
+        """
+        Public method to convert a position into a scrollbar value.
+        
+        @param position scrollbar position to convert (integer)
+        @param slider flag indicating to calculate the result for the slider
+            (boolean)
+        @return scrollbar value (integer)
+        """
+        if self._master:
+            offset = 0 if slider else 1
+            vsb = self._master.verticalScrollBar()
+            return vsb.minimum() + max(
+                0, (position - offset) / self.scaleFactor(slider))
+        else:
+            return position
+    
+    def generateIndicatorRect(self, position):
+        """
+        Public method to generate an indicator rectangle.
+        
+        @param position indicator position (integer)
+        @return indicator rectangle (QRect)
+        """
+        return QRect(self.__lineBorder, position - self.__lineHeight // 2,
+                     self.__width - self.__lineBorder, self.__lineHeight)
+    
+    def __generateSliderRange(self, scrollbar):
+        """
+        Private method to generate the slider rectangle.
+        
+        @param scrollbar reference to the vertical scrollbar (QScrollBar)
+        @return slider rectangle (QRect)
+        """
+        pos1 = self.value2Position(scrollbar.value(), slider=True)
+        pos2 = self.value2Position(scrollbar.value() + scrollbar.pageStep(),
+                                   slider=True)
+        return QRect(0, pos1, self.__width - 1, pos2 - pos1)

eric ide

mercurial