253 lines
13 KiB
Python
253 lines
13 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
Created on 2018年9月9日
|
|
@author: Irony
|
|
@site: https://github.com/892768447
|
|
@email: 892768447@qq.com
|
|
@file: Notification
|
|
@description:
|
|
"""
|
|
import base64
|
|
|
|
from PyQt5.QtCore import Qt, QRectF, QSize, pyqtSignal, QTimer
|
|
from PyQt5.QtGui import QPixmap, QImage, QPainter, QPainterPath,\
|
|
QColor
|
|
from PyQt5.QtWidgets import QWidget, QLabel, QHBoxLayout,\
|
|
QGridLayout, QSpacerItem, QSizePolicy, QGraphicsDropShadowEffect,\
|
|
QListWidget, QListWidgetItem
|
|
|
|
|
|
__Author__ = """By: Irony
|
|
QQ: 892768447
|
|
Email: 892768447@qq.com"""
|
|
__Copyright__ = 'Copyright (c) 2018 Irony'
|
|
__Version__ = 1.0
|
|
|
|
|
|
class NotificationIcon:
|
|
|
|
Info, Success, Warning, Error, Close = range(5)
|
|
Types = {
|
|
Info: None,
|
|
Success: None,
|
|
Warning: None,
|
|
Error: None,
|
|
Close: None
|
|
}
|
|
|
|
@classmethod
|
|
def init(cls):
|
|
cls.Types[cls.Info] = QPixmap(QImage.fromData(base64.b64decode('iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAC5ElEQVRYR8VX0VHbQBB9e/bkN3QQU0FMBSEVYFcQ8xPBJLJ1FWAqOMcaxogfTAWQCiAVRKkgTgfmM4zRZu6QhGzL0p0nDPr17e7bt7tv14RX/uiV48MJgAon+8TiAMRtMFogaqUJxADPwRRzg67kl8+xbWJWANR40iPQSSFgtX/mGQkaDr56V3VAKgGos4s2JXwJoF3naMPvMS+SrpTHs032GwGkdF+DsFMVnJm/oyGGeHico0EjIjpYes+YMyVd6R/flfkpBWCCQ9zaZM2LZDfLMGXsZ5kdI/lYBmINgHHyyLd1mWdBbAFAM/GY7K2WYx1AeB4T6L1N9umbGxZ0qktATaEAdCps48D39oq/LwEw3U5CN92LfczJoewfT7MAywDCaEbAuxeLrh0zz4L+0e4aAJfGy+sP3IMxlH1vpMJoSMCJDXgWtJeJVc6ACs9HBBrYODCJAFdYvAmkPJxnNqMwYht7Bn+T/lGg3z4DGEd3RPhQ54DBvwAOVkeqagRXfTLjh+x7+8sALOtfHLuiYzWOAiLoKbD58mnIGbCmLxUepS6NQmYlUGE0JeCTTXT9JvA9E9sZgO5iIpoyc6/YzcqSwQzgGgBXB7oXpH9klpRSkxY1xW/b7Iu2zk34PILPnazCqEPAtTWA8iZ0HsOu9L0bw4DzCJeNocMGNDpQ3IKO+6NUiJ4ysZNiBv5I3zPnmJmG5oM+wbS+9+qkvGi7NAXGmeUy0ioofa+XA0jH0UaMKpdRWs/adcwMqfV/tenqpqHY/Znt+j2gJi00RUzA201dXaxh9iZdZloJS+9H1otrkbRrD5InFqpPskxEshJQ468CkSmJC+i1HigaaxCAuCljgoDhwPdOjf7rFVxxuJrMkXScjtKc1rOLNpJk6nii5XmYzbngzlZn+RIb40kPJPTBYXUt6VEDJ8Pi6bWpNFb/jFYY6YGpDeKdjBmTKdMcxDGEmP73v2a2Gr/NOycGtglQZ/MPzEqCMLGckJEAAAAASUVORK5CYII=')))
|
|
cls.Types[cls.Success] = QPixmap(QImage.fromData(base64.b64decode('iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACZUlEQVRYR8VXS3LTQBDtVsDbcAPMCbB3limkcAKSG4QFdnaYE2BOQLKzxSLJCeAGSUQheSnfwLmB2VJhXmpExpFHI2sk2RWv5FJPv9evP9NieuIfPzE+VSJw8qt3IMDvmahDoDYxt2UAACXMWIIowR5ffn8TJbaBWRE4CXvHAH9RgKXOgQUI48CfXZbZbiTw8Xe/w3d0zkydMkem91IZpyWOJu5sUXS+kEAqt3B+MNOLOuDqDEBLxxFHk7eza5MfIwEJDjhXTYD1s8zinYlEjsCD7FdNI9cJpEq0RFdPR47AMOzLCn69zegz6UgCP+pmfa8RSKudnPNdgCufTOLDxJtdPP7PoA1Cd8HEL5sSUCCD0B0x8bc1f8Bi6sevcgS2VXh6hMOwDz0gsUddNaxWKRjeuKfE/KlJ9Dq4UYH/o/Ns6scj+bgiMAjdayb26xLQwTfVEwg3gRcf6ARq578KuLo7VDc8psCQqwfjr4EfjYvkrAquFJ56UYpdSkAZSmNd1rrg0leOQFELgvA58OJTxVyRaAJORPOpF6UXnFUR5sDiXjs7UqsOMGMRlrWhTkJXpFL3mNrQZhA1lH3F0TiI5FurUQyMpn58VjhkSqQA4Tbw4nSVW6sBU5VXktXSeONlJH3s8jrOVr9RgVSFuNcWfzlh5n3LoKzMAPxxWuiULiQpiR2sZNnCyzIuWUr5Z1Ml0sgdHFZaShVDuR86/0huL3VXtDk/F4e11vKsTHLSCeKx7bYkW80hjLOrV1GhWH0ZrSlyh2MwdZhYfi8oZeYgLBmUiGd8sfVPM6syr2lUSYGaGBuP3QN6rVUwYV/egwAAAABJRU5ErkJggg==')))
|
|
cls.Types[cls.Warning] = QPixmap(QImage.fromData(base64.b64decode('iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACmElEQVRYR8VXTW7TUBD+xjYSXZFukOIsSE9AskNJJMoJmq4r7OYEwAkabhBOkB/Emt4gVIojdpgbpIumEitX6gKB7UHPkauXxLHfc4F6Z3l+vvnmm/fGhAd+6IHzQwvA9cfOITMfAdQAcx1EdVEAM/tEFADsWyaPn57MfdXClABcT1qnzHSWJiwMzrwgoF91vXGRbS6AH59ajd8hDYmoURQo67tgxoij42rv62KX/04Agu44xmciVMokT32YERgGjquvZ1+y4mQCWPUa0/sk3vQlwqssEFsAVrQbU4XKL/ai2+5PPK6waQ4AOsoDnDARh83NdmwBuJq0fQI9L6p+L7rd3+/5gbAToMPI+FbkIzRRc72mbLcGIFE7jGFRIPHddmZrvstJh1X8CHGv6sxHqe1GkPYCoGcqgcoCAPPCdr2DLQC6wqMoPEj7qdqCNKllxs30sLpjYDluDUDGG5XqhY2sal3w4PiD7c7fJnHShMtJR8zpy/8CALiwndnhBgD1/t+XAXkaZAaUVHwnHulg0W6BNEWlAQD8zna8gQB0Ne70iXCm2j55jCUAei1gxvuaO+uXAcDg7zXHSy640iKUAehOEDJFqDmGQkiPLO5Fv+KADXOqvCuIsrPGsIyQdHou22YeRMJgOdHTQTkAfGk7XrLKrWlAvOhcRgBfWiZ3RQti0zxXuUFXCXMuo0TRitfxugjbIxC5RYzI6s9kIGFh+KLOpiW22id5AUuI8IaisFG4kCQg/sFKJgtPLix3KWXGeRETRbQDuCFCV2spTYMm+2FEI1WBbYIRPTeiqFtqLZeDraaD+qrbkpgQAvfl1WsXU0p/RjIjYYhTkNFgcCVlRlRKoAAc+5aF0V//NVPoc2kTLQZKZ8lx/AMXBmMwuXUwOAAAAABJRU5ErkJggg==')))
|
|
cls.Types[cls.Error] = QPixmap(QImage.fromData(base64.b64decode('iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACrklEQVRYR82XW27aQBSG/4PtiNhIpStouoImKwjZAV1B07coWCpZQcgK6kh2lLeSFZSsIOwgdAdkBaUSEBQDpxpjU9vM+EJR03nDzJz/mzm3GcIrD3plfZQCeD47O1ho2jERNRmoE9AQG2BgBGBAwIiZe5Zh3JPjiG+5oxCAEF5q2iWITnMtRhOYu5XF4mr/9naYtSYXYGLbHQCXhYVTEwlom657rVqvBOB2uz71/a+ldq1SYe6ahnEhc4sSYGzbfQKOt915eh0D/ZrrnqS/SwEmrVYXRJ92Jb4OC+C65rrtuN0NgIltNwF837V4zN5Hy3V70e9NgFZrCKJ3CQDmJ9MwDsW36XzeB/AhA/CHqeuN2WxWX2paX2JraHneeynA+Pz8lCqVbxLjV5brimxAEJxqiEA8CjZVBvFy+bl2c9MV9hInoAw85qFpGEeRYQVEQjzMokcQHWxsiPne8jzh6j8AodGfyqNlHpiGcaKAkIk/gChwm2yYuv5W2FqfwLNtN5bAQ2bwySB83zENo50A8/1McaFRAU72XVek+mpk+D/JlIKI/xkee654uCbIhjVAqZIrgSgpLhiCwN4OAEj4vEB2yDybBCjsAol4ZD0nRdMQSRcUCsKUeNSw4o2mKMRGEOamoVx8FXDZKVosDYNMUHXAsBRnppo8RQcbpTgIGEkhykpFjnWxzGhPQYxt2yHgS/oIlKVYTJxImpG482nz+VG1Wh1N84pMCCGa0ULXHwmoJwCYnyzPW5fn/68dh7EgPbrMMl3gz7gro+n/7EoWD7w4a96l1NnJ1Yz5Lt6wCgFEk0r1CIkbiPnC9DxH5aHcd4FYGD5MOqVOg/muslh0/vphkm63k5eXZvA0I6qD+ZCI3jDzLxANiHn1NNvb6+30aVYgwLeeUsgFW1svsPA3Ncq4MHzVeO8AAAAASUVORK5CYII=')))
|
|
cls.Types[cls.Close] = QPixmap(QImage.fromData(base64.b64decode(
|
|
'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAeElEQVQ4T2NkoBAwUqifgboGzJy76AIjE3NCWmL0BWwumzV/qcH/f38XpCfHGcDkUVwAUsDw9+8GBmbmAHRDcMlheAGbQnwGYw0DZA1gp+JwFUgKZyDCDQGpwuIlrGGAHHAUGUCRFygKRIqjkeKERE6+oG5eIMcFAOqSchGwiKKAAAAAAElFTkSuQmCC')))
|
|
|
|
@classmethod
|
|
def icon(cls, ntype):
|
|
return cls.Types.get(ntype)
|
|
|
|
|
|
class NotificationItem(QWidget):
|
|
|
|
closed = pyqtSignal(QListWidgetItem)
|
|
|
|
def __init__(self, title, message, item, *args, ntype=0, callback=None, **kwargs):
|
|
super(NotificationItem, self).__init__(*args, **kwargs)
|
|
self.item = item
|
|
self.callback = callback
|
|
layout = QHBoxLayout(self, spacing=0)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
bgWidget = QWidget(self)
|
|
layout.addWidget(bgWidget)
|
|
|
|
layout = QGridLayout(bgWidget)
|
|
layout.setHorizontalSpacing(15)
|
|
layout.setVerticalSpacing(10)
|
|
|
|
# 标题左边图标
|
|
layout.addWidget(
|
|
QLabel(self, pixmap=NotificationIcon.icon(ntype)), 0, 0)
|
|
|
|
# 标题
|
|
self.labelTitle = QLabel(title, self)
|
|
font = self.labelTitle.font()
|
|
font.setBold(True)
|
|
font.setPixelSize(22)
|
|
self.labelTitle.setFont(font)
|
|
|
|
# 关闭按钮
|
|
self.labelClose = QLabel(
|
|
self, cursor=Qt.PointingHandCursor, pixmap=NotificationIcon.icon(NotificationIcon.Close))
|
|
|
|
# 消息内容
|
|
self.labelMessage = QLabel(
|
|
message, self, wordWrap=True, alignment=Qt.AlignLeft | Qt.AlignTop)
|
|
font = self.labelMessage.font()
|
|
font.setPixelSize(20)
|
|
self.labelMessage.setFont(font)
|
|
|
|
# 添加到布局
|
|
layout.addWidget(self.labelTitle, 0, 1)
|
|
layout.addItem(QSpacerItem(
|
|
40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum), 0, 2)
|
|
layout.addWidget(self.labelClose, 0, 3)
|
|
layout.addWidget(self.labelMessage, 1, 1, 1, 2)
|
|
|
|
# 边框阴影
|
|
effect = QGraphicsDropShadowEffect(self)
|
|
effect.setBlurRadius(12)
|
|
effect.setColor(QColor(0, 0, 0, 25))
|
|
effect.setOffset(0, 2)
|
|
self.setGraphicsEffect(effect)
|
|
|
|
# 5秒自动关闭
|
|
self._timer = QTimer(self, timeout=self.doClose)
|
|
self._timer.setSingleShot(True) # 只触发一次
|
|
self._timer.start(5000)
|
|
|
|
def doClose(self):
|
|
try:
|
|
# 可能由于手动点击导致item已经被删除了
|
|
self.closed.emit(self.item)
|
|
except:
|
|
pass
|
|
|
|
def sizeHint(self):
|
|
return QSize(392, 96)
|
|
|
|
def mousePressEvent(self, event):
|
|
super(NotificationItem, self).mousePressEvent(event)
|
|
w = self.childAt(event.pos())
|
|
if not w:
|
|
return
|
|
if w == self.labelClose: # 点击关闭图标
|
|
# 先尝试停止计时器
|
|
self._timer.stop()
|
|
self.closed.emit(self.item)
|
|
elif w == self.labelMessage and self.callback and callable(self.callback):
|
|
# 点击消息内容
|
|
self._timer.stop()
|
|
self.closed.emit(self.item)
|
|
self.callback() # 回调
|
|
|
|
def paintEvent(self, event):
|
|
# 圆角以及背景色
|
|
super(NotificationItem, self).paintEvent(event)
|
|
painter = QPainter(self)
|
|
path = QPainterPath()
|
|
path.addRoundedRect(QRectF(self.rect()), 6, 6)
|
|
painter.fillPath(path, Qt.white)
|
|
|
|
|
|
class NotificationWindow(QListWidget):
|
|
|
|
_instance = None
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(NotificationWindow, self).__init__(*args, **kwargs)
|
|
self.setSpacing(20)
|
|
self.setMinimumWidth(412)
|
|
self.setMaximumWidth(412)
|
|
QApplication.instance().setQuitOnLastWindowClosed(True)
|
|
# 隐藏任务栏,无边框,置顶等
|
|
self.setWindowFlags(self.windowFlags() | Qt.Tool |
|
|
Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
|
|
# 去掉窗口边框
|
|
self.setFrameShape(self.NoFrame)
|
|
# 背景透明
|
|
self.viewport().setAutoFillBackground(False)
|
|
self.setAttribute(Qt.WA_TranslucentBackground, True)
|
|
# 不显示滚动条
|
|
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
|
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
|
# 获取屏幕高宽
|
|
rect = QApplication.instance().desktop().availableGeometry(self)
|
|
self.setMinimumHeight(rect.height())
|
|
self.setMaximumHeight(rect.height())
|
|
self.move(rect.width() - self.minimumWidth() - 18, 0)
|
|
|
|
def removeItem(self, item):
|
|
# 删除item
|
|
w = self.itemWidget(item)
|
|
self.removeItemWidget(item)
|
|
item = self.takeItem(self.indexFromItem(item).row())
|
|
w.close()
|
|
w.deleteLater()
|
|
del item
|
|
|
|
@classmethod
|
|
def _createInstance(cls):
|
|
# 创建实例
|
|
if not cls._instance:
|
|
cls._instance = NotificationWindow()
|
|
cls._instance.show()
|
|
NotificationIcon.init()
|
|
|
|
@classmethod
|
|
def info(cls, title, message, callback=None):
|
|
cls._createInstance()
|
|
item = QListWidgetItem(cls._instance)
|
|
w = NotificationItem(title, message, item, cls._instance,
|
|
ntype=NotificationIcon.Info, callback=callback)
|
|
w.closed.connect(cls._instance.removeItem)
|
|
item.setSizeHint(w.sizeHint())
|
|
cls._instance.setItemWidget(item, w)
|
|
|
|
@classmethod
|
|
def success(cls, title, message, callback=None):
|
|
cls._createInstance()
|
|
item = QListWidgetItem(cls._instance)
|
|
w = NotificationItem(title, message, item, cls._instance,
|
|
ntype=NotificationIcon.Success, callback=callback)
|
|
w.closed.connect(cls._instance.removeItem)
|
|
item.setSizeHint(w.sizeHint())
|
|
cls._instance.setItemWidget(item, w)
|
|
|
|
@classmethod
|
|
def warning(cls, title, message, callback=None):
|
|
cls._createInstance()
|
|
item = QListWidgetItem(cls._instance)
|
|
w = NotificationItem(title, message, item, cls._instance,
|
|
ntype=NotificationIcon.Warning, callback=callback)
|
|
w.closed.connect(cls._instance.removeItem)
|
|
item.setSizeHint(w.sizeHint())
|
|
cls._instance.setItemWidget(item, w)
|
|
|
|
@classmethod
|
|
def error(cls, title, message, callback=None):
|
|
cls._createInstance()
|
|
item = QListWidgetItem(cls._instance)
|
|
w = NotificationItem(title, message, item,
|
|
ntype=NotificationIcon.Error, callback=callback)
|
|
w.closed.connect(cls._instance.removeItem)
|
|
item.setSizeHint(w.sizeHint())
|
|
cls._instance.setItemWidget(item, w)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import sys
|
|
import cgitb
|
|
sys.excepthook = cgitb.Hook(1, None, 5, sys.stderr, 'text')
|
|
from PyQt5.QtWidgets import QApplication, QPushButton
|
|
app = QApplication(sys.argv)
|
|
w = QWidget()
|
|
layout = QHBoxLayout(w)
|
|
layout.addWidget(QPushButton(
|
|
'Info', w, clicked=lambda: NotificationWindow.info('提示', '这是一条会自动关闭的消息')))
|
|
layout.addWidget(QPushButton(
|
|
'Success', w, clicked=lambda: NotificationWindow.success('提示', '这是一条会自动关闭的消息')))
|
|
layout.addWidget(QPushButton(
|
|
'Warning', w, clicked=lambda: NotificationWindow.warning('提示', '这是一条会自动关闭的消息')))
|
|
layout.addWidget(QPushButton(
|
|
'Error', w, clicked=lambda: NotificationWindow.error('提示', '<html><head/><body><p><span style=" font-style:italic; color:teal;">这是提示文案这是提示文案这是提示文案这是提示文案这是提示文案这是提示文案这是提示文案这是提示文案</span></p></body></html>')))
|
|
w.show()
|
|
|
|
# NotificationIcon.init()
|
|
# ww = NotificationItem('提示', '<html><head/><body><p><span style=" font-style:italic; color:teal;">这是提示文案这是提示文案这是提示文案这是提示文案这是提示文案这是提示文案这是提示文案这是提示文案</span></p></body></html>', None,
|
|
# ntype=NotificationIcon.Error)
|
|
# ww.show()
|
|
sys.exit(app.exec_())
|