eric7/UI/NotificationWidget.py

branch
eric7
changeset 8312
800c432b34c8
parent 8265
0090cfa83159
child 8318
962bce857696
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2012 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a Notification widget.
8 """
9
10 import contextlib
11 import enum
12
13 from PyQt5.QtCore import Qt, QTimer, QPoint
14 from PyQt5.QtWidgets import QFrame, QWidget, QVBoxLayout
15
16 from .Ui_NotificationFrame import Ui_NotificationFrame
17
18 import Globals
19 import Preferences
20 import UI.PixmapCache
21
22
23 class NotificationTypes(enum.Enum):
24 """
25 Class implementing the notification types.
26 """
27 INFORMATION = 0
28 WARNING = 1
29 CRITICAL = 2
30 OTHER = 99
31
32
33 class NotificationFrame(QFrame, Ui_NotificationFrame):
34 """
35 Class implementing a Notification widget.
36 """
37 NotificationStyleSheetTemplate = "color:{0};background-color:{1};"
38
39 def __init__(self, icon, heading, text,
40 kind=NotificationTypes.INFORMATION, parent=None):
41 """
42 Constructor
43
44 @param icon icon to be used
45 @type QPixmap
46 @param heading heading to be used
47 @type str
48 @param text text to be used
49 @type str
50 @param kind kind of notification to be shown
51 @type NotificationTypes
52 @param parent reference to the parent widget
53 @type QWidget
54 """
55 super().__init__(parent)
56 self.setupUi(self)
57
58 self.layout().setAlignment(
59 self.verticalLayout,
60 Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter
61 )
62
63 self.setStyleSheet(NotificationFrame.getStyleSheet(kind))
64
65 if icon is None:
66 icon = NotificationFrame.getIcon(kind)
67 self.icon.setPixmap(icon)
68
69 self.heading.setText(heading)
70 self.text.setText(text)
71
72 self.show()
73 self.adjustSize()
74
75 @classmethod
76 def getIcon(cls, kind):
77 """
78 Class method to get the icon for a specific notification kind.
79
80 @param kind notification kind
81 @type NotificationTypes
82 @return icon for the notification kind
83 @rtype QPixmap
84 """
85 if kind == NotificationTypes.CRITICAL:
86 return UI.PixmapCache.getPixmap("notificationCritical48")
87 elif kind == NotificationTypes.WARNING: # __NO-TASK__
88 return UI.PixmapCache.getPixmap("notificationWarning48")
89 elif kind == NotificationTypes.INFORMATION:
90 return UI.PixmapCache.getPixmap("notificationInformation48")
91 else:
92 return UI.PixmapCache.getPixmap("notification48")
93
94 @classmethod
95 def getStyleSheet(cls, kind):
96 """
97 Class method to get a style sheet for specific notification kind.
98
99 @param kind notification kind
100 @type NotificationTypes
101 @return string containing the style sheet for the notification kind
102 @rtype str
103 """
104 if kind == NotificationTypes.CRITICAL:
105 return NotificationFrame.NotificationStyleSheetTemplate.format(
106 Preferences.getUI("NotificationCriticalForeground"),
107 Preferences.getUI("NotificationCriticalBackground")
108 )
109 elif kind == NotificationTypes.WARNING: # __NO-TASK__
110 return NotificationFrame.NotificationStyleSheetTemplate.format(
111 Preferences.getUI("NotificationWarningForeground"),
112 Preferences.getUI("NotificationWarningBackground")
113 )
114 else:
115 return ""
116
117
118 class NotificationWidget(QWidget):
119 """
120 Class implementing a Notification list widget.
121 """
122 def __init__(self, parent=None, setPosition=False):
123 """
124 Constructor
125
126 @param parent reference to the parent widget
127 @type QWidget
128 @param setPosition flag indicating to set the display
129 position interactively
130 @type bool
131 """
132 super().__init__(parent)
133
134 self.__layout = QVBoxLayout(self)
135 self.__layout.setContentsMargins(0, 0, 0, 0)
136 self.setLayout(self.__layout)
137
138 self.__timeout = 5000
139 self.__dragPosition = QPoint()
140 self.__timers = {}
141 self.__notifications = []
142
143 self.__settingPosition = setPosition
144
145 flags = (
146 Qt.WindowType.Tool |
147 Qt.WindowType.FramelessWindowHint |
148 Qt.WindowType.WindowStaysOnTopHint |
149 Qt.WindowType.X11BypassWindowManagerHint
150 )
151 if Globals.isWindowsPlatform():
152 flags |= Qt.WindowType.ToolTip
153 self.setWindowFlags(flags)
154
155 if self.__settingPosition:
156 self.setCursor(Qt.CursorShape.OpenHandCursor)
157
158 def showNotification(self, icon, heading, text,
159 kind=NotificationTypes.INFORMATION, timeout=0):
160 """
161 Public method to show a notification.
162
163 @param icon icon to be used
164 @type QPixmap
165 @param heading heading to be used
166 @type str
167 @param text text to be used
168 @type str
169 @param kind kind of notification to be shown
170 @type NotificationTypes
171 @param timeout timeout in seconds after which the notification is
172 to be removed (0 = do not remove until it is clicked on)
173 @type int
174 """
175 notificationFrame = NotificationFrame(
176 icon, heading, text, kind=kind, parent=self)
177 self.__layout.addWidget(notificationFrame)
178 self.__notifications.append(notificationFrame)
179
180 self.show()
181
182 self.__adjustSizeAndPosition()
183
184 if timeout:
185 timer = QTimer()
186 self.__timers[id(notificationFrame)] = timer
187 timer.setSingleShot(True)
188 timer.timeout.connect(
189 lambda: self.__removeNotification(notificationFrame)
190 )
191 timer.setInterval(timeout * 1000)
192 timer.start()
193
194 def __adjustSizeAndPosition(self):
195 """
196 Private slot to adjust the notification list widget size and position.
197 """
198 self.adjustSize()
199
200 if not self.__settingPosition:
201 pos = Preferences.getUI("NotificationPosition")
202 try:
203 screen = self.screen()
204 except AttributeError:
205 # < Qt 5.15
206 from PyQt5.QtGui import QGuiApplication
207 screen = QGuiApplication.screenAt(pos)
208 screenGeom = screen.geometry()
209
210 newX = pos.x()
211 newY = pos.y()
212 if newX < screenGeom.x():
213 newX = screenGeom.x()
214 if newY < screenGeom.y():
215 newY = screenGeom.y()
216 if newX + self.width() > screenGeom.width():
217 newX = screenGeom.width() - self.width()
218 if newY + self.height() > screenGeom.height():
219 newY = screenGeom.height() - self.height()
220
221 self.move(newX, newY)
222
223 def __removeNotification(self, notification):
224 """
225 Private method to remove a notification from the list.
226
227 @param notification reference to the notification to be removed
228 @type NotificationFrame
229 """
230 notification.hide()
231
232 # delete timer of an auto close notification
233 key = id(notification)
234 if key in self.__timers:
235 self.__timers[key].stop()
236 del self.__timers[key]
237
238 # delete the notification
239 index = self.__layout.indexOf(notification)
240 self.__layout.takeAt(index)
241 with contextlib.suppress(ValueError):
242 self.__notifications.remove(notification)
243 notification.deleteLater()
244
245 if self.__layout.count():
246 self.__adjustSizeAndPosition()
247 else:
248 self.hide()
249
250 def mousePressEvent(self, evt):
251 """
252 Protected method to handle presses of a mouse button.
253
254 @param evt reference to the mouse event (QMouseEvent)
255 """
256 if not self.__settingPosition:
257 clickedLabel = self.childAt(evt.pos())
258 if clickedLabel:
259 clickedNotification = clickedLabel.parent()
260 self.__removeNotification(clickedNotification)
261 return
262
263 if evt.button() == Qt.MouseButton.LeftButton:
264 self.__dragPosition = (
265 evt.globalPos() - self.frameGeometry().topLeft()
266 )
267 self.setCursor(Qt.CursorShape.ClosedHandCursor)
268 evt.accept()
269
270 def mouseReleaseEvent(self, evt):
271 """
272 Protected method to handle releases of a mouse button.
273
274 @param evt reference to the mouse event (QMouseEvent)
275 """
276 if (
277 self.__settingPosition and
278 evt.button() == Qt.MouseButton.LeftButton
279 ):
280 self.setCursor(Qt.CursorShape.OpenHandCursor)
281
282 def mouseMoveEvent(self, evt):
283 """
284 Protected method to handle dragging the window.
285
286 @param evt reference to the mouse event (QMouseEvent)
287 """
288 if evt.buttons() & Qt.MouseButton.LeftButton:
289 self.move(evt.globalPos() - self.__dragPosition)
290 evt.accept()

eric ide

mercurial