src/eric7/EricWidgets/EricMapWidget.py

Wed, 13 Jul 2022 14:55:47 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 13 Jul 2022 14:55:47 +0200
branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9473
3f23dbf37dbe
permissions
-rw-r--r--

Reformatted the source code using the 'Black' utility.

# -*- coding: utf-8 -*-

# Copyright (c) 2014 - 2022 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(int(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(int(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 int((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