eric7/E5Gui/E5PassivePopup.py

Tue, 18 May 2021 18:19:47 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Tue, 18 May 2021 18:19:47 +0200
branch
eric7
changeset 8322
b422b4e77d19
parent 8318
962bce857696
permissions
-rw-r--r--

Continued porting eric to PyQt6.

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

# Copyright (c) 2010 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing dialog-like popup that displays messages without
interrupting the user.
"""

import enum

from PyQt6.QtCore import pyqtSignal, Qt, QTimer, QPoint, QRect
from PyQt6.QtWidgets import QFrame, QVBoxLayout, QApplication


class E5PassivePopupStyle(enum.Enum):
    """
    Class defining the popup styles.
    """
    BOXED = 0           # box with no shadow
    STYLED = 1          # styled panel with no shadow
    CUSTOM = 128        # reserved for extensions


class E5PassivePopup(QFrame):
    """
    Class implementing dialog-like popup that displays messages without
    interrupting the user.
    
    @signal clicked emitted to indicate a mouse button click
    """
    DefaultPopupTime = 6 * 1000     # time im milliseconds
    
    clicked = pyqtSignal((), (QPoint, ))
    
    def __init__(self, style=E5PassivePopupStyle.BOXED, parent=None):
        """
        Constructor
        
        @param style style of the popup
        @type E5PassivePopupStyle
        @param parent reference to the parent widget
        @type QWidget
        """
        super().__init__(None)
        
        self.__msgView = None
        self.__topLayout = None
        self.__hideDelay = E5PassivePopup.DefaultPopupTime
        self.__hideTimer = QTimer(self)
        self.__autoDelete = False
        self.__fixedPosition = QPoint()
        
        self.setWindowFlags(
            Qt.WindowType.Tool |
            Qt.WindowType.X11BypassWindowManagerHint |
            Qt.WindowType.WindowStaysOnTopHint |
            Qt.WindowType.FramelessWindowHint
        )
        if style == E5PassivePopupStyle.STYLED:
            self.setFrameStyle(QFrame.Shape.StyledPanel | QFrame.Shadow.Plain)
        else:
            # default style is Boxed - Plain
            self.setFrameStyle(QFrame.Shape.Box | QFrame.Shadow.Plain)
        self.setLineWidth(2)
        self.__hideTimer.timeout.connect(self.hide)
        self.clicked.connect(self.hide)
        
        self.__customData = {}  # dictionary to store some custom data
    
    def setView(self, child):
        """
        Public method to set the message view.
        
        @param child reference to the widget to set as the message view
            (QWidget)
        """
        self.__msgView = child
        self.__topLayout = QVBoxLayout(self)
        self.__topLayout.addWidget(self.__msgView)
        self.__topLayout.activate()
    
    def view(self):
        """
        Public method to get a reference to the message view.
        
        @return reference to the message view (QWidget)
        """
        return self.__msgView
    
    def setVisible(self, visible):
        """
        Public method to show or hide the popup.
        
        @param visible flag indicating the visibility status (boolean)
        """
        if not visible:
            super().setVisible(visible)
            return
        
        if self.size() != self.sizeHint():
            self.resize(self.sizeHint())
        
        if self.__fixedPosition.isNull():
            self.__positionSelf()
        else:
            self.move(self.__fixedPosition)
        super().setVisible(True)
        
        delay = self.__hideDelay
        if delay < 0:
            delay = E5PassivePopup.DefaultPopupTime
        if delay > 0:
            self.__hideTimer.start(delay)
    
    def show(self, p=None):
        """
        Public slot to show the popup.
        
        @param p position for the popup (QPoint)
        """
        if p is not None:
            self.__fixedPosition = p
        super().show()
    
    def setTimeout(self, delay):
        """
        Public method to set the delay for the popup is removed automatically.
        
        Setting the delay to 0 disables the timeout. If you're doing this, you
        may want to connect the clicked() signal to the hide() slot. Setting
        the delay to -1 makes it use the default value.
        
        @param delay value for the delay in milliseconds (integer)
        """
        self.__hideDelay = delay
        if self.__hideTimer.isActive():
            if delay:
                if delay == -1:
                    delay = E5PassivePopup.DefaultPopupTime
                self.__hideTimer.start(delay)
            else:
                self.__hideTimer.stop()
    
    def timeout(self):
        """
        Public method to get the delay before the popup is removed
        automatically.
        
        @return the delay before the popup is removed automatically (integer)
        """
        return self.__hideDelay
    
    def mouseReleaseEvent(self, evt):
        """
        Protected method to handle a mouse release event.
        
        @param evt reference to the mouse event (QMouseEvent)
        """
        self.clicked.emit()
        self.clicked.emit(evt.position().toPoint())
    
    def hideEvent(self, evt):
        """
        Protected method to handle the hide event.
        
        @param evt reference to the hide event (QHideEvent)
        """
        self.__hideTimer.stop()
    
    def __defaultArea(self):
        """
        Private method to determine the default rectangle to be passed to
        moveNear().
        
        @return default rectangle (QRect)
        """
        return QRect(100, 100, 200, 200)
    
    def __positionSelf(self):
        """
        Private method to position the popup.
        """
        self.__moveNear(self.__defaultArea())
    
    def __moveNear(self, target):
        """
        Private method to move the popup to be adjacent to the specified
        rectangle.
        
        @param target rectangle to be placed at (QRect)
        """
        pos = self.__calculateNearbyPoint(target)
        self.move(pos.x(), pos.y())
    
    def __calculateNearbyPoint(self, target):
        """
        Private method to calculate the position to place the popup near the
        specified rectangle.
        
        @param target rectangle to be placed at (QRect)
        @return position to place the popup (QPoint)
        """
        pos = target.topLeft()
        x = pos.x()
        y = pos.y()
        w = self.minimumSizeHint().width()
        h = self.minimumSizeHint().height()
        
        r = QApplication.screenAt(QPoint(x + w // 2, y + h // 2)).geometry()
        
        if x < r.center().x():
            x += target.width()
        else:
            x -= w
        
        # It's apparently trying to go off screen, so display it ALL at the
        # bottom.
        if (y + h) > r.bottom():
            y = r.bottom() - h
        
        if (x + w) > r.right():
            x = r.right() - w
        
        if y < r.top():
            y = r.top()
        
        if x < r.left():
            x = r.left()
        
        return QPoint(x, y)
    
    def setCustomData(self, key, data):
        """
        Public method to set some custom data.
        
        @param key key for the custom data
        @type str
        @param data data to be stored
        @type any
        """
        self.__customData[key] = data
    
    def getCustomData(self, key):
        """
        Public method to get some custom data.
        
        @param key key for the custom data
        @type str
        @return stored data
        @rtype any
        """
        return self.__customData[key]

eric ide

mercurial