eric7/E5Gui/EricPassivePopup.py

branch
eric7
changeset 8356
68ec9c3d4de5
parent 8322
b422b4e77d19
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/E5Gui/EricPassivePopup.py	Sat May 22 18:51:46 2021 +0200
@@ -0,0 +1,254 @@
+# -*- 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 EricPassivePopupStyle(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 EricPassivePopup(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=EricPassivePopupStyle.BOXED, parent=None):
+        """
+        Constructor
+        
+        @param style style of the popup
+        @type EricPassivePopupStyle
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super().__init__(None)
+        
+        self.__msgView = None
+        self.__topLayout = None
+        self.__hideDelay = EricPassivePopup.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 == EricPassivePopupStyle.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 = EricPassivePopup.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 = EricPassivePopup.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