diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000000000000000000000000000000000..d702b3ce6612fb0c46bb1688b585f93a1247ed9d --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,7 @@ +# These are supported funding model platforms + +liberapay: Irony +custom: + - https://github.com/PyQt5/PyQt#donate-打赏 + - https://github.com/PyQt5/PyQt/blob/master/Donate/zhifubao.png + - https://github.com/PyQt5/PyQt/blob/master/Donate/weixin.png diff --git a/.github/ISSUE_TEMPLATE/discussion---.md b/.github/ISSUE_TEMPLATE/discussion---.md new file mode 100644 index 0000000000000000000000000000000000000000..532805df9742e0fcd3968b4cbc622bf072ea7bde --- /dev/null +++ b/.github/ISSUE_TEMPLATE/discussion---.md @@ -0,0 +1,10 @@ +--- +name: Discussion/讨论 +about: 一些反复被提及的问题. +title: "[讨论]" +labels: 归纳 +assignees: '' + +--- + + diff --git a/.github/ISSUE_TEMPLATE/question---.md b/.github/ISSUE_TEMPLATE/question---.md new file mode 100644 index 0000000000000000000000000000000000000000..22171af42368152601f8f52f05a03c177be7c17a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question---.md @@ -0,0 +1,35 @@ +--- +name: Question/提问 +about: Describe this issue template's purpose here./创建报告供帮助者复现你的问题 +title: "[问题] (保留前面格式)" +labels: '' +assignees: '' + +--- + +**Environment :** / 环境 + - OS: [e.g. Win 10] + - Python [e.g. 3.6.4 X64] + - PyQt5 [e.g. 5.10.1] + +( 读后删除以下段) +**Describe the bug** or **Expected behavior** , +**Additional context** and **Screenshots** +A clear and concise description of what the bug is. +A clear and concise description of what you expected to happen. +If applicable, add screenshots to help explain your problem. +Add any other context about the problem here. + +// 描述你的问题 , 描述程序正常运行你希望的结果 , 提供程序的错误信息 和 源代码 截图 . +// 一次性把问题描述清楚 , 帮助者没时间一来一回地协助你描述清楚问题后再帮助你解决 ; +// 2个来回后帮助者还不清楚你描述的问题 , 则会关闭提问. +// 如果代码过长 , 请创建一个仓库并附上链接. +--- +如果需要贴代码 , 从下面2中格式 选一种 + +格式2 : +```python +(代码段) +``` + +( 读后删除以上段) diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml new file mode 100644 index 0000000000000000000000000000000000000000..e3084824e3dd57faf473da9f91264658e770625a --- /dev/null +++ b/.github/workflows/mirror.yml @@ -0,0 +1,15 @@ +name: 'GitHub Actions Mirror' + +on: [push, delete] + +jobs: + mirror_to_gitee: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: pixta-dev/repository-mirroring-action@v1 + with: + target_repo_url: + git@gitee.com:PyQt5/PyQt.git + ssh_private_key: + ${{ secrets.GIT_KEY }} diff --git a/.gitignore b/.gitignore index 258a4323bd5d39d5bba6691b3b92db0d6d24f5fb..9f409dbe579add7a3270bc6c36d1ce4c06a93199 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,16 @@ __pycache__/ *.py[co] *$py.class +debug +release +.qmake.stash +Makefile +Makefile.Debug +Makefile.Release + +Tmp +Tmp/* + # C extensions *.so @@ -14,8 +24,6 @@ dist/ downloads/ eggs/ .eggs/ -lib/ -lib64/ parts/ sdist/ var/ @@ -103,6 +111,10 @@ venv.bak/ # mypy .mypy_cache/ +.idea +.settings +.project +.pydevproject *.pyc Tmps/* logs/* @@ -116,6 +128,8 @@ log.txt /tmp/* /Tmps /Tmps/* +cache/* +cache/ /tmp/Material /tmp/Material/* *.pyc diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index b81c7954b78b3bc416ac236a505b0fd58627746e..0000000000000000000000000000000000000000 --- a/.idea/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.xml \ No newline at end of file diff --git a/.idea/PyQt.iml b/.idea/PyQt.iml deleted file mode 100644 index b38140f61f87d7566690ec03d76f1cdaa9acb179..0000000000000000000000000000000000000000 --- a/.idea/PyQt.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index a55e7a179bde3e4e772c29c0c85e53354aa54618..0000000000000000000000000000000000000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index 60caf800435d1e29ff75121690c2759adfd668a9..0000000000000000000000000000000000000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index efebe5b5b8ba55563916c6ecf46a39a35d456743..0000000000000000000000000000000000000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - Buildout - - - EditorConfig - - - General - - - HTML - - - Internationalization - - - JSON and JSON5 - - - Markdown - - - Properties Files - - - Python - - - RELAX NG - - - ReST - - - RegExp - - - XML - - - YAML - - - - - Buildout - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index b32bc4cee70f43435868caa3f050f11b452d22ae..0000000000000000000000000000000000000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7f4cb416c083d265558da75d457237d671..0000000000000000000000000000000000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.project b/.project deleted file mode 100644 index d12d5878b8aa74a907f2da0657bf064ffeb353b2..0000000000000000000000000000000000000000 --- a/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - PyQt - - - - - - org.python.pydev.PyDevBuilder - - - - - - org.python.pydev.pythonNature - - diff --git a/.pydevproject b/.pydevproject deleted file mode 100644 index 5dc29c50112c6fb3c8539bd062b9bbab2befe721..0000000000000000000000000000000000000000 --- a/.pydevproject +++ /dev/null @@ -1,8 +0,0 @@ - - -Default -python 3.0 - -/${PROJECT_DIR_NAME} - - diff --git a/.settings/.gitignore b/.settings/.gitignore deleted file mode 100644 index 5983f9280a86956565e1b8667990bfef5fa855b9..0000000000000000000000000000000000000000 --- a/.settings/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.prefs diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs deleted file mode 100644 index 141fb3c4ddb973fb84d8658784700c428d1e14f0..0000000000000000000000000000000000000000 --- a/.settings/org.eclipse.core.resources.prefs +++ /dev/null @@ -1,44 +0,0 @@ -eclipse.preferences.version=1 -encoding//Demo/EmbedWindow.py=utf-8 -encoding//Demo/FacePoints.py=utf-8 -encoding//Demo/FollowWindow.py=utf-8 -encoding//Demo/FramelessWindow.py=utf-8 -encoding//Demo/Lib/Application.py=utf-8 -encoding//Demo/Lib/FramelessWindow.py=utf-8 -encoding//Demo/NativeEvent.py=utf-8 -encoding//Demo/Notification.py=utf-8 -encoding//Demo/ProbeWindow.py=utf-8 -encoding//Demo/RestartWindow.py=utf-8 -encoding//Demo/SharedMemory.py=utf-8 -encoding//Demo/SingleApplication.py=utf-8 -encoding//Demo/VerificationCode.py=utf-8 -encoding//Demo/WeltHideWindow.py=utf-8 -encoding//Demo/WindowNotify.py=utf-8 -encoding//QChart/LineChart.py=utf-8 -encoding//QFont/AwesomeFont.py=utf-8 -encoding//QFont/Lib/FontAwesome.py=utf-8 -encoding//QListView/CustomWidgetSortItem.py=utf-8 -encoding//QListView/SortItemByRole.py=utf-8 -encoding//QMessageBox/CustomColorIcon.py=utf-8 -encoding//QProgressBar/Lib/WaterRippleProgressBar.py=utf-8 -encoding//QProgressBar/MetroCircleProgress.py=utf-8 -encoding//QProgressBar/PercentProgressBar.py=utf-8 -encoding//QProgressBar/SimpleStyle.py=utf-8 -encoding//QProgressBar/WaterProgressBar.py=utf-8 -encoding//QProxyStyle/Lib/TabBarStyle.py=utf-8 -encoding//QProxyStyle/TabTextDirection.py=utf-8 -encoding//QPushButton/BottomLineProgress.py=utf-8 -encoding//QPushButton/FontRotate.py=utf-8 -encoding//QPushButton/NormalStyle.py=utf-8 -encoding//QScrollBar/StyleScrollBar.py=utf-8 -encoding//QSlider/PaintQSlider.py=utf-8 -encoding//QSlider/QssQSlider.py=utf-8 -encoding//QSplitter/RewriteHandle.py=utf-8 -encoding//QThread/moveToThread.py=utf-8 -encoding//QTreeWidget/ParsingJson.py=utf-8 -encoding//QWebView/DreamTree.py=utf-8 -encoding//QWidget/Lib/CustomPaintWidget.py=utf-8 -encoding//QWidget/Lib/CustomWidget.py=utf-8 -encoding//QWidget/WidgetStyle.py=utf-8 -encoding//Test/\u5168\u5C40\u70ED\u952E/HotKey.py=utf-8 -encoding//Test/\u81EA\u52A8\u66F4\u65B0/test.py=utf-8 diff --git a/.settings/org.eclipse.ltk.core.refactoring.prefs b/.settings/org.eclipse.ltk.core.refactoring.prefs deleted file mode 100644 index cfcd1d3c22f7ad061b0ffb72377bff737ba31120..0000000000000000000000000000000000000000 --- a/.settings/org.eclipse.ltk.core.refactoring.prefs +++ /dev/null @@ -1,2 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.ltk.core.refactoring.enable.project.refactoring.history=false diff --git a/Demo/AutoRestart.py b/Demo/AutoRestart.py index 23a3ff13e2a5e972860ddb35afae5e34ec143682..024d61bbbd1dfbfaae8ac383adb4c89a6a74d63a 100644 --- a/Demo/AutoRestart.py +++ b/Demo/AutoRestart.py @@ -1,30 +1,62 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -''' +""" Created on 2017年3月31日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: AutoRestart @description: -''' +""" -from optparse import OptionParser import os import sys -import time +from optparse import OptionParser + +try: + from PyQt5.QtWidgets import QApplication, QPushButton, QWidget, QHBoxLayout +except ImportError: + from PySide2.QtWidgets import QApplication, QPushButton, QWidget, QHBoxLayout + +canRestart = True def restart(twice): os.execl(sys.executable, sys.executable, *[sys.argv[0], "-t", twice]) + +class Window(QWidget): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + self.resize(400, 400) + layout = QHBoxLayout(self) + + self.buttonRestart = QPushButton( + "app start...%s...twice\napp pid: %s\n点击按钮重启...\n" % + (options.twice, os.getpid()), self) + self.buttonRestart.clicked.connect(self.close) + + self.buttonExit = QPushButton('退出', self, clicked=self.doExit) + + layout.addWidget(self.buttonRestart) + layout.addWidget(self.buttonExit) + + def doExit(self): + global canRestart + canRestart = False + self.close() + + if __name__ == "__main__": parser = OptionParser(usage="usage:%prog [optinos] filepath") - parser.add_option("-t", "--twice", type="int", dest="twice", default=1, help="运行次数") + parser.add_option("-t", "--twice", type="int", + dest="twice", default=1, help="运行次数") options, _ = parser.parse_args() - print("app start...%s...twice\n" % options.twice) - print("app pid: ",os.getpid()) - print("3秒后自动重启...\n") - time.sleep(3) - restart(str(options.twice + 1)) + app = QApplication(sys.argv) + w = Window() + w.show() + app.exec_() + if canRestart: + restart(str(options.twice + 1)) diff --git a/Demo/BubbleTips.py b/Demo/BubbleTips.py index 8af85c76bef7a44ec67aa0064d4fcbb2eb77e78e..6f842a3cbb5c5b5cac494fee50f91f74f4c58467 100644 --- a/Demo/BubbleTips.py +++ b/Demo/BubbleTips.py @@ -1,30 +1,31 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -''' +""" Created on 2018年1月27日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: BubbleTips @description: -''' +""" import sys -from PyQt5.QtCore import QRectF, Qt, QPropertyAnimation, pyqtProperty, \ - QPoint, QParallelAnimationGroup, QEasingCurve -from PyQt5.QtGui import QPainter, QPainterPath, QColor, QPen -from PyQt5.QtWidgets import QLabel, QWidget, QVBoxLayout, QApplication,\ - QLineEdit, QPushButton - - -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" -__Version__ = "Version 1.0" +try: + from PyQt5.QtCore import QRectF, Qt, QPropertyAnimation, pyqtProperty, \ + QPoint, QParallelAnimationGroup, QEasingCurve + from PyQt5.QtGui import QPainter, QPainterPath, QColor, QPen + from PyQt5.QtWidgets import QLabel, QWidget, QVBoxLayout, QApplication, \ + QLineEdit, QPushButton +except ImportError: + from PySide2.QtCore import QRectF, Qt, QPropertyAnimation, Property as pyqtProperty, \ + QPoint, QParallelAnimationGroup, QEasingCurve + from PySide2.QtGui import QPainter, QPainterPath, QColor, QPen + from PySide2.QtWidgets import QLabel, QWidget, QVBoxLayout, QApplication, \ + QLineEdit, QPushButton class BubbleLabel(QWidget): - BackgroundColor = QColor(195, 195, 195) BorderColor = QColor(150, 150, 150) @@ -133,10 +134,10 @@ class BubbleLabel(QWidget): opacity = pyqtProperty(float, windowOpacity, setWindowOpacity) -class TestWidget(QWidget): +class Window(QWidget): def __init__(self, *args, **kwargs): - super(TestWidget, self).__init__(*args, **kwargs) + super(Window, self).__init__(*args, **kwargs) layout = QVBoxLayout(self) self.msgEdit = QLineEdit(self, returnPressed=self.onMsgShow) self.msgButton = QPushButton("显示内容", self, clicked=self.onMsgShow) @@ -158,6 +159,6 @@ class TestWidget(QWidget): if __name__ == "__main__": app = QApplication(sys.argv) - w = TestWidget() + w = Window() w.show() sys.exit(app.exec_()) diff --git a/Demo/CallVirtualKeyboard.py b/Demo/CallVirtualKeyboard.py new file mode 100644 index 0000000000000000000000000000000000000000..4afe18ba3c1a46f90bc28757c8dad15db44858fa --- /dev/null +++ b/Demo/CallVirtualKeyboard.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2019年5月22日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: Demo.CallVirtualKeyboard +@description: 调用系统虚拟键盘 +""" +import glob + +try: + from PyQt5.QtCore import QProcess, QSysInfo + from PyQt5.QtWidgets import QApplication, QWidget, QTextEdit, QVBoxLayout, QPushButton +except ImportError: + from PySide2.QtCore import QProcess, QSysInfo + from PySide2.QtWidgets import QApplication, QWidget, QTextEdit, QVBoxLayout, QPushButton + + +class Window(QWidget): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + layout = QVBoxLayout(self) + self.resultEdit = QTextEdit(self) + self.resultEdit.setReadOnly(True) + layout.addWidget(self.resultEdit) + layout.addWidget(QPushButton( + '打开虚拟键盘', self, clicked=self._onOpenKeyboard)) + + def _onOpenKeyboard(self): + kernelType = QSysInfo.kernelType() + if kernelType == 'winnt': + try: + path = glob.glob( + r'C:\Windows\WinSxS\amd64_microsoft-windows-osk_*\osk.exe')[0] + ret = QProcess.startDetached(path) + self.resultEdit.append('start 64 osk: %s' % ret) + except Exception as e: + self.resultEdit.append('start osk error: %s' % e) + try: + # 32位程序调用64位操作系统下的程序会被重定向到SysWOW64目录 + # 可通过`Wow64DisableWow64FsRedirection`和`Wow64RevertWow64FsRedirection`控制 + ret = QProcess.startDetached(r'C:\Windows\system32\osk.exe') + self.resultEdit.append('start 32 osk: %s' % ret) + except Exception as e: + self.resultEdit.append('start osk error: %s' % e) + elif kernelType == 'darwin': + pass + # elif kernelType=='linux': + else: + ret = QProcess.startDetached('florence') + self.resultEdit.append('start florence: %s' % ret) + ret = QProcess.startDetached('onboard') + self.resultEdit.append('start onboard: %s' % ret) + ret = QProcess.startDetached('kvkbd') + self.resultEdit.append('start kvkbd: %s' % ret) + + +if __name__ == '__main__': + import sys + + app = QApplication(sys.argv) + w = Window() + w.show() + sys.exit(app.exec_()) diff --git a/Demo/CircleLine.py b/Demo/CircleLine.py new file mode 100644 index 0000000000000000000000000000000000000000..83102c7ef6234ece6fc2731955dbdf6ccfc27c8b --- /dev/null +++ b/Demo/CircleLine.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2019年3月19日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: CircleLine +@description: +""" + +from math import floor, pi, cos, sin +from random import random, randint +from time import time + +try: + from PyQt5.QtCore import QTimer, Qt + from PyQt5.QtGui import QColor, QPainter, QPainterPath, QPen + from PyQt5.QtWidgets import QWidget, QApplication +except ImportError: + from PySide2.QtCore import QTimer, Qt + from PySide2.QtGui import QColor, QPainter, QPainterPath, QPen + from PySide2.QtWidgets import QWidget, QApplication + +# 最小和最大半径、半径阈值和填充圆的百分比 +radMin = 10 +radMax = 80 +filledCircle = 30 # 填充圆的百分比 +concentricCircle = 60 # 同心圆百分比 +radThreshold = 25 # IFF special, over this radius concentric, otherwise filled +# 最小和最大移动速度 +speedMin = 0.3 +speedMax = 0.6 +# 每个圆和模糊效果的最大透明度 +maxOpacity = 0.6 + +colors = [ + QColor(52, 168, 83), + QColor(117, 95, 147), + QColor(199, 108, 23), + QColor(194, 62, 55), + QColor(0, 172, 212), + QColor(120, 120, 120) +] +circleBorder = 10 +backgroundLine = colors[0] +backgroundColor = QColor(38, 43, 46) +backgroundMlt = 0.85 + +lineBorder = 2.5 + +# 最重要的是:包含它们的整个圆和数组的数目 +maxCircles = 8 +points = [] + +# 实验变量 +circleExp = 1 +circleExpMax = 1.003 +circleExpMin = 0.997 +circleExpSp = 0.00004 +circlePulse = False + + +# 生成随机整数 a<=x<=b + + +def randint(a, b): + return floor(random() * (b - a + 1) + a) + + +# 生成随机小数 + + +def randRange(a, b): + return random() * (b - a) + a + + +# 生成接近a的随机小数 + + +def hyperRange(a, b): + return random() * random() * random() * (b - a) + a + + +class Circle: + + def __init__(self, background, width, height): + self.background = background + self.x = randRange(-width / 2, width / 2) + self.y = randRange(-height / 2, height / 2) + self.radius = hyperRange(radMin, radMax) + self.filled = (False if randint( + 0, 100) > concentricCircle else 'full') if self.radius < radThreshold else ( + False if randint(0, 100) > concentricCircle else 'concentric') + self.color = colors[randint(0, len(colors) - 1)] + self.borderColor = colors[randint(0, len(colors) - 1)] + self.opacity = 0.05 + self.speed = randRange(speedMin, speedMax) # * (radMin / self.radius) + self.speedAngle = random() * 2 * pi + self.speedx = cos(self.speedAngle) * self.speed + self.speedy = sin(self.speedAngle) * self.speed + spacex = abs((self.x - (-1 if self.speedx < 0 else 1) * + (width / 2 + self.radius)) / self.speedx) + spacey = abs((self.y - (-1 if self.speedy < 0 else 1) * + (height / 2 + self.radius)) / self.speedy) + self.ttl = min(spacex, spacey) + + +class CircleLineWindow(QWidget): + + def __init__(self, *args, **kwargs): + super(CircleLineWindow, self).__init__(*args, **kwargs) + # 设置背景颜色 + palette = self.palette() + palette.setColor(palette.Background, backgroundColor) + self.setAutoFillBackground(True) + self.setPalette(palette) + # 获取屏幕大小 + geometry = QApplication.instance().desktop().availableGeometry() + self.screenWidth = geometry.width() + self.screenHeight = geometry.height() + self._canDraw = True + self._firstDraw = True + self._timer = QTimer(self, timeout=self.update) + self.init() + + def init(self): + points.clear() + # 链接的最小距离 + self.linkDist = min(self.screenWidth, self.screenHeight) / 2.4 + # 初始化点 + for _ in range(maxCircles * 3): + points.append(Circle('', self.screenWidth, self.screenHeight)) + self.update() + + def showEvent(self, event): + super(CircleLineWindow, self).showEvent(event) + self._canDraw = True + + def hideEvent(self, event): + super(CircleLineWindow, self).hideEvent(event) + # 窗口最小化要停止绘制, 减少cpu占用 + self._canDraw = False + + def paintEvent(self, event): + super(CircleLineWindow, self).paintEvent(event) + if not self._canDraw: + return + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing) + painter.setRenderHint(QPainter.SmoothPixmapTransform) + self.draw(painter) + + def draw(self, painter): + if circlePulse: + if circleExp < circleExpMin or circleExp > circleExpMax: + circleExpSp *= -1 + circleExp += circleExpSp + + painter.translate(self.screenWidth / 2, self.screenHeight / 2) + + if self._firstDraw: + t = time() + self.renderPoints(painter, points) + if self._firstDraw: + self._firstDraw = False + # 此处有个比例关系用于设置timer的时间,如果初始窗口很小,没有比例会导致动画很快 + t = (time() - t) * 1000 * 2 + # 比例最大不能超过1920/800 + t = int(min(2.4, self.screenHeight / self.height()) * t) - 1 + t = t if t > 15 else 15 # 不能小于15s + print('start timer(%d msec)' % t) + # 开启定时器 + self._timer.start(t) + + def drawCircle(self, painter, circle): + # circle.radius *= circleExp + if circle.background: + circle.radius *= circleExp + else: + circle.radius /= circleExp + radius = circle.radius + + r = radius * circleExp + # 边框颜色设置透明度 + c = QColor(circle.borderColor) + c.setAlphaF(circle.opacity) + + painter.save() + if circle.filled == 'full': + # 设置背景刷 + painter.setBrush(c) + painter.setPen(Qt.NoPen) + else: + # 设置画笔 + painter.setPen( + QPen(c, max(1, circleBorder * (radMin - circle.radius) / (radMin - radMax)))) + + # 画实心圆或者圆圈 + painter.drawEllipse(circle.x - r, circle.y - r, 2 * r, 2 * r) + painter.restore() + + if circle.filled == 'concentric': + r = radius / 2 + # 画圆圈 + painter.save() + painter.setBrush(Qt.NoBrush) + painter.setPen( + QPen(c, max(1, circleBorder * (radMin - circle.radius) / (radMin - radMax)))) + painter.drawEllipse(circle.x - r, circle.y - r, 2 * r, 2 * r) + painter.restore() + + circle.x += circle.speedx + circle.y += circle.speedy + if (circle.opacity < maxOpacity): + circle.opacity += 0.01 + circle.ttl -= 1 + + def renderPoints(self, painter, circles): + for i, circle in enumerate(circles): + if circle.ttl < -20: + # 重新初始化一个 + circle = Circle('', self.screenWidth, self.screenHeight) + circles[i] = circle + self.drawCircle(painter, circle) + + circles_len = len(circles) + for i in range(circles_len - 1): + for j in range(i + 1, circles_len): + deltax = circles[i].x - circles[j].x + deltay = circles[i].y - circles[j].y + dist = pow(pow(deltax, 2) + pow(deltay, 2), 0.5) + # if the circles are overlapping, no laser connecting them + if dist <= circles[i].radius + circles[j].radius: + continue + # otherwise we connect them only if the dist is < linkDist + if dist < self.linkDist: + xi = (1 if circles[i].x < circles[j].x else - + 1) * abs(circles[i].radius * deltax / dist) + yi = (1 if circles[i].y < circles[j].y else - + 1) * abs(circles[i].radius * deltay / dist) + xj = (-1 if circles[i].x < circles[j].x else 1) * \ + abs(circles[j].radius * deltax / dist) + yj = (-1 if circles[i].y < circles[j].y else 1) * \ + abs(circles[j].radius * deltay / dist) + path = QPainterPath() + path.moveTo(circles[i].x + xi, circles[i].y + yi) + path.lineTo(circles[j].x + xj, circles[j].y + yj) + # samecolor = circles[i].color == circles[j].color + c = QColor(circles[i].borderColor) + c.setAlphaF(min(circles[i].opacity, circles[j].opacity) + * ((self.linkDist - dist) / self.linkDist)) + painter.setPen(QPen(c, ( + lineBorder * backgroundMlt if circles[i].background else lineBorder) * ( + (self.linkDist - dist) / self.linkDist))) + painter.drawPath(path) + + +if __name__ == '__main__': + import sys + + app = QApplication(sys.argv) + w = CircleLineWindow() + w.resize(800, 600) + w.show() + sys.exit(app.exec_()) diff --git a/Demo/CustomProperties.py b/Demo/CustomProperties.py index 8a053a2e5a2767a3816cc8cc93783d8b40b0dd8f..43eab24d83db382d0d58cf4a3df42311e09116d2 100644 --- a/Demo/CustomProperties.py +++ b/Demo/CustomProperties.py @@ -1,25 +1,25 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -''' +""" Created on 2017年4月12日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: 自定义属性测试 @description: -''' +""" from random import randint -from PyQt5.QtCore import pyqtProperty, pyqtSignal -from PyQt5.QtWidgets import QPushButton - - -__version__ = "0.0.1" +try: + from PyQt5.QtCore import pyqtProperty, pyqtSignal + from PyQt5.QtWidgets import QPushButton, QApplication +except ImportError: + from PySide2.QtCore import Property as pyqtProperty, Signal as pyqtSignal + from PySide2.QtWidgets import QPushButton, QApplication class Window(QPushButton): - bgChanged = pyqtSignal(str, str) def __init__(self): @@ -56,7 +56,7 @@ class Window(QPushButton): if __name__ == "__main__": import sys - from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) w = Window() w.setStyleSheet( diff --git a/Demo/Data/Images/Cursors/0.png b/Demo/Data/Images/Cursors/0.png new file mode 100644 index 0000000000000000000000000000000000000000..27923f3baf91dd64e402a45c6eb645f819724ea1 Binary files /dev/null and b/Demo/Data/Images/Cursors/0.png differ diff --git a/Demo/Data/Images/Cursors/1.png b/Demo/Data/Images/Cursors/1.png new file mode 100644 index 0000000000000000000000000000000000000000..e338b58a4bc7d63f6ce1c12c87c2ee165a414f89 Binary files /dev/null and b/Demo/Data/Images/Cursors/1.png differ diff --git a/Demo/Data/Images/Cursors/2.png b/Demo/Data/Images/Cursors/2.png new file mode 100644 index 0000000000000000000000000000000000000000..59ee69537accd8ece438d9ef2951aa6d0ecb7e22 Binary files /dev/null and b/Demo/Data/Images/Cursors/2.png differ diff --git a/Demo/Data/Images/Cursors/3.png b/Demo/Data/Images/Cursors/3.png new file mode 100644 index 0000000000000000000000000000000000000000..b86d752d6a1606e96344e2cc424fa324e752824e Binary files /dev/null and b/Demo/Data/Images/Cursors/3.png differ diff --git a/Demo/Data/Images/Cursors/4.png b/Demo/Data/Images/Cursors/4.png new file mode 100644 index 0000000000000000000000000000000000000000..61374dba987517b981cec9da52c80f67892b45c4 Binary files /dev/null and b/Demo/Data/Images/Cursors/4.png differ diff --git a/Demo/Data/Images/Cursors/5.png b/Demo/Data/Images/Cursors/5.png new file mode 100644 index 0000000000000000000000000000000000000000..5c2bc104c56305084a32743f3d276ba8cea69c83 Binary files /dev/null and b/Demo/Data/Images/Cursors/5.png differ diff --git a/Demo/Data/Images/Cursors/6.png b/Demo/Data/Images/Cursors/6.png new file mode 100644 index 0000000000000000000000000000000000000000..ab3996a8cc00a67421bfd548986ef275a79dabac Binary files /dev/null and b/Demo/Data/Images/Cursors/6.png differ diff --git a/Demo/Data/Images/Cursors/7.png b/Demo/Data/Images/Cursors/7.png new file mode 100644 index 0000000000000000000000000000000000000000..0962be4759d758cec9af80859d00e93f0233ce3e Binary files /dev/null and b/Demo/Data/Images/Cursors/7.png differ diff --git a/Demo/Data/frameless.ui b/Demo/Data/frameless.ui new file mode 100644 index 0000000000000000000000000000000000000000..a7919b0292608d448cd4160ae2706bcf31273134 --- /dev/null +++ b/Demo/Data/frameless.ui @@ -0,0 +1,197 @@ + + + FormFrameless + + + + 0 + 0 + 400 + 300 + + + + Form + + + + 0 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + Symbola + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 253 + 20 + + + + + + + + + 36 + 36 + + + + + 36 + 36 + + + + + webdings + + + + Minimum + + + 0 + + + + + + + + 36 + 36 + + + + + 36 + 36 + + + + + webdings + + + + Maximum + + + 1 + + + + + + + + 36 + 36 + + + + + 36 + 36 + + + + + webdings + + + + Normal + + + 2 + + + + + + + + 36 + 36 + + + + + 36 + 36 + + + + + webdings + + + + Close + + + r + + + + + + + + + + QFrame::NoFrame + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">frameless window with move and resize</p></body></html> + + + + + + + + diff --git "a/Demo/Data/\350\203\214\346\231\257\350\277\236\347\272\277\345\212\250\347\224\273.html" "b/Demo/Data/\350\203\214\346\231\257\350\277\236\347\272\277\345\212\250\347\224\273.html" new file mode 100644 index 0000000000000000000000000000000000000000..e6d25d784e3dc59f1603441d7ab8e125e4bcdeaf --- /dev/null +++ "b/Demo/Data/\350\203\214\346\231\257\350\277\236\347\272\277\345\212\250\347\224\273.html" @@ -0,0 +1,208 @@ + + + + + 背景连线动画 + + + +
+ +
+ + + diff --git a/Demo/EmbedWindow.py b/Demo/EmbedWindow.py index c3360d760fe484753cf44783426a85226ddf92ee..b6ffe04ff94881262bf9b0478d8288058fc413a5 100644 --- a/Demo/EmbedWindow.py +++ b/Demo/EmbedWindow.py @@ -4,22 +4,26 @@ """ Created on 2018年3月1日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: EmbedWindow @description: 嵌入外部窗口 """ -__Author__ = 'By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com' -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 - -from PyQt5.QtGui import QWindow -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QListWidget,\ - QLabel import win32con import win32gui +try: + from PyQt5.QtCore import Qt + from PyQt5.QtGui import QWindow + from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QListWidget, \ + QLabel, QApplication +except ImportError: + from PySide2.QtCore import Qt + from PySide2.QtGui import QWindow + from PySide2.QtWidgets import QWidget, QVBoxLayout, QPushButton, QListWidget, \ + QLabel, QApplication + class Window(QWidget): @@ -32,16 +36,22 @@ class Window(QWidget): layout.addWidget(QPushButton('获取所有可用、可视窗口', self, clicked=self._getWindowList, maximumHeight=30)) + layout.addWidget(QPushButton('释放窗口', clicked=self.releaseWidget, maximumHeight=30)) layout.addWidget( QLabel('双击列表中的项目则进行嵌入目标窗口到下方\n格式为:句柄|父句柄|标题|类名', self, maximumHeight=30)) self.windowList = QListWidget( self, itemDoubleClicked=self.onItemDoubleClicked, maximumHeight=200) layout.addWidget(self.windowList) + def releaseWidget(self): + """释放窗口""" + if self.layout().count() == 5: + self.restore() + self._getWindowList() + def closeEvent(self, event): """窗口关闭""" - if self.layout().count() == 4: - self.restore() + self.releaseWidget() super(Window, self).closeEvent(event) def _getWindowList(self): @@ -55,39 +65,41 @@ class Window(QWidget): self.windowList.takeItem(self.windowList.indexFromItem(item).row()) hwnd, phwnd, _, _ = item.text().split('|') # 开始嵌入 - - if self.layout().count() == 4: - # 如果数量等于4说明之前已经嵌入了一个窗口,现在需要把它释放出来 - self.restore() + self.releaseWidget() hwnd, phwnd = int(hwnd), int(phwnd) # 嵌入之前的属性 style = win32gui.GetWindowLong(hwnd, win32con.GWL_STYLE) exstyle = win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE) - print('save', hwnd, style, exstyle) + wrect = win32gui.GetWindowRect(hwnd)[:2] + win32gui.GetClientRect(hwnd)[2:] + print('save', hwnd, style, exstyle, wrect) widget = QWidget.createWindowContainer(QWindow.fromWinId(hwnd)) widget.hwnd = hwnd # 窗口句柄 widget.phwnd = phwnd # 父窗口句柄 widget.style = style # 窗口样式 widget.exstyle = exstyle # 窗口额外样式 + widget.wrect = wrect # 窗口位置 self.layout().addWidget(widget) + widget.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint) + win32gui.SetParent(hwnd, int(self.winId())) + def restore(self): """归还窗口""" # 有bug,归还后窗口没有了WS_VISIBLE样式,不可见 - widget = self.layout().itemAt(3).widget() - print('restore', widget.hwnd, widget.style, widget.exstyle) - win32gui.SetParent(widget.hwnd, widget.phwnd) # 让它返回它的父窗口 - win32gui.SetWindowLong( - widget.hwnd, win32con.GWL_STYLE, widget.style | win32con.WS_VISIBLE) # 恢复样式 - win32gui.SetWindowLong( - widget.hwnd, win32con.GWL_EXSTYLE, widget.exstyle) # 恢复样式 - win32gui.ShowWindow( - widget.hwnd, win32con.SW_SHOW) # 显示窗口 + widget = self.layout().itemAt(4).widget() + hwnd, phwnd, style, exstyle, wrect = widget.hwnd, widget.phwnd, widget.style, widget.exstyle, widget.wrect + print('restore', hwnd, phwnd, style, exstyle, wrect) widget.close() self.layout().removeWidget(widget) # 从布局中移出 widget.deleteLater() + win32gui.SetParent(hwnd, phwnd) # 让它返回它的父窗口 + win32gui.SetWindowLong(hwnd, win32con.GWL_STYLE, style | win32con.WS_VISIBLE) # 恢复样式 + win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE, exstyle) # 恢复样式 + win32gui.ShowWindow(hwnd, win32con.SW_SHOW) # 显示窗口 + win32gui.SetWindowPos(hwnd, 0, wrect[0], wrect[1], wrect[2], wrect[3], win32con.SWP_NOACTIVATE) + def _enumWindows(self, hwnd, _): """遍历回调函数""" if hwnd == self.myhwnd: @@ -102,7 +114,10 @@ class Window(QWidget): if __name__ == '__main__': import sys - from PyQt5.QtWidgets import QApplication + import cgitb + + cgitb.enable(format='text') + app = QApplication(sys.argv) w = Window() w.show() diff --git a/Demo/FacePoints.py b/Demo/FacePoints.py index 7ebea2b859bd4c2fd16fc1df7839750fd41e34c1..98b6165ca2069cac93df4a56a4593d2b2a4ae405 100644 --- a/Demo/FacePoints.py +++ b/Demo/FacePoints.py @@ -1,31 +1,33 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -''' +""" Created on 2018年1月29日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: FacePoints @description: 人脸特征点 -''' -from bz2 import BZ2Decompressor +""" import cgitb import os import sys +from bz2 import BZ2Decompressor -from PyQt5.QtCore import QTimer, QUrl, QFile, QIODevice -from PyQt5.QtGui import QImage, QPixmap -from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest -from PyQt5.QtWidgets import QLabel, QMessageBox, QApplication import cv2 # @UnresolvedImport import dlib import numpy - -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" -__Version__ = "Version 1.0" +try: + from PyQt5.QtCore import QTimer, QUrl, QFile, QIODevice + from PyQt5.QtGui import QImage, QPixmap + from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest + from PyQt5.QtWidgets import QLabel, QMessageBox, QApplication +except ImportError: + from PySide2.QtCore import QTimer, QUrl, QFile, QIODevice + from PySide2.QtGui import QImage, QPixmap + from PySide2.QtNetwork import QNetworkAccessManager, QNetworkRequest + from PySide2.QtWidgets import QLabel, QMessageBox, QApplication DOWNSCALE = 4 URL = 'http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2' @@ -163,7 +165,7 @@ class OpencvWidget(QLabel): if __name__ == "__main__": - sys.excepthook = cgitb.enable(1, None, 5, '') + cgitb.enable(format='text') app = QApplication(sys.argv) w = OpencvWidget() w.show() diff --git a/Demo/FollowWindow.py b/Demo/FollowWindow.py index 5e54287c36ca8ea01db6b003d5ae569edc611447..41ad74c71fbd0c07b16598844b7f7fba97708422 100644 --- a/Demo/FollowWindow.py +++ b/Demo/FollowWindow.py @@ -4,23 +4,21 @@ """ Created on 2018年10月22日 @author: Irony -@site: https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: FollowWindow @description: 跟随外部窗口 """ import os -from PyQt5.QtCore import QTimer -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton import win32gui - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" +try: + from PyQt5.QtCore import QTimer + from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QApplication +except ImportError: + from PySide2.QtCore import QTimer + from PySide2.QtWidgets import QWidget, QVBoxLayout, QPushButton, QApplication class Window(QWidget): @@ -53,7 +51,7 @@ class Window(QWidget): if __name__ == '__main__': import sys - from PyQt5.QtWidgets import QApplication + # 先检测是否已有记事本打开 hwnd = win32gui.FindWindow('Notepad', None) print('hwnd', hwnd) diff --git a/Demo/FramelessDialog.py b/Demo/FramelessDialog.py new file mode 100644 index 0000000000000000000000000000000000000000..b55bef3fe8f0ac34aaedb97a4f124e3dba0c697e --- /dev/null +++ b/Demo/FramelessDialog.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2019年4月19日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: FramelessDialog +@description: 无边框圆角对话框 +""" + +try: + from PyQt5.QtCore import Qt, QSize, QTimer + from PyQt5.QtWidgets import QDialog, QVBoxLayout, QWidget, \ + QGraphicsDropShadowEffect, QPushButton, QGridLayout, QSpacerItem, \ + QSizePolicy, QApplication +except ImportError: + from PySide2.QtCore import Qt, QSize, QTimer + from PySide2.QtWidgets import QDialog, QVBoxLayout, QWidget, \ + QGraphicsDropShadowEffect, QPushButton, QGridLayout, QSpacerItem, \ + QSizePolicy, QApplication + +Stylesheet = """ +#Custom_Widget { + background: white; + border-radius: 10px; +} + +#closeButton { + min-width: 36px; + min-height: 36px; + font-family: "Webdings"; + qproperty-text: "r"; + border-radius: 10px; +} +#closeButton:hover { + color: white; + background: red; +} +""" + + +class Dialog(QDialog): + + def __init__(self, *args, **kwargs): + super(Dialog, self).__init__(*args, **kwargs) + self.setObjectName('Custom_Dialog') + self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint) + self.setAttribute(Qt.WA_TranslucentBackground, True) + self.setStyleSheet(Stylesheet) + self.initUi() + # 添加阴影 + effect = QGraphicsDropShadowEffect(self) + effect.setBlurRadius(12) + effect.setOffset(0, 0) + effect.setColor(Qt.gray) + self.setGraphicsEffect(effect) + + def initUi(self): + layout = QVBoxLayout(self) + # 重点: 这个widget作为背景和圆角 + self.widget = QWidget(self) + self.widget.setObjectName('Custom_Widget') + layout.addWidget(self.widget) + + # 在widget中添加ui + layout = QGridLayout(self.widget) + layout.addItem(QSpacerItem( + 40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum), 0, 0) + layout.addWidget(QPushButton( + 'r', self, clicked=self.accept, objectName='closeButton'), 0, 1) + layout.addItem(QSpacerItem(20, 40, QSizePolicy.Minimum, + QSizePolicy.Expanding), 1, 0) + + def sizeHint(self): + return QSize(600, 400) + + +if __name__ == '__main__': + import sys + + app = QApplication(sys.argv) + w = Dialog() + w.exec_() + QTimer.singleShot(200, app.quit) + sys.exit(app.exec_()) diff --git a/Demo/FramelessWindow.py b/Demo/FramelessWindow.py index aef6c261ba390945df3e02029b5df0b7bf227f92..7e1608e2d95500e563feb2a43674096260836622 100644 --- a/Demo/FramelessWindow.py +++ b/Demo/FramelessWindow.py @@ -1,22 +1,24 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from PyQt5.QtGui import QIcon -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QTextEdit -from Lib.FramelessWindow import FramelessWindow # @UnresolvedImport +""" +Created on 2018年4月30日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: Test +@description: +""" + +try: + from PyQt5.QtGui import QIcon + from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QTextEdit, QApplication +except ImportError: + from PySide2.QtGui import QIcon + from PySide2.QtWidgets import QWidget, QVBoxLayout, QPushButton, QTextEdit, QApplication -# Created on 2018年4月30日 -# author: Irony -# site: https://pyqt5.com , https://github.com/892768447 -# email: 892768447@qq.com -# file: Test -# description: -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 +from Lib.FramelessWindow import FramelessWindow # @UnresolvedImport class MainWindow(QWidget): @@ -63,7 +65,7 @@ TitleBar { if __name__ == '__main__': import sys - from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) app.setStyleSheet(StyleSheet) w = FramelessWindow() diff --git a/Demo/GifCursor.py b/Demo/GifCursor.py new file mode 100644 index 0000000000000000000000000000000000000000..0b1ea8f193f39ea8929381a55cf83eeee6117048 --- /dev/null +++ b/Demo/GifCursor.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2020年3月13日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: Demo.GifCursor +@description: +""" + +try: + from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QApplication +except ImportError: + from PySide2.QtWidgets import QWidget, QVBoxLayout, QPushButton, QApplication + +from Lib.QCursorGif import QCursorGif + + +class Window(QWidget, QCursorGif): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + # 设置忙碌光标图片数组 + self.initCursor(['Data/Images/Cursors/%d.png' % + i for i in range(8)], self) + self.setCursorTimeout(100) + + layout = QVBoxLayout(self) + layout.addWidget(QPushButton( + 'start busy', self, clicked=self.startBusy)) + layout.addWidget(QPushButton( + 'stop busy', self, clicked=self.stopBusy)) + + +if __name__ == '__main__': + import sys + import cgitb + + cgitb.enable(format='text') + + app = QApplication(sys.argv) + w = Window() + w.show() + sys.exit(app.exec_()) diff --git a/Demo/IsSignalConnected.py b/Demo/IsSignalConnected.py new file mode 100644 index 0000000000000000000000000000000000000000..7d534f3fa1520a5d1d700f06591ea86032fd54f8 --- /dev/null +++ b/Demo/IsSignalConnected.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2019年2月24日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: IsSignalConnected +@description: 判断信号是否连接 +""" + +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QTextBrowser + + +class Window(QWidget): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + layout = QVBoxLayout(self) + self.button1 = QPushButton('已连接', self, clicked=self.doTest) + self.button2 = QPushButton('未连接', self) + self.retView = QTextBrowser(self) + layout.addWidget(self.button1) + layout.addWidget(self.button2) + layout.addWidget(self.retView) + + def doTest(self): + self.retView.append(""" + # button1 clicked 是否连接: %s, %s + # button2 clicked 是否连接: %s, %s + """ % ( + self.isSignalConnected(self.button1, 'clicked()'), + self.button1.receivers(self.button1.clicked) > 0, + self.isSignalConnected(self.button2, 'clicked()'), + self.button2.receivers(self.button2.clicked) > 0, + )) + + def isSignalConnected(self, obj, name): + """判断信号是否连接 + :param obj: 对象 + :param name: 信号名,如 clicked() + """ + index = obj.metaObject().indexOfMethod(name) + if index > -1: + method = obj.metaObject().method(index) + if method: + return obj.isSignalConnected(method) + return False + + +if __name__ == '__main__': + import sys + from PyQt5.QtWidgets import QApplication + + app = QApplication(sys.argv) + w = Window() + w.show() + sys.exit(app.exec_()) diff --git a/Demo/Lib/Application.py b/Demo/Lib/Application.py index c1216004ee9c4b40ed9e484d70e9028687a947ca..eed9376aa50acd03b7279921ac441e60e85ed91a 100644 --- a/Demo/Lib/Application.py +++ b/Demo/Lib/Application.py @@ -1,35 +1,34 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -''' +""" Created on 2017年3月30日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: 单实例应用.Application @description: -''' +""" + from PyQt5.QtCore import QSharedMemory, pyqtSignal, Qt from PyQt5.QtNetwork import QLocalSocket, QLocalServer from PyQt5.QtWidgets import QApplication -__version__ = "0.0.1" - class SharedApplication(QApplication): - + def __init__(self, *args, **kwargs): super(SharedApplication, self).__init__(*args, **kwargs) self._running = False - key = "SharedApplication" + __version__ + key = "SharedApplication" self._memory = QSharedMemory(key, self) - + isAttached = self._memory.isAttached() print("isAttached", isAttached) if isAttached: # 如果进程附加在共享内存上 detach = self._memory.detach() # 取消进程附加在共享内存上 print("detach", detach) - + if self._memory.create(1) and self._memory.error() != QSharedMemory.AlreadyExists: # 创建共享内存,如果创建失败,则说明已经创建,否则未创建 print("create ok") @@ -37,14 +36,14 @@ class SharedApplication(QApplication): print("create failed") self._running = True del self._memory - + def isRunning(self): return self._running + class QSingleApplication(QApplication): - messageReceived = pyqtSignal(str) - + def __init__(self, *args, **kwargs): super(QSingleApplication, self).__init__(*args, **kwargs) appid = QApplication.applicationFilePath().lower().split("/")[-1] @@ -56,13 +55,13 @@ class QSingleApplication(QApplication): self._socketIn = None self._socketOut = None self._running = False - + # 先尝试连接 self._socketOut = QLocalSocket(self) self._socketOut.connectToServer(self._socketName) self._socketOut.error.connect(self.handleError) self._running = self._socketOut.waitForConnected() - + if not self._running: # 程序未运行 self._socketOut.close() del self._socketOut @@ -70,20 +69,20 @@ class QSingleApplication(QApplication): self._socketServer.listen(self._socketName) self._socketServer.newConnection.connect(self._onNewConnection) self.aboutToQuit.connect(self.removeServer) - + def handleError(self, message): print("handleError message: ", message) - + def isRunning(self): return self._running - + def activationWindow(self): return self._activationWindow - + def setActivationWindow(self, activationWindow, activateOnMessage=True): self._activationWindow = activationWindow self._activateOnMessage = activateOnMessage - + def activateWindow(self): if not self._activationWindow: return @@ -91,7 +90,7 @@ class QSingleApplication(QApplication): self._activationWindow.windowState() & ~Qt.WindowMinimized) self._activationWindow.raise_() self._activationWindow.activateWindow() - + def sendMessage(self, message, msecs=5000): if not self._socketOut: return False @@ -99,10 +98,10 @@ class QSingleApplication(QApplication): message = str(message).encode() self._socketOut.write(message) if not self._socketOut.waitForBytesWritten(msecs): - raise RuntimeError("Bytes not written within %ss" % + raise RuntimeError("Bytes not written within %ss" % (msecs / 1000.)) return True - + def _onNewConnection(self): if self._socketIn: self._socketIn.readyRead.disconnect(self._onReadyRead) @@ -112,7 +111,7 @@ class QSingleApplication(QApplication): self._socketIn.readyRead.connect(self._onReadyRead) if self._activateOnMessage: self.activateWindow() - + def _onReadyRead(self): while 1: message = self._socketIn.readLine() @@ -123,4 +122,4 @@ class QSingleApplication(QApplication): def removeServer(self): self._socketServer.close() - self._socketServer.removeServer(self._socketName) \ No newline at end of file + self._socketServer.removeServer(self._socketName) diff --git a/Demo/Lib/FramelessWindow.py b/Demo/Lib/FramelessWindow.py index 04afccb92dc796b24753668dab9b6aa9efa1de2b..b4fa3e0ef195f60ad690e2799245b02abbdf5a8a 100644 --- a/Demo/Lib/FramelessWindow.py +++ b/Demo/Lib/FramelessWindow.py @@ -1,26 +1,29 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from PyQt5.QtCore import Qt, pyqtSignal, QPoint -from PyQt5.QtGui import QFont, QEnterEvent, QPainter, QColor, QPen -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel,\ - QSpacerItem, QSizePolicy, QPushButton -# Created on 2018年4月30日 -# author: Irony -# site: https://pyqt5.com , https://github.com/892768447 -# email: 892768447@qq.com -# file: FramelessWindow -# description: -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 +""" +Created on 2018年4月30日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: FramelessWindow +@description: +""" +try: + from PyQt5.QtCore import Qt, pyqtSignal, QPoint + from PyQt5.QtGui import QFont, QEnterEvent, QPainter, QColor, QPen + from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, \ + QSpacerItem, QSizePolicy, QPushButton +except ImportError: + from PySide2.QtCore import Qt, Signal as pyqtSignal, QPoint + from PySide2.QtGui import QFont, QEnterEvent, QPainter, QColor, QPen + from PySide2.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, \ + QSpacerItem, QSizePolicy, QPushButton -class TitleBar(QWidget): +class TitleBar(QWidget): # 窗口最小化信号 windowMinimumed = pyqtSignal() # 窗口最大化信号 @@ -48,7 +51,7 @@ class TitleBar(QWidget): layout.setContentsMargins(0, 0, 0, 0) # 窗口图标 self.iconLabel = QLabel(self) -# self.iconLabel.setScaledContents(True) + # self.iconLabel.setScaledContents(True) layout.addWidget(self.iconLabel) # 窗口标题 self.titleLabel = QLabel(self) @@ -138,7 +141,6 @@ Left, Top, Right, Bottom, LeftTop, RightTop, LeftBottom, RightBottom = range(8) class FramelessWindow(QWidget): - # 四周边距 Margins = 5 diff --git a/Demo/Lib/QCursorGif.py b/Demo/Lib/QCursorGif.py new file mode 100644 index 0000000000000000000000000000000000000000..707e7479f99b846bbd829d97263e7be33fb96ad0 --- /dev/null +++ b/Demo/Lib/QCursorGif.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2020年3月13日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: Demo.Lib.QCursorGif +@description: +""" + +try: + from PyQt5.QtCore import QTimer, Qt + from PyQt5.QtGui import QCursor, QPixmap + from PyQt5.QtWidgets import QApplication +except ImportError: + from PySide2.QtCore import QTimer, Qt + from PySide2.QtGui import QCursor, QPixmap + from PySide2.QtWidgets import QApplication + + +class QCursorGif: + + def initCursor(self, cursors, parent=None): + # 记录默认的光标 + self._oldCursor = Qt.ArrowCursor + self.setOldCursor(parent) + # 加载光标图片 + self._cursorImages = [ + QCursor(QPixmap(cursor)) for cursor in cursors] + self._cursorIndex = 0 + self._cursorCount = len(self._cursorImages) - 1 + # 创建刷新定时器 + self._cursorTimeout = 200 + self._cursorTimer = QTimer(parent) + self._cursorTimer.timeout.connect(self._doBusy) + + def _doBusy(self): + if self._cursorIndex > self._cursorCount: + self._cursorIndex = 0 + QApplication.instance().setOverrideCursor( + self._cursorImages[self._cursorIndex]) + self._cursorIndex += 1 + + def startBusy(self): + if not self._cursorTimer.isActive(): + self._cursorTimer.start(self._cursorTimeout) + + def stopBusy(self): + self._cursorTimer.stop() + QApplication.instance().setOverrideCursor(self._oldCursor) + + def setCursorTimeout(self, timeout): + self._cursorTimeout = timeout + + def setOldCursor(self, parent=None): + self._oldCursor = (parent.cursor() or Qt.ArrowCursor) if parent else ( + QApplication.instance().overrideCursor() or Qt.ArrowCursor) diff --git a/Demo/Lib/UiNotify.py b/Demo/Lib/UiNotify.py index 20d26aa4a1059a7f404cb6ba03ddb997ade8850c..38f1ad92036394663bb54d58692dc0f5cac072bb 100644 --- a/Demo/Lib/UiNotify.py +++ b/Demo/Lib/UiNotify.py @@ -6,46 +6,50 @@ # # WARNING! All changes made in this file will be lost! -from PyQt5 import QtCore, QtGui, QtWidgets +try: + from PyQt5 import QtCore, QtGui, QtWidgets +except ImportError: + from PySide2 import QtCore, QtGui, QtWidgets + class Ui_NotifyForm(object): def setupUi(self, NotifyForm): NotifyForm.setObjectName("NotifyForm") NotifyForm.resize(300, 200) NotifyForm.setStyleSheet("QWidget#widgetTitle {\n" -" background-color: rgb(76, 169, 106);\n" -"}\n" -"QWidget#widgetBottom {\n" -" border-top-style: solid;\n" -" border-top-width: 2px;\n" -" border-top-color: rgb(185, 218, 201);\n" -"}\n" -"QLabel#labelTitle {\n" -" color: rgb(255, 255, 255);\n" -"}\n" -"QLabel#labelContent {\n" -" padding: 5px;\n" -"}\n" -"QPushButton {\n" -" border: none;\n" -" background: transparent;\n" -"}\n" -"QPushButton#buttonClose {\n" -" font-family: \"webdings\";\n" -" color: rgb(255, 255, 255);\n" -"}\n" -"QPushButton#buttonClose:hover {\n" -" background-color: rgb(212, 64, 39);\n" -"}\n" -"QPushButton#buttonView {\n" -" color: rgb(255, 255, 255);\n" -" border-radius: 5px;\n" -" border: solid 1px rgb(76, 169, 106);\n" -" background-color: rgb(76, 169, 106);\n" -"}\n" -"QPushButton#buttonView:hover {\n" -" color: rgb(0, 0, 0);\n" -"}") + " background-color: rgb(76, 169, 106);\n" + "}\n" + "QWidget#widgetBottom {\n" + " border-top-style: solid;\n" + " border-top-width: 2px;\n" + " border-top-color: rgb(185, 218, 201);\n" + "}\n" + "QLabel#labelTitle {\n" + " color: rgb(255, 255, 255);\n" + "}\n" + "QLabel#labelContent {\n" + " padding: 5px;\n" + "}\n" + "QPushButton {\n" + " border: none;\n" + " background: transparent;\n" + "}\n" + "QPushButton#buttonClose {\n" + " font-family: \"webdings\";\n" + " color: rgb(255, 255, 255);\n" + "}\n" + "QPushButton#buttonClose:hover {\n" + " background-color: rgb(212, 64, 39);\n" + "}\n" + "QPushButton#buttonView {\n" + " color: rgb(255, 255, 255);\n" + " border-radius: 5px;\n" + " border: solid 1px rgb(76, 169, 106);\n" + " background-color: rgb(76, 169, 106);\n" + "}\n" + "QPushButton#buttonView:hover {\n" + " color: rgb(0, 0, 0);\n" + "}") self.verticalLayout = QtWidgets.QVBoxLayout(NotifyForm) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setSpacing(6) @@ -61,7 +65,8 @@ class Ui_NotifyForm(object): self.labelTitle.setText("") self.labelTitle.setObjectName("labelTitle") self.horizontalLayout_3.addWidget(self.labelTitle) - spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_3.addItem(spacerItem) self.buttonClose = QtWidgets.QPushButton(self.widgetTitle) self.buttonClose.setMinimumSize(QtCore.QSize(26, 26)) @@ -80,7 +85,8 @@ class Ui_NotifyForm(object): self.horizontalLayout.setContentsMargins(0, 5, 5, 5) self.horizontalLayout.setSpacing(0) self.horizontalLayout.setObjectName("horizontalLayout") - spacerItem1 = QtWidgets.QSpacerItem(170, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem1 = QtWidgets.QSpacerItem(170, 20, QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem1) self.buttonView = QtWidgets.QPushButton(self.widgetBottom) self.buttonView.setMinimumSize(QtCore.QSize(75, 25)) @@ -102,10 +108,10 @@ class Ui_NotifyForm(object): if __name__ == "__main__": import sys + app = QtWidgets.QApplication(sys.argv) NotifyForm = QtWidgets.QWidget() ui = Ui_NotifyForm() ui.setupUi(NotifyForm) NotifyForm.show() sys.exit(app.exec_()) - diff --git a/Demo/Lib/ui_frameless.py b/Demo/Lib/ui_frameless.py new file mode 100644 index 0000000000000000000000000000000000000000..94bc7b7a853991576fe74af45a5ec8ec2a2843e5 --- /dev/null +++ b/Demo/Lib/ui_frameless.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'frameless.ui' +# +# Created by: PyQt5 UI code generator 5.15.2 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +try: + from PyQt5 import QtCore, QtGui, QtWidgets +except ImportError: + from PySide2 import QtCore, QtGui, QtWidgets + + +class Ui_FormFrameless(object): + def setupUi(self, FormFrameless): + FormFrameless.setObjectName("FormFrameless") + FormFrameless.resize(400, 300) + self.verticalLayout = QtWidgets.QVBoxLayout(FormFrameless) + self.verticalLayout.setContentsMargins(3, 3, 3, 3) + self.verticalLayout.setSpacing(0) + self.verticalLayout.setObjectName("verticalLayout") + self.widgetTitleBar = QtWidgets.QWidget(FormFrameless) + font = QtGui.QFont() + font.setFamily("Symbola") + self.widgetTitleBar.setFont(font) + self.widgetTitleBar.setObjectName("widgetTitleBar") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.widgetTitleBar) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setSpacing(0) + self.horizontalLayout.setObjectName("horizontalLayout") + spacerItem = QtWidgets.QSpacerItem(253, 20, QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.buttonMinimum = QtWidgets.QPushButton(self.widgetTitleBar) + self.buttonMinimum.setMinimumSize(QtCore.QSize(36, 36)) + self.buttonMinimum.setMaximumSize(QtCore.QSize(36, 36)) + font = QtGui.QFont() + font.setFamily("webdings") + self.buttonMinimum.setFont(font) + self.buttonMinimum.setObjectName("buttonMinimum") + self.horizontalLayout.addWidget(self.buttonMinimum) + self.buttonMaximum = QtWidgets.QPushButton(self.widgetTitleBar) + self.buttonMaximum.setMinimumSize(QtCore.QSize(36, 36)) + self.buttonMaximum.setMaximumSize(QtCore.QSize(36, 36)) + font = QtGui.QFont() + font.setFamily("webdings") + self.buttonMaximum.setFont(font) + self.buttonMaximum.setObjectName("buttonMaximum") + self.horizontalLayout.addWidget(self.buttonMaximum) + self.buttonNormal = QtWidgets.QPushButton(self.widgetTitleBar) + self.buttonNormal.setMinimumSize(QtCore.QSize(36, 36)) + self.buttonNormal.setMaximumSize(QtCore.QSize(36, 36)) + font = QtGui.QFont() + font.setFamily("webdings") + self.buttonNormal.setFont(font) + self.buttonNormal.setObjectName("buttonNormal") + self.horizontalLayout.addWidget(self.buttonNormal) + self.buttonClose = QtWidgets.QPushButton(self.widgetTitleBar) + self.buttonClose.setMinimumSize(QtCore.QSize(36, 36)) + self.buttonClose.setMaximumSize(QtCore.QSize(36, 36)) + font = QtGui.QFont() + font.setFamily("webdings") + self.buttonClose.setFont(font) + self.buttonClose.setObjectName("buttonClose") + self.horizontalLayout.addWidget(self.buttonClose) + self.verticalLayout.addWidget(self.widgetTitleBar) + self.textEdit = QtWidgets.QTextEdit(FormFrameless) + self.textEdit.setFrameShape(QtWidgets.QFrame.NoFrame) + self.textEdit.setObjectName("textEdit") + self.verticalLayout.addWidget(self.textEdit) + self.verticalLayout.setStretch(1, 1) + + self.retranslateUi(FormFrameless) + QtCore.QMetaObject.connectSlotsByName(FormFrameless) + + def retranslateUi(self, FormFrameless): + _translate = QtCore.QCoreApplication.translate + FormFrameless.setWindowTitle(_translate("FormFrameless", "Form")) + self.buttonMinimum.setToolTip(_translate("FormFrameless", "Minimum")) + self.buttonMinimum.setText(_translate("FormFrameless", "0")) + self.buttonMaximum.setToolTip(_translate("FormFrameless", "Maximum")) + self.buttonMaximum.setText(_translate("FormFrameless", "1")) + self.buttonNormal.setToolTip(_translate("FormFrameless", "Normal")) + self.buttonNormal.setText(_translate("FormFrameless", "2")) + self.buttonClose.setToolTip(_translate("FormFrameless", "Close")) + self.buttonClose.setText(_translate("FormFrameless", "r")) + self.textEdit.setHtml(_translate("FormFrameless", + "\n" + "\n" + "

frameless window with move and resize

")) + + +if __name__ == "__main__": + import sys + + app = QtWidgets.QApplication(sys.argv) + FormFrameless = QtWidgets.QWidget() + ui = Ui_FormFrameless() + ui.setupUi(FormFrameless) + FormFrameless.show() + sys.exit(app.exec_()) diff --git a/Demo/NativeEvent.py b/Demo/NativeEvent.py index 2ccf1693736a45440f661f8c38bd5b75baeb0a75..173124050882ab7cbdb18892169bfb44f8ed4dc8 100644 --- a/Demo/NativeEvent.py +++ b/Demo/NativeEvent.py @@ -2,42 +2,42 @@ # -*- coding: utf-8 -*- """Created on 2018年8月2日 author: Irony -site: https://pyqt5.com , https://github.com/892768447 +site: https://pyqt.site , https://github.com/PyQt5 email: 892768447@qq.com file: win无边框调整大小 description: """ -from ctypes.wintypes import POINT import ctypes.wintypes +from ctypes.wintypes import POINT -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QWidget, QPushButton -from PyQt5.QtWinExtras import QtWin import win32api import win32con import win32gui - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 +try: + from PyQt5.QtCore import Qt + from PyQt5.QtGui import QCursor + from PyQt5.QtWidgets import QApplication, QPushButton, QWidget + from PyQt5.QtWinExtras import QtWin +except ImportError: + from PySide2.QtCore import Qt + from PySide2.QtGui import QCursor + from PySide2.QtWidgets import QApplication, QPushButton, QWidget + from PySide2.QtWinExtras import QtWin class MINMAXINFO(ctypes.Structure): _fields_ = [ - ("ptReserved", POINT), - ("ptMaxSize", POINT), - ("ptMaxPosition", POINT), - ("ptMinTrackSize", POINT), - ("ptMaxTrackSize", POINT), + ("ptReserved", POINT), + ("ptMaxSize", POINT), + ("ptMaxPosition", POINT), + ("ptMinTrackSize", POINT), + ("ptMaxTrackSize", POINT), ] class Window(QWidget): - BorderWidth = 5 def __init__(self, *args, **kwargs): @@ -45,16 +45,15 @@ class Window(QWidget): # 主屏幕的可用大小(去掉任务栏) self._rect = QApplication.instance().desktop().availableGeometry(self) self.resize(800, 600) - self.setWindowFlags(Qt.Window - | Qt.FramelessWindowHint - | Qt.WindowSystemMenuHint - | Qt.WindowMinimizeButtonHint - | Qt.WindowMaximizeButtonHint - | Qt.WindowCloseButtonHint) + self.setWindowFlags(Qt.Window | Qt.FramelessWindowHint | + Qt.WindowSystemMenuHint | + Qt.WindowMinimizeButtonHint | + Qt.WindowMaximizeButtonHint | + Qt.WindowCloseButtonHint) # 增加薄边框 style = win32gui.GetWindowLong(int(self.winId()), win32con.GWL_STYLE) - win32gui.SetWindowLong( - int(self.winId()), win32con.GWL_STYLE, style | win32con.WS_THICKFRAME) + win32gui.SetWindowLong(int(self.winId()), win32con.GWL_STYLE, + style | win32con.WS_THICKFRAME) if QtWin.isCompositionEnabled(): # 加上 Aero 边框阴影 @@ -67,8 +66,9 @@ class Window(QWidget): if eventType == "windows_generic_MSG": msg = ctypes.wintypes.MSG.from_address(message.__int__()) # 获取鼠标移动经过时的坐标 - x = win32api.LOWORD(msg.lParam) - self.frameGeometry().x() - y = win32api.HIWORD(msg.lParam) - self.frameGeometry().y() + pos = QCursor.pos() + x = pos.x() - self.frameGeometry().x() + y = pos.y() - self.frameGeometry().y() # 判断鼠标位置是否有其它控件 if self.childAt(x, y) != None: return retval, result @@ -77,8 +77,8 @@ class Window(QWidget): return True, 0 if msg.message == win32con.WM_GETMINMAXINFO: # 当窗口位置改变或者大小改变时会触发该消息 - info = ctypes.cast( - msg.lParam, ctypes.POINTER(MINMAXINFO)).contents + info = ctypes.cast(msg.lParam, + ctypes.POINTER(MINMAXINFO)).contents # 修改最大化的窗口大小为主屏幕的可用大小 info.ptMaxSize.x = self._rect.width() info.ptMaxSize.y = self._rect.height() @@ -121,7 +121,7 @@ class Window(QWidget): if __name__ == '__main__': import sys - from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) w = Window() btn = QPushButton('exit', w, clicked=app.quit) diff --git a/Demo/NewFramelessWindow.py b/Demo/NewFramelessWindow.py new file mode 100644 index 0000000000000000000000000000000000000000..75dcc742feb20f0a1a5191445756fe8f47bb775d --- /dev/null +++ b/Demo/NewFramelessWindow.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Created on 2018年4月30日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: NewFramelessWindow +@description: +""" + +import sys + +try: + from PyQt5.QtCore import QEvent, QObject, QPoint, Qt, QTimer + from PyQt5.QtGui import QColor, QMouseEvent, QPainter, QWindow + from PyQt5.QtWidgets import QApplication, QMessageBox, QWidget +except ImportError: + from PySide2.QtCore import QTimer, Qt, QEvent, QObject, QPoint + from PySide2.QtGui import QWindow, QPainter, QColor, QMouseEvent + from PySide2.QtWidgets import QApplication, QWidget, QMessageBox + +from Lib.ui_frameless import Ui_FormFrameless + + +class FramelessObject(QObject): + Margins = 3 # 边缘边距 + TitleHeight = 36 # 标题栏高度 + Widgets = set() # 无边框窗口集合 + + @classmethod + def set_margins(cls, margins): + cls.Margins = margins + + @classmethod + def set_title_height(cls, height): + cls.TitleHeight = height + + @classmethod + def add_widget(cls, widget): + cls.Widgets.add(widget) + + @classmethod + def del_widget(cls, widget): + if widget in cls.Widgets: + cls.Widgets.remove(widget) + + def _get_edges(self, pos, width, height): + """根据坐标获取方向 + :param pos: QPoint + :param width: int + :param height: int + :return: Qt.Edges + """ + edge = 0 + x, y = pos.x(), pos.y() + + if y <= self.Margins: + edge |= Qt.TopEdge + if x <= self.Margins: + edge |= Qt.LeftEdge + if x >= width - self.Margins: + edge |= Qt.RightEdge + if y >= height - self.Margins: + edge |= Qt.BottomEdge + + return edge + + def _get_cursor(self, edges): + """调整鼠标样式 + :param edges: int or None + :return: Qt.CursorShape + """ + if edges == Qt.LeftEdge | Qt.TopEdge or edges == Qt.RightEdge | Qt.BottomEdge: + return Qt.SizeFDiagCursor + elif edges == Qt.RightEdge | Qt.TopEdge or edges == Qt.LeftEdge | Qt.BottomEdge: + return Qt.SizeBDiagCursor + elif edges == Qt.LeftEdge or edges == Qt.RightEdge: + return Qt.SizeHorCursor + elif edges == Qt.TopEdge or edges == Qt.BottomEdge: + return Qt.SizeVerCursor + + return Qt.ArrowCursor + + def is_titlebar(self, pos): + """判断是否是标题栏 + :param pos: QPoint + :return: bool + """ + return pos.y() <= self.TitleHeight + + def moveOrResize(self, window, pos, width, height): + edges = self._get_edges(pos, width, height) + if edges: + if window.windowState() == Qt.WindowNoState: + window.startSystemResize(edges) + else: + if self.is_titlebar(pos): + window.startSystemMove() + # Fixed #172 主动触发一次鼠标释放事件,否则会导致鼠标悬停出问题 + QApplication.instance().postEvent( + window, + QMouseEvent(QEvent.MouseButtonRelease, QPoint(-1, -1), + Qt.LeftButton, Qt.NoButton, Qt.NoModifier)) + + def eventFilter(self, obj, event): + if obj.isWindowType(): + # top window 处理光标样式 + if event.type() == QEvent.MouseMove and obj.windowState( + ) == Qt.WindowNoState: + obj.setCursor( + self._get_cursor( + self._get_edges(event.pos(), obj.width(), + obj.height()))) + elif event.type() == QEvent.TouchUpdate: + self.moveOrResize(obj, event.pos(), obj.width(), obj.height()) + elif obj in self.Widgets and isinstance( + event, QMouseEvent) and event.button() == Qt.LeftButton: + if event.type() == QEvent.MouseButtonDblClick: + # 双击最大化还原 + if self.is_titlebar(event.pos()): + if obj.windowState() == Qt.WindowFullScreen: + pass + elif obj.windowState() == Qt.WindowMaximized: + obj.showNormal() + else: + obj.showMaximized() + elif event.type() == QEvent.MouseButtonPress: + self.moveOrResize(obj.windowHandle(), event.pos(), obj.width(), + obj.height()) + + return False + + +class FramelessWindow(QWidget, Ui_FormFrameless): + + def __init__(self, *args, **kwargs): + super(FramelessWindow, self).__init__(*args, **kwargs) + self.setupUi(self) + # 无边框 + self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint) + self.setAttribute(Qt.WA_TranslucentBackground, True) + self.setMouseTracking(True) + # 隐藏还原按钮 + self.buttonNormal.setVisible(False) + # 标题栏按钮信号 + self.buttonMinimum.clicked.connect(self.showMinimized) + self.buttonMaximum.clicked.connect(self.showMaximized) + self.buttonNormal.clicked.connect(self.showNormal) + self.buttonClose.clicked.connect(self.close) + self.setStyleSheet('#widgetTitleBar{background: rgb(232, 232, 232);}') + + def showMinimized(self): + flags = self.windowFlags() + if sys.platform == 'darwin': + # fix mac 最小化失效问题 + self.setWindowFlags((self.windowFlags() | Qt.CustomizeWindowHint) & + (~Qt.WindowTitleHint)) + super(FramelessWindow, self).showMinimized() + if sys.platform == 'darwin': + # fix mac 最小化失效问题 + self.setWindowFlags(flags) + self.show() + + def changeEvent(self, event): + """窗口状态改变 + :param event: + """ + super(FramelessWindow, self).changeEvent(event) + # 窗口状态改变时修改标题栏控制按钮 + visible = self.isMaximized() + self.buttonMaximum.setVisible(not visible) + self.buttonNormal.setVisible(visible) + if visible: + self.layout().setContentsMargins(0, 0, 0, 0) + else: + # TODO 与UI文件中的布局边距一致 + m = FramelessObject.Margins + self.layout().setContentsMargins(m, m, m, m) + + def paintEvent(self, event): + # 透明背景但是需要留下一个透明度用于鼠标捕获 + painter = QPainter(self) + painter.fillRect(self.rect(), QColor(255, 255, 255, 1)) + + +if __name__ == '__main__': + import cgitb + import sys + + cgitb.enable(format='text') + + app = QApplication(sys.argv) + if not hasattr(QWindow, 'startSystemMove'): + QWindow.startSystemResize() + # 不支持 + QMessageBox.critical(None, '错误', '当前Qt版本不支持该例子') + QTimer.singleShot(100, app.quit) + else: + # 安装全局事件过滤器 + fo = FramelessObject() + app.installEventFilter(fo) + + w1 = FramelessWindow() + fo.add_widget(w1) + w1.show() + + w2 = FramelessWindow() + fo.add_widget(w2) + w2.show() + sys.exit(app.exec_()) diff --git a/Demo/Notification.py b/Demo/Notification.py index f7a0ab15e777cb0221c987728af0cc0f13c99ad3..889c103fb3f32b19aaa52f0fbbf58be7106ddc7d 100644 --- a/Demo/Notification.py +++ b/Demo/Notification.py @@ -4,30 +4,30 @@ """ Created on 2018年9月9日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @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 +try: + 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, QApplication, QPushButton +except ImportError: + from PySide2.QtCore import Qt, QRectF, QSize, Signal as pyqtSignal, QTimer + from PySide2.QtGui import QPixmap, QImage, QPainter, QPainterPath, \ + QColor + from PySide2.QtWidgets import QWidget, QLabel, QHBoxLayout, \ + QGridLayout, QSpacerItem, QSizePolicy, QGraphicsDropShadowEffect, \ + QListWidget, QListWidgetItem, QApplication, QPushButton class NotificationIcon: - Info, Success, Warning, Error, Close = range(5) Types = { Info: None, @@ -39,10 +39,14 @@ class NotificationIcon: @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.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'))) @@ -52,7 +56,6 @@ class NotificationIcon: class NotificationItem(QWidget): - closed = pyqtSignal(QListWidgetItem) def __init__(self, title, message, item, *args, ntype=0, callback=None, **kwargs): @@ -152,7 +155,6 @@ class NotificationItem(QWidget): class NotificationWindow(QListWidget): - _instance = None def __init__(self, *args, **kwargs): @@ -243,15 +245,18 @@ class NotificationWindow(QListWidget): if __name__ == '__main__': import sys import cgitb - sys.excepthook = cgitb.Hook(1, None, 5, sys.stderr, 'text') - from PyQt5.QtWidgets import QApplication, QPushButton + + cgitb.enable(format='text') + app = QApplication(sys.argv) w = QWidget() layout = QHBoxLayout(w) + def callback(): print('回调点击') + layout.addWidget(QPushButton( 'Info', w, clicked=lambda: NotificationWindow.info('提示', '这是一条会自动关闭的消息', callback=callback))) layout.addWidget(QPushButton( @@ -268,9 +273,9 @@ if __name__ == '__main__': callback=callback))) w.show() -# NotificationIcon.init() -# ww = NotificationItem('提示', '

这是提示文案这是提示文案这是提示文案这是提示文案这是提示文案这是提示文案这是提示文案这是提示文案

', None, -# ntype=NotificationIcon.Error) -# ww.bgWidget.setVisible(True) -# ww.show() + # NotificationIcon.init() + # ww = NotificationItem('提示', '

这是提示文案这是提示文案这是提示文案这是提示文案这是提示文案这是提示文案这是提示文案这是提示文案

', None, + # ntype=NotificationIcon.Error) + # ww.bgWidget.setVisible(True) + # ww.show() sys.exit(app.exec_()) diff --git a/Demo/ProbeWindow.py b/Demo/ProbeWindow.py index 7bc4b0b08aa2a63c4803d13cc429c84c07438fa8..e023359cfe423d2dd38d62c1933aa2e397fe8f32 100644 --- a/Demo/ProbeWindow.py +++ b/Demo/ProbeWindow.py @@ -3,23 +3,22 @@ """ Created on 2018年6月8日 author: Irony -site: https://pyqt5.com , https://github.com/892768447 +site: https://pyqt.site , https://github.com/PyQt5 email: 892768447@qq.com file: ProbeWindow description: 简单探测窗口和放大截图 """ -from PyQt5.QtCore import Qt, QRect -from PyQt5.QtGui import QPainter, QPen, QCursor, QColor -from PyQt5.QtWidgets import QLabel, QWidget, QApplication import win32gui - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 +try: + from PyQt5.QtCore import Qt, QRect + from PyQt5.QtGui import QPainter, QPen, QCursor, QColor + from PyQt5.QtWidgets import QLabel, QWidget, QApplication +except ImportError: + from PySide2.QtCore import Qt, QRect + from PySide2.QtGui import QPainter, QPen, QCursor, QColor + from PySide2.QtWidgets import QLabel, QWidget, QApplication class FrameWidget(QWidget): @@ -111,11 +110,12 @@ class Label(QLabel): painter.setPen(Qt.white) painter.drawText(self.rect(), Qt.AlignLeft | Qt.AlignBottom, '({}, {})\nRGB: ({}, {}, {})\n{}'.format( - pos.x(), pos.y(), r, g, b, QColor(r, g, b).name())) + pos.x(), pos.y(), r, g, b, QColor(r, g, b).name())) if __name__ == '__main__': import sys + app = QApplication(sys.argv) app.setQuitOnLastWindowClosed(True) w = Label() diff --git a/Demo/QtThreading.py b/Demo/QtThreading.py new file mode 100644 index 0000000000000000000000000000000000000000..d0533850879b848e702eb1dcec465409e738db93 --- /dev/null +++ b/Demo/QtThreading.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2019年3月8日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: Threading.QtThreading +@description: +""" +from threading import Thread +from time import sleep + +try: + from PyQt5.QtCore import QObject, pyqtSignal, QTimer, Qt + from PyQt5.QtWidgets import QWidget, QVBoxLayout, QProgressBar, QApplication +except ImportError: + from PySide2.QtCore import QObject, Signal as pyqtSignal, QTimer, Qt + from PySide2.QtWidgets import QWidget, QVBoxLayout, QProgressBar, QApplication + + +class _Signals(QObject): + updateProgress = pyqtSignal(int) + + +Signals = _Signals() + + +class UpdateThread(Thread): + + def run(self): + self.i = 0 + for i in range(101): + self.i += 1 + Signals.updateProgress.emit(i) + sleep(1) + self.i = 0 + Signals.updateProgress.emit(i) + + +class Window(QWidget): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + self.resize(400, 400) + layout = QVBoxLayout(self) + self.progressBar = QProgressBar(self) + layout.addWidget(self.progressBar) + Signals.updateProgress.connect( + self.progressBar.setValue, type=Qt.QueuedConnection) + + QTimer.singleShot(2000, self.doStart) + + def doStart(self): + self.updateThread = UpdateThread(daemon=True) + self.updateThread.start() + + +if __name__ == '__main__': + import sys + + app = QApplication(sys.argv) + w = Window() + w.show() + sys.exit(app.exec_()) diff --git a/Demo/README.md b/Demo/README.md index 9f3afa955b7868dc7080f3f72f33904bc3051532..175dd969a444f3f1cff8c58bcb8600ed3b1ca10d 100644 --- a/Demo/README.md +++ b/Demo/README.md @@ -1,5 +1,31 @@ # Demo +- 目录 + - [重启窗口Widget](#1重启窗口Widget) + - [简单的窗口贴边隐藏](#2简单的窗口贴边隐藏) + - [嵌入外部窗口](#3嵌入外部窗口) + - [简单跟随其它窗口](#4简单跟随其它窗口) + - [简单探测窗口和放大截图](#5简单探测窗口和放大截图) + - [无边框自定义标题栏窗口](#6无边框自定义标题栏窗口) + - [右下角弹出框](#7右下角弹出框) + - [程序重启](#8程序重启) + - [自定义属性](#9自定义属性) + - [调用截图DLL](#10调用截图DLL) + - [单实例应用](#11单实例应用) + - [简单的右下角气泡提示](#12简单的右下角气泡提示) + - [右侧消息通知栏](#13右侧消息通知栏) + - [验证码控件](#14验证码控件) + - [人脸特征点](#15人脸特征点) + - [使用Threading](#16使用Threading) + - [背景连线动画](#17背景连线动画) + - [无边框圆角对话框](#18无边框圆角对话框) + - [调整窗口显示边框](#19调整窗口显示边框) + - [判断信号是否连接](#20判断信号是否连接) + - [调用虚拟键盘](#21调用虚拟键盘) + - [动态忙碌光标](#22动态忙碌光标) + - [屏幕变动监听](#23屏幕变动监听) + - [无边框窗口](#24无边框窗口) + ## 1、重启窗口Widget [运行 RestartWindow.py](RestartWindow.py) @@ -74,14 +100,14 @@ ![FramelessWindow](ScreenShot/FramelessWindow.gif) ## 7、右下角弹出框 -[运行 WindowNotify.py](WindowNotify.py) +[运行 WindowNotify.py](WindowNotify.py) | [查看 notify.ui](Data/notify.ui) ![WindowNotify](ScreenShot/WindowNotify.gif) ## 8、程序重启 [运行 AutoRestart.py](AutoRestart.py) -![AutoRestart](ScreenShot/AutoRestart.png) +![AutoRestart](ScreenShot/AutoRestart.gif) ## 9、自定义属性 [运行 CustomProperties.py](CustomProperties.py) @@ -113,7 +139,7 @@ ![Notification](ScreenShot/Notification.gif) -## 14、验证码 +## 14、验证码控件 [运行 VerificationCode.py](VerificationCode.py) 1. 更新为paintEvent方式,采用上下跳动 @@ -138,4 +164,81 @@ PyQt 结合 Opencv 进行人脸检测; 3. [dlib-19.4.0.win32-py3.5.exe](Data/dlib-19.4.0.win32-py3.5.exe) 4. [shape-predictor-68-face-landmarks.dat.bz2](http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2) -![FacePoints](ScreenShot/FacePoints.png) \ No newline at end of file +![FacePoints](ScreenShot/FacePoints.png) + +## 16、使用Threading +[运行 QtThreading.py](QtThreading.py) + +在PyQt中使用Theading线程 + +![QtThreading](ScreenShot/QtThreading.gif) + +## 17、背景连线动画 +[运行 CircleLine.py](CircleLine.py) + +主要参考 [背景连线动画.html](Data/背景连线动画.html) + +![CircleLine](ScreenShot/CircleLine.gif) + + +## 18、无边框圆角对话框 +[运行 FramelessDialog.py](FramelessDialog.py) + +1. 通过设置 `self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)` 和 `self.setAttribute(Qt.WA_TranslucentBackground, True)` 达到无边框和背景透明 +2. 在`QDialog`中放置一个`QWidget`作为背景和圆角 +3. 在`QWidget`中放置其他内容 + +![FramelessDialog1](ScreenShot/FramelessDialog1.png) +![FramelessDialog](ScreenShot/FramelessDialog.png) + +## 19、调整窗口显示边框 +[运行 ShowFrameWhenDrag.py](ShowFrameWhenDrag.py) + +1. 全局设置是【】在控制面板中->调整Windows的外观和性能->去掉勾选 拖动时显示窗口内容】 +2. 但是为了不影响其它应用,可以在窗口处理函数wndproc中对其进行判断处理 +3. 必须先要替换wndproc为自己的函数 +4. 当消息事件==WM_NCLBUTTONDOWN的时候, 先强制开启,然后处理完成后再还原 + +好处在于可以减少窗口更新的次数(用途有频繁渲染的界面) + +![ShowFrameWhenDrag](ScreenShot/ShowFrameWhenDrag.gif) + +## 20、判断信号是否连接 +[运行 IsSignalConnected.py](IsSignalConnected.py) + +1. 通过 `isSignalConnected` 判断是否连接 +2. 通过对象的 `receivers` 获取连接的数量来判断 + +![IsSignalConnected](ScreenShot/IsSignalConnected.png) + +## 21、调用虚拟键盘 +[运行 CallVirtualKeyboard.py](CallVirtualKeyboard.py) + +1. Windows上调用的是`osk.exe` +2. Linux上调用的是`florence`,`onboard`,`kvkbd`,这三种屏幕键盘需要自行安装 + +![CallVirtualKeyboard1](ScreenShot/CallVirtualKeyboard1.png) +![CallVirtualKeyboard2](ScreenShot/CallVirtualKeyboard2.png) + +## 22、动态忙碌光标 +[运行 GifCursor.py](GifCursor.py) + +通过定时器不停的修改光标图片来实现动态效果 + +![GifCursor](ScreenShot/GifCursor.gif) + +## 23、屏幕变动监听 +[运行 ScreenNotify.py](ScreenNotify.py) + +通过定时器减少不同的变化信号,尽量保证只调用一次槽函数来获取信息 + +![ScreenNotify](ScreenShot/ScreenNotify.png) + +## 24、无边框窗口 +[运行 NewFramelessWindow.py](NewFramelessWindow.py) + +1. 该方法只针对 `Qt5.15` 以上版本有效 +2. 通过事件过滤器判断边缘设置鼠标样式 +3. 处理点击事件交通过 `QWindow.startSystemMove` 和 `QWindow.startSystemResize` 传递给系统处理 + +![NewFramelessWindow](ScreenShot/NewFramelessWindow.gif) \ No newline at end of file diff --git a/Demo/RestartWindow.py b/Demo/RestartWindow.py index 9eec22425fd5604d99316fb93feaeb9ed6b6bb31..bd9709b31322afe0140c84ad0c28d27d74e0940a 100644 --- a/Demo/RestartWindow.py +++ b/Demo/RestartWindow.py @@ -1,26 +1,26 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -''' +""" Created on 2018年1月17日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: RestartWindow @description: 窗口重启 -''' -from PyQt5.QtCore import pyqtSignal -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton, QLineEdit,\ - QMessageBox +""" - -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" -__Version__ = "Version 1.0" +try: + from PyQt5.QtCore import pyqtSignal + from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton, QLineEdit, \ + QMessageBox, QApplication +except ImportError: + from PySide2.QtCore import Signal as pyqtSignal + from PySide2.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton, QLineEdit, \ + QMessageBox, QApplication class RestartWindow(QWidget): - restarted = pyqtSignal(QWidget, str) _Self = None # 很重要,保留窗口引用 @@ -55,7 +55,7 @@ class RestartWindow(QWidget): if __name__ == "__main__": import sys - from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) w = RestartWindow("test") w.show() diff --git a/Demo/ScreenNotify.py b/Demo/ScreenNotify.py new file mode 100644 index 0000000000000000000000000000000000000000..f2c9f9c58f9b3319473abc2e9e2abc96fbdb017c --- /dev/null +++ b/Demo/ScreenNotify.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2021/4/13 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: ScreenNotify +@description: 屏幕、分辨率、DPI变化通知 +""" +import sys + +try: + from PyQt5.QtCore import QTimer, QRect + from PyQt5.QtWidgets import QApplication, QPlainTextEdit +except ImportError: + from PySide2.QtCore import QTimer, QRect + from PySide2.QtWidgets import QApplication, QPlainTextEdit + + +class Window(QPlainTextEdit): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + self.appendPlainText('修改分辨率后查看') + # 记录最后一次的值(减少槽调用) + self.m_rect = QRect() + # 使用定时器来延迟触发最后一次变化 + self.m_timer = QTimer(self, timeout=self.onSolutionChanged) + self.m_timer.setSingleShot(True) # **重要** 保证多次信号尽量少的调用函数 + + # 主要是多屏幕->无屏幕->有屏幕 + QApplication.instance().primaryScreenChanged.connect(lambda _: self.m_timer.start(1000)) + # 其它信号最终基本上都会调用该信号 + QApplication.instance().primaryScreen().virtualGeometryChanged.connect( + lambda _: self.m_timer.start(1000)) + # DPI变化 + QApplication.instance().primaryScreen().logicalDotsPerInchChanged.connect( + lambda _: self.m_timer.start(1000)) + + def onSolutionChanged(self): + # 获取主屏幕 + screen = QApplication.instance().primaryScreen() + if self.m_rect == screen.availableVirtualGeometry(): + return + self.m_rect = screen.availableVirtualGeometry() + # 所有屏幕可用大小 + self.appendPlainText('\navailableVirtualGeometry: {0}'.format(str(screen.availableVirtualGeometry()))) + # 获取所有屏幕 + screens = QApplication.instance().screens() + for screen in screens: + self.appendPlainText( + 'screen: {0}, geometry({1}), availableGeometry({2}), logicalDotsPerInch({3}), ' + 'physicalDotsPerInch({4}), refreshRate({5})'.format( + screen.name(), screen.geometry(), screen.availableGeometry(), screen.logicalDotsPerInch(), + screen.physicalDotsPerInch(), screen.refreshRate())) + + +if __name__ == '__main__': + app = QApplication(sys.argv) + w = Window() + w.show() + sys.exit(app.exec_()) diff --git a/Demo/ScreenShot/AutoRestart.gif b/Demo/ScreenShot/AutoRestart.gif new file mode 100644 index 0000000000000000000000000000000000000000..ba12918d7bb4bdbfc196c534fc78879f3cdab4d4 Binary files /dev/null and b/Demo/ScreenShot/AutoRestart.gif differ diff --git a/Demo/ScreenShot/AutoRestart.png b/Demo/ScreenShot/AutoRestart.png deleted file mode 100644 index 18667c7d37fb21094c8e3b3f765b089ac7a8f4c4..0000000000000000000000000000000000000000 Binary files a/Demo/ScreenShot/AutoRestart.png and /dev/null differ diff --git a/Demo/ScreenShot/CallVirtualKeyboard1.png b/Demo/ScreenShot/CallVirtualKeyboard1.png new file mode 100644 index 0000000000000000000000000000000000000000..90bb7bf04b0dc31a1855d4a8dc131bb6424c5969 Binary files /dev/null and b/Demo/ScreenShot/CallVirtualKeyboard1.png differ diff --git a/Demo/ScreenShot/CallVirtualKeyboard2.png b/Demo/ScreenShot/CallVirtualKeyboard2.png new file mode 100644 index 0000000000000000000000000000000000000000..b3556cf1930dddeb35bee628965cddb07449c63e Binary files /dev/null and b/Demo/ScreenShot/CallVirtualKeyboard2.png differ diff --git a/Demo/ScreenShot/CircleLine.gif b/Demo/ScreenShot/CircleLine.gif new file mode 100644 index 0000000000000000000000000000000000000000..46729734ac6786ec626d850bfa11091fada3e388 Binary files /dev/null and b/Demo/ScreenShot/CircleLine.gif differ diff --git a/Demo/ScreenShot/FramelessDialog.png b/Demo/ScreenShot/FramelessDialog.png new file mode 100644 index 0000000000000000000000000000000000000000..a5d24a3c1ed7e6eb0f4f2a6567dd78292935c2ac Binary files /dev/null and b/Demo/ScreenShot/FramelessDialog.png differ diff --git a/Demo/ScreenShot/FramelessDialog1.png b/Demo/ScreenShot/FramelessDialog1.png new file mode 100644 index 0000000000000000000000000000000000000000..7f89494a24055aff7f03c20435d207a6e85dcd5d Binary files /dev/null and b/Demo/ScreenShot/FramelessDialog1.png differ diff --git a/Demo/ScreenShot/GifCursor.gif b/Demo/ScreenShot/GifCursor.gif new file mode 100644 index 0000000000000000000000000000000000000000..ed42f619d02d76e6598d28feffb12df802179f36 Binary files /dev/null and b/Demo/ScreenShot/GifCursor.gif differ diff --git a/Demo/ScreenShot/IsSignalConnected.png b/Demo/ScreenShot/IsSignalConnected.png new file mode 100644 index 0000000000000000000000000000000000000000..829976f96ba2101097f1cf028fb9d190c5bcc1e2 Binary files /dev/null and b/Demo/ScreenShot/IsSignalConnected.png differ diff --git a/Demo/ScreenShot/NewFramelessWindow.gif b/Demo/ScreenShot/NewFramelessWindow.gif new file mode 100644 index 0000000000000000000000000000000000000000..88f4333d8cd458dedbc0b3e45808aa50e90eacdc Binary files /dev/null and b/Demo/ScreenShot/NewFramelessWindow.gif differ diff --git a/Demo/ScreenShot/QtThreading.gif b/Demo/ScreenShot/QtThreading.gif new file mode 100644 index 0000000000000000000000000000000000000000..c832a19da62c58dd23494525b0fddd5770c8194e Binary files /dev/null and b/Demo/ScreenShot/QtThreading.gif differ diff --git a/Demo/ScreenShot/ScreenNotify.png b/Demo/ScreenShot/ScreenNotify.png new file mode 100644 index 0000000000000000000000000000000000000000..e95330cca5fbf6d60fd5e77cc7cf60b4e7d975da Binary files /dev/null and b/Demo/ScreenShot/ScreenNotify.png differ diff --git a/Demo/ScreenShot/ShowFrameWhenDrag.gif b/Demo/ScreenShot/ShowFrameWhenDrag.gif new file mode 100644 index 0000000000000000000000000000000000000000..b06c3e4fa3509c77ec9d4371120709814deea434 Binary files /dev/null and b/Demo/ScreenShot/ShowFrameWhenDrag.gif differ diff --git a/Demo/ScreenShotDll.py b/Demo/ScreenShotDll.py index 964df60e2ddfb9dad74bef200ba1984e4bf8b64f..0def9e7cb2e1773240866bb12d381496a47f32ff 100644 --- a/Demo/ScreenShotDll.py +++ b/Demo/ScreenShotDll.py @@ -1,3 +1,4 @@ from ctypes import CDLL + dll = CDLL('Data/ScreenShot.dll') dll.PrScrn() diff --git a/Demo/SharedMemory.py b/Demo/SharedMemory.py index 0ec5919a566ca85fa226d2d14fe9232c5ce0dc6e..f654ad1e8f05f78cd16f55d945cf53fd64ca2e0c 100644 --- a/Demo/SharedMemory.py +++ b/Demo/SharedMemory.py @@ -1,28 +1,29 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -''' +""" Created on 2017年3月30日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: TestQSharedMemory @description: -''' -from PyQt5.QtWidgets import QWidget +""" +from PyQt5.QtWidgets import QWidget from Lib.Application import SharedApplication # @UnresolvedImport -__version__ = "0.0.1" class Widget(QWidget): - - def __init__(self,*args,**kwargs): - super(Widget, self).__init__(*args,**kwargs) + + def __init__(self, *args, **kwargs): + super(Widget, self).__init__(*args, **kwargs) + if __name__ == "__main__": - import sys,os + import sys, os + print(os.getpid()) app = SharedApplication(sys.argv) if app.isRunning(): @@ -30,4 +31,4 @@ if __name__ == "__main__": sys.exit(0) w = Widget() w.show() - sys.exit(app.exec_()) \ No newline at end of file + sys.exit(app.exec_()) diff --git a/Demo/ShowFrameWhenDrag.py b/Demo/ShowFrameWhenDrag.py new file mode 100644 index 0000000000000000000000000000000000000000..5f0993fb46848c237da7fa7fd9dcbbf3ea7ca0ed --- /dev/null +++ b/Demo/ShowFrameWhenDrag.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2019年4月23日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: ShowFrameWhenDrag +@description: 调整窗口显示边框 +""" +from ctypes import sizeof, windll, c_int, byref, c_long, c_void_p, c_ulong, c_longlong, \ + c_ulonglong, WINFUNCTYPE, c_uint + +try: + from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QApplication +except ImportError: + from PySide2.QtWidgets import QWidget, QVBoxLayout, QLabel, QApplication + +if sizeof(c_long) == sizeof(c_void_p): + WPARAM = c_ulong + LPARAM = c_long +elif sizeof(c_longlong) == sizeof(c_void_p): + WPARAM = c_ulonglong + LPARAM = c_longlong + +WM_NCLBUTTONDOWN = 0x00a1 +GWL_WNDPROC = -4 +SPI_GETDRAGFULLWINDOWS = 38 +SPI_SETDRAGFULLWINDOWS = 37 +WNDPROC = WINFUNCTYPE(c_long, c_void_p, c_uint, WPARAM, LPARAM) + +try: + CallWindowProc = windll.user32.CallWindowProcW + SetWindowLong = windll.user32.SetWindowLongW + SystemParametersInfo = windll.user32.SystemParametersInfoW +except: + CallWindowProc = windll.user32.CallWindowProcA + SetWindowLong = windll.user32.SetWindowLongA + SystemParametersInfo = windll.user32.SystemParametersInfoA + + +def GetDragFullwindows(): + rv = c_int() + SystemParametersInfo(SPI_GETDRAGFULLWINDOWS, 0, byref(rv), 0) + return rv.value + + +def SetDragFullwindows(value): + SystemParametersInfo(SPI_SETDRAGFULLWINDOWS, value, 0, 0) + + +class Window(QWidget): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + layout = QVBoxLayout(self) + layout.addWidget(QLabel('拖动或者调整窗口试试看')) + + # 重点替换窗口处理过程 + self._newwndproc = WNDPROC(self._wndproc) + self._oldwndproc = SetWindowLong( + int(self.winId()), GWL_WNDPROC, self._newwndproc) + + def _wndproc(self, hwnd, msg, wparam, lparam): + if msg == WM_NCLBUTTONDOWN: + # 获取系统本身是否已经开启 + isDragFullWindow = GetDragFullwindows() + if isDragFullWindow != 0: + # 开启虚线框 + SetDragFullwindows(0) + # 系统本身处理 + ret = CallWindowProc( + self._oldwndproc, hwnd, msg, wparam, lparam) + # 关闭虚线框 + SetDragFullwindows(1) + return ret + return CallWindowProc(self._oldwndproc, hwnd, msg, wparam, lparam) + + +if __name__ == '__main__': + import sys + + app = QApplication(sys.argv) + w = Window() + w.show() + sys.exit(app.exec_()) diff --git a/Demo/SingleApplication.py b/Demo/SingleApplication.py index d452e1670402083e0fe6bdbe9693c6d126b13ccc..1f5f74ee90ef2e4a3633fc226cab25f751d218e5 100644 --- a/Demo/SingleApplication.py +++ b/Demo/SingleApplication.py @@ -1,28 +1,29 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -''' +""" Created on 2017年3月30日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: TestQSingleApplication @description: -''' +""" + from PyQt5.QtWidgets import QTextEdit from Lib.Application import QSingleApplication # @UnresolvedImport -__version__ = "0.0.1" - class Widget(QTextEdit): - + def __init__(self, *args, **kwargs): super(Widget, self).__init__(*args, **kwargs) + if __name__ == "__main__": import sys + app = QSingleApplication(sys.argv) if app.isRunning(): app.sendMessage("app is running") diff --git a/Demo/VerificationCode.py b/Demo/VerificationCode.py index df25ba7f97a97819702df406f7063a9aa8b21ff2..9490d8eff429f9104216af99d5dd724773a3efee 100644 --- a/Demo/VerificationCode.py +++ b/Demo/VerificationCode.py @@ -1,23 +1,25 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -''' +""" Created on 2017年4月5日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: widgets.WidgetCode @description: -''' -from random import sample +""" import string +from random import sample -from PyQt5.QtCore import Qt, qrand, QPointF, QPoint, QBasicTimer -from PyQt5.QtGui import QPainter, QBrush, QPen, QPalette, QFontMetrics -from PyQt5.QtWidgets import QLabel - - -__version__ = "0.0.1" +try: + from PyQt5.QtCore import Qt, qrand, QPointF, QPoint, QBasicTimer + from PyQt5.QtGui import QPainter, QBrush, QPen, QPalette, QFontMetrics, QFontDatabase + from PyQt5.QtWidgets import QLabel, QApplication, QWidget, QHBoxLayout, QLineEdit +except ImportError: + from PySide2.QtCore import Qt, qrand, QPointF, QPoint, QBasicTimer + from PySide2.QtGui import QPainter, QBrush, QPen, QPalette, QFontMetrics, QFontDatabase + from PySide2.QtWidgets import QLabel, QApplication, QWidget, QHBoxLayout, QLineEdit DEF_NOISYPOINTCOUNT = 60 # 噪点数量 COLORLIST = ("black", "gray", "red", "green", "blue", "magenta") @@ -28,8 +30,9 @@ FONT = "{word}" WORDS = list(string.ascii_letters + string.digits) SINETABLE = (0, 38, 71, 92, 100, 92, 71, 38, 0, -38, -71, -92, -100, -92, -71, -38) + class WidgetCode(QLabel): - + def __init__(self, *args, **kwargs): super(WidgetCode, self).__init__(*args, **kwargs) self._sensitive = False # 是否大小写敏感 @@ -47,33 +50,33 @@ class WidgetCode(QLabel): self.step = 0 self.timer = QBasicTimer() self.timer.start(60, self) - + def reset(self): self._code = "".join(sample(WORDS, 4)) # 随机4个字符 self.setText(self._code) - + def check(self, code): return self._code == str(code) if self._sensitive else self._code.lower() == str(code).lower() - + def setSensitive(self, sensitive): self._sensitive = sensitive - -# def setText(self, text): -# text = text if (text and len(text) == 4) else "".join(sample(WORDS, 4)) # 随机4个字符 -# self._code = str(text) -# html = "".join([FONT.format(color=COLORLIST[qrand() % 6], word=t) for t in text]) -# super(WidgetCode, self).setText(HTML.format(html=html)) - + + # def setText(self, text): + # text = text if (text and len(text) == 4) else "".join(sample(WORDS, 4)) # 随机4个字符 + # self._code = str(text) + # html = "".join([FONT.format(color=COLORLIST[qrand() % 6], word=t) for t in text]) + # super(WidgetCode, self).setText(HTML.format(html=html)) + def mouseReleaseEvent(self, event): super(WidgetCode, self).mouseReleaseEvent(event) self.reset() - + def timerEvent(self, event): if event.timerId() == self.timer.timerId(): self.step += 1 return self.update() return super(WidgetCode, self).timerEvent(event) - + def paintEvent(self, event): painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) @@ -107,21 +110,20 @@ class WidgetCode(QLabel): painter.drawText(x, y - ((SINETABLE[index] * metrics.height()) / 400), ch) x += metrics.width(ch) + if __name__ == "__main__": import sys - from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout - from PyQt5.QtGui import QFontDatabase - from PyQt5.QtWidgets import QLineEdit + app = QApplication(sys.argv) app.setApplicationName("Validate Code") QFontDatabase.addApplicationFont("Data/itckrist.ttf") w = QWidget() layout = QHBoxLayout(w) - + cwidget = WidgetCode(w, minimumHeight=35, minimumWidth=80) layout.addWidget(cwidget) lineEdit = QLineEdit(w, maxLength=4, placeholderText="请输入验证码并按回车验证", - returnPressed=lambda:print(cwidget.check(lineEdit.text()))) + returnPressed=lambda: print(cwidget.check(lineEdit.text()))) layout.addWidget(lineEdit) w.show() sys.exit(app.exec_()) diff --git a/Demo/WeltHideWindow.py b/Demo/WeltHideWindow.py index 73ba7b0d361204b710ace71d12ca729e8b36a80d..5faa20bd02023fe627f723ae7e004a5abc8c1783 100644 --- a/Demo/WeltHideWindow.py +++ b/Demo/WeltHideWindow.py @@ -4,18 +4,18 @@ """ Created on 2018年3月1日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: WeltHideWindow @description: 简单的窗口贴边隐藏 """ -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton - -__Author__ = 'By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com' -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 +try: + from PyQt5.QtCore import Qt + from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QApplication +except ImportError: + from PySide2.QtCore import Qt + from PySide2.QtWidgets import QWidget, QVBoxLayout, QPushButton, QApplication class WeltHideWindow(QWidget): @@ -88,7 +88,7 @@ class WeltHideWindow(QWidget): if __name__ == '__main__': import sys - from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) w = WeltHideWindow() w.show() diff --git a/Demo/WindowNotify.py b/Demo/WindowNotify.py index 408ff5e9afc30de2b21a1dc8db311612bd74346b..490dbd4ee4c56602752de2c1c083dfd6dae833ca 100644 --- a/Demo/WindowNotify.py +++ b/Demo/WindowNotify.py @@ -1,27 +1,27 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -''' +""" Created on 2017年3月30日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: WindowNotify @description: 右下角弹窗 -''' +""" import webbrowser -from PyQt5.QtCore import Qt, QPropertyAnimation, QPoint, QTimer, pyqtSignal -from PyQt5.QtWidgets import QWidget, QPushButton +try: + from PyQt5.QtCore import Qt, QPropertyAnimation, QPoint, QTimer, pyqtSignal + from PyQt5.QtWidgets import QWidget, QPushButton, QApplication, QHBoxLayout +except ImportError: + from PySide2.QtCore import Qt, QPropertyAnimation, QPoint, QTimer, Signal as pyqtSignal + from PySide2.QtWidgets import QWidget, QPushButton, QApplication, QHBoxLayout from Lib.UiNotify import Ui_NotifyForm # @UnresolvedImport -__version__ = "0.0.1" - - class WindowNotify(QWidget, Ui_NotifyForm): - SignalClosed = pyqtSignal() # 弹窗关闭信号 def __init__(self, title="", content="", timeout=5000, *args, **kwargs): @@ -60,10 +60,10 @@ class WindowNotify(QWidget, Ui_NotifyForm): webbrowser.open_new_tab("http://alyl.vip") def onClose(self): - #点击关闭按钮时 + # 点击关闭按钮时 print("onClose") self.isShow = False - QTimer.singleShot(100, self.closeAnimation)#启动弹回动画 + QTimer.singleShot(100, self.closeAnimation) # 启动弹回动画 def _init(self): # 隐藏任务栏|去掉边框|顶层显示 @@ -112,13 +112,14 @@ class WindowNotify(QWidget, Ui_NotifyForm): print("showAnimation isShow = True") # 显示动画 self.isShow = True - self.animation.stop()#先停止之前的动画,重新开始 + self.animation.stop() # 先停止之前的动画,重新开始 self.animation.setStartValue(self.pos()) self.animation.setEndValue(self._endPos) self.animation.start() # 弹出5秒后,如果没有焦点则弹回去 self._timer.start(self._timeout) -# QTimer.singleShot(self._timeout, self.closeAnimation) + + # QTimer.singleShot(self._timeout, self.closeAnimation) def closeAnimation(self): print("closeAnimation hasFocus", self.hasFocus()) @@ -158,9 +159,10 @@ class WindowNotify(QWidget, Ui_NotifyForm): if self._timeouted: QTimer.singleShot(1000, self.closeAnimation) + if __name__ == "__main__": import sys - from PyQt5.QtWidgets import QApplication, QHBoxLayout + app = QApplication(sys.argv) window = QWidget() diff --git a/Donate/PyQt_Group.png b/Donate/PyQt_Group.png new file mode 100644 index 0000000000000000000000000000000000000000..4be38ded7c3ee3ea7eac2fe04b801d1e200ef3de Binary files /dev/null and b/Donate/PyQt_Group.png differ diff --git a/Donate/PyQt_Guild.png b/Donate/PyQt_Guild.png new file mode 100644 index 0000000000000000000000000000000000000000..68ffa8e61096a26773ea526caf260d6fb437e9e3 Binary files /dev/null and b/Donate/PyQt_Guild.png differ diff --git a/Donate/weixin.png b/Donate/weixin.png index 0e7fe9d30b42ea5eb1ebb2f166c18aa7ccce0e57..ac0a728497a05717787c910660ee96468a64de5e 100644 Binary files a/Donate/weixin.png and b/Donate/weixin.png differ diff --git a/Donate/weixin2.png b/Donate/weixin2.png new file mode 100644 index 0000000000000000000000000000000000000000..0e7fe9d30b42ea5eb1ebb2f166c18aa7ccce0e57 Binary files /dev/null and b/Donate/weixin2.png differ diff --git a/Donate/wxblog.jpg b/Donate/wxblog.jpg new file mode 100644 index 0000000000000000000000000000000000000000..95fd81a6e3e8defe47d154148bb9b8274067c03d Binary files /dev/null and b/Donate/wxblog.jpg differ diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 94a9ed024d3859793618152ea559a168bbcbb5e2..0000000000000000000000000000000000000000 --- a/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/PyQtGraph/Data/__init__.py b/PyQtGraph/Data/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..ce54303d2303812b15c2db2fe2b7e8d8a6497a41 --- /dev/null +++ b/PyQtGraph/Data/__init__.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +@author: wxj +@license: (C) Hefei tongzhi electromechanical control technology co.LTD +@contact: +@software: garner +@file: __init__.py.py +@time: 2019/5/21 18:07 +@desc: +""" diff --git a/PyQtGraph/Data/graphAnalysis.py b/PyQtGraph/Data/graphAnalysis.py new file mode 100644 index 0000000000000000000000000000000000000000..0f30b3a60e26c1d5a518a6d46f00c5191244b203 --- /dev/null +++ b/PyQtGraph/Data/graphAnalysis.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'graphAnalysis.ui' +# +# Created by: PyQt5 UI code generator 5.11.3 +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class graph_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(1024, 768) + self.gridLayout_3 = QtWidgets.QGridLayout(Form) + self.gridLayout_3.setContentsMargins(20, 20, 20, 20) + self.gridLayout_3.setSpacing(20) + self.gridLayout_3.setObjectName("gridLayout_3") + self.pushButton_7 = QtWidgets.QPushButton(Form) + self.pushButton_7.setMinimumSize(QtCore.QSize(0, 80)) + font = QtGui.QFont() + font.setPointSize(15) + self.pushButton_7.setFont(font) + self.pushButton_7.setObjectName("pushButton_7") + self.gridLayout_3.addWidget(self.pushButton_7, 2, 2, 1, 2) + self.label = QtWidgets.QLabel(Form) + font = QtGui.QFont() + font.setPointSize(15) + self.label.setFont(font) + self.label.setAlignment(QtCore.Qt.AlignCenter) + self.label.setObjectName("label") + self.gridLayout_3.addWidget(self.label, 0, 0, 1, 4) + self.tabWidget = QtWidgets.QTabWidget(Form) + self.tabWidget.setObjectName("tabWidget") + self.tab = QtWidgets.QWidget() + self.tab.setObjectName("tab") + self.tabWidget.addTab(self.tab, "") + self.tab_2 = QtWidgets.QWidget() + self.tab_2.setObjectName("tab_2") + self.tabWidget.addTab(self.tab_2, "") + self.gridLayout_3.addWidget(self.tabWidget, 1, 1, 1, 4) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Form")) + self.pushButton_7.setText(_translate("Form", "分析")) + self.label.setText(_translate("Form", "图形分析")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("Form", "折线图")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("Form", "Tab 2")) diff --git a/PyQtGraph/Data/graphAnalysis.ui b/PyQtGraph/Data/graphAnalysis.ui new file mode 100644 index 0000000000000000000000000000000000000000..627d66ba656276658e185011e04469b2b6fdd46f --- /dev/null +++ b/PyQtGraph/Data/graphAnalysis.ui @@ -0,0 +1,83 @@ + + + Form + + + + 0 + 0 + 1024 + 768 + + + + Form + + + + 20 + + + 20 + + + 20 + + + 20 + + + 20 + + + + + + 0 + 80 + + + + + 15 + + + + 分析 + + + + + + + + 15 + + + + 图形分析 + + + Qt::AlignCenter + + + + + + + + 折线图 + + + + + Tab 2 + + + + + + + + + diff --git a/PyQtGraph/Data/graphTest.py b/PyQtGraph/Data/graphTest.py new file mode 100644 index 0000000000000000000000000000000000000000..06ffde6d12aba73d86e78c85371235060c3b7ef1 --- /dev/null +++ b/PyQtGraph/Data/graphTest.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'graphTest.ui' +# +# Created by: PyQt5 UI code generator 5.11.3 +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class graph_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(1024, 768) + self.verticalLayout = QtWidgets.QVBoxLayout(Form) + self.verticalLayout.setContentsMargins(20, 20, 20, 20) + self.verticalLayout.setSpacing(20) + self.verticalLayout.setObjectName("verticalLayout") + self.label = QtWidgets.QLabel(Form) + font = QtGui.QFont() + font.setPointSize(15) + self.label.setFont(font) + self.label.setAlignment(QtCore.Qt.AlignCenter) + self.label.setObjectName("label") + self.verticalLayout.addWidget(self.label) + self.tabWidget = QtWidgets.QTabWidget(Form) + self.tabWidget.setObjectName("tabWidget") + self.tab = QtWidgets.QWidget() + self.tab.setObjectName("tab") + self.tabWidget.addTab(self.tab, "") + self.tab_2 = QtWidgets.QWidget() + self.tab_2.setObjectName("tab_2") + self.tabWidget.addTab(self.tab_2, "") + self.verticalLayout.addWidget(self.tabWidget) + self.pushButton_7 = QtWidgets.QPushButton(Form) + self.pushButton_7.setMinimumSize(QtCore.QSize(0, 80)) + font = QtGui.QFont() + font.setPointSize(15) + self.pushButton_7.setFont(font) + self.pushButton_7.setObjectName("pushButton_7") + self.verticalLayout.addWidget(self.pushButton_7) + + self.retranslateUi(Form) + self.tabWidget.setCurrentIndex(0) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Form")) + self.label.setText(_translate("Form", "图形分析")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("Form", "折线图")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("Form", "Tab 2")) + self.pushButton_7.setText(_translate("Form", "分析")) diff --git a/PyQtGraph/Data/graphTest.ui b/PyQtGraph/Data/graphTest.ui new file mode 100644 index 0000000000000000000000000000000000000000..2dbb64ff05c6162394abad5f474b70c43f8b7ab1 --- /dev/null +++ b/PyQtGraph/Data/graphTest.ui @@ -0,0 +1,86 @@ + + + Form + + + + 0 + 0 + 1024 + 768 + + + + Form + + + + 20 + + + 20 + + + 20 + + + 20 + + + 20 + + + + + + 15 + + + + 图形分析 + + + Qt::AlignCenter + + + + + + + 0 + + + + 折线图 + + + + + Tab 2 + + + + + + + + + 0 + 80 + + + + + 15 + + + + 分析 + + + + + + + + diff --git a/PyQtGraph/README.en.md b/PyQtGraph/README.en.md new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/PyQtGraph/README.md b/PyQtGraph/README.md new file mode 100644 index 0000000000000000000000000000000000000000..3ac9f29d0e0f1b014aeac8d7152e089b8d423722 --- /dev/null +++ b/PyQtGraph/README.md @@ -0,0 +1,36 @@ +# PyQtGraph + +## 说明 + +正在汇总Demo中,Be continued + +本章节预告,实际使用中的包`PyQtGraph`相关BUG,以及解决方案,如有精力会添加相应解决方案的作者 + +1. `PyQtGraph`右键保存图片功能异常,以及解决防范 +2. 依据`PyQtGraph` examples例子的使用心得 +3. `PyQtGraph`如何禁止左键,右键,滑轮事件功能 +4. 使用`QScrollArea`后添加`pg.PlotWidget()`不能选择图片尺寸问题。 +5. 多图共享轴(Difficult) +6. `pg.PlotWidget()`鼠标获取X轴坐标 + +## 目录 +- [鼠标获取X轴坐标](#1鼠标获取X轴坐标) +- [禁止右键点击功能、鼠标滚轮,添加滚动条等功能](#2禁止右键点击功能、鼠标滚轮,添加滚动条等功能) + +## 1、鼠标获取X轴坐标 +[运行 mouseFlow.py](mouseFlow.py) + +![mouseFlow](ScreenShot/mouseFlow.gif) + +## 2、禁止右键点击功能、鼠标滚轮,添加滚动条等功能 +[运行 graph1.py](graph1.py) | [查看 graphTest.ui](Data/graphTest.ui) + +![mouseFlow](ScreenShot/function.gif) + +## 3、不用修改源码,重加载,解决右键保存图片异常;解决自定义坐标轴密集显示;禁止鼠标事件; +[加载 tools.py](tools.py) + +## 4、QScrollArea添加和修改大小例子; +[运行 testGraphAnalysis.py](testGraphAnalysis.py) | [查看 graphAnalysis.ui](Data/graphAnalysis.ui) + +![testGraphAnalysis](ScreenShot/GraphAnalysis.gif) diff --git a/PyQtGraph/ScreenShot/GraphAnalysis.gif b/PyQtGraph/ScreenShot/GraphAnalysis.gif new file mode 100644 index 0000000000000000000000000000000000000000..006265ef10eab7cd020dd892af72518e8e6335e4 Binary files /dev/null and b/PyQtGraph/ScreenShot/GraphAnalysis.gif differ diff --git a/PyQtGraph/ScreenShot/function.gif b/PyQtGraph/ScreenShot/function.gif new file mode 100644 index 0000000000000000000000000000000000000000..708e0f096030077fcfeb42cf9fe3f250f0d49a86 Binary files /dev/null and b/PyQtGraph/ScreenShot/function.gif differ diff --git a/PyQtGraph/ScreenShot/mouseFlow.gif b/PyQtGraph/ScreenShot/mouseFlow.gif new file mode 100644 index 0000000000000000000000000000000000000000..646ab999db20088b4017a7145fb2fa38b1f5b24a Binary files /dev/null and b/PyQtGraph/ScreenShot/mouseFlow.gif differ diff --git a/PyQtGraph/graph1.py b/PyQtGraph/graph1.py new file mode 100644 index 0000000000000000000000000000000000000000..b37b8ade66f72cbf01263bfc67158b2fdfa79e3a --- /dev/null +++ b/PyQtGraph/graph1.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Created on 2019年5月21日 +@author: weike32 +@site: https://pyqt.site ,https://github.com/weike32 +@email: 394967319@qq.com +@file: CopyContent +@description: 禁止右键,添加滑动窗口,点击按钮生成图片,自定义Y轴坐标,背景颜色调整 +""" +import sys + +import pyqtgraph as pg +from PyQt5.QtGui import QSpacerItem, QSizePolicy +from PyQt5.QtWidgets import QDialog, QApplication, QWidget, QScrollArea, QVBoxLayout + +from PyQtGraph.Data.graphTest import graph_Form + + +class CustomViewBox(pg.ViewBox): + def __init__(self, *args, **kwds): + pg.ViewBox.__init__(self, *args, **kwds) + self.RectMode = 3 + self.setMouseMode(self.RectMode) + + def mouseClickEvent(self, ev): + if ev.button() == pg.QtCore.Qt.RightButton: + self.autoRange() + + def mouseDragEvent(self, ev): + pg.ViewBox.mouseDragEvent(self, ev) + + def wheelEvent(self, ev, axis=None): + # pg.ViewBox.wheelEvent(self, ev, axis) + ev.ignore() + + +class graphAnalysis(QDialog, graph_Form): + def __init__(self): + super(graphAnalysis, self).__init__() + self.setupUi(self) + self.pushButton_7.clicked.connect(self.test) + self.tabWidget.clear() + + def test(self): + tab1 = QWidget() + scrollArea = QScrollArea(tab1) + scrollArea.setMinimumSize(984, 550) + scrollArea.setWidgetResizable(True) + labelsContainer = QWidget() + labelsContainer.setMinimumSize(0, 1500) + scrollArea.setWidget(labelsContainer) + layout = QVBoxLayout(labelsContainer) + time = ['2019-04-20 08:09:00', '2019-04-20 08:09:00', '2019-04-20 08:09:00', '2019-04-20 08:09:00'] + value = [1.2, 2, 1, 4] + xdict = dict(enumerate(time)) + ticks = [list(zip(range(4), tuple(time)))] + vb = CustomViewBox() + plt = pg.PlotWidget(title="标题这里填写", viewBox=vb) + plt.setBackground(background=None) + plt.plot(list(xdict.keys()), value) + plt.getPlotItem().getAxis("bottom").setTicks(ticks) + temp = QWidget() + temp.setMinimumSize(900, 300) + temp.setMaximumSize(900, 300) + layout1 = QVBoxLayout(temp) + layout1.addWidget(plt) + layout.addWidget(temp) + spacerItem = QSpacerItem(20, 40, QSizePolicy.Minimum, + QSizePolicy.Expanding) + layout.addItem(spacerItem) + self.tabWidget.addTab(tab1, '这里tabWidget修改标签') + + +if __name__ == "__main__": + app = QApplication(sys.argv) + w = graphAnalysis() + w.show() + sys.exit(app.exec_()) diff --git a/PyQtGraph/mouseFlow.py b/PyQtGraph/mouseFlow.py new file mode 100644 index 0000000000000000000000000000000000000000..1d3503b7f34ae59c7077072ceed429e32082fc59 --- /dev/null +++ b/PyQtGraph/mouseFlow.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Created on 2019年5月2日 +@author: weike32 +@site: https://pyqt.site ,https://github.com/weike32 +@email: 394967319@qq.com +@file: CopyContent +@description: 查阅了很多博客,如果有异,可以联系作者邮箱。本Demo仅作学习参考用,保有后续相关权益。 +""" +import sys + +import numpy as np +import pyqtgraph as pg +from PyQt5 import QtCore +from PyQt5.QtWidgets import QApplication, QMainWindow + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(726, 595) + self.graphicsView = pg.PlotWidget(Form) + self.graphicsView.setGeometry(QtCore.QRect(75, 131, 621, 441)) + self.graphicsView.setObjectName("graphicsView") + + +class MyWindow(QMainWindow, Ui_Form): + def __init__(self, parent=None): + super(MyWindow, self).__init__(parent) + self.setupUi(self) + x = np.linspace(-100, 100, 1000) + data = np.sin(x) / x + self.graphicsView.plot(data, pen=(255, 255, 255, 200)) + self.label = pg.TextItem(text="横坐标:{}".format(0)) + self.graphicsView.addItem(self.label) + self.setMouseTracking(True) + self.graphicsView.scene().sigMouseMoved.connect(self.onMouseMoved) + + def onMouseMoved(self, evt): + if self.graphicsView.plotItem.vb.mapSceneToView(evt): + point = self.graphicsView.plotItem.vb.mapSceneToView(evt) + self.label.setHtml("

横坐标:{0}

".format(point.x())) + + +if __name__ == '__main__': + app = QApplication(sys.argv) + myWin = MyWindow() + myWin.show() + sys.exit(app.exec_()) diff --git a/PyQtGraph/requirements.txt b/PyQtGraph/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..0e9e4ca01da4d5e4bf4196642d17b4e16063f2b7 --- /dev/null +++ b/PyQtGraph/requirements.txt @@ -0,0 +1 @@ +pyqtgraph diff --git a/PyQtGraph/testGraphAnalysis.py b/PyQtGraph/testGraphAnalysis.py new file mode 100644 index 0000000000000000000000000000000000000000..23f23a4c27b62b1562e5f9b670844b7d7891b243 --- /dev/null +++ b/PyQtGraph/testGraphAnalysis.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Created on 2019年8月17日 +@author: weike32 +@site: https://pyqt.site ,https://github.com/weike32 +@email: 394967319@qq.com +@file: CopyContent +@description: +""" +import sys + +import pyqtgraph as pg +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QSpacerItem, QSizePolicy +from PyQt5.QtWidgets import QDialog, QApplication, QWidget, QScrollArea, QVBoxLayout + +from PyQtGraph.Data.graphAnalysis import graph_Form + + +class CustomViewBox(pg.ViewBox): + def __init__(self, *args, **kwds): + pg.ViewBox.__init__(self, *args, **kwds) + self.RectMode = 3 + self.setMouseMode(self.RectMode) + + def mouseClickEvent(self, ev): + if ev.button() == pg.QtCore.Qt.RightButton: + self.autoRange() + + def mouseDragEvent(self, ev): + pg.ViewBox.mouseDragEvent(self, ev) + + def wheelEvent(self, ev, axis=None): + ev.ignore() + + +class graphAnalysis(QDialog, graph_Form): + def __init__(self): + super(graphAnalysis, self).__init__() + self.setupUi(self) + self.pushButton_7.clicked.connect(self.test) + self.tabWidget.clear() + + def handleChanged(self, item, column): + count = item.childCount() + if item.checkState(column) == Qt.Checked: + for index in range(count): + item.child(index).setCheckState(0, Qt.Checked) + if item.checkState(column) == Qt.Unchecked: + for index in range(count): + item.child(index).setCheckState(0, Qt.Unchecked) + + def test(self): + + tab1 = QWidget() + scrollArea = QScrollArea(tab1) + scrollArea.setMinimumSize(650, 550) + scrollArea.setWidgetResizable(True) + + labelsContainer = QWidget() + labelsContainer.setMinimumSize(0, 3000 + 200) + scrollArea.setWidget(labelsContainer) + layout = QVBoxLayout(labelsContainer) + + time = ['2019-04-20 08:09:00', '2019-04-20 08:09:00', '2019-04-20 08:09:00', '2019-04-20 08:09:00'] + value = [1.2, 2, 1, 4] + xdict = dict(enumerate(time)) + ticks = [list(zip(range(4), tuple(time)))] + for i in range(11): + vb1 = CustomViewBox() + plt1 = pg.PlotWidget(title="Basic array plotting%s" % i, viewBox=vb1) + plt1.resize(500, 500) + plt1.setBackground(background=None) + plt1.plot(list(xdict.keys()), value) + plt1.getPlotItem().getAxis("bottom").setTicks(ticks) + temp1 = QWidget() + temp1.setMinimumSize(600, 300) + temp1.setMaximumSize(600, 300) + layout2 = QVBoxLayout(temp1) + layout2.addWidget(plt1) + layout.addWidget(temp1) + spacerItem = QSpacerItem(20, 40, QSizePolicy.Minimum, + QSizePolicy.Expanding) + layout.addItem(spacerItem) + # print(layout.count()) + self.tabWidget.addTab(tab1, '12') + for i in range(self.tabWidget.count()): + self.tabWidget.widget(i) + + +if __name__ == "__main__": + app = QApplication(sys.argv) + w = graphAnalysis() + w.show() + sys.exit(app.exec_()) diff --git a/PyQtGraph/tools.py b/PyQtGraph/tools.py new file mode 100644 index 0000000000000000000000000000000000000000..5a867de382c4fc3ab21963a1f3aaed9008d2b3d3 --- /dev/null +++ b/PyQtGraph/tools.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Created on 2019年5月21日 +@author: weike32 +@site: https://pyqt.site ,https://github.com/weike32 +@email: 394967319@qq.com +@file: CopyContent +@description: 工具类 +""" +import pyqtgraph as pg +from pyqtgraph.exporters.ImageExporter import ImageExporter, Exporter +from pyqtgraph.parametertree import Parameter + + +# 不用修改源码,重加载,解决右键保存图片异常 +def widthChanged(self): + sr = self.getSourceRect() + ar = float(sr.height()) / sr.width() + self.params.param('height').setValue(int(self.params['width'] * ar), blockSignal=self.heightChanged) + + +def heightChanged(self): + sr = self.getSourceRect() + ar = float(sr.width()) / sr.height() + self.params.param('width').setValue(int(self.params['height'] * ar), blockSignal=self.widthChanged) + + +def New__init__(self, item): + Exporter.__init__(self, item) + tr = self.getTargetRect() + if isinstance(item, pg.Qt.QtGui.QGraphicsItem): + scene = item.scene() + else: + scene = item + bgbrush = scene.views()[0].backgroundBrush() + bg = bgbrush.color() + if bgbrush.style() == pg.Qt.QtCore.Qt.NoBrush: + bg.setAlpha(0) + + self.params = Parameter(name='params', type='group', children=[ + {'name': 'width', 'type': 'int', 'value': int(tr.width()), 'limits': (0, None)}, + {'name': 'height', 'type': 'int', 'value': int(tr.height()), 'limits': (0, None)}, + {'name': 'antialias', 'type': 'bool', 'value': True}, + {'name': 'background', 'type': 'color', 'value': bg}, + ]) + self.params.param('width').sigValueChanged.connect(self.widthChanged) + self.params.param('height').sigValueChanged.connect(self.heightChanged) + + +ImageExporter.heightChanged = heightChanged +ImageExporter.widthChanged = widthChanged +ImageExporter.__init__ = New__init__ + + +# 解决自定义坐标轴密集显示 +class MyStringAxis(pg.AxisItem): + def __init__(self, xdict, *args, **kwargs): + pg.AxisItem.__init__(self, *args, **kwargs) + self.xdict = xdict + + def tickStrings(self, values, scale, spacing): + strings = [] + for v in values: + vs = v * scale + if vs in self.xdict.keys(): + vstr = self.xdict[vs] + else: + vstr = "" + strings.append(vstr) + return strings + + +# 禁止鼠标事件 +class CustomViewBox(pg.ViewBox): + def __init__(self, *args, **kwds): + pg.ViewBox.__init__(self, *args, **kwds) + self.RectMode = 3 + self.setMouseMode(self.RectMode) + + def mouseClickEvent(self, ev): + if ev.button() == pg.QtCore.Qt.RightButton: + self.autoRange() + + def mouseDragEvent(self, ev): + pg.ViewBox.mouseDragEvent(self, ev) + + def wheelEvent(self, ev, axis=None): + pg.ViewBox.wheelEvent(self, ev, axis) diff --git a/QAxWidget/README.en.md b/QAxWidget/README.en.md new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/QAxWidget/README.md b/QAxWidget/README.md index 56835bb8faa054480149812cf0b31a10e02d7b83..8811751d7fc1ab5c6f9c6f21e40a9f351757d591 100644 --- a/QAxWidget/README.md +++ b/QAxWidget/README.md @@ -1,5 +1,8 @@ # QAxWidget +- 目录 + - [显示Word、Excel、PDF文件](#1显示WordExcelPDF文件) + ## 1、显示Word、Excel、PDF文件 [运行 ViewOffice.py](ViewOffice.py) diff --git a/QAxWidget/ViewOffice.py b/QAxWidget/ViewOffice.py index f30d753fa81a42d16a92809b523e5593400161bb..3c3318097ab665fe1ec15e43ffb33680471555a1 100644 --- a/QAxWidget/ViewOffice.py +++ b/QAxWidget/ViewOffice.py @@ -1,24 +1,20 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -''' +""" Created on 2017年4月6日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: ViewOffice @description: -''' +""" + from PyQt5.QAxContainer import QAxWidget -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QFileDialog,\ +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QFileDialog, \ QMessageBox -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]" -__Version__ = "Version 1.0" - - class AxWidget(QWidget): def __init__(self, *args, **kwargs): @@ -68,6 +64,7 @@ class AxWidget(QWidget): if __name__ == '__main__': import sys from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) w = AxWidget() w.show() diff --git a/QCalendarWidget/CalendarQssStyle.py b/QCalendarWidget/CalendarQssStyle.py index 6a1ed681968f02ab27ca75073ba17aa23ff18614..9d2e0924f2fa5eb014df742a9eb42712209603df 100644 --- a/QCalendarWidget/CalendarQssStyle.py +++ b/QCalendarWidget/CalendarQssStyle.py @@ -1,17 +1,23 @@ """ Created on 2018年1月30日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: CalendarQssStyle @description: 日历美化样式 """ import sys -from PyQt5.QtWidgets import QApplication, QCalendarWidget +try: + from PyQt5.QtCore import Qt + from PyQt5.QtGui import QTextCharFormat, QBrush, QColor + from PyQt5.QtWidgets import QApplication, QCalendarWidget +except ImportError: + from PySide2.QtCore import Qt + from PySide2.QtGui import QTextCharFormat, QBrush, QColor + from PySide2.QtWidgets import QApplication, QCalendarWidget - -StyleSheet = ''' +StyleSheet = """ /*顶部导航区域*/ #qt_calendar_navigationbar { background-color: rgb(0, 188, 212); @@ -99,7 +105,7 @@ CalendarWidget QToolButton::menu-indicator { outline: 0px;/*去掉选中后的虚线框*/ selection-background-color: rgb(0, 188, 212); /*选中背景颜色*/ } -''' +""" class CalendarWidget(QCalendarWidget): @@ -109,6 +115,17 @@ class CalendarWidget(QCalendarWidget): # 隐藏左边的序号 self.setVerticalHeaderFormat(self.NoVerticalHeader) + # 修改周六周日颜色 + + fmtGreen = QTextCharFormat() + fmtGreen.setForeground(QBrush(Qt.green)) + self.setWeekdayTextFormat(Qt.Saturday, fmtGreen) + + fmtOrange = QTextCharFormat() + fmtOrange.setForeground(QBrush(QColor(252, 140, 28))) + self.setWeekdayTextFormat(Qt.Sunday, fmtOrange) + + if __name__ == "__main__": app = QApplication(sys.argv) app.setStyleSheet(StyleSheet) diff --git a/QCalendarWidget/README.md b/QCalendarWidget/README.md index 42ee21d5ebd5f751a38df850c637c96955b6d91f..62bd881536f06fe12eadfba943cbbfe1a8813ead 100644 --- a/QCalendarWidget/README.md +++ b/QCalendarWidget/README.md @@ -1,5 +1,8 @@ # QCalendarWidget +- 目录 + - [QSS美化日历样式](#1QSS美化日历样式) + ## 1、QSS美化日历样式 [运行 CalendarQssStyle.py](CalendarQssStyle.py) diff --git a/QChart/README.md b/QChart/README.md deleted file mode 100644 index fb1ca58663c4d3c216a5d2b0b2746bf07933e4c4..0000000000000000000000000000000000000000 --- a/QChart/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# QChart - -## 1、折线图 -[运行 LineChart.py](LineChart.py) - -![LineChart](ScreenShot/LineChart.png) - -## 2、折线堆叠图 -[运行 LineStack.py](LineStack.py) - -仿照 [line-stack](http://echarts.baidu.com/demo.html#line-stack) - -![LineStack](ScreenShot/LineStack.gif) - -## 3、柱状堆叠图 -[运行 BarStack.py](BarStack.py) - -仿照 [bar-stack](http://echarts.baidu.com/demo.html#bar-stack) - -![BarStack](ScreenShot/BarStack.gif) - -## 4、LineChart自定义xy轴 -[运行 CustomXYaxis.py](CustomXYaxis.py) - -![CustomXYaxis](ScreenShot/CustomXYaxis.png) - -## 5、ToolTip提示 -[运行 ToolTip.py](ToolTip.py) | [运行 ToolTip2.py](ToolTip2.py) - -![ToolTip](ScreenShot/ToolTip.gif) ![ToolTip2](ScreenShot/ToolTip2.gif) \ No newline at end of file diff --git a/QComboBox/CenterText.py b/QComboBox/CenterText.py new file mode 100644 index 0000000000000000000000000000000000000000..30eef32a7c11a71ec0822dfe5a3bda078afa13e9 --- /dev/null +++ b/QComboBox/CenterText.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Created on 2022/09/03 +@author: Irony +@site: https://pyqt.site https://github.com/PyQt5 +@email: 892768447@qq.com +@file: CenterText.py +@description: 文字居中对齐 +""" + +import sys + +try: + from PyQt5.QtCore import Qt + from PyQt5.QtWidgets import QApplication, QStyle, QVBoxLayout, QWidget +except ImportError: + from PySide2.QtCore import Qt + from PySide2.QtWidgets import QApplication, QStyle, QVBoxLayout, QWidget + +from Lib.CtComboBox import CtComboBox + + +class Window(QWidget): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + layout = QVBoxLayout(self) + + c1 = CtComboBox(self) + c1.addItems(['item-%s' % i for i in range(10)]) + layout.addWidget(c1) + + # 可编辑 + c2 = CtComboBox(self) + c2.setEditable(True) + c2.lineEdit().setAlignment(Qt.AlignCenter) + c2.addItems(['item-%s' % i for i in range(10)]) + layout.addWidget(c2) + + # 带图标 + c3 = CtComboBox(self) + for i in range(10): + c3.addItem(c3.style().standardIcon(QStyle.SP_ComputerIcon), + 'item-%s' % i) + layout.addWidget(c3) + + +if __name__ == '__main__': + app = QApplication(sys.argv) + w = Window() + w.show() + sys.exit(app.exec_()) diff --git a/QComboBox/CityLinkage.py b/QComboBox/CityLinkage.py index 603f342d3b69bd2185e78fab4475cd415091487b..203888f49bec45524b48dfe903205c049f0ce243 100644 --- a/QComboBox/CityLinkage.py +++ b/QComboBox/CityLinkage.py @@ -3,8 +3,8 @@ """ Created on 2018年1月27日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: CityLinkage @description: 下拉联动 @@ -12,16 +12,18 @@ Created on 2018年1月27日 import json import sys -from PyQt5.QtCore import Qt, QSortFilterProxyModel, QRegExp -from PyQt5.QtGui import QStandardItemModel, QStandardItem -from PyQt5.QtWidgets import QWidget, QApplication, QHBoxLayout, QComboBox,\ - QLabel, QSpacerItem, QSizePolicy import chardet - -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" -__Version__ = "Version 1.0" +try: + from PyQt5.QtCore import Qt, QSortFilterProxyModel, QRegExp + from PyQt5.QtGui import QStandardItemModel, QStandardItem + from PyQt5.QtWidgets import QWidget, QApplication, QHBoxLayout, QComboBox, \ + QLabel, QSpacerItem, QSizePolicy +except ImportError: + from PySide2.QtCore import Qt, QSortFilterProxyModel, QRegExp + from PySide2.QtGui import QStandardItemModel, QStandardItem + from PySide2.QtWidgets import QWidget, QApplication, QHBoxLayout, QComboBox, \ + QLabel, QSpacerItem, QSizePolicy class SortFilterProxyModel(QSortFilterProxyModel): diff --git a/QComboBox/Lib/CtComboBox.py b/QComboBox/Lib/CtComboBox.py new file mode 100644 index 0000000000000000000000000000000000000000..3539ff1117d04a1c2b1c2250ab65993c6cc75791 --- /dev/null +++ b/QComboBox/Lib/CtComboBox.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Created on 2022/09/04 +@author: Irony +@site: https://pyqt.site https://github.com/PyQt5 +@email: 892768447@qq.com +@file: CtComboBox.py +@description: 文字居中对齐 +""" + +try: + from PyQt5.QtCore import QRect, Qt + from PyQt5.QtGui import QIcon, QPalette + from PyQt5.QtWidgets import QComboBox, QProxyStyle +except ImportError: + from PySide2.QtCore import QRect, Qt + from PySide2.QtGui import QIcon, QPalette + from PySide2.QtWidgets import QComboBox, QProxyStyle + + +class ComboBoxStyle(QProxyStyle): + + def drawControl(self, element, option, painter, widget=None): + if element == QProxyStyle.CE_ComboBoxLabel: + # https://github.com/qt/qtbase/blob/5.15.2/src/widgets/styles/qcommonstyle.cpp#L2200 + editRect = self.subControlRect(QProxyStyle.CC_ComboBox, option, + QProxyStyle.SC_ComboBoxEditField, + widget) + painter.save() + painter.setClipRect(editRect) + if not option.currentIcon.isNull(): + # 绘制图标 + mode = QIcon.Normal if ( + option.state & + QProxyStyle.State_Enabled) else QIcon.Disabled + pixmap = option.currentIcon.pixmap( + widget.window().windowHandle() if widget else None, + option.iconSize, mode) + iconRect = QRect(editRect) + iconRect.setWidth(option.iconSize.width() + 4) + iconRect = self.alignedRect(option.direction, + Qt.AlignLeft | Qt.AlignVCenter, + iconRect.size(), editRect) + if option.editable: + painter.fillRect(iconRect, + option.palette.brush(QPalette.Base)) + self.drawItemPixmap(painter, iconRect, Qt.AlignCenter, pixmap) + + if option.direction == Qt.RightToLeft: + editRect.translate(-4 - option.iconSize.width(), 0) + else: + editRect.translate(option.iconSize.width() + 4, 0) + if option.currentText and not option.editable: + # 考虑右边箭头位置 + arrowRect = self.subControlRect(QProxyStyle.CC_ComboBox, option, + QProxyStyle.SC_ComboBoxArrow, + widget) + editRect.setWidth(editRect.width() + arrowRect.width()) + # 绘制居中文字 + self.drawItemText( + painter, editRect.adjusted(1, 0, -1, 0), + self.visualAlignment(option.direction, Qt.AlignCenter), + option.palette, option.state & QProxyStyle.State_Enabled, + option.currentText) + painter.restore() + return + super(ComboBoxStyle, self).drawControl(element, option, painter, widget) + + +class CtComboBox(QComboBox): + + def __init__(self, *args, **kwargs): + super(CtComboBox, self).__init__(*args, **kwargs) + # 绑定每个元素添加信号,用于设置文本居中 + self.model().rowsInserted.connect(self._onRowsInserted) + self.setStyle(ComboBoxStyle()) + + def _onRowsInserted(self, index, first, last): + if first < 0: + return + for i in range(first, last + 1): + self.view().model().item(i).setTextAlignment(Qt.AlignCenter) diff --git a/QComboBox/Lib/__init__.py b/QComboBox/Lib/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/QComboBox/README.en.md b/QComboBox/README.en.md index b3bd077727c362838ec07ba45a1bbea77a80ec06..9a4f5214accfa2a5987d6294d78520339a2b2f4a 100644 --- a/QComboBox/README.en.md +++ b/QComboBox/README.en.md @@ -1,5 +1,8 @@ # QComboBox +- Catalog + - [Data Linkage](#1data-linkage) + ## 1、Data Linkage [Run CityLinkage.py](CityLinkage.py) diff --git a/QComboBox/README.md b/QComboBox/README.md index 22d75d385e62a0c31a410f86ca723ccbd15ee66d..8a914d23a5f5847f2cdb81457bedf93f8af3bf3e 100644 --- a/QComboBox/README.md +++ b/QComboBox/README.md @@ -1,6 +1,11 @@ # QComboBox +- 目录 + - [下拉数据关联](#1下拉数据关联) + - [文本居中显示](#2文本居中显示) + ## 1、下拉数据关联 + [运行 CityLinkage.py](CityLinkage.py) 一个省市区关联的三级联动,数据源在data.json中 @@ -9,4 +14,13 @@ 2. 并根据唯一编码过滤,为了不影响内容显示,唯一编码的角色为`ToolTipRole` 3. 用`QColumnView`可以实现类似效果 -![CityLinkage](ScreenShot/CityLinkage.gif) \ No newline at end of file +![CityLinkage](ScreenShot/CityLinkage.gif) + +## 2、文本居中显示 + +[运行 CenterText.py](CenterText.py) + +1. 使用`QProxyStyle`对文件居中显示 +2. 新增得item数据使用`setTextAlignment`对齐 + +![CenterText](ScreenShot/CenterText.png) \ No newline at end of file diff --git a/QComboBox/ScreenShot/CenterText.png b/QComboBox/ScreenShot/CenterText.png new file mode 100644 index 0000000000000000000000000000000000000000..a99d8fad3e34b135c6b8f691c995c9381a1b87e0 Binary files /dev/null and b/QComboBox/ScreenShot/CenterText.png differ diff --git a/QFileSystemModel/CustomIcon.py b/QFileSystemModel/CustomIcon.py index 9eb15b80c6c846f18429d13faa3f9a77c28f94cc..9b322288b70e91788337718e2a0a956991e1cec4 100644 --- a/QFileSystemModel/CustomIcon.py +++ b/QFileSystemModel/CustomIcon.py @@ -3,28 +3,29 @@ """ Created on 2018年1月26日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com -@file: FileSystemModel +@file: CustomIcon @description: """ -import sys - -from PyQt5.QtCore import QFileInfo -from PyQt5.QtGui import QIcon -from PyQt5.QtWidgets import QFileSystemModel, QFileIconProvider, QApplication,\ - QTreeView +import sys -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" -__Version__ = "Version 1.0" - -# 图标提供类 +try: + from PyQt5.QtCore import QFileInfo + from PyQt5.QtGui import QIcon + from PyQt5.QtWidgets import QFileSystemModel, QFileIconProvider, QApplication, \ + QTreeView +except ImportError: + from PySide2.QtCore import QFileInfo + from PySide2.QtGui import QIcon + from PySide2.QtWidgets import QFileSystemModel, QFileIconProvider, QApplication, \ + QTreeView class FileIconProvider(QFileIconProvider): + """图标提供类""" def __init__(self, *args, **kwargs): super(FileIconProvider, self).__init__(*args, **kwargs) diff --git a/QFileSystemModel/README.md b/QFileSystemModel/README.md index 0f95051e2f6d0c35e77afb6e4ba828c017b9d5b8..fc76971f0f8a451c9fbaea2b642d0284bf616bab 100644 --- a/QFileSystemModel/README.md +++ b/QFileSystemModel/README.md @@ -1,5 +1,8 @@ # QFileSystemModel +- 目录 + - [自定义图标](#1自定义图标) + ## 1、自定义图标 [运行 CustomIcon.py](CustomIcon.py) diff --git a/QFlowLayout/Data/CoverItemWidget.ui b/QFlowLayout/Data/CoverItemWidget.ui new file mode 100644 index 0000000000000000000000000000000000000000..2f245e1344895af124b9ed3ea8b387c232077a79 --- /dev/null +++ b/QFlowLayout/Data/CoverItemWidget.ui @@ -0,0 +1,85 @@ + + + CoverItemWidget + + + + 200 + 256 + + + + + 200 + 256 + + + + + + + + 12 + + + 10 + + + 10 + + + 10 + + + 10 + + + + + + 180 + 180 + + + + + + + true + + + Qt::AlignCenter + + + + + + + + 10 + + + + + + + + + + + + + + + + + + + CoverLabel + QLabel +
.CoverLabel
+
+
+ + +
diff --git a/QFlowLayout/Data/CoverLabel.ui b/QFlowLayout/Data/CoverLabel.ui new file mode 100644 index 0000000000000000000000000000000000000000..2e7e037880e23bace4731623a4f1c25620fe267b --- /dev/null +++ b/QFlowLayout/Data/CoverLabel.ui @@ -0,0 +1,109 @@ + + + CoverLabel + + + + 0 + 0 + 180 + 180 + + + + PointingHandCursor + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 20 + 135 + + + + + + + + #widgetBottom { + background-color: rgba(0, 0, 0, 150); +} + + + + + + + 16 + 16 + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 16 + 16 + + + + + + + + + + + + + + + diff --git a/QFlowLayout/Data/Svg_icon_headset_sm.svg b/QFlowLayout/Data/Svg_icon_headset_sm.svg new file mode 100644 index 0000000000000000000000000000000000000000..98a1a3591d4f0741b27d27862023bc75bddf4a8d --- /dev/null +++ b/QFlowLayout/Data/Svg_icon_headset_sm.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/QFlowLayout/Data/Svg_icon_loading.svg b/QFlowLayout/Data/Svg_icon_loading.svg new file mode 100644 index 0000000000000000000000000000000000000000..0b00b7993dc84f2a6221b942d35dc62421e70ac4 --- /dev/null +++ b/QFlowLayout/Data/Svg_icon_loading.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/QFlowLayout/Data/Svg_icon_play_sm.svg b/QFlowLayout/Data/Svg_icon_play_sm.svg new file mode 100644 index 0000000000000000000000000000000000000000..08a47f20e76885d4e77b0f31221ff4d9e8bacafd --- /dev/null +++ b/QFlowLayout/Data/Svg_icon_play_sm.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/QFlowLayout/HotPlaylist.py b/QFlowLayout/HotPlaylist.py index 2fef16729a0027cd8fa2c74233e23bfe4072b5cc..6b276c841686310259203c46a0844ec9bd593971 100644 --- a/QFlowLayout/HotPlaylist.py +++ b/QFlowLayout/HotPlaylist.py @@ -1,194 +1,47 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -''' -Created on 2018年2月4日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +""" +Created on 2023/02/22 +@author: Irony +@site: https://pyqt.site https://github.com/PyQt5 @email: 892768447@qq.com -@file: TencentMovieHotPlay_Flow -@description: -''' +@file: HotPlaylist.py +@description: +""" + import os import sys -import webbrowser - -from PyQt5.QtCore import QSize, Qt, QUrl, QTimer, pyqtSignal -from PyQt5.QtGui import QPainter, QFont, QLinearGradient, QGradient, QColor,\ - QBrush, QPaintEvent, QPixmap -from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest -from PyQt5.QtSvg import QSvgWidget -from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QLabel,\ - QHBoxLayout, QSpacerItem, QSizePolicy, QScrollArea, QAbstractSlider +from Lib.CoverItemWidget import CoverItemWidget from Lib.flowlayout import FlowLayout # @UnresolvedImport from lxml.etree import HTML # @UnresolvedImport +try: + from PyQt5.QtCore import Qt, QTimer, QUrl, pyqtSignal + from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest + from PyQt5.QtSvg import QSvgWidget + from PyQt5.QtWidgets import (QAbstractSlider, QApplication, QScrollArea, + QWidget) +except ImportError: + from PySide2.QtCore import Qt, QTimer, QUrl, pyqtSignal + from PySide2.QtNetwork import QNetworkAccessManager, QNetworkRequest + from PySide2.QtSvg import QSvgWidget + from PySide2.QtWidgets import (QAbstractSlider, QApplication, QScrollArea, + QWidget) -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" -__Version__ = "Version 1.0" - -# offset=0,30,60,90 -Url = "http://v.qq.com/x/list/movie?pay=-1&offset={0}" +# offset=0,35,70,105 +Url = "https://music.163.com/discover/playlist/?order=hot&cat=%E5%85%A8%E9%83%A8&limit=35&offset={0}" -# 播放量图标 -Svg_icon_play_sm = ''' - - -'''.encode() +Agent = b"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.50" -Svg_icon_loading = ''' - - - - - - - - - - - - - - - - - -'''.encode() +Referer = b"https://music.163.com" -# 主演 +# 作者 Actor = '''{title} ''' -class CoverLabel(QLabel): - - def __init__(self, cover_path, cover_title, video_url, *args, **kwargs): - super(CoverLabel, self).__init__(*args, **kwargs) -# super(CoverLabel, self).__init__( -# ''.format(os.path.abspath(cover_path)), *args, **kwargs) - self.setCursor(Qt.PointingHandCursor) - self.setScaledContents(True) - self.setMinimumSize(220, 308) - self.setMaximumSize(220, 308) - self.cover_path = cover_path - self.cover_title = cover_title - self.video_url = video_url - self.setPixmap(QPixmap(cover_path)) - - def setCoverPath(self, path): - self.cover_path = path - - def mouseReleaseEvent(self, event): - super(CoverLabel, self).mouseReleaseEvent(event) - webbrowser.open_new_tab(self.video_url) - - def paintEvent(self, event): - super(CoverLabel, self).paintEvent(event) - if hasattr(self, "cover_title") and self.cover_title != "": - # 底部绘制文字 - painter = QPainter(self) - rect = self.rect() - # 粗略字体高度 - painter.save() - fheight = self.fontMetrics().height() - # 底部矩形框背景渐变颜色 - bottomRectColor = QLinearGradient( - rect.width() / 2, rect.height() - 24 - fheight, - rect.width() / 2, rect.height()) - bottomRectColor.setSpread(QGradient.PadSpread) - bottomRectColor.setColorAt(0, QColor(255, 255, 255, 70)) - bottomRectColor.setColorAt(1, QColor(0, 0, 0, 50)) - # 画半透明渐变矩形框 - painter.setPen(Qt.NoPen) - painter.setBrush(QBrush(bottomRectColor)) - painter.drawRect(rect.x(), rect.height() - 24 - - fheight, rect.width(), 24 + fheight) - painter.restore() - # 距离底部一定高度画文字 - font = self.font() or QFont() - font.setPointSize(8) - painter.setFont(font) - painter.setPen(Qt.white) - rect.setHeight(rect.height() - 12) # 底部减去一定高度 - painter.drawText(rect, Qt.AlignHCenter | - Qt.AlignBottom, self.cover_title) - - -class ItemWidget(QWidget): - - def __init__(self, cover_path, figure_info, figure_title, - figure_score, figure_desc, figure_count, video_url, cover_url, img_path, *args, **kwargs): - super(ItemWidget, self).__init__(*args, **kwargs) - self.setMaximumSize(220, 420) - self.setMaximumSize(220, 420) - self.img_path = img_path - self.cover_url = cover_url - layout = QVBoxLayout(self) - layout.setContentsMargins(10, 20, 10, 0) - # 图片label - self.clabel = CoverLabel(cover_path, figure_info, video_url, self) - layout.addWidget(self.clabel) - - # 片名和分数 - flayout = QHBoxLayout() - flayout.addWidget(QLabel(figure_title, self)) - flayout.addItem(QSpacerItem( - 20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) - flayout.addWidget(QLabel(figure_score, self, styleSheet="color: red;")) - layout.addLayout(flayout) - - # 主演 - layout.addWidget( - QLabel(figure_desc, self, styleSheet="color: #999999;", openExternalLinks=True)) - - # 播放量 - blayout = QHBoxLayout() - count_icon = QSvgWidget(self) - count_icon.setMaximumSize(16, 16) - count_icon.load(Svg_icon_play_sm) - blayout.addWidget(count_icon) - blayout.addWidget( - QLabel(figure_count, self, styleSheet="color: #999999;")) - layout.addLayout(blayout) - - def setCover(self, path): - self.clabel.setCoverPath(path) - self.clabel.setPixmap(QPixmap(path)) -# self.clabel.setText(''.format(os.path.abspath(path))) - - def sizeHint(self): - # 每个item控件的大小 - return QSize(220, 420) - - def event(self, event): - if isinstance(event, QPaintEvent): - if event.rect().height() > 20 and hasattr(self, "clabel"): - if self.clabel.cover_path.find("pic_v.png") > -1: # 封面未加载 - # print("start download img:", self.cover_url) - req = QNetworkRequest(QUrl(self.cover_url)) - # 设置两个自定义属性方便后期reply中处理 - req.setAttribute(QNetworkRequest.User + 1, self) - req.setAttribute(QNetworkRequest.User + 2, self.img_path) - self.parentWidget()._manager.get(req) # 调用父窗口中的下载器下载 - return super(ItemWidget, self).event(event) - - class GridWidget(QWidget): - Page = 0 loadStarted = pyqtSignal(bool) @@ -200,16 +53,18 @@ class GridWidget(QWidget): self._manager.finished.connect(self.onFinished) def load(self): - if self.Page == -1: + if self.Page == -1 or self.Page > 10: return self.loadStarted.emit(True) # 延迟一秒后调用目的在于显示进度条 QTimer.singleShot(1000, self._load) def _load(self): - print("load url:", Url.format(self.Page * 30)) - url = QUrl(Url.format(self.Page * 30)) - self._manager.get(QNetworkRequest(url)) + print("load url:", Url.format(self.Page * 35)) + url = QUrl(Url.format(self.Page * 35)) + req = QNetworkRequest(url) + req.setRawHeader(b"User-Agent", Agent) + self._manager.get(req) def onFinished(self, reply): # 请求完成后会调用该函数 @@ -229,11 +84,13 @@ class GridWidget(QWidget): self.loadStarted.emit(False) def _parseHtml(self, html): + # print(html) # encoding = chardet.detect(html) or {} # html = html.decode(encoding.get("encoding","utf-8")) html = HTML(html) # 查找所有的li list_item - lis = html.xpath("//li[@class='list_item']") + lis = html.xpath("//ul[@id='m-pl-container']/li") + # print(lis) if not lis: self.Page = -1 # 后面没有页面了 return @@ -242,28 +99,30 @@ class GridWidget(QWidget): def _makeItem(self, lis): for li in lis: - a = li.find("a") - video_url = a.get("href") # 视频播放地址 - img = a.find("img") - cover_url = "http:" + img.get("r-lazyload") # 封面图片 - figure_title = img.get("alt") # 电影名 - figure_info = a.find("div/span") - figure_info = "" if figure_info is None else figure_info.text # 影片信息 - figure_score = "".join(li.xpath(".//em/text()")) # 评分 - # 主演 - figure_desc = "主演:" + \ - "".join([Actor.format(**dict(fd.items())) - for fd in li.xpath(".//div[@class='figure_desc']/a")]) + a = li.find('.//div/a') + play_url = "https://music.163.com" + a.get("href") # 歌单播放地址 + img = li.find(".//div/img") + cover_url = img.get("src") # 封面图片 + playlist_title = a.get("title") # 歌单名 + # 歌手 + author_info = li.xpath(".//p[2]/a")[0] + playlist_author = "".format( + Actor.format(href="https://music.163.com" + + author_info.get("href"), + title=author_info.get("title"))) # 播放数 - figure_count = ( - li.xpath(".//div[@class='figure_count']/span/text()") or [""])[0] + play_count = (li.xpath(".//div/div/span[2]/text()") or [""])[0] path = "cache/{0}.jpg".format( - os.path.splitext(os.path.basename(video_url))[0]) + os.path.splitext(os.path.basename(cover_url).split('?')[0])[0]) cover_path = "Data/pic_v.png" if os.path.isfile(path): cover_path = path - iwidget = ItemWidget(cover_path, figure_info, figure_title, - figure_score, figure_desc, figure_count, video_url, cover_url, path, self) + + # print(cover_path, playlist_title, + # playlist_author, play_count, play_url, cover_url, path) + iwidget = CoverItemWidget(self, manager=self._manager) + iwidget.init(cover_path, playlist_title, playlist_author, + play_count, play_url, cover_url, path) self._layout.addWidget(iwidget) @@ -283,9 +142,11 @@ class Window(QScrollArea): # 连接竖着的滚动条滚动事件 self.verticalScrollBar().actionTriggered.connect(self.onActionTriggered) # 进度条 - self.loadWidget = QSvgWidget( - self, minimumHeight=120, minimumWidth=120, visible=False) - self.loadWidget.load(Svg_icon_loading) + self.loadWidget = QSvgWidget(self, + minimumHeight=120, + minimumWidth=120, + visible=False) + self.loadWidget.load('Data/Svg_icon_loading.svg') def setLoadStarted(self, started): self._loadStart = started @@ -297,7 +158,8 @@ class Window(QScrollArea): if action != QAbstractSlider.SliderMove or self._loadStart: return # 使用sliderPosition获取值可以同时满足鼠标滑动和拖动判断 - if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar().maximum(): + if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar( + ).maximum(): # 可以下一页了 self._widget.load() @@ -306,9 +168,7 @@ class Window(QScrollArea): self.loadWidget.setGeometry( int((self.width() - self.loadWidget.minimumWidth()) / 2), int((self.height() - self.loadWidget.minimumHeight()) / 2), - self.loadWidget.minimumWidth(), - self.loadWidget.minimumHeight() - ) + self.loadWidget.minimumWidth(), self.loadWidget.minimumHeight()) if __name__ == "__main__": diff --git a/QFlowLayout/Lib/CoverItemWidget.py b/QFlowLayout/Lib/CoverItemWidget.py new file mode 100644 index 0000000000000000000000000000000000000000..5ef3ff00225af2269e33bd3c3ea4c47ba54c2adf --- /dev/null +++ b/QFlowLayout/Lib/CoverItemWidget.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Created on 2023/02/22 +@author: Irony +@site: https://pyqt.site https://github.com/PyQt5 +@email: 892768447@qq.com +@file: CoverItemWidget.py +@description: +""" + +try: + from PyQt5.QtCore import QSize, QUrl + from PyQt5.QtGui import QPaintEvent, QPixmap + from PyQt5.QtNetwork import QNetworkRequest + from PyQt5.QtWidgets import QWidget +except ImportError: + from PySide2.QtCore import QSize, QUrl + from PySide2.QtGui import QPaintEvent, QPixmap + from PySide2.QtNetwork import QNetworkRequest + from PySide2.QtWidgets import QWidget + +from .Ui_CoverItemWidget import Ui_CoverItemWidget # @UnresolvedImport + + +class CoverItemWidget(QWidget, Ui_CoverItemWidget): + + def __init__(self, *args, **kwargs): + self._manager = kwargs.pop('manager', None) + super(CoverItemWidget, self).__init__(*args, **kwargs) + self.setupUi(self) + + def init(self, cover_path, playlist_title, playlist_author, play_count, + play_url, cover_url, img_path): + self.img_path = img_path + self.cover_url = cover_url + # 图片label + self.labelCover.init(cover_path, play_url, play_count) + + # 歌单 + self.labelTitle.setText(playlist_title) + + # 作者 + self.labelAuthor.setText(playlist_author) + + def setCover(self, path): + self.labelCover.setCoverPath(path) + self.labelCover.setPixmap(QPixmap(path)) + + def sizeHint(self): + # 每个item控件的大小 + return QSize(200, 256) + + def event(self, event): + if isinstance(event, QPaintEvent): + if event.rect().height() > 20 and hasattr(self, "labelCover"): + if self.labelCover.cover_path.find("pic_v.png") > -1: # 封面未加载 + # print("start download img:", self.cover_url) + req = QNetworkRequest(QUrl(self.cover_url)) + # 设置两个自定义属性方便后期reply中处理 + req.setAttribute(QNetworkRequest.User + 1, self) + req.setAttribute(QNetworkRequest.User + 2, self.img_path) + if self._manager: + self._manager.get(req) # 调用父窗口中的下载器下载 + return super(CoverItemWidget, self).event(event) diff --git a/QFlowLayout/Lib/CoverLabel.py b/QFlowLayout/Lib/CoverLabel.py new file mode 100644 index 0000000000000000000000000000000000000000..dbee15af4cf251de21d8d7d4194b18cb8a192bbc --- /dev/null +++ b/QFlowLayout/Lib/CoverLabel.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Created on 2023/02/22 +@author: Irony +@site: https://pyqt.site https://github.com/PyQt5 +@email: 892768447@qq.com +@file: CoverLabel.py +@description: +""" +import webbrowser + +try: + from PyQt5.QtGui import QPixmap + from PyQt5.QtWidgets import QLabel +except ImportError: + from PySide2.QtGui import QPixmap + from PySide2.QtWidgets import QLabel + +from .Ui_CoverLabel import Ui_CoverLabel # @UnresolvedImport + + +class CoverLabel(QLabel, Ui_CoverLabel): + + def __init__(self, *args, **kwargs): + super(CoverLabel, self).__init__(*args, **kwargs) + self.setupUi(self) + + def init(self, cover_path, play_url, play_count): + self.cover_path = cover_path + self.play_url = play_url + self.setPixmap(QPixmap(cover_path)) + self.labelHeadset.setPixmap(QPixmap('Data/Svg_icon_headset_sm.svg')) + self.labelPlay.setPixmap(QPixmap('Data/Svg_icon_play_sm.svg')) + self.labelCount.setStyleSheet('color: #999999;') + self.labelCount.setText(play_count) + + def setCoverPath(self, path): + self.cover_path = path + + def mouseReleaseEvent(self, event): + super(CoverLabel, self).mouseReleaseEvent(event) + webbrowser.open_new_tab(self.play_url) diff --git a/QFlowLayout/Lib/Ui_CoverItemWidget.py b/QFlowLayout/Lib/Ui_CoverItemWidget.py new file mode 100644 index 0000000000000000000000000000000000000000..c048f686e4662268e855850c42d7da8a3182c91a --- /dev/null +++ b/QFlowLayout/Lib/Ui_CoverItemWidget.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'CoverItemWidget.ui' +# +# Created by: PyQt5 UI code generator 5.15.2 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +try: + from PyQt5 import QtCore, QtGui, QtWidgets +except ImportError: + from PySide2 import QtCore, QtGui, QtWidgets + + +class Ui_CoverItemWidget(object): + def setupUi(self, CoverItemWidget): + CoverItemWidget.setObjectName("CoverItemWidget") + CoverItemWidget.setMinimumSize(QtCore.QSize(200, 256)) + CoverItemWidget.setMaximumSize(QtCore.QSize(200, 256)) + CoverItemWidget.setWindowTitle("") + self.verticalLayout = QtWidgets.QVBoxLayout(CoverItemWidget) + self.verticalLayout.setContentsMargins(10, 10, 10, 10) + self.verticalLayout.setSpacing(12) + self.verticalLayout.setObjectName("verticalLayout") + self.labelCover = CoverLabel(CoverItemWidget) + self.labelCover.setMinimumSize(QtCore.QSize(180, 180)) + self.labelCover.setText("") + self.labelCover.setScaledContents(True) + self.labelCover.setAlignment(QtCore.Qt.AlignCenter) + self.labelCover.setObjectName("labelCover") + self.verticalLayout.addWidget(self.labelCover) + self.labelTitle = QtWidgets.QLabel(CoverItemWidget) + font = QtGui.QFont() + font.setPointSize(10) + self.labelTitle.setFont(font) + self.labelTitle.setText("") + self.labelTitle.setObjectName("labelTitle") + self.verticalLayout.addWidget(self.labelTitle) + self.labelAuthor = QtWidgets.QLabel(CoverItemWidget) + self.labelAuthor.setText("") + self.labelAuthor.setObjectName("labelAuthor") + self.verticalLayout.addWidget(self.labelAuthor) + + self.retranslateUi(CoverItemWidget) + QtCore.QMetaObject.connectSlotsByName(CoverItemWidget) + + def retranslateUi(self, CoverItemWidget): + pass +from .CoverLabel import CoverLabel + +if __name__ == "__main__": + import sys + app = QtWidgets.QApplication(sys.argv) + CoverItemWidget = QtWidgets.QWidget() + ui = Ui_CoverItemWidget() + ui.setupUi(CoverItemWidget) + CoverItemWidget.show() + sys.exit(app.exec_()) diff --git a/QFlowLayout/Lib/Ui_CoverLabel.py b/QFlowLayout/Lib/Ui_CoverLabel.py new file mode 100644 index 0000000000000000000000000000000000000000..86e1526a548fbd165e68d695447c530166faaee1 --- /dev/null +++ b/QFlowLayout/Lib/Ui_CoverLabel.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'CoverLabel.ui' +# +# Created by: PyQt5 UI code generator 5.15.2 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +try: + from PyQt5 import QtCore, QtGui, QtWidgets +except ImportError: + from PySide2 import QtCore, QtGui, QtWidgets + + +class Ui_CoverLabel(object): + def setupUi(self, CoverLabel): + CoverLabel.setObjectName("CoverLabel") + CoverLabel.resize(180, 180) + CoverLabel.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) + CoverLabel.setWindowTitle("") + self.verticalLayout = QtWidgets.QVBoxLayout(CoverLabel) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setSpacing(0) + self.verticalLayout.setObjectName("verticalLayout") + spacerItem = QtWidgets.QSpacerItem(20, 135, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout.addItem(spacerItem) + self.widgetBottom = QtWidgets.QWidget(CoverLabel) + self.widgetBottom.setStyleSheet("#widgetBottom {\n" +" background-color: rgba(0, 0, 0, 150);\n" +"}") + self.widgetBottom.setObjectName("widgetBottom") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.widgetBottom) + self.horizontalLayout.setObjectName("horizontalLayout") + self.labelHeadset = QtWidgets.QLabel(self.widgetBottom) + self.labelHeadset.setMinimumSize(QtCore.QSize(16, 16)) + self.labelHeadset.setText("") + self.labelHeadset.setObjectName("labelHeadset") + self.horizontalLayout.addWidget(self.labelHeadset) + self.labelCount = QtWidgets.QLabel(self.widgetBottom) + self.labelCount.setText("") + self.labelCount.setObjectName("labelCount") + self.horizontalLayout.addWidget(self.labelCount) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem1) + self.labelPlay = QtWidgets.QLabel(self.widgetBottom) + self.labelPlay.setMinimumSize(QtCore.QSize(16, 16)) + self.labelPlay.setText("") + self.labelPlay.setObjectName("labelPlay") + self.horizontalLayout.addWidget(self.labelPlay) + self.verticalLayout.addWidget(self.widgetBottom) + + self.retranslateUi(CoverLabel) + QtCore.QMetaObject.connectSlotsByName(CoverLabel) + + def retranslateUi(self, CoverLabel): + pass + + +if __name__ == "__main__": + import sys + app = QtWidgets.QApplication(sys.argv) + CoverLabel = QtWidgets.QWidget() + ui = Ui_CoverLabel() + ui.setupUi(CoverLabel) + CoverLabel.show() + sys.exit(app.exec_()) diff --git a/QFlowLayout/Lib/flowlayout.py b/QFlowLayout/Lib/flowlayout.py index ef1d54e3e5785c926aaa9e785815b5f5a56af43e..3cc9a9a382f1a42d0b1734bb56e60b15b10798bb 100644 --- a/QFlowLayout/Lib/flowlayout.py +++ b/QFlowLayout/Lib/flowlayout.py @@ -41,10 +41,14 @@ ## ############################################################################# - -from PyQt5.QtCore import QPoint, QRect, QSize, Qt -from PyQt5.QtWidgets import (QApplication, QLayout, QPushButton, QSizePolicy, - QWidget) +try: + from PyQt5.QtCore import QPoint, QRect, QSize, Qt + from PyQt5.QtWidgets import (QApplication, QLayout, QPushButton, + QSizePolicy, QWidget) +except ImportError: + from PySide2.QtCore import QPoint, QRect, QSize, Qt + from PySide2.QtWidgets import (QApplication, QLayout, QPushButton, + QSizePolicy, QWidget) class Window(QWidget): @@ -152,7 +156,6 @@ class FlowLayout(QLayout): if __name__ == '__main__': - import sys app = QApplication(sys.argv) diff --git a/QFlowLayout/README.md b/QFlowLayout/README.md index 3b6e82015498ce398f832f3f4b7008fab105b9dc..9fa52ee63fd14ca8d94629f349e481643982a39c 100644 --- a/QFlowLayout/README.md +++ b/QFlowLayout/README.md @@ -1,6 +1,9 @@ # QListView -## 1、腾讯视频热播列表 +- 目录 + - [音乐热歌列表](#1音乐热歌列表) + +## 1、音乐热歌列表 [运行 HotPlaylist.py](HotPlaylist.py) 简单思路说明: diff --git a/QFlowLayout/testxpath.py b/QFlowLayout/testxpath.py new file mode 100644 index 0000000000000000000000000000000000000000..e167f2315f054d9b872e799e5ca59b7f55dca546 --- /dev/null +++ b/QFlowLayout/testxpath.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Created on 2023/02/21 +@author: Irony +@site: https://pyqt.site https://github.com/PyQt5 +@email: 892768447@qq.com +@file: testxpath.py +@description: +""" + +import os + +from lxml.etree import HTML # @UnresolvedImport + +# 作者 +Actor = '''{title} ''' + + +def _makeItem(lis): + for li in lis: + a = li.find('.//div/a') + play_url = "https://music.163.com" + a.get("href") # 视频播放地址 + img = li.find(".//div/img") + cover_url = img.get("src") # 封面图片 + playlist_title = a.get("title") # 歌单名 + # figure_info = a.find("div/span") + figure_info = "aaa" #if figure_info is None else figure_info.text # 影片信息 + figure_score = "" # 评分 + # 歌手 + figure = li.xpath(".//p[2]/a")[0] + playlist_author = "".format( + Actor.format(href="https://music.163.com" +figure.get("href"),title=figure.get("title"))) + # 播放数 + play_count = (li.xpath(".//div/div/span[2]/text()") or [""])[0] + path = "cache/{0}.jpg".format( + os.path.splitext(os.path.basename(cover_url).split('?')[0])[0]) + cover_path = "Data/pic_v.png" + if os.path.isfile(path): + cover_path = path + + print(cover_path, playlist_title, playlist_author, + play_count, play_url, cover_url, path) + # iwidget = ItemWidget(cover_path, playlist_title, + # playlist_author, play_count, play_url, + # cover_url, path, self) + # self._layout.addWidget(iwidget) + + +def _parseHtml(html): + html = HTML(html) + # 查找所有的li + lis = html.xpath("//ul[@id='m-pl-container']/li") + # if not lis: + # self.Page = -1 # 后面没有页面了 + # return + # self.Page += 1 + _makeItem(lis) + + +if __name__ == '__main__': + data = open(r'D:\Computer\Desktop\163.html', 'rb').read() + _parseHtml(data) \ No newline at end of file diff --git a/QFont/AwesomeFont.py b/QFont/AwesomeFont.py index 7d8098dee7cac51f39bb48ac57a40f5e989d6f2b..58b1baf23851439325acccadc6721028602d1cb7 100644 --- a/QFont/AwesomeFont.py +++ b/QFont/AwesomeFont.py @@ -1,24 +1,25 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -''' +""" Created on 2017年3月30日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: AwesomeFont @description: -''' - -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]" -__Version__ = "Version 1.0" +""" import sys -from PyQt5.QtGui import QFontDatabase, QFont -from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout,\ - QScrollArea, QPushButton +try: + from PyQt5.QtGui import QFontDatabase, QFont + from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout, \ + QScrollArea, QPushButton +except ImportError: + from PySide2.QtGui import QFontDatabase, QFont + from PySide2.QtWidgets import QApplication, QWidget, QGridLayout, \ + QScrollArea, QPushButton from Lib.FontAwesome import FontAwesomes # @UnresolvedImport @@ -44,7 +45,7 @@ class ScrollArea(QScrollArea): minimumHeight=33, font=QFont("FontAwesome", 14)), row, col, 1, 1) - + self.showMaximized() def resizeEvent(self, event): diff --git "a/QFont/Data/\346\217\220\345\217\226\345\255\227\347\254\246/get.py" "b/QFont/Data/\346\217\220\345\217\226\345\255\227\347\254\246/get.py" index 624999c63527b542fe4b016caee34ba33072cc08..216d2e359cbc99b582fbfb277e9ef490c725ee6c 100644 --- "a/QFont/Data/\346\217\220\345\217\226\345\255\227\347\254\246/get.py" +++ "b/QFont/Data/\346\217\220\345\217\226\345\255\227\347\254\246/get.py" @@ -1,5 +1,6 @@ # from bs4 import BeautifulSoup import re + cheatsheet = open("cheatsheet.txt", "rb").read().decode() re_fa = re.compile(" fa(.*)") diff --git a/QFont/Lib/FontAwesome.py b/QFont/Lib/FontAwesome.py index 60ca9cae1425ef54eab234f0ebe7345fc1fea6b5..3527c2ea500f1c5cb8e3f27373c3f6b730ebf8d4 100644 --- a/QFont/Lib/FontAwesome.py +++ b/QFont/Lib/FontAwesome.py @@ -1,21 +1,22 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -''' +""" Created on 2017年3月30日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: FontAwesome @description: -''' +""" + + # FontAwesome 版本: 4.7.0 # 字体图标地址: http://fontawesome.io/icons/ # 字体字符地址: http://fontawesome.io/cheatsheet/ class FontAwesomes: - FA = None @classmethod diff --git a/QFont/README.md b/QFont/README.md index 6cbd4933c963ad9468f0b186c6aa9c6311eec7dc..21a1296923c340bb45bcf123276ca248abce47a5 100644 --- a/QFont/README.md +++ b/QFont/README.md @@ -1,9 +1,11 @@ -# 字体测试 +# QFont -### [Python3.4.4 or Python3.5][PyQt5] +- 目录 + - [加载自定义字体](#1加载自定义字体) -### 其中Roboto字体通过TTF编辑器修改了family,方便QFont加载 +## 1、加载自定义字体 +[运行 AwesomeFont.py](AwesomeFont.py) -# 截图 -![截图](ScreenShot/1.png) -![截图](ScreenShot/2.png) \ No newline at end of file +通过`QFontDatabase.addApplicationFont`加载字体文件 + +![AwesomeFont](ScreenShot/AwesomeFont.png) \ No newline at end of file diff --git a/QFont/ScreenShot/2.png b/QFont/ScreenShot/2.png deleted file mode 100644 index c5f4b9db6c0fe69c87766b4cb7a5a82d4bd257d9..0000000000000000000000000000000000000000 Binary files a/QFont/ScreenShot/2.png and /dev/null differ diff --git a/QFont/ScreenShot/1.png b/QFont/ScreenShot/AwesomeFont.png similarity index 100% rename from QFont/ScreenShot/1.png rename to QFont/ScreenShot/AwesomeFont.png diff --git a/QGraphicsDropShadowEffect/Lib/AnimationShadowEffect.py b/QGraphicsDropShadowEffect/Lib/AnimationShadowEffect.py index 9cbbd75920e7f30a177ad3806757fc7c13afb5ed..4551bd4f924543330c2f3cd9c41b15a5ce6d79cf 100644 --- a/QGraphicsDropShadowEffect/Lib/AnimationShadowEffect.py +++ b/QGraphicsDropShadowEffect/Lib/AnimationShadowEffect.py @@ -4,20 +4,18 @@ """ Created on 2018年9月25日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: AnimationShadowEffect @description: 边框动画阴影动画 """ -from PyQt5.QtCore import QPropertyAnimation, pyqtProperty -from PyQt5.QtWidgets import QGraphicsDropShadowEffect - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 +try: + from PyQt5.QtCore import QPropertyAnimation, pyqtProperty + from PyQt5.QtWidgets import QGraphicsDropShadowEffect +except ImportError: + from PySide2.QtCore import QPropertyAnimation, Property as pyqtProperty + from PySide2.QtWidgets import QGraphicsDropShadowEffect class AnimationShadowEffect(QGraphicsDropShadowEffect): diff --git a/QGraphicsDropShadowEffect/README.md b/QGraphicsDropShadowEffect/README.md index bfb723f1556e3170e4e8eb75083f972eb7f6e064..6e5be6fbf66bbbbfaafb18244d948da5f10044df 100644 --- a/QGraphicsDropShadowEffect/README.md +++ b/QGraphicsDropShadowEffect/README.md @@ -1,5 +1,8 @@ # QGraphicsDropShadowEffect +- 目录 + - [边框阴影动画](#1边框阴影动画) + ## 1、边框阴影动画 [运行 ShadowEffect.py](ShadowEffect.py) diff --git a/QGraphicsDropShadowEffect/ShadowEffect.py b/QGraphicsDropShadowEffect/ShadowEffect.py index 9c03cc6effc4e3b38052ed1722928b6cdb30cea3..b121e852fbc4765b69d24e157196375412378219 100644 --- a/QGraphicsDropShadowEffect/ShadowEffect.py +++ b/QGraphicsDropShadowEffect/ShadowEffect.py @@ -4,23 +4,22 @@ """ Created on 2018年9月25日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: ShadowEffect @description: """ -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QPixmap -from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QPushButton, QLineEdit - -from Lib.AnimationShadowEffect import AnimationShadowEffect # @UnresolvedImport +try: + from PyQt5.QtCore import Qt + from PyQt5.QtGui import QPixmap + from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QPushButton, QLineEdit, QApplication +except ImportError: + from PySide2.QtCore import Qt + from PySide2.QtGui import QPixmap + from PySide2.QtWidgets import QWidget, QHBoxLayout, QLabel, QPushButton, QLineEdit, QApplication -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 +from Lib.AnimationShadowEffect import AnimationShadowEffect # @UnresolvedImport class Window(QWidget): @@ -64,7 +63,7 @@ class Window(QWidget): if __name__ == '__main__': import sys - from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) w = Window() w.show() diff --git a/QGraphicsView/AddQWidget.py b/QGraphicsView/AddQWidget.py index 1689972cd2418aefbe000234b35768480ae3deb0..ff8026c93258e5406f651ae4d3b75b78cd171deb 100644 --- a/QGraphicsView/AddQWidget.py +++ b/QGraphicsView/AddQWidget.py @@ -3,22 +3,22 @@ """ Created on 2017年12月23日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: AddQWidget @description: """ import sys -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QVBoxLayout,\ - QApplication, QGraphicsView, QGraphicsScene - - -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]" -__Version__ = "Version 1.0" +try: + from PyQt5.QtCore import Qt + from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QVBoxLayout, \ + QApplication, QGraphicsView, QGraphicsScene +except ImportError: + from PySide2.QtCore import Qt + from PySide2.QtWidgets import QWidget, QHBoxLayout, QLabel, QVBoxLayout, \ + QApplication, QGraphicsView, QGraphicsScene class ToolTipItem(QWidget): diff --git a/QGraphicsView/Data/bg.jpg b/QGraphicsView/Data/bg.jpg new file mode 100644 index 0000000000000000000000000000000000000000..db164572913a0c85bb9f0c8687e097dd82dcf19a Binary files /dev/null and b/QGraphicsView/Data/bg.jpg differ diff --git a/QGraphicsView/Data/icons/basic.png b/QGraphicsView/Data/icons/basic.png new file mode 100644 index 0000000000000000000000000000000000000000..7627b2449bb89276ee65afbf55cbf2ef74adf4c0 Binary files /dev/null and b/QGraphicsView/Data/icons/basic.png differ diff --git a/QGraphicsView/Data/icons/business.png b/QGraphicsView/Data/icons/business.png new file mode 100644 index 0000000000000000000000000000000000000000..479d2e0dd330a6bdfca8657c637aa377b12dbd31 Binary files /dev/null and b/QGraphicsView/Data/icons/business.png differ diff --git a/QGraphicsView/Data/icons/cloudService.png b/QGraphicsView/Data/icons/cloudService.png new file mode 100644 index 0000000000000000000000000000000000000000..3c3579f1d2b1a1777a78084285cef0ccb0a68e67 Binary files /dev/null and b/QGraphicsView/Data/icons/cloudService.png differ diff --git a/QGraphicsView/Data/icons/dataBase.png b/QGraphicsView/Data/icons/dataBase.png new file mode 100644 index 0000000000000000000000000000000000000000..716e0cff90aae747505c2bcf777cb0d6f62f4a1a Binary files /dev/null and b/QGraphicsView/Data/icons/dataBase.png differ diff --git a/QGraphicsView/Data/icons/engineering.png b/QGraphicsView/Data/icons/engineering.png new file mode 100644 index 0000000000000000000000000000000000000000..4c68bd454db1912a2dbe70b7f3361ddde6d43f59 Binary files /dev/null and b/QGraphicsView/Data/icons/engineering.png differ diff --git a/QGraphicsView/Data/icons/network.png b/QGraphicsView/Data/icons/network.png new file mode 100644 index 0000000000000000000000000000000000000000..dde507a2d3d4a14ce57ec6cb4d13c42a14b5b6a1 Binary files /dev/null and b/QGraphicsView/Data/icons/network.png differ diff --git a/QGraphicsView/Data/icons/science.png b/QGraphicsView/Data/icons/science.png new file mode 100644 index 0000000000000000000000000000000000000000..01c377119005818e41dfa1c6c5aa1ac6b9afae7a Binary files /dev/null and b/QGraphicsView/Data/icons/science.png differ diff --git a/QGraphicsView/Data/icons/softwareEngineering.png b/QGraphicsView/Data/icons/softwareEngineering.png new file mode 100644 index 0000000000000000000000000000000000000000..51be32213141501a87f919e75f12b19f51385d1a Binary files /dev/null and b/QGraphicsView/Data/icons/softwareEngineering.png differ diff --git a/QGraphicsView/Data/icons/wireframe.png b/QGraphicsView/Data/icons/wireframe.png new file mode 100644 index 0000000000000000000000000000000000000000..040e0f422be16187190555da374452244779abec Binary files /dev/null and b/QGraphicsView/Data/icons/wireframe.png differ diff --git a/QGraphicsView/DragGraphics.py b/QGraphicsView/DragGraphics.py new file mode 100644 index 0000000000000000000000000000000000000000..2ba5606080c0c83104b53d3d9ed1a5f5476ec862 --- /dev/null +++ b/QGraphicsView/DragGraphics.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Created on 2023/02/09 +@author: Irony +@site: https://pyqt.site https://github.com/PyQt5 +@email: 892768447@qq.com +@file: DragGraphics.py +@description: +""" + +import json +import os + +try: + from PyQt5.QtCore import QMimeData, Qt + from PyQt5.QtGui import QDrag, QIcon, QPixmap + from PyQt5.QtWidgets import (QApplication, QGraphicsPixmapItem, + QGraphicsScene, QGraphicsView, QHBoxLayout, + QListWidget, QListWidgetItem, QTreeWidget, + QTreeWidgetItem, QWidget) +except ImportError: + from PySide2.QtCore import QMimeData, Qt + from PySide2.QtGui import QDrag, QIcon, QPixmap + from PySide2.QtWidgets import (QApplication, QGraphicsPixmapItem, + QGraphicsScene, QGraphicsView, QHBoxLayout, + QListWidget, QListWidgetItem, QTreeWidget, + QTreeWidgetItem, QWidget) + + +class ListWidget(QListWidget): + + def __init__(self, *args, **kwargs): + super(ListWidget, self).__init__(*args, **kwargs) + self.setDragEnabled(True) + self.setDragDropMode(QListWidget.DragOnly) + self.setDefaultDropAction(Qt.IgnoreAction) + self.setEditTriggers(QListWidget.NoEditTriggers) + self.setResizeMode(QListWidget.Adjust) + self.setViewMode(QListWidget.IconMode) + + def startDrag(self, supportedActions): + items = self.selectedItems() + if not items: + return + # 这里就简单的根据名字提示来传递数据了,实际上可以传递任意数据 + data = QMimeData() + data.setData('application/node-items', + json.dumps([item.toolTip() for item in items]).encode()) + # 这里简单显示第一个缩略图 + pixmap = items[0].icon().pixmap(36, 36) + drag = QDrag(self) + drag.setMimeData(data) + drag.setPixmap(pixmap) + drag.setHotSpot(pixmap.rect().center()) + drag.exec_(supportedActions) + + +class GraphicsView(QGraphicsView): + + def __init__(self, *args, **kwargs): + super(GraphicsView, self).__init__(*args, **kwargs) + self.setAcceptDrops(True) + self._scene = QGraphicsScene(self) # 场景 + self.setScene(self._scene) + + def dragEnterEvent(self, event): + """判断拖入的数据是否支持""" + mimeData = event.mimeData() + if not mimeData.hasFormat('application/node-items'): + event.ignore() + return + + event.acceptProposedAction() + + dragMoveEvent = dragEnterEvent + + def dropEvent(self, event): + """获取拖拽的数据并绘制对于的图形""" + datas = event.mimeData().data('application/node-items') + datas = json.loads(datas.data().decode()) + print('datas:', datas) + + path = os.path.join(os.path.dirname(__file__), 'Data/icons') + for name in datas: + item = QGraphicsPixmapItem(QPixmap(os.path.join(path, name))) + item.setFlags(QGraphicsPixmapItem.ItemIsFocusable | + QGraphicsPixmapItem.ItemIsMovable) + self._scene.addItem(item) + pos = self.mapToScene(event.pos()) + item.moveBy(pos.x(), pos.y()) + + +class DragGraphics(QWidget): + + def __init__(self, *args, **kwargs): + super(DragGraphics, self).__init__(*args, **kwargs) + self.resize(800, 600) + layout = QHBoxLayout(self) + + # 左侧树形控制 + self.treeWidget = QTreeWidget(self) + self.treeWidget.header().setVisible(False) + self.treeWidget.setMaximumWidth(300) + layout.addWidget(self.treeWidget) + + # 右侧图形显示 + self.graphicsView = GraphicsView(self) + layout.addWidget(self.graphicsView) + + self._init_trees() + + def _init_trees(self): + """初始化树形控件中的图形节点列表""" + # 1. 获取所有图标 + path = os.path.join(os.path.dirname(__file__), 'Data/icons') + icons = [os.path.join(path, name) for name in os.listdir(path)] + + # 2. 添加根节点 + for i in range(2): + item = QTreeWidgetItem(self.treeWidget) + item.setText(0, 'View %d' % i) + + # 3. 添加子节点作为容器用于存放图标 + itemc = QTreeWidgetItem(item) + child = ListWidget(self.treeWidget) + self.treeWidget.setItemWidget(itemc, 0, child) + + # 4. 添加图标 + for icon in icons: + item = QListWidgetItem(child) + item.setIcon(QIcon(icon)) + item.setToolTip(os.path.basename(icon)) + + self.treeWidget.expandAll() + + +if __name__ == '__main__': + import cgitb + import sys + + cgitb.enable(format='text') + app = QApplication(sys.argv) + w = DragGraphics() + w.show() + sys.exit(app.exec_()) diff --git a/QGraphicsView/ImageView.py b/QGraphicsView/ImageView.py new file mode 100644 index 0000000000000000000000000000000000000000..f6789c54ba7002654fb810b715dff05a7d93d808 --- /dev/null +++ b/QGraphicsView/ImageView.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2020/11/12 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: ImageView +@description: 图片查看控件,支持移动、放大、缩小 +""" + +import os + +try: + from PyQt5.QtCore import QPointF, Qt, QRectF, QSizeF + from PyQt5.QtGui import QPainter, QColor, QImage, QPixmap + from PyQt5.QtWidgets import QApplication, QGraphicsView, QGraphicsPixmapItem, QGraphicsScene +except ImportError: + from PySide2.QtCore import QPointF, Qt, QRectF, QSizeF + from PySide2.QtGui import QPainter, QColor, QImage, QPixmap + from PySide2.QtWidgets import QApplication, QGraphicsView, QGraphicsPixmapItem, QGraphicsScene + + +class ImageView(QGraphicsView): + """图片查看控件""" + + def __init__(self, *args, **kwargs): + image = kwargs.pop('image', None) + background = kwargs.pop('background', None) + super(ImageView, self).__init__(*args, **kwargs) + self.setCursor(Qt.OpenHandCursor) + self.setBackground(background) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setRenderHints(QPainter.Antialiasing | QPainter.HighQualityAntialiasing | + QPainter.SmoothPixmapTransform) + self.setCacheMode(self.CacheBackground) + self.setViewportUpdateMode(self.SmartViewportUpdate) + self._item = QGraphicsPixmapItem() # 放置图像 + self._item.setFlags(QGraphicsPixmapItem.ItemIsFocusable | + QGraphicsPixmapItem.ItemIsMovable) + self._scene = QGraphicsScene(self) # 场景 + self.setScene(self._scene) + self._scene.addItem(self._item) + rect = QApplication.instance().desktop().availableGeometry(self) + self.resize(int(rect.width() * 2 / 3), int(rect.height() * 2 / 3)) + + self.pixmap = None + self._delta = 0.1 # 缩放 + self.setPixmap(image) + + def setBackground(self, color): + """设置背景颜色 + :param color: 背景颜色 + :type color: QColor or str or GlobalColor + """ + if isinstance(color, QColor): + self.setBackgroundBrush(color) + elif isinstance(color, (str, Qt.GlobalColor)): + color = QColor(color) + if color.isValid(): + self.setBackgroundBrush(color) + + def setPixmap(self, pixmap, fitIn=True): + """加载图片 + :param pixmap: 图片或者图片路径 + :param fitIn: 是否适应 + :type pixmap: QPixmap or QImage or str + :type fitIn: bool + """ + if isinstance(pixmap, QPixmap): + self.pixmap = pixmap + elif isinstance(pixmap, QImage): + self.pixmap = QPixmap.fromImage(pixmap) + elif isinstance(pixmap, str) and os.path.isfile(pixmap): + self.pixmap = QPixmap(pixmap) + else: + return + self._item.setPixmap(self.pixmap) + self._item.update() + self.setSceneDims() + if fitIn: + self.fitInView(QRectF(self._item.pos(), QSizeF( + self.pixmap.size())), Qt.KeepAspectRatio) + self.update() + + def setSceneDims(self): + if not self.pixmap: + return + self.setSceneRect(QRectF(QPointF(0, 0), QPointF(self.pixmap.width(), self.pixmap.height()))) + + def fitInView(self, rect, flags=Qt.IgnoreAspectRatio): + """剧中适应 + :param rect: 矩形范围 + :param flags: + :return: + """ + if not self.scene() or rect.isNull(): + return + unity = self.transform().mapRect(QRectF(0, 0, 1, 1)) + self.scale(1 / unity.width(), 1 / unity.height()) + viewRect = self.viewport().rect() + sceneRect = self.transform().mapRect(rect) + x_ratio = viewRect.width() / sceneRect.width() + y_ratio = viewRect.height() / sceneRect.height() + if flags == Qt.KeepAspectRatio: + x_ratio = y_ratio = min(x_ratio, y_ratio) + elif flags == Qt.KeepAspectRatioByExpanding: + x_ratio = y_ratio = max(x_ratio, y_ratio) + self.scale(x_ratio, y_ratio) + self.centerOn(rect.center()) + + def wheelEvent(self, event): + if event.angleDelta().y() > 0: + self.zoomIn() + else: + self.zoomOut() + + def zoomIn(self): + """放大""" + self.zoom(1 + self._delta) + + def zoomOut(self): + """缩小""" + self.zoom(1 - self._delta) + + def zoom(self, factor): + """缩放 + :param factor: 缩放的比例因子 + """ + _factor = self.transform().scale( + factor, factor).mapRect(QRectF(0, 0, 1, 1)).width() + if _factor < 0.07 or _factor > 100: + # 防止过大过小 + return + self.scale(factor, factor) + + +if __name__ == '__main__': + import sys + import cgitb + + cgitb.enable(format='text') + + app = QApplication(sys.argv) + w = ImageView(image='Data/bg.jpg', background=Qt.black) + w.show() + sys.exit(app.exec_()) diff --git a/QGraphicsView/README.md b/QGraphicsView/README.md index 52901d4ad28e96adce0b49b2893ce9894f7ee86b..ef66543d52f69139236c896b928c91a6c22631a3 100644 --- a/QGraphicsView/README.md +++ b/QGraphicsView/README.md @@ -1,5 +1,11 @@ # QGraphicsView +- 目录 + - [绘制世界地图](#1绘制世界地图) + - [添加QWidget](#2添加QWidget) + - [图片查看器](#3图片查看器) + - [图标拖拽](#4图标拖拽) + ## 1、绘制世界地图 [运行 WorldMap.py](WorldMap.py) @@ -13,4 +19,21 @@ 通过 `QGraphicsScene.addWidget` 添加自定义QWidget -![AddQWidget](ScreenShot/AddQWidget.png) \ No newline at end of file +![AddQWidget](ScreenShot/AddQWidget.png) + +## 3、图片查看器 +[运行 ImageView.py](ImageView.py) + +支持放大缩小和移动 + +![ImageView](ScreenShot/ImageView.gif) + +## 3、图标拖拽 +[运行 DragGraphics.py](DragGraphics.py) + +该示例主要是包含左侧树状图标列表和右侧视图显示,从左侧拖拽到右侧 + +1. 重写`QListWidget`的`startDrag`函数用来封装拖拽数据 +2. 重写`QGraphicsView`的`dragEnterEvent`、`dragMoveEvent`、`dropEvent`函数用来处理拖拽事件 + +![DragGraphics](ScreenShot/DragGraphics.gif) \ No newline at end of file diff --git a/QGraphicsView/ScreenShot/DragGraphics.gif b/QGraphicsView/ScreenShot/DragGraphics.gif new file mode 100644 index 0000000000000000000000000000000000000000..65f0c4ba67b67f97b7aec7081f8617f5b7382f59 Binary files /dev/null and b/QGraphicsView/ScreenShot/DragGraphics.gif differ diff --git a/QGraphicsView/ScreenShot/ImageView.gif b/QGraphicsView/ScreenShot/ImageView.gif new file mode 100644 index 0000000000000000000000000000000000000000..150634c25f24d0a74e01e9e43004f1ad973232ef Binary files /dev/null and b/QGraphicsView/ScreenShot/ImageView.gif differ diff --git a/QGraphicsView/WorldMap.py b/QGraphicsView/WorldMap.py index 3efc7b17479ca1d818235ee4ba78ab3539703667..32428dfba457683e39a36092710885ce681e99c1 100644 --- a/QGraphicsView/WorldMap.py +++ b/QGraphicsView/WorldMap.py @@ -3,8 +3,8 @@ """ Created on 2017年12月17日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: WorldMap @description: @@ -12,19 +12,19 @@ Created on 2017年12月17日 import json import math -from PyQt5.QtCore import Qt, QPointF, QRectF -from PyQt5.QtGui import QColor, QPainter, QPolygonF, QPen, QBrush -from PyQt5.QtOpenGL import QGLFormat -from PyQt5.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsPolygonItem - - -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]" -__Version__ = "Version 1.0" +try: + from PyQt5.QtCore import Qt, QPointF, QRectF + from PyQt5.QtGui import QColor, QPainter, QPolygonF, QPen, QBrush + from PyQt5.QtOpenGL import QGLFormat + from PyQt5.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QGraphicsPolygonItem +except ImportError: + from PySide2.QtCore import Qt, QPointF, QRectF + from PySide2.QtGui import QColor, QPainter, QPolygonF, QPen, QBrush + from PySide2.QtOpenGL import QGLFormat + from PySide2.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QGraphicsPolygonItem class GraphicsView(QGraphicsView): - # 背景区域颜色 backgroundColor = QColor(31, 31, 47) # 边框颜色 @@ -94,8 +94,8 @@ class GraphicsView(QGraphicsView): AnchorUnderMouse 鼠标当前位置被用作锚点 ''' self.setTransformationAnchor(self.AnchorUnderMouse) -# if QGLFormat.hasOpenGL(): # 如果开启了OpenGL则使用OpenGL Widget -# self.setViewport(QGLWidget(QGLFormat(QGL.SampleBuffers))) + # if QGLFormat.hasOpenGL(): # 如果开启了OpenGL则使用OpenGL Widget + # self.setViewport(QGLWidget(QGLFormat(QGL.SampleBuffers))) ''' #参考 http://doc.qt.io/qt-5/qgraphicsview.html#ViewportUpdateMode-enum FullViewportUpdate 当场景的任何可见部分改变或重新显示时,QGraphicsView将更新整个视口。 当QGraphicsView花费更多的时间来计算绘制的内容(比如重复更新很多小项目)时,这种方法是最快的。 这是不支持部分更新(如QGLWidget)的视口以及需要禁用滚动优化的视口的首选更新模式。 @@ -157,7 +157,7 @@ class GraphicsView(QGraphicsView): if __name__ == "__main__": import sys - from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) print("OpenGL Status:", QGLFormat.hasOpenGL()) view = GraphicsView() diff --git a/QGridLayout/Data/CoverItemWidget.ui b/QGridLayout/Data/CoverItemWidget.ui new file mode 100644 index 0000000000000000000000000000000000000000..2f245e1344895af124b9ed3ea8b387c232077a79 --- /dev/null +++ b/QGridLayout/Data/CoverItemWidget.ui @@ -0,0 +1,85 @@ + + + CoverItemWidget + + + + 200 + 256 + + + + + 200 + 256 + + + + + + + + 12 + + + 10 + + + 10 + + + 10 + + + 10 + + + + + + 180 + 180 + + + + + + + true + + + Qt::AlignCenter + + + + + + + + 10 + + + + + + + + + + + + + + + + + + + CoverLabel + QLabel +
.CoverLabel
+
+
+ + +
diff --git a/QGridLayout/Data/CoverLabel.ui b/QGridLayout/Data/CoverLabel.ui new file mode 100644 index 0000000000000000000000000000000000000000..2e7e037880e23bace4731623a4f1c25620fe267b --- /dev/null +++ b/QGridLayout/Data/CoverLabel.ui @@ -0,0 +1,109 @@ + + + CoverLabel + + + + 0 + 0 + 180 + 180 + + + + PointingHandCursor + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 20 + 135 + + + + + + + + #widgetBottom { + background-color: rgba(0, 0, 0, 150); +} + + + + + + + 16 + 16 + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 16 + 16 + + + + + + + + + + + + + + + diff --git a/QGridLayout/Data/Svg_icon_headset_sm.svg b/QGridLayout/Data/Svg_icon_headset_sm.svg new file mode 100644 index 0000000000000000000000000000000000000000..98a1a3591d4f0741b27d27862023bc75bddf4a8d --- /dev/null +++ b/QGridLayout/Data/Svg_icon_headset_sm.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/QGridLayout/Data/Svg_icon_loading.svg b/QGridLayout/Data/Svg_icon_loading.svg new file mode 100644 index 0000000000000000000000000000000000000000..0b00b7993dc84f2a6221b942d35dc62421e70ac4 --- /dev/null +++ b/QGridLayout/Data/Svg_icon_loading.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/QGridLayout/Data/Svg_icon_play_sm.svg b/QGridLayout/Data/Svg_icon_play_sm.svg new file mode 100644 index 0000000000000000000000000000000000000000..08a47f20e76885d4e77b0f31221ff4d9e8bacafd --- /dev/null +++ b/QGridLayout/Data/Svg_icon_play_sm.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/QGridLayout/HotPlaylist.py b/QGridLayout/HotPlaylist.py index a2022c2ca5be6bc0a9fca01c867db6ebe03a3999..ff5500ad0412d47b7ddf8530063f971219341c0a 100644 --- a/QGridLayout/HotPlaylist.py +++ b/QGridLayout/HotPlaylist.py @@ -1,192 +1,45 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- - -''' -Created on 2018年2月4日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +""" +Created on 2023/02/22 +@author: Irony +@site: https://pyqt.site https://github.com/PyQt5 @email: 892768447@qq.com -@file: TencentMovieHotPlay -@description: -''' +@file: HotPlaylist.py +@description: +""" + import os import sys -import webbrowser - -from PyQt5.QtCore import QSize, Qt, QUrl, QTimer, pyqtSignal -from PyQt5.QtGui import QPainter, QFont, QLinearGradient, QGradient, QColor,\ - QBrush, QPaintEvent, QPixmap -from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest -from PyQt5.QtSvg import QSvgWidget -from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QLabel,\ - QHBoxLayout, QSpacerItem, QSizePolicy, QScrollArea, QGridLayout,\ - QAbstractSlider +from Lib.CoverItemWidget import CoverItemWidget from lxml.etree import HTML # @UnresolvedImport +try: + from PyQt5.QtCore import Qt, QTimer, QUrl, pyqtSignal + from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest + from PyQt5.QtSvg import QSvgWidget + from PyQt5.QtWidgets import (QAbstractSlider, QApplication, QGridLayout, + QScrollArea, QWidget) +except ImportError: + from PySide2.QtCore import Qt, QTimer, QUrl, pyqtSignal + from PySide2.QtNetwork import QNetworkAccessManager, QNetworkRequest + from PySide2.QtSvg import QSvgWidget + from PySide2.QtWidgets import (QAbstractSlider, QApplication, QGridLayout, + QScrollArea, QWidget) -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" -__Version__ = "Version 1.0" +# offset=0,35,70,105 +Url = "https://music.163.com/discover/playlist/?order=hot&cat=%E5%85%A8%E9%83%A8&limit=35&offset={0}" -# offset=0,30,60,90 -Url = "http://v.qq.com/x/list/movie?pay=-1&offset={0}" +Agent = b"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.50" -# 播放量图标 -Svg_icon_play_sm = ''' - - -'''.encode() +Referer = b"https://music.163.com" -Svg_icon_loading = ''' - - - - - - - - - - - - - - - - - -'''.encode() - -# 主演 +# 作者 Actor = '''{title} ''' -class CoverLabel(QLabel): - - def __init__(self, cover_path, cover_title, video_url, *args, **kwargs): - super(CoverLabel, self).__init__(*args, **kwargs) - self.setCursor(Qt.PointingHandCursor) - self.setScaledContents(True) - self.setMinimumSize(220, 308) - self.setMaximumSize(220, 308) - self.cover_path = cover_path - self.cover_title = cover_title - self.video_url = video_url - self.setPixmap(QPixmap(cover_path)) - - def setCoverPath(self, path): - self.cover_path = path - - def mouseReleaseEvent(self, event): - super(CoverLabel, self).mouseReleaseEvent(event) - webbrowser.open_new_tab(self.video_url) - - def paintEvent(self, event): - super(CoverLabel, self).paintEvent(event) - if hasattr(self, "cover_title") and self.cover_title != "": - # 底部绘制文字 - painter = QPainter(self) - rect = self.rect() - # 粗略字体高度 - painter.save() - fheight = self.fontMetrics().height() - # 底部矩形框背景渐变颜色 - bottomRectColor = QLinearGradient( - rect.width() / 2, rect.height() - 24 - fheight, - rect.width() / 2, rect.height()) - bottomRectColor.setSpread(QGradient.PadSpread) - bottomRectColor.setColorAt(0, QColor(255, 255, 255, 70)) - bottomRectColor.setColorAt(1, QColor(0, 0, 0, 50)) - # 画半透明渐变矩形框 - painter.setPen(Qt.NoPen) - painter.setBrush(QBrush(bottomRectColor)) - painter.drawRect(rect.x(), rect.height() - 24 - - fheight, rect.width(), 24 + fheight) - painter.restore() - # 距离底部一定高度画文字 - font = self.font() or QFont() - font.setPointSize(8) - painter.setFont(font) - painter.setPen(Qt.white) - rect.setHeight(rect.height() - 12) # 底部减去一定高度 - painter.drawText(rect, Qt.AlignHCenter | - Qt.AlignBottom, self.cover_title) - - -class ItemWidget(QWidget): - - def __init__(self, cover_path, figure_info, figure_title, - figure_score, figure_desc, figure_count, video_url, cover_url, img_path, *args, **kwargs): - super(ItemWidget, self).__init__(*args, **kwargs) - self.setMaximumSize(220, 380) - self.setMaximumSize(220, 380) - self.img_path = img_path - self.cover_url = cover_url - layout = QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - # 图片label - self.clabel = CoverLabel(cover_path, figure_info, video_url, self) - layout.addWidget(self.clabel) - - # 片名和分数 - flayout = QHBoxLayout() - flayout.addWidget(QLabel(figure_title, self)) - flayout.addItem(QSpacerItem( - 20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) - flayout.addWidget(QLabel(figure_score, self, styleSheet="color: red;")) - layout.addLayout(flayout) - - # 主演 - layout.addWidget( - QLabel(figure_desc, self, styleSheet="color: #999999;", openExternalLinks=True)) - - # 播放量 - blayout = QHBoxLayout() - count_icon = QSvgWidget(self) - count_icon.setMaximumSize(16, 16) - count_icon.load(Svg_icon_play_sm) - blayout.addWidget(count_icon) - blayout.addWidget( - QLabel(figure_count, self, styleSheet="color: #999999;")) - layout.addLayout(blayout) - - def setCover(self, path): - self.clabel.setCoverPath(path) - self.clabel.setPixmap(QPixmap(path)) -# self.clabel.setText(''.format(os.path.abspath(path))) - - def sizeHint(self): - # 每个item控件的大小 - return QSize(220, 380) - - def event(self, event): - if isinstance(event, QPaintEvent): - if event.rect().height() > 20 and hasattr(self, "clabel"): - if self.clabel.cover_path.find("pic_v.png") > -1: # 封面未加载 - # print("start download img:", self.cover_url) - req = QNetworkRequest(QUrl(self.cover_url)) - # 设置两个自定义属性方便后期reply中处理 - req.setAttribute(QNetworkRequest.User + 1, self) - req.setAttribute(QNetworkRequest.User + 2, self.img_path) - self.parentWidget()._manager.get(req) # 调用父窗口中的下载器下载 - return super(ItemWidget, self).event(event) - - class GridWidget(QWidget): - Page = 0 loadStarted = pyqtSignal(bool) @@ -199,16 +52,18 @@ class GridWidget(QWidget): self._manager.finished.connect(self.onFinished) def load(self): - if self.Page == -1: + if self.Page == -1 or self.Page > 10: return self.loadStarted.emit(True) # 延迟一秒后调用目的在于显示进度条 QTimer.singleShot(1000, self._load) def _load(self): - print("load url:", Url.format(self.Page * 30)) - url = QUrl(Url.format(self.Page * 30)) - self._manager.get(QNetworkRequest(url)) + print("load url:", Url.format(self.Page * 35)) + url = QUrl(Url.format(self.Page * 35)) + req = QNetworkRequest(url) + req.setRawHeader(b"User-Agent", Agent) + self._manager.get(req) def onFinished(self, reply): # 请求完成后会调用该函数 @@ -232,19 +87,21 @@ class GridWidget(QWidget): return (src[i:i + length] for i in range(len(src)) if i % length == 0) def _parseHtml(self, html): + # print(html) # encoding = chardet.detect(html) or {} # html = html.decode(encoding.get("encoding","utf-8")) html = HTML(html) # 查找所有的li list_item - lis = html.xpath("//li[@class='list_item']") + lis = html.xpath("//ul[@id='m-pl-container']/li") + # print(lis) if not lis: self.Page = -1 # 后面没有页面了 return - lack_count = self._layout.count() % 30 # 获取布局中上次还缺几个5行*6列的标准 - row_count = int(self._layout.count() / 6) # 行数 + lack_count = self._layout.count() % 35 # 获取布局中上次还缺几个5行*6列的标准 + row_count = int(self._layout.count() / 5) # 行数 print("lack_count:", lack_count) self.Page += 1 # 自增+1 - if lack_count != 0: # 上一次没有满足一行6个,需要补齐 + if lack_count != 0: # 上一次没有满足一行5个,需要补齐 lack_li = lis[:lack_count] lis = lis[lack_count:] self._makeItem(lack_li, row_count) # 补齐 @@ -255,31 +112,34 @@ class GridWidget(QWidget): self._makeItem(lis, row_count) def _makeItem(self, li_s, row_count): - li_s = self.splist(li_s, 6) + li_s = self.splist(li_s, 5) for row, lis in enumerate(li_s): for col, li in enumerate(lis): - a = li.find("a") - video_url = a.get("href") # 视频播放地址 - img = a.find("img") - cover_url = "http:" + img.get("r-lazyload") # 封面图片 - figure_title = img.get("alt") # 电影名 - figure_info = a.find("div/span") - figure_info = "" if figure_info is None else figure_info.text # 影片信息 - figure_score = "".join(li.xpath(".//em/text()")) # 评分 - # 主演 - figure_desc = "主演:" + \ - "".join([Actor.format(**dict(fd.items())) - for fd in li.xpath(".//div[@class='figure_desc']/a")]) + a = li.find('.//div/a') + play_url = "https://music.163.com" + a.get("href") # 歌单播放地址 + img = li.find(".//div/img") + cover_url = img.get("src") # 封面图片 + playlist_title = a.get("title") # 歌单名 + # 歌手 + author_info = li.xpath(".//p[2]/a")[0] + playlist_author = "".format( + Actor.format(href="https://music.163.com" + + author_info.get("href"), + title=author_info.get("title"))) # 播放数 - figure_count = ( - li.xpath(".//div[@class='figure_count']/span/text()") or [""])[0] + play_count = (li.xpath(".//div/div/span[2]/text()") or [""])[0] path = "cache/{0}.jpg".format( - os.path.splitext(os.path.basename(video_url))[0]) + os.path.splitext( + os.path.basename(cover_url).split('?')[0])[0]) cover_path = "Data/pic_v.png" if os.path.isfile(path): cover_path = path - iwidget = ItemWidget(cover_path, figure_info, figure_title, - figure_score, figure_desc, figure_count, video_url, cover_url, path, self) + + # print(cover_path, playlist_title, + # playlist_author, play_count, play_url, cover_url, path) + iwidget = CoverItemWidget(self, manager=self._manager) + iwidget.init(cover_path, playlist_title, playlist_author, + play_count, play_url, cover_url, path) self._layout.addWidget(iwidget, row_count + row, col) @@ -299,9 +159,11 @@ class Window(QScrollArea): # 连接竖着的滚动条滚动事件 self.verticalScrollBar().actionTriggered.connect(self.onActionTriggered) # 进度条 - self.loadWidget = QSvgWidget( - self, minimumHeight=120, minimumWidth=120, visible=False) - self.loadWidget.load(Svg_icon_loading) + self.loadWidget = QSvgWidget(self, + minimumHeight=120, + minimumWidth=120, + visible=False) + self.loadWidget.load('Data/Svg_icon_loading.svg') def setLoadStarted(self, started): self._loadStart = started @@ -313,7 +175,8 @@ class Window(QScrollArea): if action != QAbstractSlider.SliderMove or self._loadStart: return # 使用sliderPosition获取值可以同时满足鼠标滑动和拖动判断 - if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar().maximum(): + if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar( + ).maximum(): # 可以下一页了 self._widget.load() @@ -322,9 +185,7 @@ class Window(QScrollArea): self.loadWidget.setGeometry( int((self.width() - self.loadWidget.minimumWidth()) / 2), int((self.height() - self.loadWidget.minimumHeight()) / 2), - self.loadWidget.minimumWidth(), - self.loadWidget.minimumHeight() - ) + self.loadWidget.minimumWidth(), self.loadWidget.minimumHeight()) if __name__ == "__main__": diff --git a/QGridLayout/Lib/CoverItemWidget.py b/QGridLayout/Lib/CoverItemWidget.py new file mode 100644 index 0000000000000000000000000000000000000000..5ef3ff00225af2269e33bd3c3ea4c47ba54c2adf --- /dev/null +++ b/QGridLayout/Lib/CoverItemWidget.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Created on 2023/02/22 +@author: Irony +@site: https://pyqt.site https://github.com/PyQt5 +@email: 892768447@qq.com +@file: CoverItemWidget.py +@description: +""" + +try: + from PyQt5.QtCore import QSize, QUrl + from PyQt5.QtGui import QPaintEvent, QPixmap + from PyQt5.QtNetwork import QNetworkRequest + from PyQt5.QtWidgets import QWidget +except ImportError: + from PySide2.QtCore import QSize, QUrl + from PySide2.QtGui import QPaintEvent, QPixmap + from PySide2.QtNetwork import QNetworkRequest + from PySide2.QtWidgets import QWidget + +from .Ui_CoverItemWidget import Ui_CoverItemWidget # @UnresolvedImport + + +class CoverItemWidget(QWidget, Ui_CoverItemWidget): + + def __init__(self, *args, **kwargs): + self._manager = kwargs.pop('manager', None) + super(CoverItemWidget, self).__init__(*args, **kwargs) + self.setupUi(self) + + def init(self, cover_path, playlist_title, playlist_author, play_count, + play_url, cover_url, img_path): + self.img_path = img_path + self.cover_url = cover_url + # 图片label + self.labelCover.init(cover_path, play_url, play_count) + + # 歌单 + self.labelTitle.setText(playlist_title) + + # 作者 + self.labelAuthor.setText(playlist_author) + + def setCover(self, path): + self.labelCover.setCoverPath(path) + self.labelCover.setPixmap(QPixmap(path)) + + def sizeHint(self): + # 每个item控件的大小 + return QSize(200, 256) + + def event(self, event): + if isinstance(event, QPaintEvent): + if event.rect().height() > 20 and hasattr(self, "labelCover"): + if self.labelCover.cover_path.find("pic_v.png") > -1: # 封面未加载 + # print("start download img:", self.cover_url) + req = QNetworkRequest(QUrl(self.cover_url)) + # 设置两个自定义属性方便后期reply中处理 + req.setAttribute(QNetworkRequest.User + 1, self) + req.setAttribute(QNetworkRequest.User + 2, self.img_path) + if self._manager: + self._manager.get(req) # 调用父窗口中的下载器下载 + return super(CoverItemWidget, self).event(event) diff --git a/QGridLayout/Lib/CoverLabel.py b/QGridLayout/Lib/CoverLabel.py new file mode 100644 index 0000000000000000000000000000000000000000..dbee15af4cf251de21d8d7d4194b18cb8a192bbc --- /dev/null +++ b/QGridLayout/Lib/CoverLabel.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Created on 2023/02/22 +@author: Irony +@site: https://pyqt.site https://github.com/PyQt5 +@email: 892768447@qq.com +@file: CoverLabel.py +@description: +""" +import webbrowser + +try: + from PyQt5.QtGui import QPixmap + from PyQt5.QtWidgets import QLabel +except ImportError: + from PySide2.QtGui import QPixmap + from PySide2.QtWidgets import QLabel + +from .Ui_CoverLabel import Ui_CoverLabel # @UnresolvedImport + + +class CoverLabel(QLabel, Ui_CoverLabel): + + def __init__(self, *args, **kwargs): + super(CoverLabel, self).__init__(*args, **kwargs) + self.setupUi(self) + + def init(self, cover_path, play_url, play_count): + self.cover_path = cover_path + self.play_url = play_url + self.setPixmap(QPixmap(cover_path)) + self.labelHeadset.setPixmap(QPixmap('Data/Svg_icon_headset_sm.svg')) + self.labelPlay.setPixmap(QPixmap('Data/Svg_icon_play_sm.svg')) + self.labelCount.setStyleSheet('color: #999999;') + self.labelCount.setText(play_count) + + def setCoverPath(self, path): + self.cover_path = path + + def mouseReleaseEvent(self, event): + super(CoverLabel, self).mouseReleaseEvent(event) + webbrowser.open_new_tab(self.play_url) diff --git a/QGridLayout/Lib/Ui_CoverItemWidget.py b/QGridLayout/Lib/Ui_CoverItemWidget.py new file mode 100644 index 0000000000000000000000000000000000000000..c048f686e4662268e855850c42d7da8a3182c91a --- /dev/null +++ b/QGridLayout/Lib/Ui_CoverItemWidget.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'CoverItemWidget.ui' +# +# Created by: PyQt5 UI code generator 5.15.2 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +try: + from PyQt5 import QtCore, QtGui, QtWidgets +except ImportError: + from PySide2 import QtCore, QtGui, QtWidgets + + +class Ui_CoverItemWidget(object): + def setupUi(self, CoverItemWidget): + CoverItemWidget.setObjectName("CoverItemWidget") + CoverItemWidget.setMinimumSize(QtCore.QSize(200, 256)) + CoverItemWidget.setMaximumSize(QtCore.QSize(200, 256)) + CoverItemWidget.setWindowTitle("") + self.verticalLayout = QtWidgets.QVBoxLayout(CoverItemWidget) + self.verticalLayout.setContentsMargins(10, 10, 10, 10) + self.verticalLayout.setSpacing(12) + self.verticalLayout.setObjectName("verticalLayout") + self.labelCover = CoverLabel(CoverItemWidget) + self.labelCover.setMinimumSize(QtCore.QSize(180, 180)) + self.labelCover.setText("") + self.labelCover.setScaledContents(True) + self.labelCover.setAlignment(QtCore.Qt.AlignCenter) + self.labelCover.setObjectName("labelCover") + self.verticalLayout.addWidget(self.labelCover) + self.labelTitle = QtWidgets.QLabel(CoverItemWidget) + font = QtGui.QFont() + font.setPointSize(10) + self.labelTitle.setFont(font) + self.labelTitle.setText("") + self.labelTitle.setObjectName("labelTitle") + self.verticalLayout.addWidget(self.labelTitle) + self.labelAuthor = QtWidgets.QLabel(CoverItemWidget) + self.labelAuthor.setText("") + self.labelAuthor.setObjectName("labelAuthor") + self.verticalLayout.addWidget(self.labelAuthor) + + self.retranslateUi(CoverItemWidget) + QtCore.QMetaObject.connectSlotsByName(CoverItemWidget) + + def retranslateUi(self, CoverItemWidget): + pass +from .CoverLabel import CoverLabel + +if __name__ == "__main__": + import sys + app = QtWidgets.QApplication(sys.argv) + CoverItemWidget = QtWidgets.QWidget() + ui = Ui_CoverItemWidget() + ui.setupUi(CoverItemWidget) + CoverItemWidget.show() + sys.exit(app.exec_()) diff --git a/QGridLayout/Lib/Ui_CoverLabel.py b/QGridLayout/Lib/Ui_CoverLabel.py new file mode 100644 index 0000000000000000000000000000000000000000..86e1526a548fbd165e68d695447c530166faaee1 --- /dev/null +++ b/QGridLayout/Lib/Ui_CoverLabel.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'CoverLabel.ui' +# +# Created by: PyQt5 UI code generator 5.15.2 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +try: + from PyQt5 import QtCore, QtGui, QtWidgets +except ImportError: + from PySide2 import QtCore, QtGui, QtWidgets + + +class Ui_CoverLabel(object): + def setupUi(self, CoverLabel): + CoverLabel.setObjectName("CoverLabel") + CoverLabel.resize(180, 180) + CoverLabel.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) + CoverLabel.setWindowTitle("") + self.verticalLayout = QtWidgets.QVBoxLayout(CoverLabel) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setSpacing(0) + self.verticalLayout.setObjectName("verticalLayout") + spacerItem = QtWidgets.QSpacerItem(20, 135, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout.addItem(spacerItem) + self.widgetBottom = QtWidgets.QWidget(CoverLabel) + self.widgetBottom.setStyleSheet("#widgetBottom {\n" +" background-color: rgba(0, 0, 0, 150);\n" +"}") + self.widgetBottom.setObjectName("widgetBottom") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.widgetBottom) + self.horizontalLayout.setObjectName("horizontalLayout") + self.labelHeadset = QtWidgets.QLabel(self.widgetBottom) + self.labelHeadset.setMinimumSize(QtCore.QSize(16, 16)) + self.labelHeadset.setText("") + self.labelHeadset.setObjectName("labelHeadset") + self.horizontalLayout.addWidget(self.labelHeadset) + self.labelCount = QtWidgets.QLabel(self.widgetBottom) + self.labelCount.setText("") + self.labelCount.setObjectName("labelCount") + self.horizontalLayout.addWidget(self.labelCount) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem1) + self.labelPlay = QtWidgets.QLabel(self.widgetBottom) + self.labelPlay.setMinimumSize(QtCore.QSize(16, 16)) + self.labelPlay.setText("") + self.labelPlay.setObjectName("labelPlay") + self.horizontalLayout.addWidget(self.labelPlay) + self.verticalLayout.addWidget(self.widgetBottom) + + self.retranslateUi(CoverLabel) + QtCore.QMetaObject.connectSlotsByName(CoverLabel) + + def retranslateUi(self, CoverLabel): + pass + + +if __name__ == "__main__": + import sys + app = QtWidgets.QApplication(sys.argv) + CoverLabel = QtWidgets.QWidget() + ui = Ui_CoverLabel() + ui.setupUi(CoverLabel) + CoverLabel.show() + sys.exit(app.exec_()) diff --git a/QGridLayout/Lib/__init__.py b/QGridLayout/Lib/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/QGridLayout/README.md b/QGridLayout/README.md index 624bc1c6e0630cb6af5ef8cacd16eef61c6cc6b0..8c851274764f94cfa004849ac82a5940ccb7412f 100644 --- a/QGridLayout/README.md +++ b/QGridLayout/README.md @@ -1,6 +1,9 @@ # QListView -## 1、腾讯视频热播列表 +- 目录 + - [音乐热歌列表](#1音乐热歌列表) + +## 1、音乐热歌列表 [运行 HotPlaylist.py](HotPlaylist.py) 简单思路说明: diff --git a/QHBoxLayout/Data/BaseHorizontalLayout.ui b/QHBoxLayout/Data/BaseHorizontalLayout.ui new file mode 100644 index 0000000000000000000000000000000000000000..0d0410ecbf27df083a9dc7db11e909cdcf5187d4 --- /dev/null +++ b/QHBoxLayout/Data/BaseHorizontalLayout.ui @@ -0,0 +1,38 @@ + + + BaseHorizontalLayout + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + 通过 右键 -> 布局 -> 水平布局 + + + Qt::AlignCenter + + + + + + + 右侧按钮 + + + + + + + + diff --git a/QHBoxLayout/Data/HorizontalLayoutMargin.ui b/QHBoxLayout/Data/HorizontalLayoutMargin.ui new file mode 100644 index 0000000000000000000000000000000000000000..89673b3418b8ed37fab3890f2810e6e094375a16 --- /dev/null +++ b/QHBoxLayout/Data/HorizontalLayoutMargin.ui @@ -0,0 +1,60 @@ + + + HorizontalLayoutMargin + + + + 0 + 0 + 457 + 216 + + + + Form + + + #VerticalLayoutMargin { + background: #5aaadb; +} +#label { + background: #85c440; +} + + + + 20 + + + 20 + + + + + 通过设置Margin和Spacing设置边距 +以及两个控件之间的间隔距离 + + + Qt::AlignCenter + + + + + + + Spcasing 为 20 + + + + + + + Spcasing 为 20 + + + + + + + + diff --git a/QHBoxLayout/Data/HorizontalLayoutStretch.ui b/QHBoxLayout/Data/HorizontalLayoutStretch.ui new file mode 100644 index 0000000000000000000000000000000000000000..a45f50cb7e3d285167f9863dd27b2aaddb2ec1f4 --- /dev/null +++ b/QHBoxLayout/Data/HorizontalLayoutStretch.ui @@ -0,0 +1,62 @@ + + + HorizontalLayoutStretch + + + + 0 + 0 + 400 + 300 + + + + Form + + + #label { + background: #5aaadb; +} +#label_2 { + background: #85c440; +} +#label_3 { + background: #f2b63c; +} + + + + + + 1/6 + + + Qt::AlignCenter + + + + + + + 2/6 + + + Qt::AlignCenter + + + + + + + 3/6 + + + Qt::AlignCenter + + + + + + + + diff --git a/QHBoxLayout/README.md b/QHBoxLayout/README.md index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a20a92b51fd102762ab55e3ffcdaef0e45e82c0a 100644 --- a/QHBoxLayout/README.md +++ b/QHBoxLayout/README.md @@ -0,0 +1,32 @@ +# QHBoxLayout + +- 目录 + - [水平布局](#1水平布局) + - [边距和间隔](#2边距和间隔) + - [比例分配](#3比例分配) + +## 1、水平布局 +[查看 BaseHorizontalLayout.ui](Data/BaseHorizontalLayout.ui) + +![BaseHorizontalLayout](ScreenShot/BaseHorizontalLayout.png) + +## 2、边距和间隔 +[查看 HorizontalLayoutMargin.ui](Data/HorizontalLayoutMargin.ui) + +1. 通过`setContentsMargins(-1, -1, 20, -1)`设置左上右下的边距,-1表示默认值 +2. 通过`setSpacing`设置控件之间的间隔 + +![HorizontalLayoutMargin](ScreenShot/HorizontalLayoutMargin.png) + +## 3、比例分配 +[查看 HorizontalLayoutStretch.ui](Data/HorizontalLayoutStretch.ui) + +通过`setStretch`设置各个部分的占比 分别为:1/6 2/6 3/6 + +```python +self.horizontalLayout.setStretch(0, 1) +self.horizontalLayout.setStretch(1, 2) +self.horizontalLayout.setStretch(2, 3) +``` + +![HorizontalLayoutStretch](ScreenShot/HorizontalLayoutStretch.png) \ No newline at end of file diff --git a/QHBoxLayout/ScreenShot/BaseHorizontalLayout.png b/QHBoxLayout/ScreenShot/BaseHorizontalLayout.png new file mode 100644 index 0000000000000000000000000000000000000000..37f0c3f8e163bd0ea616a97a7a2ce38162299155 Binary files /dev/null and b/QHBoxLayout/ScreenShot/BaseHorizontalLayout.png differ diff --git a/QHBoxLayout/ScreenShot/HorizontalLayoutMargin.png b/QHBoxLayout/ScreenShot/HorizontalLayoutMargin.png new file mode 100644 index 0000000000000000000000000000000000000000..32dc9ec4956753618808a6bb983ae4c9e0502e8a Binary files /dev/null and b/QHBoxLayout/ScreenShot/HorizontalLayoutMargin.png differ diff --git a/QHBoxLayout/ScreenShot/HorizontalLayoutStretch.png b/QHBoxLayout/ScreenShot/HorizontalLayoutStretch.png new file mode 100644 index 0000000000000000000000000000000000000000..3c568f9a6c757dd4d9395ce144184121b8ad5abe Binary files /dev/null and b/QHBoxLayout/ScreenShot/HorizontalLayoutStretch.png differ diff --git a/QLabel/CircleImage.py b/QLabel/CircleImage.py index 0c805cb06effcfcb09401ed2df35c1524ae0356a..7b59420eb35742b3acb032ba07b7d5049756f061 100644 --- a/QLabel/CircleImage.py +++ b/QLabel/CircleImage.py @@ -3,19 +3,21 @@ """ Created on 2018年1月20日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: CircleImage @description: 圆形图片 """ -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QPixmap, QPainter, QPainterPath -from PyQt5.QtWidgets import QLabel, QWidget, QHBoxLayout -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" -__Version__ = "Version 1.0" +try: + from PyQt5.QtCore import Qt + from PyQt5.QtGui import QPixmap, QPainter, QPainterPath + from PyQt5.QtWidgets import QLabel, QWidget, QHBoxLayout, QApplication +except ImportError: + from PySide2.QtCore import Qt + from PySide2.QtGui import QPixmap, QPainter, QPainterPath + from PySide2.QtWidgets import QLabel, QWidget, QHBoxLayout, QApplication class Label(QLabel): @@ -67,7 +69,6 @@ class Window(QWidget): if __name__ == "__main__": import sys - from PyQt5.QtWidgets import QApplication app = QApplication(sys.argv) w = Window() diff --git a/QLabel/ImageRotate.py b/QLabel/ImageRotate.py index f0dd48d635faf2f95d08237d2e351be5b3c0064e..142299b77ca534ec9aad32a8a4a87d707b609041 100644 --- a/QLabel/ImageRotate.py +++ b/QLabel/ImageRotate.py @@ -4,22 +4,22 @@ """ Created on 2018年11月19日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: @description: """ -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QPixmap, QPainter, QImage -from PyQt5.QtWidgets import QWidget, QLabel, QPushButton,\ - QVBoxLayout, QHBoxLayout, QSpacerItem, QSizePolicy - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" +try: + from PyQt5.QtCore import Qt + from PyQt5.QtGui import QPixmap, QPainter, QImage + from PyQt5.QtWidgets import QWidget, QLabel, QPushButton, \ + QVBoxLayout, QHBoxLayout, QSpacerItem, QSizePolicy, QApplication +except ImportError: + from PySide2.QtCore import Qt + from PySide2.QtGui import QPixmap, QPainter, QImage + from PySide2.QtWidgets import QWidget, QLabel, QPushButton, \ + QVBoxLayout, QHBoxLayout, QSpacerItem, QSizePolicy, QApplication class Window(QWidget): @@ -73,12 +73,12 @@ class Window(QWidget): self.srcImage = image # 替换 self.imageLabel.setPixmap(QPixmap.fromImage(self.srcImage)) -# # 下面这个旋转方法针对90度的倍数,否则图片会变大 -# trans = QTransform() -# trans.rotate(90) -# self.srcImage = self.srcImage.transformed( -# trans, Qt.SmoothTransformation) -# self.imageLabel.setPixmap(QPixmap.fromImage(self.srcImage)) + # # 下面这个旋转方法针对90度的倍数,否则图片会变大 + # trans = QTransform() + # trans.rotate(90) + # self.srcImage = self.srcImage.transformed( + # trans, Qt.SmoothTransformation) + # self.imageLabel.setPixmap(QPixmap.fromImage(self.srcImage)) def doAnticlockwise(self): # 逆时针45度 @@ -96,6 +96,7 @@ class Window(QWidget): self.srcImage = image # 替换 self.imageLabel.setPixmap(QPixmap.fromImage(self.srcImage)) + # # 下面这个旋转方法针对90度的倍数,否则图片会变大 # trans = QTransform() # trans.rotate(90) @@ -106,7 +107,7 @@ class Window(QWidget): if __name__ == '__main__': import sys - from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) w = Window() w.show() diff --git a/QLabel/ImageSlipped.py b/QLabel/ImageSlipped.py index dfcac07d4236ba5499d744df04f8fffbb35c5dd1..1ea7818ba198ea0ac38a37e1bad4a25949dfe6ab 100644 --- a/QLabel/ImageSlipped.py +++ b/QLabel/ImageSlipped.py @@ -4,20 +4,18 @@ """ Created on 2018年10月18日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: ImageSlipped @description: """ -from PyQt5.QtGui import QPixmap, QPainter -from PyQt5.QtWidgets import QWidget - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" +try: + from PyQt5.QtGui import QPixmap, QPainter + from PyQt5.QtWidgets import QWidget, QApplication +except ImportError: + from PySide2.QtGui import QPixmap, QPainter + from PySide2.QtWidgets import QWidget, QApplication class SlippedImgWidget(QWidget): @@ -73,7 +71,7 @@ class SlippedImgWidget(QWidget): if __name__ == '__main__': import sys - from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) w = SlippedImgWidget('Data/bg1.jpg', 'Data/fg1.png') w.show() diff --git a/QLabel/Lib/NinePatch.py b/QLabel/Lib/NinePatch.py index 966903ae84201f51d4641bb16a0274781cbce0f9..e157f1bf24e76c235be2933078be2e194b7a285c 100644 --- a/QLabel/Lib/NinePatch.py +++ b/QLabel/Lib/NinePatch.py @@ -4,22 +4,19 @@ """ Created on 2018年10月25日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: NinePatch @description: """ from math import fabs -from PyQt5.QtCore import QRect -from PyQt5.QtGui import QImage, QColor, QPainter, qRed, qGreen, qBlue, qAlpha - - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" +try: + from PyQt5.QtCore import QRect + from PyQt5.QtGui import QImage, QColor, QPainter, qRed, qGreen, qBlue, qAlpha +except ImportError: + from PySide2.QtCore import QRect + from PySide2.QtGui import QImage, QColor, QPainter, qRed, qGreen, qBlue, qAlpha class _Exception(Exception): @@ -44,7 +41,8 @@ class ExceptionIncorrectWidth(_Exception): class ExceptionIncorrectWidthAndHeight(_Exception): def __str__(self): - return "Input incorrect width width and height. Minimum width = :{imgW} . Minimum height = :{imgH}".format(imgW=self.imgW, imgH=self.imgH) + return "Input incorrect width width and height. Minimum width = :{imgW} . Minimum height = :{imgH}".format( + imgW=self.imgW, imgH=self.imgH) class ExceptionIncorrectHeight(_Exception): @@ -62,11 +60,11 @@ class ExceptionNot9Patch(Exception): class NinePatch: def __init__(self, fileName): - self.CachedImage = None # 缓存图片 + self.CachedImage = None # 缓存图片 self.OldWidth = -1 self.OldHeight = -1 self.ResizeDistancesX = [] - self.ResizeDistancesY = [] # [(int,int)]数组 + self.ResizeDistancesY = [] # [(int,int)]数组 self.setImage(fileName) def width(self): @@ -103,7 +101,8 @@ class NinePatch: for i in range(len(self.ResizeDistancesY)): resizeHeight += self.ResizeDistancesY[i][1] - if (width < (self.Image.width() - 2 - resizeWidth) and height < (self.Image.height() - 2 - resizeHeight)): + if (width < (self.Image.width() - 2 - resizeWidth) and height < ( + self.Image.height() - 2 - resizeHeight)): raise ExceptionIncorrectWidthAndHeight( self.Image.width() - 2, self.Image.height() - 2) @@ -123,7 +122,8 @@ class NinePatch: @classmethod def GetContentAreaRect(self, width, height): # print("GetContentAreaRect : width:%d height:%d" % (width, height)) - return (QRect(self.ContentArea.x(), self.ContentArea.y(), (width - (self.Image.width() - 2 - self.ContentArea.width())), + return (QRect(self.ContentArea.x(), self.ContentArea.y(), + (width - (self.Image.width() - 2 - self.ContentArea.width())), (height - (self.Image.height() - 2 - self.ContentArea.height())))) def DrawScaledPart(self, oldRect, newRect, painter): @@ -189,7 +189,8 @@ class NinePatch: for i in range(self.Image.width()): if (self.IsColorBlack(self.Image.pixel(i, j)) and left == 0): left = i - if (left and self.IsColorBlack(self.Image.pixel(i, j)) and not self.IsColorBlack(self.Image.pixel(i + 1, j))): + if (left and self.IsColorBlack(self.Image.pixel(i, j)) and not self.IsColorBlack( + self.Image.pixel(i + 1, j))): right = i left -= 1 # print("ResizeDistancesX.append ", left, " ", right - left) @@ -204,7 +205,8 @@ class NinePatch: if (self.IsColorBlack(self.Image.pixel(i, j)) and top == 0): top = j - if (top and self.IsColorBlack(self.Image.pixel(i, j)) and not self.IsColorBlack(self.Image.pixel(i, j + 1))): + if (top and self.IsColorBlack(self.Image.pixel(i, j)) and not self.IsColorBlack( + self.Image.pixel(i, j + 1))): bot = j top -= 1 # print("ResizeDistancesY.append ", top, " ", bot - top) @@ -241,10 +243,10 @@ class NinePatch: # print("after GetFactor: ", width, height, factorX, factorY) lostX = 0.0 lostY = 0.0 - x1 = 0 # for image parts X - y1 = 0 # for image parts Y -# widthResize # width for image parts -# heightResize # height for image parts + x1 = 0 # for image parts X + y1 = 0 # for image parts Y + # widthResize # width for image parts + # heightResize # height for image parts resizeX = 0 resizeY = 0 offsetX = 0 @@ -310,7 +312,8 @@ class NinePatch: offsetY = 0 for i in range(len(self.ResizeDistancesY)): self.DrawConstPart(QRect(x1 + 1, y1 + 1, widthResize, self.ResizeDistancesY[i][0] - y1), - QRect(x1 + offsetX, y1 + offsetY, widthResize, self.ResizeDistancesY[i][0] - y1), painter) + QRect(x1 + offsetX, y1 + offsetY, widthResize, + self.ResizeDistancesY[i][0] - y1), painter) y1 = self.ResizeDistancesY[i][0] resizeY = round(float(self.ResizeDistancesY[i][1]) * factorY) lostY += resizeY - (float(self.ResizeDistancesY[i][1]) * factorY) @@ -334,7 +337,8 @@ class NinePatch: offsetX = 0 for i in range(len(self.ResizeDistancesX)): self.DrawConstPart(QRect(x1 + 1, y1 + 1, self.ResizeDistancesX[i][0] - x1, heightResize), - QRect(x1 + offsetX, y1 + offsetY, self.ResizeDistancesX[i][0] - x1, heightResize), painter) + QRect(x1 + offsetX, y1 + offsetY, self.ResizeDistancesX[i][0] - x1, + heightResize), painter) x1 = self.ResizeDistancesX[i][0] resizeX = round(float(self.ResizeDistancesX[i][1]) * factorX) lostX += resizeX - (float(self.ResizeDistancesX[i][1]) * factorX) diff --git a/QLabel/Lib/QtNinePatch/sip/configure.py b/QLabel/Lib/QtNinePatch/sip/configure.py index eae22a59927d790f3380a11b7063111b7470a823..a3cc255c76fd76375145e48c98d211bde4334106 100644 --- a/QLabel/Lib/QtNinePatch/sip/configure.py +++ b/QLabel/Lib/QtNinePatch/sip/configure.py @@ -5,9 +5,8 @@ import os import shutil import PyQt5 -from PyQt5.QtCore import PYQT_CONFIGURATION import sipconfig - +from PyQt5.QtCore import PYQT_CONFIGURATION # 模块名 moduleName = 'QtNinePatch' @@ -35,14 +34,13 @@ sip_cmd = ' '.join([ '-b', "build/" + build_file, '-I', config.default_sip_dir + '/PyQt5', PYQT_CONFIGURATION.get('sip_flags', ''), - '%s.sip' % moduleName, + '%s.sip' % moduleName, ]) os.makedirs('build', exist_ok=True) print(sip_cmd) os.system(sip_cmd) - # Create the Makefile. makefile = sipconfig.SIPModuleMakefile( config, build_file, dir='build' diff --git a/QLabel/Lib/QtNinePatch2.py b/QLabel/Lib/QtNinePatch2.py index b0a84354cde6dbe6819ac670006f6cb5cd3dce32..7697cc69920a2107c29c064d565f582bfc8a9145 100644 --- a/QLabel/Lib/QtNinePatch2.py +++ b/QLabel/Lib/QtNinePatch2.py @@ -4,22 +4,19 @@ """ Created on 2018年10月25日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: QtNinePatch @description: """ from math import floor -from PyQt5.QtCore import Qt, QRect -from PyQt5.QtGui import qAlpha, QPixmap, QPainter - - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" +try: + from PyQt5.QtCore import Qt, QRect + from PyQt5.QtGui import qAlpha, QPixmap, QPainter +except ImportError: + from PySide2.QtCore import Qt, QRect + from PySide2.QtGui import qAlpha, QPixmap, QPainter class Part: diff --git a/QLabel/Lib/res_rc.py b/QLabel/Lib/res_rc.py index baab0b942aeed3f63a1f90df7428ecdaa636a830..0bebdc4c7d8003496a1268f642a3b4ca29389098 100644 --- a/QLabel/Lib/res_rc.py +++ b/QLabel/Lib/res_rc.py @@ -6,7 +6,10 @@ # # WARNING! All changes made in this file will be lost! -from PyQt5 import QtCore +try: + from PyQt5 import QtCore +except ImportError: + from PySide2 import QtCore qt_resource_data = b"\ \x00\x00\x19\xf0\ @@ -462,10 +465,13 @@ else: rcc_version = 2 qt_resource_struct = qt_resource_struct_v2 + def qInitResources(): QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) + def qCleanupResources(): QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) + qInitResources() diff --git a/QLabel/Lib/xpmres.py b/QLabel/Lib/xpmres.py index fea8f797da6f03a5206bbf3b201a1cb9bce2e81f..b589c8d1497afd50600004e3a3533236569e75a7 100644 --- a/QLabel/Lib/xpmres.py +++ b/QLabel/Lib/xpmres.py @@ -1,18 +1,14 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -''' +""" Created on 2017年12月23日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: xpmres @description: -''' - -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]" -__Version__ = "Version 1.0" +""" # 这里把转换的xpm数组直接放到py文件中当做一个变量 diff --git a/QLabel/NinePatch.py b/QLabel/NinePatch.py index 936faf7e2fff7ffdd0660c5acd9c5cf59cb1c011..f072dc10ca5f4bec095b3045f0ab326f128e53df 100644 --- a/QLabel/NinePatch.py +++ b/QLabel/NinePatch.py @@ -4,23 +4,20 @@ """ Created on 2018年10月25日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: NinePatch @description: """ - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" - import sys -from PyQt5.QtGui import QImage, QPainter -from PyQt5.QtWidgets import QApplication, QLabel, QWidget +try: + from PyQt5.QtGui import QPainter + from PyQt5.QtWidgets import QApplication, QWidget +except ImportError: + from PySide2.QtGui import QPainter + from PySide2.QtWidgets import QApplication, QWidget from Lib.NinePatch import NinePatch @@ -29,7 +26,7 @@ class Label(QWidget): def __init__(self, *args, **kwargs): super(Label, self).__init__(*args, **kwargs) - #.9 格式的图片 + # .9 格式的图片 self.image = NinePatch('Data/skin_aio_friend_bubble_pressed.9.png') def paintEvent(self, event): diff --git a/QLabel/QtNinePatch.py b/QLabel/QtNinePatch.py index 8fbdbc9c03886affcd92e54f1f4d9b5ba0bb4774..497b7bfa7132b1d4072cdc0ce9709cdb52046da3 100644 --- a/QLabel/QtNinePatch.py +++ b/QLabel/QtNinePatch.py @@ -4,22 +4,15 @@ """ Created on 2018年10月25日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: testQtNinePatch @description: """ - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" - import sys - from ctypes import CDLL + from PyQt5.QtGui import QImage from PyQt5.QtWidgets import QApplication, QLabel @@ -32,7 +25,7 @@ class Label(QLabel): def __init__(self, *args, **kwargs): super(Label, self).__init__(*args, **kwargs) - #.9 格式的图片 + # .9 格式的图片 self.image = QImage('Data/skin_aio_friend_bubble_pressed.9.png') def showEvent(self, event): diff --git a/QLabel/QtNinePatch2.py b/QLabel/QtNinePatch2.py index 187b15ce90455d2763a1a198a2888d955692f624..f861461202910e350997206582347888d7efcb7e 100644 --- a/QLabel/QtNinePatch2.py +++ b/QLabel/QtNinePatch2.py @@ -4,23 +4,20 @@ """ Created on 2018年10月25日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: QtNinePatch2 @description: """ - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" - import sys -from PyQt5.QtGui import QImage -from PyQt5.QtWidgets import QApplication, QLabel +try: + from PyQt5.QtGui import QImage + from PyQt5.QtWidgets import QApplication, QLabel +except ImportError: + from PySide2.QtGui import QImage + from PySide2.QtWidgets import QApplication, QLabel from Lib import QtNinePatch2 @@ -29,7 +26,7 @@ class Label(QLabel): def __init__(self, *args, **kwargs): super(Label, self).__init__(*args, **kwargs) - #.9 格式的图片 + # .9 格式的图片 self.image = QImage('Data/skin_aio_friend_bubble_pressed.9.png') def showEvent(self, event): diff --git a/QLabel/README.md b/QLabel/README.md index 2d068acffbb75348d8ac76cf1a7d5afcbdb7aeb3..acfc3d535ac5eca6b075889155ca20aee40c2f8b 100644 --- a/QLabel/README.md +++ b/QLabel/README.md @@ -1,5 +1,12 @@ # QLabel +- 目录 + - [图片加载显示](#1图片加载显示) + - [图片旋转](#2图片旋转) + - [仿网页图片错位显示](#3仿网页图片错位显示) + - [显示.9格式图片(气泡)](#4显示9格式图片气泡) + - [圆形图片](#5圆形图片) + ## 1、图片加载显示 [运行 ShowImage.py](ShowImage.py) diff --git a/QLabel/ShowImage.py b/QLabel/ShowImage.py index ac3fb9f6e82dc23fc874120d1ee5ef2bf8e1a951..41ca08ead5aa359faa973babd02f9dae7a212ef5 100644 --- a/QLabel/ShowImage.py +++ b/QLabel/ShowImage.py @@ -1,29 +1,28 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -''' +""" Created on 2017年12月23日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: ShowImage @description: -''' +""" import sys -from PyQt5.QtCore import QResource -from PyQt5.QtGui import QPixmap, QMovie -from PyQt5.QtWidgets import QWidget, QApplication, QHBoxLayout, QLabel +try: + from PyQt5.QtCore import QResource + from PyQt5.QtGui import QPixmap, QMovie + from PyQt5.QtWidgets import QWidget, QApplication, QHBoxLayout, QLabel +except ImportError: + from PySide2.QtCore import QResource + from PySide2.QtGui import QPixmap, QMovie + from PySide2.QtWidgets import QWidget, QApplication, QHBoxLayout, QLabel -from Lib import res_rc # @UnresolvedImport @UnusedImport from Lib.xpmres import image_head # @UnresolvedImport -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]" -__Version__ = "Version 1.0" - - class ImageView(QWidget): def __init__(self, *args, **kwargs): diff --git a/QListView/CustomWidgetItem.py b/QListView/CustomWidgetItem.py index 6a8a1be81bf261bc1ed64c0ee125ac6f5c746022..3bffcac13242fc713509cbd40067d91438905c1b 100644 --- a/QListView/CustomWidgetItem.py +++ b/QListView/CustomWidgetItem.py @@ -1,22 +1,26 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from PyQt5.QtCore import QSize -from PyQt5.QtGui import QStandardItemModel, QStandardItem -from PyQt5.QtWidgets import QListView, QWidget, QHBoxLayout, QLineEdit,\ - QPushButton -# Created on 2018年8月4日 -# author: Irony -# site: https://pyqt5.com , https://github.com/892768447 -# email: 892768447@qq.com -# file: QListView.显示自定义Widget -# description: -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 +""" +Created on 2018年8月4日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: QListView.显示自定义Widget +@description: +""" + +try: + from PyQt5.QtCore import QSize + from PyQt5.QtGui import QStandardItemModel, QStandardItem + from PyQt5.QtWidgets import QListView, QWidget, QHBoxLayout, QLineEdit, \ + QPushButton, QApplication +except ImportError: + from PySide2.QtCore import QSize + from PySide2.QtGui import QStandardItemModel, QStandardItem + from PySide2.QtWidgets import QListView, QWidget, QHBoxLayout, QLineEdit, \ + QPushButton, QApplication class CustomWidget(QWidget): @@ -56,7 +60,7 @@ class ListView(QListView): if __name__ == '__main__': import sys - from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) w = ListView() w.show() diff --git a/QListView/CustomWidgetSortItem.py b/QListView/CustomWidgetSortItem.py index 09fe903614e0282c476caee9a012f44c2ac4fcd9..3a68ef20f82ab66e9645e4393ab3504c380c00bf 100644 --- a/QListView/CustomWidgetSortItem.py +++ b/QListView/CustomWidgetSortItem.py @@ -1,26 +1,29 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from random import choice, randint -import string -from time import time -from PyQt5.QtCore import QSortFilterProxyModel, Qt, QSize -from PyQt5.QtGui import QStandardItem, QStandardItemModel -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QListView,\ - QHBoxLayout, QLineEdit +""" +Created on 2018年8月4日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: QListView.显示自定义Widget并排序 +@description: +""" +import string +from random import choice, randint +from time import time -# Created on 2018年8月4日 -# author: Irony -# site: https://pyqt5.com , https://github.com/892768447 -# email: 892768447@qq.com -# file: QListView.显示自定义Widget并排序 -# description: -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 +try: + from PyQt5.QtCore import QSortFilterProxyModel, Qt, QSize + from PyQt5.QtGui import QStandardItem, QStandardItemModel + from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QListView, \ + QHBoxLayout, QLineEdit, QApplication +except ImportError: + from PySide2.QtCore import QSortFilterProxyModel, Qt, QSize + from PySide2.QtGui import QStandardItem, QStandardItemModel + from PySide2.QtWidgets import QWidget, QVBoxLayout, QPushButton, QListView, \ + QHBoxLayout, QLineEdit, QApplication def randomChar(y): @@ -55,11 +58,11 @@ class SortFilterProxyModel(QSortFilterProxyModel): leftData = leftData.split('-')[-1] rightData = rightData.split('-')[-1] return leftData < rightData -# elif self.sortOrder() == Qt.AscendingOrder: -# #按照名字升序排序 -# leftData = leftData.split('-')[0] -# rightData = rightData.split('-')[0] -# return leftData < rightData + # elif self.sortOrder() == Qt.AscendingOrder: + # #按照名字升序排序 + # leftData = leftData.split('-')[0] + # rightData = rightData.split('-')[0] + # return leftData < rightData return super(SortFilterProxyModel, self).lessThan(source_left, source_right) @@ -89,7 +92,7 @@ class Window(QWidget): times = time() + randint(0, 30) # 当前时间随机+ value = '{}-{}'.format(name, times) # 内容用-分开 item = QStandardItem(value) -# item.setData(value, Qt.UserRole + 2) + # item.setData(value, Qt.UserRole + 2) self.dmodel.appendRow(item) # 索引 index = self.fmodel.mapFromSource(item.index()) @@ -109,7 +112,7 @@ class Window(QWidget): if __name__ == '__main__': import sys - from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) w = Window() w.show() diff --git a/QListView/ImageView.py b/QListView/ImageView.py new file mode 100644 index 0000000000000000000000000000000000000000..b030806c14dd16017263a0b2e94e910083e8f1e2 --- /dev/null +++ b/QListView/ImageView.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2021/4/15 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: ImageView +@description: +""" +import os + +try: + from PyQt5.QtCore import QPointF, Qt, QRectF, QSizeF + from PyQt5.QtGui import QStandardItem, QStandardItemModel, QPainter, QColor, QImage, QPixmap + from PyQt5.QtWidgets import QApplication, QListView, QGraphicsView, QGraphicsPixmapItem, QGraphicsScene +except ImportError: + from PySide2.QtCore import QPointF, Qt, QRectF, QSizeF + from PySide2.QtGui import QStandardItem, QStandardItemModel, QPainter, QColor, QImage, QPixmap + from PySide2.QtWidgets import QApplication, QListView, QGraphicsView, QGraphicsPixmapItem, QGraphicsScene + +ScrollPixel = 40 + + +class BigImageView(QGraphicsView): + """图片查看控件""" + + def __init__(self, *args, **kwargs): + image = kwargs.pop('image', None) + background = kwargs.pop('background', None) + super(BigImageView, self).__init__(*args, **kwargs) + self.setCursor(Qt.OpenHandCursor) + self.setBackground(background) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setRenderHints(QPainter.Antialiasing | QPainter.HighQualityAntialiasing | + QPainter.SmoothPixmapTransform) + self.setCacheMode(self.CacheBackground) + self.setViewportUpdateMode(self.SmartViewportUpdate) + self._item = QGraphicsPixmapItem() # 放置图像 + self._item.setFlags(QGraphicsPixmapItem.ItemIsFocusable | + QGraphicsPixmapItem.ItemIsMovable) + self._scene = QGraphicsScene(self) # 场景 + self.setScene(self._scene) + self._scene.addItem(self._item) + rect = QApplication.instance().desktop().availableGeometry() + self.resize(int(rect.width() * 2 / 3), int(rect.height() * 2 / 3)) + + self.pixmap = None + self._delta = 0.1 # 缩放 + self.setPixmap(image) + + def setBackground(self, color): + """设置背景颜色 + :param color: 背景颜色 + :type color: QColor or str or GlobalColor + """ + if isinstance(color, QColor): + self.setBackgroundBrush(color) + elif isinstance(color, (str, Qt.GlobalColor)): + color = QColor(color) + if color.isValid(): + self.setBackgroundBrush(color) + + def setPixmap(self, pixmap, fitIn=True): + """加载图片 + :param pixmap: 图片或者图片路径 + :param fitIn: 是否适应 + :type pixmap: QPixmap or QImage or str + :type fitIn: bool + """ + if isinstance(pixmap, QPixmap): + self.pixmap = pixmap + elif isinstance(pixmap, QImage): + self.pixmap = QPixmap.fromImage(pixmap) + elif isinstance(pixmap, str) and os.path.isfile(pixmap): + self.pixmap = QPixmap(pixmap) + else: + return + self._item.setPixmap(self.pixmap) + self._item.update() + self.setSceneDims() + if fitIn: + self.fitInView(QRectF(self._item.pos(), QSizeF( + self.pixmap.size())), Qt.KeepAspectRatio) + self.update() + + def setSceneDims(self): + if not self.pixmap: + return + self.setSceneRect(QRectF(QPointF(0, 0), QPointF(self.pixmap.width(), self.pixmap.height()))) + + def fitInView(self, rect, flags=Qt.IgnoreAspectRatio): + """剧中适应 + :param rect: 矩形范围 + :param flags: + :return: + """ + if not self.scene() or rect.isNull(): + return + unity = self.transform().mapRect(QRectF(0, 0, 1, 1)) + self.scale(1 / unity.width(), 1 / unity.height()) + viewRect = self.viewport().rect() + sceneRect = self.transform().mapRect(rect) + x_ratio = viewRect.width() / sceneRect.width() + y_ratio = viewRect.height() / sceneRect.height() + if flags == Qt.KeepAspectRatio: + x_ratio = y_ratio = min(x_ratio, y_ratio) + elif flags == Qt.KeepAspectRatioByExpanding: + x_ratio = y_ratio = max(x_ratio, y_ratio) + self.scale(x_ratio, y_ratio) + self.centerOn(rect.center()) + + def wheelEvent(self, event): + if event.angleDelta().y() > 0: + self.zoomIn() + else: + self.zoomOut() + + def zoomIn(self): + """放大""" + self.zoom(1 + self._delta) + + def zoomOut(self): + """缩小""" + self.zoom(1 - self._delta) + + def zoom(self, factor): + """缩放 + :param factor: 缩放的比例因子 + """ + _factor = self.transform().scale( + factor, factor).mapRect(QRectF(0, 0, 1, 1)).width() + if _factor < 0.07 or _factor > 100: + # 防止过大过小 + return + self.scale(factor, factor) + + +class ImageView(QListView): + + def __init__(self, *args, **kwargs): + super(ImageView, self).__init__(*args, **kwargs) + self.setFrameShape(self.NoFrame) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setEditTriggers(self.NoEditTriggers) + self.setDropIndicatorShown(True) + self.setDragDropMode(self.DragDrop) + self.setDefaultDropAction(Qt.IgnoreAction) + self.setSelectionMode(self.ExtendedSelection) + self.setVerticalScrollMode(self.ScrollPerPixel) + self.setHorizontalScrollMode(self.ScrollPerPixel) + self.setFlow(self.LeftToRight) + self.setWrapping(True) + self.setResizeMode(self.Adjust) + self.setSpacing(6) + self.setViewMode(self.IconMode) + self.setWordWrap(True) + self.setSelectionRectVisible(True) + self.setContextMenuPolicy(Qt.CustomContextMenu) + # 解决拖动到顶部或者底部自动滚动 + self.setAutoScrollMargin(150) + self.verticalScrollBar().setSingleStep(ScrollPixel) + # 设置model + self.dmodel = QStandardItemModel(self) + self.setModel(self.dmodel) + + # 大图控件 + self.bigView = BigImageView(background='#323232') + + def addItem(self, image): + if isinstance(image, str): + image = QPixmap(image) + # 添加一个item + item = QStandardItem() + # 记录原始图片 + item.setData(image, Qt.UserRole + 1) # 用于双击的时候取出来 + # 缩放成小图并显示 + item.setData(image.scaled(60, 60, Qt.IgnoreAspectRatio, Qt.SmoothTransformation), Qt.DecorationRole) + # 添加item到界面中 + self.dmodel.appendRow(item) + + def count(self): + return self.dmodel.rowCount() + + def setCurrentRow(self, row): + self.setCurrentIndex(self.dmodel.index(row, 0)) + + def currentRow(self): + return self.currentIndex().row() + + def updateGeometries(self): + # 一次滑动20px + super(ImageView, self).updateGeometries() + self.verticalScrollBar().setSingleStep(ScrollPixel) + + def closeEvent(self, event): + # 关闭预览窗口 + self.bigView.close() + super(ImageView, self).closeEvent(event) + + def wheelEvent(self, event): + # 修复滑动bug + if self.flow() == QListView.LeftToRight: + bar = self.horizontalScrollBar() + value = ScrollPixel if event.angleDelta().y() < 0 else (0 - ScrollPixel) + bar.setSliderPosition(bar.value() + value) + else: + super(ImageView, self).wheelEvent(event) + + def mouseDoubleClickEvent(self, event): + # 列表双击,如果有item则进入item处理流程,否则调用打开图片功能 + index = self.indexAt(event.pos()) + if index and index.isValid(): + item = self.dmodel.itemFromIndex(index) + if item: + # 取出原图用来新窗口显示 + image = item.data(Qt.UserRole + 1) + self.bigView.setPixmap(image) + self.bigView.show() + return + super(ImageView, self).mouseDoubleClickEvent(event) + + +if __name__ == '__main__': + import sys + import cgitb + + cgitb.enable(format='text') + + app = QApplication(sys.argv) + w = ImageView() + w.show() + + # 添加模拟图片 + for i in range(3): + for name in os.listdir('ScreenShot'): + w.addItem(os.path.join('ScreenShot', name)) + sys.exit(app.exec_()) diff --git a/QListView/README.md b/QListView/README.md index 74d7644fab32aa29f453a25e6a130baef56c7653..95a16ef94b466be79c72e03d851edc236680eddf 100644 --- a/QListView/README.md +++ b/QListView/README.md @@ -1,5 +1,10 @@ # QListView +- 目录 + - [显示自定义Widget](#1显示自定义Widget) + - [显示自定义Widget并排序](#2显示自定义Widget并排序) + - [自定义角色排序](#3自定义角色排序) + ## 1、显示自定义Widget [运行 CustomWidgetItem.py](CustomWidgetItem.py) diff --git a/QListView/SortItemByRole.py b/QListView/SortItemByRole.py index 269bd6f876694b0f09bcf834015dbed8b4c3bb05..d4ccd876945878126e6831e9cf0fcb57afac982a 100644 --- a/QListView/SortItemByRole.py +++ b/QListView/SortItemByRole.py @@ -4,23 +4,21 @@ """ Created on 2018年12月27日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: QListView.SortItemByRole @description: """ from random import choice -from PyQt5.QtCore import QSortFilterProxyModel, Qt -from PyQt5.QtGui import QStandardItem, QStandardItemModel -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QListView, QPushButton - - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" +try: + from PyQt5.QtCore import QSortFilterProxyModel, Qt + from PyQt5.QtGui import QStandardItem, QStandardItemModel + from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QListView, QPushButton +except ImportError: + from PySide2.QtCore import QSortFilterProxyModel, Qt + from PySide2.QtGui import QStandardItem, QStandardItemModel + from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout, QListView, QPushButton class SortFilterProxyModel(QSortFilterProxyModel): @@ -72,8 +70,8 @@ IndexDict = { 4: '清', } -IdRole = Qt.UserRole + 1 # 用于恢复排序 -ClassifyRole = Qt.UserRole + 2 # 用于按照分类序号排序 +IdRole = Qt.UserRole + 1 # 用于恢复排序 +ClassifyRole = Qt.UserRole + 2 # 用于按照分类序号排序 class Window(QWidget): @@ -96,8 +94,8 @@ class Window(QWidget): def restoreSort(self): # 恢复默认排序 - self.fmodel.setSortRole(IdRole) # 必须设置排序角色为ID - self.fmodel.sort(0) # 排序第一列按照ID升序 + self.fmodel.setSortRole(IdRole) # 必须设置排序角色为ID + self.fmodel.sort(0) # 排序第一列按照ID升序 def sortByClassify(self): self.fmodel.setSortIndex(NameDict.get( @@ -141,8 +139,9 @@ class Window(QWidget): if __name__ == '__main__': import sys import cgitb - sys.excepthook = cgitb.enable(1, None, 5, '') - from PyQt5.QtWidgets import QApplication + + cgitb.enable(format='text') + app = QApplication(sys.argv) w = Window() w.show() diff --git a/QListWidget/Data/CoverItemWidget.ui b/QListWidget/Data/CoverItemWidget.ui new file mode 100644 index 0000000000000000000000000000000000000000..2f245e1344895af124b9ed3ea8b387c232077a79 --- /dev/null +++ b/QListWidget/Data/CoverItemWidget.ui @@ -0,0 +1,85 @@ + + + CoverItemWidget + + + + 200 + 256 + + + + + 200 + 256 + + + + + + + + 12 + + + 10 + + + 10 + + + 10 + + + 10 + + + + + + 180 + 180 + + + + + + + true + + + Qt::AlignCenter + + + + + + + + 10 + + + + + + + + + + + + + + + + + + + CoverLabel + QLabel +
.CoverLabel
+
+
+ + +
diff --git a/QListWidget/Data/CoverLabel.ui b/QListWidget/Data/CoverLabel.ui new file mode 100644 index 0000000000000000000000000000000000000000..2e7e037880e23bace4731623a4f1c25620fe267b --- /dev/null +++ b/QListWidget/Data/CoverLabel.ui @@ -0,0 +1,109 @@ + + + CoverLabel + + + + 0 + 0 + 180 + 180 + + + + PointingHandCursor + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 20 + 135 + + + + + + + + #widgetBottom { + background-color: rgba(0, 0, 0, 150); +} + + + + + + + 16 + 16 + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 16 + 16 + + + + + + + + + + + + + + + diff --git a/QListWidget/Data/Svg_icon_headset_sm.svg b/QListWidget/Data/Svg_icon_headset_sm.svg new file mode 100644 index 0000000000000000000000000000000000000000..98a1a3591d4f0741b27d27862023bc75bddf4a8d --- /dev/null +++ b/QListWidget/Data/Svg_icon_headset_sm.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/QListWidget/Data/Svg_icon_loading.svg b/QListWidget/Data/Svg_icon_loading.svg new file mode 100644 index 0000000000000000000000000000000000000000..0b00b7993dc84f2a6221b942d35dc62421e70ac4 --- /dev/null +++ b/QListWidget/Data/Svg_icon_loading.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/QListWidget/Data/Svg_icon_play_sm.svg b/QListWidget/Data/Svg_icon_play_sm.svg new file mode 100644 index 0000000000000000000000000000000000000000..08a47f20e76885d4e77b0f31221ff4d9e8bacafd --- /dev/null +++ b/QListWidget/Data/Svg_icon_play_sm.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/QListWidget/DeleteCustomItem.py b/QListWidget/DeleteCustomItem.py index a2d493304491e8633507d586b024883aaa92c658..ab8516e06bc10bb2cfd97a97137dc332c5542029 100644 --- a/QListWidget/DeleteCustomItem.py +++ b/QListWidget/DeleteCustomItem.py @@ -4,25 +4,23 @@ """ Created on 2018年11月4日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: 删除Item @description: """ -from PyQt5.QtCore import QSize, pyqtSignal -from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLineEdit, QPushButton,\ - QListWidgetItem, QVBoxLayout, QListWidget - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 +try: + from PyQt5.QtCore import QSize, pyqtSignal + from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLineEdit, QPushButton, \ + QListWidgetItem, QVBoxLayout, QListWidget, QApplication +except ImportError: + from PySide2.QtCore import QSize, Signal as pyqtSignal + from PySide2.QtWidgets import QWidget, QHBoxLayout, QLineEdit, QPushButton, \ + QListWidgetItem, QVBoxLayout, QListWidget, QApplication class ItemWidget(QWidget): - itemDeleted = pyqtSignal(QListWidgetItem) def __init__(self, text, item, *args, **kwargs): @@ -91,8 +89,9 @@ class Window(QWidget): if __name__ == '__main__': import sys import cgitb - sys.excepthook = cgitb.enable(1, None, 5, 'text') - from PyQt5.QtWidgets import QApplication + + cgitb.enable(format='text') + app = QApplication(sys.argv) w = Window() w.show() diff --git a/QListWidget/DragDrop.py b/QListWidget/DragDrop.py index dcc8818c9368516af568e14636bce1321f13bde8..0df4479e16c06dc115ce826737577d6c1bcda2d8 100644 --- a/QListWidget/DragDrop.py +++ b/QListWidget/DragDrop.py @@ -4,21 +4,20 @@ """ Created on 2018年9月14日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: DragListWidget @description: """ -from PyQt5.QtCore import Qt, QSize, QRect, QPoint -from PyQt5.QtGui import QColor, QPixmap, QDrag, QPainter, QCursor -from PyQt5.QtWidgets import QListWidget, QListWidgetItem, QLabel, QRubberBand - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" +try: + from PyQt5.QtCore import Qt, QSize, QRect, QPoint + from PyQt5.QtGui import QColor, QPixmap, QDrag, QPainter, QCursor + from PyQt5.QtWidgets import QListWidget, QListWidgetItem, QLabel, QRubberBand, QApplication +except ImportError: + from PySide2.QtCore import Qt, QSize, QRect, QPoint + from PySide2.QtGui import QColor, QPixmap, QDrag, QPainter, QCursor + from PySide2.QtWidgets import QListWidget, QListWidgetItem, QLabel, QRubberBand, QApplication class DropListWidget(QListWidget): @@ -162,7 +161,7 @@ class DragListWidget(QListWidget): if __name__ == '__main__': import sys - from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) app.setStyleSheet("""QListWidget { outline: 0px; diff --git a/QListWidget/FoldWidget.py b/QListWidget/FoldWidget.py new file mode 100644 index 0000000000000000000000000000000000000000..7f75a8d800f7774c0abc8368f69661c54ad5ee5b --- /dev/null +++ b/QListWidget/FoldWidget.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2019年5月27日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: FoldWidget +@description: 自定义item折叠控件仿QTreeWidget +""" + +from PyQt5.QtCore import QSize +from PyQt5.QtWidgets import QWidget, QPushButton, QFormLayout, \ + QLineEdit, QListWidget, QListWidgetItem, QCheckBox + + +class CustomWidget(QWidget): + + def __init__(self, item, *args, **kwargs): + super(CustomWidget, self).__init__(*args, **kwargs) + self.oldSize = None + self.item = item + layout = QFormLayout(self) + layout.addRow('我是label', QLineEdit(self)) + layout.addRow('点击', QCheckBox( + '隐藏下面的按钮', self, toggled=self.hideChild)) + self.button = QPushButton('我是被隐藏的', self) + layout.addRow(self.button) + + def hideChild(self, v): + self.button.setVisible(not v) + # 这里很重要 当隐藏内部子控件时 需要重新计算高度 + self.adjustSize() + + def resizeEvent(self, event): + # 解决item的高度问题 + super(CustomWidget, self).resizeEvent(event) + self.item.setSizeHint(QSize(self.minimumWidth(), self.height())) + + +class CustomButton(QPushButton): + # 按钮作为开关 + + def __init__(self, item, *args, **kwargs): + super(CustomButton, self).__init__(*args, **kwargs) + self.item = item + self.setCheckable(True) # 设置可选中 + + def resizeEvent(self, event): + # 解决item的高度问题 + super(CustomButton, self).resizeEvent(event) + self.item.setSizeHint(QSize(self.minimumWidth(), self.height())) + + +class Window(QListWidget): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + + for _ in range(3): + # 开关 + item = QListWidgetItem(self) + btn = CustomButton(item, '折叠', self, objectName='testBtn') + self.setItemWidget(item, btn) + + # 被折叠控件 + item = QListWidgetItem(self) + # 通过按钮的选中来隐藏下面的item + btn.toggled.connect(item.setHidden) + self.setItemWidget(item, CustomWidget(item, self)) + + +if __name__ == '__main__': + import sys + import cgitb + + cgitb.enable(format='text') + from PyQt5.QtWidgets import QApplication + + app = QApplication(sys.argv) + # 通过qss改变按钮的高度 + app.setStyleSheet('#testBtn{min-height:40px;}') + w = Window() + w.show() + sys.exit(app.exec_()) diff --git a/QListWidget/HotPlaylist.py b/QListWidget/HotPlaylist.py index bcba0d65a5d4ba08431f2c51516d1a1ca72749e9..e457b5cbc78b9567984b7ca8e224ba713cdeb1d3 100644 --- a/QListWidget/HotPlaylist.py +++ b/QListWidget/HotPlaylist.py @@ -1,195 +1,45 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- - -''' -Created on 2018年2月4日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +""" +Created on 2023/02/22 +@author: Irony +@site: https://pyqt.site https://github.com/PyQt5 @email: 892768447@qq.com -@file: TencentMovieHotPlay_ListWidget -@description: -''' +@file: HotPlaylist.py +@description: +""" + import os import sys -import webbrowser - -from PyQt5.QtCore import QSize, Qt, QUrl, QTimer -from PyQt5.QtGui import QPainter, QFont, QLinearGradient, QGradient, QColor,\ - QBrush, QPaintEvent, QPixmap -from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest -from PyQt5.QtSvg import QSvgWidget -from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QLabel,\ - QHBoxLayout, QSpacerItem, QSizePolicy, QAbstractSlider,\ - QListWidget, QListWidgetItem +from Lib.CoverItemWidget import CoverItemWidget from lxml.etree import HTML # @UnresolvedImport +try: + from PyQt5.QtCore import QTimer, QUrl + from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest + from PyQt5.QtSvg import QSvgWidget + from PyQt5.QtWidgets import (QAbstractSlider, QApplication, QListWidget, + QListWidgetItem) +except ImportError: + from PySide2.QtCore import QTimer, QUrl + from PySide2.QtNetwork import QNetworkAccessManager, QNetworkRequest + from PySide2.QtSvg import QSvgWidget + from PySide2.QtWidgets import (QAbstractSlider, QApplication, QListWidget, + QListWidgetItem) -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" -__Version__ = "Version 1.0" +# offset=0,35,70,105 +Url = "https://music.163.com/discover/playlist/?order=hot&cat=%E5%85%A8%E9%83%A8&limit=35&offset={0}" -# offset=0,30,60,90 -Url = "http://v.qq.com/x/list/movie?pay=-1&offset={0}" +Agent = b"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.50" -# 播放量图标 -Svg_icon_play_sm = ''' - - -'''.encode() +Referer = b"https://music.163.com" -Svg_icon_loading = ''' - - - - - - - - - - - - - - - - - -'''.encode() - -# 主演 +# 作者 Actor = '''{title} ''' -class CoverLabel(QLabel): - - def __init__(self, cover_path, cover_title, video_url, *args, **kwargs): - super(CoverLabel, self).__init__(*args, **kwargs) -# super(CoverLabel, self).__init__( -# ''.format(os.path.abspath(cover_path)), *args, **kwargs) - self.setCursor(Qt.PointingHandCursor) - self.setScaledContents(True) - self.setMinimumSize(220, 308) - self.setMaximumSize(220, 308) - self.cover_path = cover_path - self.cover_title = cover_title - self.video_url = video_url - self.setPixmap(QPixmap(cover_path)) - - def setCoverPath(self, path): - self.cover_path = path - - def mouseReleaseEvent(self, event): - super(CoverLabel, self).mouseReleaseEvent(event) - webbrowser.open_new_tab(self.video_url) - - def paintEvent(self, event): - super(CoverLabel, self).paintEvent(event) - if hasattr(self, "cover_title") and self.cover_title != "": - # 底部绘制文字 - painter = QPainter(self) - rect = self.rect() - # 粗略字体高度 - painter.save() - fheight = self.fontMetrics().height() - # 底部矩形框背景渐变颜色 - bottomRectColor = QLinearGradient( - rect.width() / 2, rect.height() - 24 - fheight, - rect.width() / 2, rect.height()) - bottomRectColor.setSpread(QGradient.PadSpread) - bottomRectColor.setColorAt(0, QColor(255, 255, 255, 70)) - bottomRectColor.setColorAt(1, QColor(0, 0, 0, 50)) - # 画半透明渐变矩形框 - painter.setPen(Qt.NoPen) - painter.setBrush(QBrush(bottomRectColor)) - painter.drawRect(rect.x(), rect.height() - 24 - - fheight, rect.width(), 24 + fheight) - painter.restore() - # 距离底部一定高度画文字 - font = self.font() or QFont() - font.setPointSize(8) - painter.setFont(font) - painter.setPen(Qt.white) - rect.setHeight(rect.height() - 12) # 底部减去一定高度 - painter.drawText(rect, Qt.AlignHCenter | - Qt.AlignBottom, self.cover_title) - - -class ItemWidget(QWidget): - - def __init__(self, cover_path, figure_info, figure_title, - figure_score, figure_desc, figure_count, video_url, cover_url, img_path, manager, *args, **kwargs): - super(ItemWidget, self).__init__(*args, **kwargs) - self.setMaximumSize(220, 420) - self.setMaximumSize(220, 420) - self.img_path = img_path - self.cover_url = cover_url - self._manager = manager - layout = QVBoxLayout(self) - layout.setContentsMargins(10, 20, 10, 0) - # 图片label - self.clabel = CoverLabel(cover_path, figure_info, video_url, self) - layout.addWidget(self.clabel) - - # 片名和分数 - flayout = QHBoxLayout() - flayout.addWidget(QLabel(figure_title, self)) - flayout.addItem(QSpacerItem( - 20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) - flayout.addWidget(QLabel(figure_score, self, styleSheet="color: red;")) - layout.addLayout(flayout) - - # 主演 - layout.addWidget( - QLabel(figure_desc, self, styleSheet="color: #999999;", openExternalLinks=True)) - - # 播放量 - blayout = QHBoxLayout() - count_icon = QSvgWidget(self) - count_icon.setMaximumSize(16, 16) - count_icon.load(Svg_icon_play_sm) - blayout.addWidget(count_icon) - blayout.addWidget( - QLabel(figure_count, self, styleSheet="color: #999999;")) - layout.addLayout(blayout) - - def setCover(self, path): - self.clabel.setCoverPath(path) - self.clabel.setPixmap(QPixmap(path)) -# self.clabel.setText(''.format(os.path.abspath(path))) - - def sizeHint(self): - # 每个item控件的大小 - return QSize(220, 420) - - def event(self, event): - if isinstance(event, QPaintEvent): - if event.rect().height() > 20 and hasattr(self, "clabel"): - if self.clabel.cover_path.find("pic_v.png") > -1: # 封面未加载 - # print("start download img:", self.cover_url) - req = QNetworkRequest(QUrl(self.cover_url)) - # 设置两个自定义属性方便后期reply中处理 - req.setAttribute(QNetworkRequest.User + 1, self) - req.setAttribute(QNetworkRequest.User + 2, self.img_path) - self._manager.get(req) # 调用父窗口中的下载器下载 - return super(ItemWidget, self).event(event) - - class Window(QListWidget): - Page = 0 def __init__(self, *args, **kwargs): @@ -204,16 +54,18 @@ class Window(QListWidget): # 连接竖着的滚动条滚动事件 self.verticalScrollBar().actionTriggered.connect(self.onActionTriggered) # 进度条 - self.loadWidget = QSvgWidget( - self, minimumHeight=120, minimumWidth=120, visible=False) - self.loadWidget.load(Svg_icon_loading) + self.loadWidget = QSvgWidget(self, + minimumHeight=120, + minimumWidth=120, + visible=False) + self.loadWidget.load('Data/Svg_icon_loading.svg') # 异步网络下载管理器 self._manager = QNetworkAccessManager(self) self._manager.finished.connect(self.onFinished) def load(self): - if self.Page == -1: + if self.Page == -1 or self.Page > 10: return self._loadStart = True self.loadWidget.setVisible(True) @@ -221,9 +73,11 @@ class Window(QListWidget): QTimer.singleShot(1000, self._load) def _load(self): - print("load url:", Url.format(self.Page * 30)) - url = QUrl(Url.format(self.Page * 30)) - self._manager.get(QNetworkRequest(url)) + print("load url:", Url.format(self.Page * 35)) + url = QUrl(Url.format(self.Page * 35)) + req = QNetworkRequest(url) + req.setRawHeader(b"User-Agent", Agent) + self._manager.get(req) def onFinished(self, reply): # 请求完成后会调用该函数 @@ -244,11 +98,13 @@ class Window(QListWidget): self.loadWidget.setVisible(False) def _parseHtml(self, html): + # print(html) # encoding = chardet.detect(html) or {} # html = html.decode(encoding.get("encoding","utf-8")) html = HTML(html) # 查找所有的li list_item - lis = html.xpath("//li[@class='list_item']") + lis = html.xpath("//ul[@id='m-pl-container']/li") + # print(lis) if not lis: self.Page = -1 # 后面没有页面了 return @@ -257,28 +113,30 @@ class Window(QListWidget): def _makeItem(self, lis): for li in lis: - a = li.find("a") - video_url = a.get("href") # 视频播放地址 - img = a.find("img") - cover_url = "http:" + img.get("r-lazyload") # 封面图片 - figure_title = img.get("alt") # 电影名 - figure_info = a.find("div/span") - figure_info = "" if figure_info is None else figure_info.text # 影片信息 - figure_score = "".join(li.xpath(".//em/text()")) # 评分 - # 主演 - figure_desc = "主演:" + \ - "".join([Actor.format(**dict(fd.items())) - for fd in li.xpath(".//div[@class='figure_desc']/a")]) + a = li.find('.//div/a') + play_url = "https://music.163.com" + a.get("href") # 歌单播放地址 + img = li.find(".//div/img") + cover_url = img.get("src") # 封面图片 + playlist_title = a.get("title") # 歌单名 + # 歌手 + author_info = li.xpath(".//p[2]/a")[0] + playlist_author = "".format( + Actor.format(href="https://music.163.com" + + author_info.get("href"), + title=author_info.get("title"))) # 播放数 - figure_count = ( - li.xpath(".//div[@class='figure_count']/span/text()") or [""])[0] + play_count = (li.xpath(".//div/div/span[2]/text()") or [""])[0] path = "cache/{0}.jpg".format( - os.path.splitext(os.path.basename(video_url))[0]) + os.path.splitext(os.path.basename(cover_url).split('?')[0])[0]) cover_path = "Data/pic_v.png" if os.path.isfile(path): cover_path = path - iwidget = ItemWidget(cover_path, figure_info, figure_title, - figure_score, figure_desc, figure_count, video_url, cover_url, path, self._manager, self) + + # print(cover_path, playlist_title, + # playlist_author, play_count, play_url, cover_url, path) + iwidget = CoverItemWidget(self, manager=self._manager) + iwidget.init(cover_path, playlist_title, playlist_author, + play_count, play_url, cover_url, path) item = QListWidgetItem(self) item.setSizeHint(iwidget.sizeHint()) self.setItemWidget(item, iwidget) @@ -289,7 +147,8 @@ class Window(QListWidget): if action != QAbstractSlider.SliderMove or self._loadStart: return # 使用sliderPosition获取值可以同时满足鼠标滑动和拖动判断 - if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar().maximum(): + if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar( + ).maximum(): # 可以下一页了 self.load() @@ -298,9 +157,7 @@ class Window(QListWidget): self.loadWidget.setGeometry( int((self.width() - self.loadWidget.minimumWidth()) / 2), int((self.height() - self.loadWidget.minimumHeight()) / 2), - self.loadWidget.minimumWidth(), - self.loadWidget.minimumHeight() - ) + self.loadWidget.minimumWidth(), self.loadWidget.minimumHeight()) if __name__ == "__main__": diff --git a/QListWidget/Lib/CoverItemWidget.py b/QListWidget/Lib/CoverItemWidget.py new file mode 100644 index 0000000000000000000000000000000000000000..5ef3ff00225af2269e33bd3c3ea4c47ba54c2adf --- /dev/null +++ b/QListWidget/Lib/CoverItemWidget.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Created on 2023/02/22 +@author: Irony +@site: https://pyqt.site https://github.com/PyQt5 +@email: 892768447@qq.com +@file: CoverItemWidget.py +@description: +""" + +try: + from PyQt5.QtCore import QSize, QUrl + from PyQt5.QtGui import QPaintEvent, QPixmap + from PyQt5.QtNetwork import QNetworkRequest + from PyQt5.QtWidgets import QWidget +except ImportError: + from PySide2.QtCore import QSize, QUrl + from PySide2.QtGui import QPaintEvent, QPixmap + from PySide2.QtNetwork import QNetworkRequest + from PySide2.QtWidgets import QWidget + +from .Ui_CoverItemWidget import Ui_CoverItemWidget # @UnresolvedImport + + +class CoverItemWidget(QWidget, Ui_CoverItemWidget): + + def __init__(self, *args, **kwargs): + self._manager = kwargs.pop('manager', None) + super(CoverItemWidget, self).__init__(*args, **kwargs) + self.setupUi(self) + + def init(self, cover_path, playlist_title, playlist_author, play_count, + play_url, cover_url, img_path): + self.img_path = img_path + self.cover_url = cover_url + # 图片label + self.labelCover.init(cover_path, play_url, play_count) + + # 歌单 + self.labelTitle.setText(playlist_title) + + # 作者 + self.labelAuthor.setText(playlist_author) + + def setCover(self, path): + self.labelCover.setCoverPath(path) + self.labelCover.setPixmap(QPixmap(path)) + + def sizeHint(self): + # 每个item控件的大小 + return QSize(200, 256) + + def event(self, event): + if isinstance(event, QPaintEvent): + if event.rect().height() > 20 and hasattr(self, "labelCover"): + if self.labelCover.cover_path.find("pic_v.png") > -1: # 封面未加载 + # print("start download img:", self.cover_url) + req = QNetworkRequest(QUrl(self.cover_url)) + # 设置两个自定义属性方便后期reply中处理 + req.setAttribute(QNetworkRequest.User + 1, self) + req.setAttribute(QNetworkRequest.User + 2, self.img_path) + if self._manager: + self._manager.get(req) # 调用父窗口中的下载器下载 + return super(CoverItemWidget, self).event(event) diff --git a/QListWidget/Lib/CoverLabel.py b/QListWidget/Lib/CoverLabel.py new file mode 100644 index 0000000000000000000000000000000000000000..dbee15af4cf251de21d8d7d4194b18cb8a192bbc --- /dev/null +++ b/QListWidget/Lib/CoverLabel.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Created on 2023/02/22 +@author: Irony +@site: https://pyqt.site https://github.com/PyQt5 +@email: 892768447@qq.com +@file: CoverLabel.py +@description: +""" +import webbrowser + +try: + from PyQt5.QtGui import QPixmap + from PyQt5.QtWidgets import QLabel +except ImportError: + from PySide2.QtGui import QPixmap + from PySide2.QtWidgets import QLabel + +from .Ui_CoverLabel import Ui_CoverLabel # @UnresolvedImport + + +class CoverLabel(QLabel, Ui_CoverLabel): + + def __init__(self, *args, **kwargs): + super(CoverLabel, self).__init__(*args, **kwargs) + self.setupUi(self) + + def init(self, cover_path, play_url, play_count): + self.cover_path = cover_path + self.play_url = play_url + self.setPixmap(QPixmap(cover_path)) + self.labelHeadset.setPixmap(QPixmap('Data/Svg_icon_headset_sm.svg')) + self.labelPlay.setPixmap(QPixmap('Data/Svg_icon_play_sm.svg')) + self.labelCount.setStyleSheet('color: #999999;') + self.labelCount.setText(play_count) + + def setCoverPath(self, path): + self.cover_path = path + + def mouseReleaseEvent(self, event): + super(CoverLabel, self).mouseReleaseEvent(event) + webbrowser.open_new_tab(self.play_url) diff --git a/QListWidget/Lib/Ui_CoverItemWidget.py b/QListWidget/Lib/Ui_CoverItemWidget.py new file mode 100644 index 0000000000000000000000000000000000000000..c048f686e4662268e855850c42d7da8a3182c91a --- /dev/null +++ b/QListWidget/Lib/Ui_CoverItemWidget.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'CoverItemWidget.ui' +# +# Created by: PyQt5 UI code generator 5.15.2 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +try: + from PyQt5 import QtCore, QtGui, QtWidgets +except ImportError: + from PySide2 import QtCore, QtGui, QtWidgets + + +class Ui_CoverItemWidget(object): + def setupUi(self, CoverItemWidget): + CoverItemWidget.setObjectName("CoverItemWidget") + CoverItemWidget.setMinimumSize(QtCore.QSize(200, 256)) + CoverItemWidget.setMaximumSize(QtCore.QSize(200, 256)) + CoverItemWidget.setWindowTitle("") + self.verticalLayout = QtWidgets.QVBoxLayout(CoverItemWidget) + self.verticalLayout.setContentsMargins(10, 10, 10, 10) + self.verticalLayout.setSpacing(12) + self.verticalLayout.setObjectName("verticalLayout") + self.labelCover = CoverLabel(CoverItemWidget) + self.labelCover.setMinimumSize(QtCore.QSize(180, 180)) + self.labelCover.setText("") + self.labelCover.setScaledContents(True) + self.labelCover.setAlignment(QtCore.Qt.AlignCenter) + self.labelCover.setObjectName("labelCover") + self.verticalLayout.addWidget(self.labelCover) + self.labelTitle = QtWidgets.QLabel(CoverItemWidget) + font = QtGui.QFont() + font.setPointSize(10) + self.labelTitle.setFont(font) + self.labelTitle.setText("") + self.labelTitle.setObjectName("labelTitle") + self.verticalLayout.addWidget(self.labelTitle) + self.labelAuthor = QtWidgets.QLabel(CoverItemWidget) + self.labelAuthor.setText("") + self.labelAuthor.setObjectName("labelAuthor") + self.verticalLayout.addWidget(self.labelAuthor) + + self.retranslateUi(CoverItemWidget) + QtCore.QMetaObject.connectSlotsByName(CoverItemWidget) + + def retranslateUi(self, CoverItemWidget): + pass +from .CoverLabel import CoverLabel + +if __name__ == "__main__": + import sys + app = QtWidgets.QApplication(sys.argv) + CoverItemWidget = QtWidgets.QWidget() + ui = Ui_CoverItemWidget() + ui.setupUi(CoverItemWidget) + CoverItemWidget.show() + sys.exit(app.exec_()) diff --git a/QListWidget/Lib/Ui_CoverLabel.py b/QListWidget/Lib/Ui_CoverLabel.py new file mode 100644 index 0000000000000000000000000000000000000000..86e1526a548fbd165e68d695447c530166faaee1 --- /dev/null +++ b/QListWidget/Lib/Ui_CoverLabel.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'CoverLabel.ui' +# +# Created by: PyQt5 UI code generator 5.15.2 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +try: + from PyQt5 import QtCore, QtGui, QtWidgets +except ImportError: + from PySide2 import QtCore, QtGui, QtWidgets + + +class Ui_CoverLabel(object): + def setupUi(self, CoverLabel): + CoverLabel.setObjectName("CoverLabel") + CoverLabel.resize(180, 180) + CoverLabel.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) + CoverLabel.setWindowTitle("") + self.verticalLayout = QtWidgets.QVBoxLayout(CoverLabel) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setSpacing(0) + self.verticalLayout.setObjectName("verticalLayout") + spacerItem = QtWidgets.QSpacerItem(20, 135, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout.addItem(spacerItem) + self.widgetBottom = QtWidgets.QWidget(CoverLabel) + self.widgetBottom.setStyleSheet("#widgetBottom {\n" +" background-color: rgba(0, 0, 0, 150);\n" +"}") + self.widgetBottom.setObjectName("widgetBottom") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.widgetBottom) + self.horizontalLayout.setObjectName("horizontalLayout") + self.labelHeadset = QtWidgets.QLabel(self.widgetBottom) + self.labelHeadset.setMinimumSize(QtCore.QSize(16, 16)) + self.labelHeadset.setText("") + self.labelHeadset.setObjectName("labelHeadset") + self.horizontalLayout.addWidget(self.labelHeadset) + self.labelCount = QtWidgets.QLabel(self.widgetBottom) + self.labelCount.setText("") + self.labelCount.setObjectName("labelCount") + self.horizontalLayout.addWidget(self.labelCount) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem1) + self.labelPlay = QtWidgets.QLabel(self.widgetBottom) + self.labelPlay.setMinimumSize(QtCore.QSize(16, 16)) + self.labelPlay.setText("") + self.labelPlay.setObjectName("labelPlay") + self.horizontalLayout.addWidget(self.labelPlay) + self.verticalLayout.addWidget(self.widgetBottom) + + self.retranslateUi(CoverLabel) + QtCore.QMetaObject.connectSlotsByName(CoverLabel) + + def retranslateUi(self, CoverLabel): + pass + + +if __name__ == "__main__": + import sys + app = QtWidgets.QApplication(sys.argv) + CoverLabel = QtWidgets.QWidget() + ui = Ui_CoverLabel() + ui.setupUi(CoverLabel) + CoverLabel.show() + sys.exit(app.exec_()) diff --git a/QListWidget/Lib/__init__.py b/QListWidget/Lib/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/QListWidget/README.md b/QListWidget/README.md index 0a25cfae4899492195c0d5222c0385846801a4a2..69610a6f4b5adc142f263e05ef4fd240515488f7 100644 --- a/QListWidget/README.md +++ b/QListWidget/README.md @@ -1,5 +1,12 @@ # QListView +- 目录 + - [删除自定义Item](#1删除自定义Item) + - [自定义可拖拽Item](#2自定义可拖拽Item) + - [音乐热歌列表](#3音乐热歌列表) + - [仿折叠控件效果](#4仿折叠控件效果) + - [列表常用信号](#5列表常用信号) + ## 1、删除自定义Item [运行 DeleteCustomItem.py](DeleteCustomItem.py) @@ -15,7 +22,7 @@ ![CustomWidgetSortItem](ScreenShot/DragDrop.gif) -## 3、腾讯视频热播列表 +## 3、音乐热歌列表 [运行 HotPlaylist.py](HotPlaylist.py) 简单思路说明: @@ -36,4 +43,21 @@ 2. `setWrapping(True)` 3. `setResizeMode(QListWidget.Adjust)` -![HotPlaylist](ScreenShot/HotPlaylist.gif) \ No newline at end of file +![HotPlaylist](ScreenShot/HotPlaylist.gif) + +## 4、仿折叠控件效果 +[运行 FoldWidget.py](FoldWidget.py) + +1. 利用`QListWidget`设置Item的自定义控件 +2. `QListWidget`通过间隔设置`QPushButton`和`CustomWidget`来添加 +3. 绑定按钮的选中状态通过`setHidden`设置Item的隐藏和显示 +4. 自定义控件中尺寸发生变化后需要调用`adjustSize()`来同步 + +![FoldWidget](ScreenShot/FoldWidget.gif) + +## 5、列表常用信号 +[运行 SignalsExample.py](SignalsExample.py) + +根据官网文档 https://doc.qt.io/qt-5/qlistwidget.html#signals 中的信号介绍编写 + +![SignalsExample](ScreenShot/SignalsExample.gif) \ No newline at end of file diff --git a/QListWidget/ScreenShot/FoldWidget.gif b/QListWidget/ScreenShot/FoldWidget.gif new file mode 100644 index 0000000000000000000000000000000000000000..bf9a2ab06082c6b74a4c93c270c98f3ff5aa5980 Binary files /dev/null and b/QListWidget/ScreenShot/FoldWidget.gif differ diff --git a/QListWidget/ScreenShot/SignalsExample.gif b/QListWidget/ScreenShot/SignalsExample.gif new file mode 100644 index 0000000000000000000000000000000000000000..37357e61948e224beb27ee3a985bcef3848dba8a Binary files /dev/null and b/QListWidget/ScreenShot/SignalsExample.gif differ diff --git a/QListWidget/SignalsExample.py b/QListWidget/SignalsExample.py new file mode 100644 index 0000000000000000000000000000000000000000..38f20c2bf3159df53f5d4baaec78abd86f2cab45 --- /dev/null +++ b/QListWidget/SignalsExample.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2019年7月3日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: QListWidget.SignalsExample +@description: +""" + +try: + from PyQt5.QtCore import Qt + from PyQt5.QtGui import QColor + from PyQt5.QtWidgets import QWidget, QHBoxLayout, QListWidget, QPlainTextEdit, \ + QListWidgetItem, QAbstractItemView, QListView, QApplication +except ImportError: + from PySide2.QtCore import Qt + from PySide2.QtGui import QColor + from PySide2.QtWidgets import QWidget, QHBoxLayout, QListWidget, QPlainTextEdit, \ + QListWidgetItem, QAbstractItemView, QListView, QApplication + + +def formatColor(text, color): + return '{1}'.format(color.name(), text) + + +class Window(QWidget): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + layout = QHBoxLayout(self) + + self.listWidget = QListWidget(self) + self.listWidget.setAlternatingRowColors(True) + self.listWidget.setSelectionMode(QAbstractItemView.ExtendedSelection) + self.listWidget.setMovement(QListView.Free) + self.listWidget.setMouseTracking(True) # 用于itemEntered信号 + + self.resultView = QPlainTextEdit(self) + self.resultView.setReadOnly(True) + + layout.addWidget(self.listWidget) + layout.addWidget(self.resultView) + + self.initData() + self.initSignals() + + def initData(self): + # 初始化模拟数据 + for i in range(100): + item = QListWidgetItem('Item {0}'.format(i), self.listWidget) + if i % 3 == 0: + item.setFlags(item.flags() | Qt.ItemIsEditable) + + def initSignals(self): + # 初始化信号 + self.listWidget.currentItemChanged.connect(self.onCurrentItemChanged) + self.listWidget.currentRowChanged.connect(self.onCurrentRowChanged) + self.listWidget.currentTextChanged.connect(self.onCurrentTextChanged) + self.listWidget.itemActivated.connect(self.onItemActivated) + self.listWidget.itemChanged.connect(self.onItemChanged) + self.listWidget.itemClicked.connect(self.onItemClicked) + self.listWidget.itemDoubleClicked.connect(self.onItemDoubleClicked) + self.listWidget.itemEntered.connect(self.onItemEntered) + self.listWidget.itemPressed.connect(self.onItemPressed) + self.listWidget.itemSelectionChanged.connect( + self.onItemSelectionChanged) + + def onCurrentItemChanged(self, current, previous): + current = current.text() if current else '' + previous = previous.text() if previous else '' + self.resultView.appendHtml( + '{0}: [{1}] -> [{2}]'.format( + formatColor('currentItemChanged', QColor(Qt.red)), + current, previous)) + + def onCurrentRowChanged(self, currentRow): + self.resultView.appendHtml( + '{0}: {1}'.format( + formatColor('currentRowChanged', QColor(Qt.green)), + currentRow)) + + def onCurrentTextChanged(self, currentText): + self.resultView.appendHtml( + '{0}: {1}'.format( + formatColor('currentTextChanged', QColor(Qt.yellow)), currentText)) + + def onItemActivated(self, item): + self.resultView.appendHtml( + '{0}: {1}'.format( + formatColor('itemActivated', QColor(Qt.blue)), item.text())) + + def onItemChanged(self, item): + self.resultView.appendHtml( + '{0}: {1}'.format( + formatColor('itemChanged', QColor(Qt.cyan)), item.text())) + + def onItemClicked(self, item): + self.resultView.appendHtml( + '{0}: {1}'.format(formatColor('itemClicked', QColor(Qt.magenta)), item.text())) + + def onItemDoubleClicked(self, item): + self.resultView.appendHtml( + '{0}: {1}'.format(formatColor('itemDoubleClicked', QColor(Qt.darkGreen)), item.text())) + + def onItemEntered(self, item): + self.resultView.appendHtml( + '{0}: {1}'.format(formatColor('itemEntered', QColor(Qt.darkCyan)), item.text())) + + def onItemPressed(self, item): + print(item) + self.resultView.appendHtml( + '{0}: {1}'.format(formatColor('itemPressed', QColor(Qt.darkYellow)), item.text())) + + def onItemSelectionChanged(self): + self.resultView.appendPlainText('itemSelectionChanged') + + +if __name__ == '__main__': + import sys + + app = QApplication(sys.argv) + w = Window() + w.show() + sys.exit(app.exec_()) diff --git a/QMenu/MultiSelect.py b/QMenu/MultiSelect.py index 17bf9bd57240df2424dc7b9487e60968f764c636..31a78c8ae4c25a3b6630882486ba695091aaa949 100644 --- a/QMenu/MultiSelect.py +++ b/QMenu/MultiSelect.py @@ -4,20 +4,16 @@ """ Created on 2018年10月24日 @author: Irony -@site: https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: MultiSelect @description: """ -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton, QMenu,\ - QAction - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" +try: + from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QPushButton, QMenu, QAction +except ImportError: + from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QPushButton, QMenu, QAction class Window(QWidget): @@ -70,8 +66,9 @@ class Window(QWidget): if __name__ == '__main__': import sys import cgitb - sys.excepthook = cgitb.enable(1, None, 5, 'text') - from PyQt5.QtWidgets import QApplication + + cgitb.enable(format='text') + app = QApplication(sys.argv) w = Window() w.resize(400, 400) diff --git a/QMenu/QQMenu.py b/QMenu/QQMenu.py new file mode 100644 index 0000000000000000000000000000000000000000..a561eb4727080b9217718bb714497fe372dcd610 --- /dev/null +++ b/QMenu/QQMenu.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2021/4/7 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: QQMenu +@description: +""" +import string +from random import choice, randint + +try: + from PyQt5.QtCore import Qt + from PyQt5.QtGui import QPixmap, QPainter, QFont, QIcon + from PyQt5.QtWidgets import QLabel, QMenu, QApplication +except ImportError: + from PySide2.QtCore import Qt + from PySide2.QtGui import QPixmap, QPainter, QFont, QIcon + from PySide2.QtWidgets import QLabel, QMenu, QApplication + +Style = """ +QMenu { + /* 半透明效果 */ + background-color: rgba(255, 255, 255, 230); + border: none; + border-radius: 4px; +} + +QMenu::item { + border-radius: 4px; + /* 这个距离很麻烦需要根据菜单的长度和图标等因素微调 */ + padding: 8px 48px 8px 36px; /* 36px是文字距离左侧距离*/ + background-color: transparent; +} + +/* 鼠标悬停和按下效果 */ +QMenu::item:selected { + border-radius: 0px; + /* 半透明效果 */ + background-color: rgba(232, 232, 232, 232); +} + +/* 禁用效果 */ +QMenu::item:disabled { + background-color: transparent; +} + +/* 图标距离左侧距离 */ +QMenu::icon { + left: 15px; +} + +/* 分割线效果 */ +QMenu::separator { + height: 1px; + background-color: rgb(232, 236, 243); +} +""" + + +def get_icon(): + # 测试模拟图标 + pixmap = QPixmap(16, 16) + pixmap.fill(Qt.transparent) + painter = QPainter() + painter.begin(pixmap) + painter.setFont(QFont('Webdings', 11)) + painter.setPen(Qt.GlobalColor(randint(4, 18))) + painter.drawText(0, 0, 16, 16, Qt.AlignCenter, + choice(string.ascii_letters)) + painter.end() + return QIcon(pixmap) + + +def about_qt(): + # 关于Qt + QApplication.instance().aboutQt() + + +class Window(QLabel): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + self.resize(400, 400) + self.setAlignment(Qt.AlignCenter) + self.setText('右键弹出菜单') + self.context_menu = QMenu(self) + self.init_menu() + + def contextMenuEvent(self, event): + self.context_menu.exec_(event.globalPos()) + + def init_menu(self): + # 背景透明 + self.context_menu.setAttribute(Qt.WA_TranslucentBackground) + # 无边框、去掉自带阴影 + self.context_menu.setWindowFlags( + self.context_menu.windowFlags() | Qt.FramelessWindowHint | Qt.NoDropShadowWindowHint) + + # 模拟菜单项 + for i in range(10): + if i % 2 == 0: + action = self.context_menu.addAction('菜单 %d' % i, about_qt) + action.setEnabled(i % 4) + elif i % 3 == 0: + self.context_menu.addAction(get_icon(), '菜单 %d' % i, about_qt) + if i % 4 == 0: + self.context_menu.addSeparator() + if i % 5 == 0: + # 二级菜单 + # 二级菜单 + menu = QMenu('二级菜单 %d' % i, self.context_menu) + # 背景透明 + menu.setAttribute(Qt.WA_TranslucentBackground) + # 无边框、去掉自带阴影 + menu.setWindowFlags(menu.windowFlags() | Qt.FramelessWindowHint | Qt.NoDropShadowWindowHint) + for j in range(3): + menu.addAction(get_icon(), '子菜单 %d' % j) + self.context_menu.addMenu(menu) + + +if __name__ == '__main__': + import sys + import cgitb + + cgitb.enable(format='text') + app = QApplication(sys.argv) + app.setStyleSheet(Style) + w = Window() + w.show() + sys.exit(app.exec_()) diff --git a/QMenu/README.en.md b/QMenu/README.en.md new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/QMenu/README.md b/QMenu/README.md index 7895d4e7a60901e58e346f6710af3f8442fd4139..33271cab8d4bd25973d51ee2bd5be0b4654bdfc3 100644 --- a/QMenu/README.md +++ b/QMenu/README.md @@ -1,5 +1,9 @@ # QMenu +- 目录 + - [菜单设置多选并且不关闭](#1菜单设置多选并且不关闭) + - [仿QQ右键菜单](#2仿QQ右键菜单) + ## 1、菜单设置多选并且不关闭 [运行 MultiSelect.py](MultiSelect.py) @@ -32,4 +36,9 @@ def _menu_mouseReleaseEvent(self, event): action.activate(action.Trigger) ``` -![MultiSelect](ScreenShot/MultiSelect.gif) \ No newline at end of file +![MultiSelect](ScreenShot/MultiSelect.gif) + +## 2、仿QQ右键菜单 +[运行 QQMenu.py](QQMenu.py) + +![QQMenu](ScreenShot/QQMenu.gif) \ No newline at end of file diff --git a/QMenu/ScreenShot/QQMenu.gif b/QMenu/ScreenShot/QQMenu.gif new file mode 100644 index 0000000000000000000000000000000000000000..e989d9dd36c392396759ff3b19a6c12e7cb7c109 Binary files /dev/null and b/QMenu/ScreenShot/QQMenu.gif differ diff --git a/QMessageBox/ChineseText.py b/QMessageBox/ChineseText.py new file mode 100644 index 0000000000000000000000000000000000000000..8ba727789bcebd66de9d9f6ac8b0910938bc78e5 --- /dev/null +++ b/QMessageBox/ChineseText.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2019年7月10日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: ChineseText +@description: 修改消息对话框文字汉化 +""" +import sys + +try: + from PyQt5.QtWidgets import QApplication, QMessageBox +except ImportError: + from PySide2.QtWidgets import QApplication, QMessageBox + +TextStyle = """ +QMessageBox QPushButton[text="OK"] { + qproperty-text: "好的"; +} +QMessageBox QPushButton[text="Open"] { + qproperty-text: "打开"; +} +QMessageBox QPushButton[text="Save"] { + qproperty-text: "保存"; +} +QMessageBox QPushButton[text="Cancel"] { + qproperty-text: "取消"; +} +QMessageBox QPushButton[text="Close"] { + qproperty-text: "关闭"; +} +QMessageBox QPushButton[text="Discard"] { + qproperty-text: "不保存"; +} +QMessageBox QPushButton[text="Don't Save"] { + qproperty-text: "不保存"; +} +QMessageBox QPushButton[text="Apply"] { + qproperty-text: "应用"; +} +QMessageBox QPushButton[text="Reset"] { + qproperty-text: "重置"; +} +QMessageBox QPushButton[text="Restore Defaults"] { + qproperty-text: "恢复默认"; +} +QMessageBox QPushButton[text="Help"] { + qproperty-text: "帮助"; +} +QMessageBox QPushButton[text="Save All"] { + qproperty-text: "保存全部"; +} +QMessageBox QPushButton[text="&Yes"] { + qproperty-text: "是"; +} +QMessageBox QPushButton[text="Yes to &All"] { + qproperty-text: "全部都是"; +} +QMessageBox QPushButton[text="&No"] { + qproperty-text: "不"; +} +QMessageBox QPushButton[text="N&o to All"] { + qproperty-text: "全部都不"; +} +QMessageBox QPushButton[text="Abort"] { + qproperty-text: "终止"; +} +QMessageBox QPushButton[text="Retry"] { + qproperty-text: "重试"; +} +QMessageBox QPushButton[text="Ignore"] { + qproperty-text: "忽略"; +} +""" + +app = QApplication(sys.argv) + +# 通过QSS样式的方式设置按钮文字 +app.setStyleSheet(TextStyle) + +# 由于年代久远,Qt5的翻译功能没有更新,还是用的旧的结构导致无法翻译 +# 这里不使用(需要修改ts源码重新编译成qm) +# translator = QTranslator() +# print(translator.load(QLocale(), 'qt', '_', QLibraryInfo.location( +# QLibraryInfo.TranslationsPath))) +# app.installTranslator(translator) + +QMessageBox.information( + None, 'information', '消息', + QMessageBox.Ok | + QMessageBox.Open | + QMessageBox.Save | + QMessageBox.Cancel | + QMessageBox.Close | + QMessageBox.Discard | + QMessageBox.Apply | + QMessageBox.Reset | + QMessageBox.RestoreDefaults | + QMessageBox.Help | + QMessageBox.SaveAll | + QMessageBox.Yes | + QMessageBox.YesToAll | + QMessageBox.No | + QMessageBox.NoToAll | + QMessageBox.Abort | + QMessageBox.Retry | + QMessageBox.Ignore +) +sys.exit() diff --git a/QMessageBox/CountDownClose.py b/QMessageBox/CountDownClose.py index 2252ce00c9dbc6b0c56e12768b594cdc10636807..e582bb58057d83d28917736210a650073baca27b 100644 --- a/QMessageBox/CountDownClose.py +++ b/QMessageBox/CountDownClose.py @@ -4,22 +4,19 @@ """ Created on 2018年6月22日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: MessageBox @description: """ from random import randrange -from PyQt5.QtCore import QTimer -from PyQt5.QtWidgets import QMessageBox - - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" +try: + from PyQt5.QtCore import QTimer + from PyQt5.QtWidgets import QApplication, QMessageBox, QPushButton +except ImportError: + from PySide2.QtCore import QTimer + from PySide2.QtWidgets import QApplication, QMessageBox, QPushButton class MessageBox(QMessageBox): @@ -53,7 +50,7 @@ class MessageBox(QMessageBox): if __name__ == '__main__': import sys - from PyQt5.QtWidgets import QApplication, QPushButton + app = QApplication(sys.argv) w = QPushButton('点击弹出对话框') w.resize(200, 200) diff --git a/QMessageBox/CustomColorIcon.py b/QMessageBox/CustomColorIcon.py index 53d95c135f89187e20a4fd94d4936f9940514489..e41748c3271a46f27c7f98d20d0307a99167c3d1 100644 --- a/QMessageBox/CustomColorIcon.py +++ b/QMessageBox/CustomColorIcon.py @@ -1,23 +1,21 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -''' +""" Created on 2018年1月17日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: CustomBtnIcon @description: -''' - -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" -__Version__ = "Version 1.0" +""" import sys -from PyQt5.QtWidgets import QApplication, QMessageBox - +try: + from PyQt5.QtWidgets import QApplication, QMessageBox +except ImportError: + from PySide2.QtWidgets import QApplication, QMessageBox app = QApplication(sys.argv) app.setStyleSheet('''QDialogButtonBox { @@ -62,15 +60,15 @@ QMessageBox QPushButton[text="Apply"] { } ''') QMessageBox.information(None, "information", "消息", - QMessageBox.Apply | - QMessageBox.Cancel | - QMessageBox.Close | - QMessageBox.Discard | - QMessageBox.Help | - QMessageBox.No | - QMessageBox.Ok | - QMessageBox.Open | - QMessageBox.Reset | - QMessageBox.Save | - QMessageBox.Yes) + QMessageBox.Apply | + QMessageBox.Cancel | + QMessageBox.Close | + QMessageBox.Discard | + QMessageBox.Help | + QMessageBox.No | + QMessageBox.Ok | + QMessageBox.Open | + QMessageBox.Reset | + QMessageBox.Save | + QMessageBox.Yes) sys.exit() diff --git a/QMessageBox/README.md b/QMessageBox/README.md index db685931097e8cebdf61ac484f050a82a507a8df..84a1e91419a5696342c0fce5560086b51718bbba 100644 --- a/QMessageBox/README.md +++ b/QMessageBox/README.md @@ -1,5 +1,10 @@ # QMessageBox +- 目录 + - [消息对话框倒计时关闭](#1消息对话框倒计时关闭) + - [自定义图标等](#2自定义图标等) + - [消息框按钮文字汉化](#3消息框按钮文字汉化) + ## 1、消息对话框倒计时关闭 [运行 CountDownClose.py](CountDownClose.py) @@ -11,4 +16,13 @@ ## 2、自定义图标等 [运行 CustomColorIcon.py](CustomColorIcon.py) -![CustomColorIcon](ScreenShot/CustomColorIcon.png) \ No newline at end of file +![CustomColorIcon](ScreenShot/CustomColorIcon.png) + +## 3、消息框按钮文字汉化 +[运行 ChineseText.py](ChineseText.py) + +1. 因为Qt5的翻译文件还是沿用旧的Qt4的结构导致部分地方无法翻译 +2. 可以通过手动重新编译翻译文件解决问题 +3. 这里可以通过QSS特性修改按钮文字,详细见代码 + +![ChineseText](ScreenShot/ChineseText.png) \ No newline at end of file diff --git a/QMessageBox/ScreenShot/ChineseText.png b/QMessageBox/ScreenShot/ChineseText.png new file mode 100644 index 0000000000000000000000000000000000000000..e2cc9b97e1773d15bba50187dcc981b8b0076bb2 Binary files /dev/null and b/QMessageBox/ScreenShot/ChineseText.png differ diff --git a/QMetaObject/CallInThread.py b/QMetaObject/CallInThread.py new file mode 100644 index 0000000000000000000000000000000000000000..f5fdf824b958ad44cf701623837622db193a9724 --- /dev/null +++ b/QMetaObject/CallInThread.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Created on 2023/02/23 +@author: Irony +@site: https://pyqt.site https://github.com/PyQt5 +@email: 892768447@qq.com +@file: CallInThread.py +@description: +""" +import time +from datetime import datetime +from threading import Thread + +from PyQt5.QtCore import (Q_ARG, Q_RETURN_ARG, QMetaObject, Qt, QThread, + pyqtSignal, pyqtSlot) +from PyQt5.QtWidgets import QApplication, QHBoxLayout, QTextBrowser, QWidget + + +class ThreadQt(QThread): + + def __init__(self, textBrowser, *args, **kwargs): + super(ThreadQt, self).__init__(*args, **kwargs) + self._textBrowser = textBrowser + + def stop(self): + self.requestInterruption() + + def run(self): + while not self.isInterruptionRequested(): + text = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + # 通过`invokeMethod`直接调用对应的槽函数 + # 1. 获取函数`isReadOnly1`返回值, self.parent() 是Window窗口对象 + # NOTE:注意这里获取返回值要用 `Qt.DirectConnection` 方式 + retValue = QMetaObject.invokeMethod(self.parent(), 'isReadOnly1', + Qt.DirectConnection, + Q_RETURN_ARG(bool)) + # 2. 通过`invokeMethod`队列调用对应控件槽函数`append` + argValue = Q_ARG(str, text + ' readOnly: ' + str(retValue)) + QMetaObject.invokeMethod(self._textBrowser, 'append', + Qt.QueuedConnection, argValue) + self.sleep(1) + + +class ThreadPy(Thread): + + def __init__(self, textBrowser, parent, *args, **kwargs): + super(ThreadPy, self).__init__(*args, **kwargs) + self._running = True + self._textBrowser = textBrowser + self._parent = parent + + def stop(self): + self._running = False + + def run(self): + while self._running: + text = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + # 通过`invokeMethod`队列调用对应控件信号,`self._parent`是Window窗口对象 + QMetaObject.invokeMethod(self._parent, 'appendText', + Qt.QueuedConnection, + Q_ARG(str, text + ' from Signal')) + # 通过`invokeMethod`队列调用对应控件槽函数`append` + QMetaObject.invokeMethod(self._textBrowser, 'append', + Qt.QueuedConnection, + Q_ARG(str, text + ' to Slot')) + time.sleep(1) + + +class Window(QWidget): + + # 更新信号 + appendText = pyqtSignal(str) + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + layout = QHBoxLayout(self) + self.textBrowser1 = QTextBrowser(self) + self.textBrowser2 = QTextBrowser(self) + layout.addWidget(self.textBrowser1) + layout.addWidget(self.textBrowser2) + + self.appendText.connect(self.textBrowser2.append) + + # Qt线程 + self.thread1 = ThreadQt(self.textBrowser1, self) + self.thread1.start() + + # PY线程 + self.thread2 = ThreadPy(self.textBrowser2, self) + self.thread2.start() + + @pyqtSlot(result=bool) + def isReadOnly1(self): + # 线程中直接调用该槽函数获取UI中的内容 + return self.textBrowser1.isReadOnly() + + def closeEvent(self, event): + self.thread1.stop() + self.thread2.stop() + self.thread1.wait() + self.thread2.join() + super(Window, self).closeEvent(event) + + +if __name__ == '__main__': + import cgitb + import sys + + cgitb.enable(format='text') + app = QApplication(sys.argv) + w = Window() + w.show() + sys.exit(app.exec_()) diff --git a/QMetaObject/README.en.md b/QMetaObject/README.en.md new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/QMetaObject/README.md b/QMetaObject/README.md new file mode 100644 index 0000000000000000000000000000000000000000..06f2560dd49ee77a1c0a423b51cda0ab32f41743 --- /dev/null +++ b/QMetaObject/README.md @@ -0,0 +1,24 @@ +# QMetaObject + +- 目录 + - [在线程中操作UI](#1在线程中操作UI) + +## 1、在线程中操作UI +[运行 CallInThread.py](CallInThread.py) + +如果想在`QThread`或者`threading.Thread`中不通过信号直接操作UI,则可以使用`QMetaObject.invokeMethod`调用。 + +该函数一般有常用的几种调用方法: + +1. 直接调用槽函数:`QMetaObject.invokeMethod(uiobj, 'slot_method', Qt.QueuedConnection)` +2. 直接调用信号:`QMetaObject.invokeMethod(uiobj, 'signal_method', Qt.QueuedConnection)` +3. 调用信号或槽函数并传递参数:`QMetaObject.invokeMethod(uiobj, 'method', Qt.QueuedConnection, Q_ARG(str, 'text'))` +4. 调用槽函数得到返回值:`QMetaObject.invokeMethod(uiobj, 'slot_method', Qt.DirectConnection, Q_RETURN_ARG(str))` +5. 调用带参数的槽函数得到返回值:`QMetaObject.invokeMethod(uiobj, 'slot_method', Qt.DirectConnection, Q_RETURN_ARG(int), Q_ARG(bool, False))`, 传入bool类型的参数,获取int类型返回值 + +这里需要注意: + +1. 调用函数都是异步队列方式,需要使用`Qt.QueuedConnection` +2. 而要得到返回值则必须使用同步方式, 即`Qt.DirectConnection` + +![CallInThread](ScreenShot/CallInThread.png) \ No newline at end of file diff --git a/QMetaObject/ScreenShot/CallInThread.png b/QMetaObject/ScreenShot/CallInThread.png new file mode 100644 index 0000000000000000000000000000000000000000..62ed7f62b8990d7528747891ff5ad13981ad208d Binary files /dev/null and b/QMetaObject/ScreenShot/CallInThread.png differ diff --git a/QPainter/Data/qt-logo.png b/QPainter/Data/qt-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..90e6f905aeec1eaf412616c829329dde84ba2f33 Binary files /dev/null and b/QPainter/Data/qt-logo.png differ diff --git a/QPainter/Draw.py b/QPainter/Draw.py new file mode 100644 index 0000000000000000000000000000000000000000..c1e8f8089fe76ad3c84be6bcd29aee85d6efc7e0 --- /dev/null +++ b/QPainter/Draw.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2022年12月12日 +@site: https://pyqt.site , https://github.com/PyQt5 +@description: QPainter画图 +""" +import sys +try: + from PyQt5.QtWidgets import QApplication, QWidget, qApp + from PyQt5.QtGui import QPainter, QFont, QColor, QPixmap + from PyQt5.QtCore import Qt, pyqtSignal + from PyQt5.Qt import QPoint, QPolygon +except ImportError: + from PySide2.QtWidgets import QApplication, QWidget, qApp + from PySide2.QtGui import QPainter, QFont, QColor, QPixmap + from PySide2.QtCore import Qt, pyqtSignal + from PySide2.Qt import QPoint, QPolygon + + + +class draw(QWidget): + + + drawsig = pyqtSignal(bool) + + def __init__(self): + super(draw, self).__init__() + self.setWindowTitle("QPainter画图") + self._painter = QPainter() + self.scale = 1.0 + self.pixmap = QPixmap() + self.setMouseTracking(True) + self.setFocusPolicy(Qt.WheelFocus) + self.drawEnable = False + self.points = [] + self.current_points = [] + self.drawsig.connect(self.setDrawEnable) + + + def setDrawEnable(self, enable=False): + self.drawEnable = enable + self.update() + + def mouseMoveEvent(self, ev): + if self.drawEnable: + def in_end_range(curr, first): + return first.x() - 5 <= curr.x() <= first.x() + 5 and first.y() - 5 <= curr.y() <= first.y() + 5 + + if len(self.current_points) > 0 and in_end_range(ev.pos(), self.current_points[0]): + self.current_points.append(self.current_points[0]) + self.points.append(self.current_points) + self.current_points = [] + else: + self.current_points.append(ev.pos()) + elif len(self.current_points) > 0: + self.current_points.append(ev.pos()) + self.points.append(self.current_points) + self.current_points = [] + + self.update() + + def mousePressEvent(self, ev): + if Qt.LeftButton & ev.button(): + self.drawsig.emit(True) + def mouseReleaseEvent(self, ev): + if Qt.LeftButton & ev.button(): + self.drawsig.emit(False) + + def paintEvent(self, ev): + if len(self.points) <= 0 and len(self.current_points) <= 0 : return + p = self._painter + p.begin(self) + p.setRenderHint(QPainter.Antialiasing) + p.setRenderHint(QPainter.HighQualityAntialiasing) + p.setRenderHint(QPainter.SmoothPixmapTransform) + p.scale(self.scale, self.scale) + p.setPen(QColor(0, 0, 0)) + for pts in self.points: + p.drawPolyline(QPolygon(pts)) + if len(self.current_points) > 0: + p.drawPolyline(QPolygon(self.current_points)) + p.end() + + + +if __name__ == '__main__': + app = QApplication(sys.argv) + mainWin = draw() + mainWin.show() + sys.exit(app.exec_()) + diff --git a/QPainter/README.en.md b/QPainter/README.en.md new file mode 100644 index 0000000000000000000000000000000000000000..42512411c5309df459e310d53d74207b307f1f60 --- /dev/null +++ b/QPainter/README.en.md @@ -0,0 +1 @@ +# QPainter \ No newline at end of file diff --git a/QPainter/README.md b/QPainter/README.md new file mode 100644 index 0000000000000000000000000000000000000000..81206483e3fc76c1876b6765c8451e2f1425676a --- /dev/null +++ b/QPainter/README.md @@ -0,0 +1,16 @@ +# QPainter + + +- 目录 + - [利用QPainter绘制各种图形](#QPainter绘制各种图形) + - [简易画板](#简易画板) + +## 1、QPainter绘制各种图形 +[运行 StockDialog.py](StockDialog.py) + +![CountDownClose](ScreenShot/StockDialog.gif) + +## 2、简易画板 +[运行 Draw.py](Draw.py) + +![CustomColorIcon](ScreenShot/Draw.gif) \ No newline at end of file diff --git a/QPainter/ScreenShot/Draw.gif b/QPainter/ScreenShot/Draw.gif new file mode 100644 index 0000000000000000000000000000000000000000..65f85619a3ced54ff87b514283d1205b37e8a81d Binary files /dev/null and b/QPainter/ScreenShot/Draw.gif differ diff --git a/QPainter/ScreenShot/StockDialog.gif b/QPainter/ScreenShot/StockDialog.gif new file mode 100644 index 0000000000000000000000000000000000000000..97d964e7fc2f30067dbe8c14f7daf1363ec0c8a9 Binary files /dev/null and b/QPainter/ScreenShot/StockDialog.gif differ diff --git a/QPainter/StockDialog.py b/QPainter/StockDialog.py new file mode 100644 index 0000000000000000000000000000000000000000..996dbe1fd59391ebd1e6246214105790c672dbaa --- /dev/null +++ b/QPainter/StockDialog.py @@ -0,0 +1,298 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2022年12月12日 +@site: https://pyqt.site , https://github.com/PyQt5 +@description: QPainter绘制各种图形 +""" +import sys + +try: + from PyQt5.QtWidgets import QApplication, QWidget, qApp + from PyQt5.QtGui import QPainter, QFont, QColor, QPixmap + from PyQt5.QtCore import Qt, pyqtSignal, QPointF + from PyQt5.Qt import QPoint, QPolygon, QSplitter, QFrame, QGridLayout, QLabel,\ + QComboBox, QSpinBox, QPalette, QStackedWidget, QVBoxLayout,\ + QPushButton, QColorDialog, QPen,QLinearGradient, QConicalGradient,\ + QRadialGradient, QBrush, QRect, QPainterPath, QFileDialog +except ImportError: + from PySide2.QtWidgets import QApplication, QWidget, qApp + from PySide2.QtGui import QPainter, QFont, QColor, QPixmap + from PySide2.QtCore import Qt, pyqtSignal, QPointF + from PySide2.Qt import QPoint, QPolygon, QSplitter, QFrame, QGridLayout, QLabel,\ + QComboBox, QSpinBox, QPalette, QStackedWidget, QVBoxLayout,\ + QPushButton, QColorDialog, QPen, QLinearGradient, QConicalGradient,\ + QRadialGradient, QBrush, QRect , QPainterPath, QFileDialog + + +class StockDialog(QWidget): + def __init__(self, parent=None): + super(StockDialog, self).__init__(parent) + self.setWindowTitle("QPainter绘制各种图形") + + mainSplitter = QSplitter(Qt.Horizontal) + mainSplitter.setOpaqueResize(True) + frame = QFrame(mainSplitter) + mainLayout = QGridLayout(frame) + mainLayout.setSpacing(6) + + label1 = QLabel("形状:") + label2 = QLabel("画笔线宽:") + label3 = QLabel("画笔颜色:") + label4 = QLabel("画笔风格:") + label5 = QLabel("画笔顶端:") + label6 = QLabel("画笔连接点:") + label7 = QLabel("画刷风格:") + label8 = QLabel("画刷颜色:") + + self.shapeComboBox = QComboBox() + self.shapeComboBox.addItem("Line", "Line") + self.shapeComboBox.addItem("Rectangle", "Rectangle") + self.shapeComboBox.addItem('Rounded Rectangle', 'Rounded Rectangle') + self.shapeComboBox.addItem('Ellipse', 'Ellipse') + self.shapeComboBox.addItem('Pie', 'Pie') + self.shapeComboBox.addItem('Chord', 'Chord') + self.shapeComboBox.addItem('Path', 'Path') + self.shapeComboBox.addItem('Polygon', 'Polygon') + self.shapeComboBox.addItem('Polyline', 'Polyline') + self.shapeComboBox.addItem('Arc', 'Arc') + self.shapeComboBox.addItem('Points', 'Points') + self.shapeComboBox.addItem('Text', 'Text') + self.shapeComboBox.addItem('Pixmap', 'Pixmap') + + self.widthSpinBox = QSpinBox() + self.widthSpinBox.setRange(0, 20) + + self.penColorFrame = QFrame() + self.penColorFrame.setAutoFillBackground(True) + self.penColorFrame.setPalette(QPalette(Qt.blue)) + self.penColorPushButton = QPushButton("更改") + + self.penStyleComboBox = QComboBox() + self.penStyleComboBox.addItem("Solid", Qt.SolidLine) + self.penStyleComboBox.addItem('Dash', Qt.DashLine) + self.penStyleComboBox.addItem('Dot', Qt.DotLine) + self.penStyleComboBox.addItem('Dash Dot', Qt.DashDotLine) + self.penStyleComboBox.addItem('Dash Dot Dot', Qt.DashDotDotLine) + self.penStyleComboBox.addItem('None', Qt.NoPen) + + self.penCapComboBox = QComboBox() + self.penCapComboBox.addItem("Flat", Qt.FlatCap) + self.penCapComboBox.addItem('Square', Qt.SquareCap) + self.penCapComboBox.addItem('Round', Qt.RoundCap) + + self.penJoinComboBox = QComboBox() + self.penJoinComboBox.addItem("Miter", Qt.MiterJoin) + self.penJoinComboBox.addItem('Bebel', Qt.BevelJoin) + self.penJoinComboBox.addItem('Round', Qt.RoundJoin) + + self.brushStyleComboBox = QComboBox() + self.brushStyleComboBox.addItem("Linear Gradient", Qt.LinearGradientPattern) + self.brushStyleComboBox.addItem('Radial Gradient', Qt.RadialGradientPattern) + self.brushStyleComboBox.addItem('Conical Gradient', Qt.ConicalGradientPattern) + self.brushStyleComboBox.addItem('Texture', Qt.TexturePattern) + self.brushStyleComboBox.addItem('Solid', Qt.SolidPattern) + self.brushStyleComboBox.addItem('Horizontal', Qt.HorPattern) + self.brushStyleComboBox.addItem('Vertical', Qt.VerPattern) + self.brushStyleComboBox.addItem('Cross', Qt.CrossPattern) + self.brushStyleComboBox.addItem('Backward Diagonal', Qt.BDiagPattern) + self.brushStyleComboBox.addItem('Forward Diagonal', Qt.FDiagPattern) + self.brushStyleComboBox.addItem('Diagonal Cross', Qt.DiagCrossPattern) + self.brushStyleComboBox.addItem('Dense 1', Qt.Dense1Pattern) + self.brushStyleComboBox.addItem('Dense 2', Qt.Dense2Pattern) + self.brushStyleComboBox.addItem('Dense 3', Qt.Dense3Pattern) + self.brushStyleComboBox.addItem('Dense 4', Qt.Dense4Pattern) + self.brushStyleComboBox.addItem('Dense 5', Qt.Dense5Pattern) + self.brushStyleComboBox.addItem('Dense 6', Qt.Dense6Pattern) + self.brushStyleComboBox.addItem('Dense 7', Qt.Dense7Pattern) + self.brushStyleComboBox.addItem('None', Qt.NoBrush) + + self.brushColorFrame = QFrame() + self.brushColorFrame.setAutoFillBackground(True) + self.brushColorFrame.setPalette(QPalette(Qt.green)) + self.brushColorPushButton = QPushButton("更改") + + labelCol = 0 + contentCol = 1 + + # 建立布局 + mainLayout.addWidget(label1, 1, labelCol) + mainLayout.addWidget(self.shapeComboBox, 1, contentCol) + mainLayout.addWidget(label2, 2, labelCol) + mainLayout.addWidget(self.widthSpinBox, 2, contentCol) + mainLayout.addWidget(label3, 4, labelCol) + mainLayout.addWidget(self.penColorFrame, 4, contentCol) + mainLayout.addWidget(self.penColorPushButton, 4, 3) + mainLayout.addWidget(label4, 6, labelCol) + mainLayout.addWidget(self.penStyleComboBox, 6, contentCol) + mainLayout.addWidget(label5, 8, labelCol) + mainLayout.addWidget(self.penCapComboBox, 8, contentCol) + mainLayout.addWidget(label6, 10, labelCol) + mainLayout.addWidget(self.penJoinComboBox, 10, contentCol) + mainLayout.addWidget(label7, 12, labelCol) + mainLayout.addWidget(self.brushStyleComboBox, 12, contentCol) + mainLayout.addWidget(label8, 14, labelCol) + mainLayout.addWidget(self.brushColorFrame, 14, contentCol) + mainLayout.addWidget(self.brushColorPushButton, 14, 3) + mainSplitter1 = QSplitter(Qt.Horizontal) + mainSplitter1.setOpaqueResize(True) + + stack1 = QStackedWidget() + stack1.setFrameStyle(QFrame.Panel | QFrame.Raised) + self.area = PaintArea() + stack1.addWidget(self.area) + frame1 = QFrame(mainSplitter1) + mainLayout1 = QVBoxLayout(frame1) + mainLayout1.setSpacing(6) + mainLayout1.addWidget(stack1) + + layout = QGridLayout(self) + layout.addWidget(mainSplitter1, 0, 0) + layout.addWidget(mainSplitter, 0, 1) + self.setLayout(layout) + + # 信号和槽函数 + self.shapeComboBox.activated.connect(self.slotShape) + self.widthSpinBox.valueChanged.connect(self.slotPenWidth) + self.penColorPushButton.clicked.connect(self.slotPenColor) + self.penStyleComboBox.activated.connect(self.slotPenStyle) + self.penCapComboBox.activated.connect(self.slotPenCap) + self.penJoinComboBox.activated.connect(self.slotPenJoin) + self.brushStyleComboBox.activated.connect(self.slotBrush) + self.brushColorPushButton.clicked.connect(self.slotBrushColor) + + self.slotShape(self.shapeComboBox.currentIndex()) + self.slotPenWidth(self.widthSpinBox.value()) + self.slotBrush(self.brushStyleComboBox.currentIndex()) + + def slotShape(self, value): + shape = self.area.Shape[value] + self.area.setShape(shape) + + def slotPenWidth(self, value): + color = self.penColorFrame.palette().color(QPalette.Window) + style = Qt.PenStyle(self.penStyleComboBox.itemData(self.penStyleComboBox.currentIndex(), Qt.UserRole)) + cap = Qt.PenCapStyle(self.penCapComboBox.itemData(self.penCapComboBox.currentIndex(), Qt.UserRole)) + join = Qt.PenJoinStyle(self.penJoinComboBox.itemData(self.penJoinComboBox.currentIndex(), Qt.UserRole)) + self.area.setPen(QPen(color, value, style, cap, join)) + + def slotPenStyle(self, value): + self.slotPenWidth(value) + + def slotPenCap(self, value): + self.slotPenWidth(value) + + def slotPenJoin(self, value): + self.slotPenWidth(value) + + def slotPenColor(self): + color = QColorDialog.getColor(Qt.blue) + self.penColorFrame.setPalette(QPalette(color)) + self.area.setPen(QPen(color)) + + def slotBrushColor(self): + color = QColorDialog.getColor(Qt.blue) + self.brushColorFrame.setPalette(QPalette(color)) + self.slotBrush(self.brushStyleComboBox.currentIndex()) + + def slotBrush(self, value): + color = self.brushColorFrame.palette().color(QPalette.Window) + style = Qt.BrushStyle(self.brushStyleComboBox.itemData(value, Qt.UserRole)) + + if (style == Qt.LinearGradientPattern): + linearGradient = QLinearGradient(0, 0, 400, 400) + linearGradient.setColorAt(0.0, Qt.white) + linearGradient.setColorAt(0.2, color) + linearGradient.setColorAt(1.0, Qt.black) + self.area.setBrush(linearGradient) + elif style == Qt.RadialGradientPattern: + radialGradient = QRadialGradient(200, 200, 80, 70, 70); + radialGradient.setColorAt(0.0, Qt.white) + radialGradient.setColorAt(0.2, Qt.green) + radialGradient.setColorAt(1.0, Qt.black) + self.area.setBrush(radialGradient) + elif (style == Qt.ConicalGradientPattern): + conicalGradient = QConicalGradient(200, 200, 30) + conicalGradient.setColorAt(0.0, Qt.white) + conicalGradient.setColorAt(0.2, color) + conicalGradient.setColorAt(1.0, Qt.black) + self.area.setBrush(conicalGradient) + elif (style == Qt.TexturePattern): + self.area.setBrush(QBrush(QPixmap("./Data/qt-logo.png"))) + else: + self.area.setBrush(QBrush(color, style)) + + +class PaintArea(QWidget): + def __init__(self): + super(PaintArea, self).__init__() + self.Shape = ["Line", "Rectangle", 'Rounded Rectangle', "Ellipse", "Pie", 'Chord', + "Path", "Polygon", "Polyline", "Arc", "Points", "Text", "Pixmap"] + self.setPalette(QPalette(Qt.white)) + self.setAutoFillBackground(True) + self.setMinimumSize(500, 500) + self.pen = QPen() + self.brush = QBrush() + + def setShape(self, s): + self.shape = s + self.update() + + def setPen(self, p): + self.pen = p + self.update() + + def setBrush(self, b): + self.brush = b + self.update() + + def paintEvent(self, QPaintEvent): + p = QPainter(self) + p.setPen(self.pen) + p.setBrush(self.brush) + + rect = QRect(50, 100, 300, 200) + points = [QPoint(150, 100), QPoint(300, 150), QPoint(350, 250), QPoint(100, 300)] + startAngle = 30 * 16 + spanAngle = 120 * 16 + + if self.shape == "Line": + p.drawLine(rect.topLeft(), rect.bottomRight()) + elif self.shape == "Rectangle": + p.drawRect(rect) + elif self.shape == 'Rounded Rectangle': + p.drawRoundedRect(rect, 25, 25, Qt.RelativeSize) + elif self.shape == "Ellipse": + p.drawEllipse(rect) + elif self.shape == "Polygon": + p.drawPolygon(QPolygon(points), Qt.WindingFill) + elif self.shape == "Polyline": + p.drawPolyline(QPolygon(points)) + elif self.shape == "Points": + p.drawPoints(QPolygon(points)) + elif self.shape == "Pie": + p.drawPie(rect, startAngle, spanAngle) + elif self.shape == "Arc": + p.drawArc(rect, startAngle, spanAngle) + elif self.shape == "Chord": + p.drawChord(rect, startAngle, spanAngle) + elif self.shape == "Path": + path = QPainterPath() + path.addRect(150, 150, 100, 100) + path.moveTo(100, 100) + path.cubicTo(300, 100, 200, 200, 300, 300) + path.cubicTo(100, 300, 200, 200, 100, 100) + p.drawPath(path) + elif self.shape == "Text": + p.drawText(rect, Qt.AlignCenter, "Hello Qt!") + elif self.shape == "Pixmap": + p.drawPixmap(150, 150, QPixmap("./Data/qt-logo.png")) + + +if __name__ == '__main__': + app = QApplication(sys.argv) + form = StockDialog() + form.show() + app.exec_() + diff --git a/QProcess/GetCmdResult.py b/QProcess/GetCmdResult.py new file mode 100644 index 0000000000000000000000000000000000000000..f7ea5469421025d44d260af5315d31aa43025418 --- /dev/null +++ b/QProcess/GetCmdResult.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Created on 2023/02/01 +@author: Irony +@site: https://pyqt.site https://github.com/PyQt5 +@email: 892768447@qq.com +@file: GetCmdResult.py +@description: +""" + +import sys + +try: + from PyQt5.QtCore import QProcess + from PyQt5.QtWidgets import (QApplication, QLabel, QPushButton, + QTextBrowser, QVBoxLayout, QWidget) +except ImportError: + from PySide2.QtCore import QProcess + from PySide2.QtWidgets import (QApplication, QLabel, QPushButton, + QTextBrowser, QVBoxLayout, QWidget) + + +class Window(QWidget): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + self.setWindowTitle('执行命令得到结果') + layout = QVBoxLayout(self) + layout.addWidget(QLabel('点击执行 ping www.baidu.com', self)) + + self.buttonRunSync = QPushButton('同步执行', self) + layout.addWidget(self.buttonRunSync) + self.buttonRunSync.clicked.connect(self.run_ping) + + self.buttonRunASync = QPushButton('异步执行', self) + layout.addWidget(self.buttonRunASync) + self.buttonRunASync.clicked.connect(self.run_ping) + + self.resultView = QTextBrowser(self) + layout.addWidget(self.resultView) + + self._pingProcess = None + + def run_ping(self): + sender = self.sender() # 同步或者异步按钮 + self.buttonRunSync.setEnabled(False) + self.buttonRunASync.setEnabled(False) + + if self._pingProcess: + self._pingProcess.terminate() + + self._pingProcess = QProcess(self) + self._pingProcess.setProgram('ping') + if sys.platform.startswith('win'): + self._pingProcess.setArguments(['-n', '5', 'www.baidu.com']) + self._pingProcess.setArguments(['-n', '5', 'www.baidu.com']) + elif sys.platform.startswith('darwin') or sys.platform.startswith( + 'linux'): + self._pingProcess.setArguments(['-c', '5', 'www.baidu.com']) + # 合并输出流和错误流,执行完毕后通过readAll可以一次性读取所有结果 + self._pingProcess.setProcessChannelMode(QProcess.MergedChannels) + self._pingProcess.started.connect(self.on_started) + + if sender == self.buttonRunASync: + # 异步执行 + self._pingProcess.finished.connect(self.on_finished) + self._pingProcess.errorOccurred.connect(self.on_error) + self._pingProcess.start() + elif sender == self.buttonRunSync: + # 同步执行 + self._pingProcess.start() + if self._pingProcess.waitForFinished(): + self.on_finished(self._pingProcess.exitCode(), + self._pingProcess.exitStatus()) + else: + self.resultView.append('ping process read timeout') + self.on_error(self._pingProcess.error()) + + def on_started(self): + self.resultView.append('ping process started') + + def on_finished(self, exitCode, exitStatus): + self.resultView.append( + 'ping process finished, exitCode: %s, exitStatus: %s' % + (exitCode, exitStatus)) + # 读取所有结果 + result = self._pingProcess.readAll().data() + try: + import chardet + encoding = chardet.detect(result) + self.resultView.append(result.decode(encoding['encoding'])) + except Exception: + self.resultView.append(result.decode('utf-8', errors='ignore')) + self._pingProcess.kill() + self._pingProcess = None + self.buttonRunSync.setEnabled(True) + self.buttonRunASync.setEnabled(True) + + def on_error(self, error): + self.resultView.append('ping process error: %s, message: %s' % + (error, self._pingProcess.errorString())) + self._pingProcess.kill() + self._pingProcess = None + self.buttonRunSync.setEnabled(True) + self.buttonRunASync.setEnabled(True) + + +if __name__ == '__main__': + import cgitb + + cgitb.enable(format='text') + app = QApplication(sys.argv) + w = Window() + w.show() + sys.exit(app.exec_()) diff --git a/QProcess/InteractiveRun.py b/QProcess/InteractiveRun.py new file mode 100644 index 0000000000000000000000000000000000000000..2c538c1ca7d24d8fd8cb61307427b6b9fcf428df --- /dev/null +++ b/QProcess/InteractiveRun.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Created on 2023/02/01 +@author: Irony +@site: https://pyqt.site https://github.com/PyQt5 +@email: 892768447@qq.com +@file: InteractiveRun.py +@description: +""" + +import os +import sys + +try: + import chardet +except ImportError: + print('chardet not found') + +try: + from PyQt5.QtCore import QProcess + from PyQt5.QtWidgets import (QApplication, QLineEdit, QPushButton, + QTextBrowser, QVBoxLayout, QWidget) +except ImportError: + from PySide2.QtCore import QProcess + from PySide2.QtWidgets import (QApplication, QLineEdit, QPushButton, + QTextBrowser, QVBoxLayout, QWidget) + + +class Window(QWidget): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + layout = QVBoxLayout(self) + + command = 'ping www.baidu.com' + if sys.platform.startswith('win'): + command = 'ping -n 5 www.baidu.com' + elif sys.platform.startswith('darwin') or sys.platform.startswith( + 'linux'): + command = 'ping -c 5 www.baidu.com' + else: + raise RuntimeError('Unsupported platform: %s' % sys.platform) + + self.cmdEdit = QLineEdit(command, self) + layout.addWidget(self.cmdEdit) + + self.buttonRun = QPushButton('执行命令', self) + layout.addWidget(self.buttonRun) + self.buttonRun.clicked.connect(self.run_command) + + self.resultView = QTextBrowser(self) + layout.addWidget(self.resultView) + + self._cmdProcess = None + self._init() + + def closeEvent(self, event): + if self._cmdProcess: + self._cmdProcess.writeData('exit'.encode() + os.linesep.encode()) + self._cmdProcess.waitForFinished() + if self._cmdProcess: + self._cmdProcess.terminate() + super(Window, self).closeEvent(event) + + def _init(self): + if self._cmdProcess: + return + # 打开终端shell + self._cmdProcess = QProcess(self) + self._cmdProcess.setProgram( + 'cmd' if sys.platform.startswith('win') else 'bash') + # 合并输出流和错误流,只从标准输出流读取数据 + self._cmdProcess.setProcessChannelMode(QProcess.MergedChannels) + self._cmdProcess.started.connect(self.on_started) + self._cmdProcess.finished.connect(self.on_finished) + self._cmdProcess.errorOccurred.connect(self.on_error) + self._cmdProcess.readyReadStandardOutput.connect( + self.on_readyReadStandardOutput) + self._cmdProcess.start() + + def run_command(self): + self._init() + command = self.cmdEdit.text().strip() + if not command: + return + command = command.encode(sys.getdefaultencoding()) + os.linesep.encode( + sys.getdefaultencoding()) + self._cmdProcess.writeData(command) + + def on_started(self): + self.resultView.append('ping process started, pid: %s' % + self._cmdProcess.processId()) + + def on_finished(self, exitCode, exitStatus): + print('ping process finished, exitCode: %s, exitStatus: %s' % + (exitCode, exitStatus)) + self._cmdProcess.kill() + self._cmdProcess = None + + def on_error(self, error): + self.resultView.append('ping process error: %s, message: %s' % + (error, self._cmdProcess.errorString())) + self._cmdProcess.kill() + self._cmdProcess = None + + def on_readyReadStandardOutput(self): + # 读取已有结果 + result = self._cmdProcess.readAllStandardOutput().data() + try: + encoding = chardet.detect(result) + self.resultView.append(result.decode(encoding['encoding'])) + except Exception: + self.resultView.append(result.decode('utf-8', errors='ignore')) + + +if __name__ == '__main__': + import cgitb + + cgitb.enable(format='text') + app = QApplication(sys.argv) + w = Window() + w.show() + sys.exit(app.exec_()) diff --git a/QProcess/README.en.md b/QProcess/README.en.md new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/QProcess/README.md b/QProcess/README.md new file mode 100644 index 0000000000000000000000000000000000000000..0136bd7bdcab620957991f7adb02ea8167655be4 --- /dev/null +++ b/QProcess/README.md @@ -0,0 +1,33 @@ +# QProcess + +- 目录 + - [执行命令得到结果](#1执行命令得到结果) + - [交互执行命令](#2交互执行命令) + +## 1、执行命令得到结果 +[运行 GetCmdResult.py](GetCmdResult.py) + +`QProcess` 常用执行命令方式有以下几种: +1. `QProcess.execute('ping', ['www.baidu.com'])`:同步执行,返回值为进程退出码 +2. `QProcess.startDetached('ping', ['www.baidu.com'], '工作路径')`:返回值为是否启动成功,该命令一般用于启动某个程序后就不管了 +3. 通过构造`QProcess`对象,然后通过`QProcess.start()`启动进程,并分为同步和异步两种方式获取输出 + +示例代码为第3种方式: + +1. 通过`setProcessChannelMode(QProcess.MergedChannels)`合并标准输出和错误输出 +2. `waitForFinished`为同步方式,然后调用`readAll`读取所有输出 +3. 也可以绑定`finished`信号,然后通过`readAll`读取所有输出 + +![GetCmdResult](ScreenShot/GetCmdResult.gif) + +## 2、交互执行命令 +[运行 InteractiveRun.py](InteractiveRun.py) + +`QProcess` 也可以用于交互式执行命令,具体需要如下几步: + +1. 通过`setProcessChannelMode(QProcess.MergedChannels)`合并标准输出和错误输出 +2. 通过`start`启动进程 +3. 通过`readyReadStandardOutput`信号读取进程输出 +4. 通过`writeData`向进程写入数据 + +![InteractiveRun](ScreenShot/InteractiveRun.gif) \ No newline at end of file diff --git a/QProcess/ScreenShot/GetCmdResult.gif b/QProcess/ScreenShot/GetCmdResult.gif new file mode 100644 index 0000000000000000000000000000000000000000..bdf059608b36c0ba9111480fc84f057e561b32f7 Binary files /dev/null and b/QProcess/ScreenShot/GetCmdResult.gif differ diff --git a/QProcess/ScreenShot/InteractiveRun.gif b/QProcess/ScreenShot/InteractiveRun.gif new file mode 100644 index 0000000000000000000000000000000000000000..225144da81f60e539254439a8c7e4e3bb075dcaf Binary files /dev/null and b/QProcess/ScreenShot/InteractiveRun.gif differ diff --git a/QProgressBar/ColourfulProgress.py b/QProgressBar/ColourfulProgress.py new file mode 100644 index 0000000000000000000000000000000000000000..2c629d0cbb7cea62a7635797e176d4882bc50443 --- /dev/null +++ b/QProgressBar/ColourfulProgress.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Created on 2022/02/25 +@author: Irony +@site: https://pyqt.site, https://github.com/PyQt5 +@email: 892768447@qq.com +@file: ColourfulProgress.py +@description: +""" + +try: + from PyQt5.QtCore import QLineF, QRect, QRectF, Qt + from PyQt5.QtGui import QColor, QPainter, QPainterPath, QPen, QTransform + from PyQt5.QtWidgets import (QApplication, QGridLayout, QProgressBar, + QSlider, QStyleOptionProgressBar, QWidget) +except ImportError: + from PySide2.QtCore import QRect, Qt, QRectF, QLineF + from PySide2.QtGui import QColor, QPainter, QPen, QTransform, QPainterPath + from PySide2.QtWidgets import (QApplication, QProgressBar, + QStyleOptionProgressBar, QWidget, QSlider, + QGridLayout) + +from Lib.QStyleAnimation import QProgressStyleAnimation + + +class ColourfulProgress(QProgressBar): + + def __init__(self, *args, **kwargs): + self._color = kwargs.pop('color', QColor(43, 194, 83)) + self._fps = kwargs.pop('fps', 60) + self._lineWidth = kwargs.pop('lineWidth', 50) # 线条宽度 + self._radius = kwargs.pop('radius', None) # None为自动计算圆角 + self._animation = None + super(ColourfulProgress, self).__init__(*args, **kwargs) + self.setColor(self._color) + self.setFps(self._fps) + self.setLineWidth(self._lineWidth) + self.setRadius(self._radius) + + def setColor(self, color): + """ + :type color: QColor + :param color: 颜色 + """ + self._color = QColor(color) if isinstance( + color, (QColor, Qt.GlobalColor)) else QColor(43, 194, 83) + + def setFps(self, fps): + """ + :type fps: int + :param fps: 帧率 + """ + self._fps = max(int(fps), 1) if isinstance(fps, (int, float)) else 60 + + def setLineWidth(self, width): + """ + :type width: int + :param width: 线条宽度 + """ + self._lineWidth = max(int(width), 0) if isinstance(width, + (int, float)) else 50 + + def setRadius(self, radius): + """ + :type radius: int + :param radius: 半径 + """ + self._radius = max(int(radius), 1) if isinstance(radius, + (int, float)) else None + + def paintEvent(self, _): + """ + 重写绘制事件,参考 qfusionstyle.cpp 中的 CE_ProgressBarContents 绘制方法 + """ + option = QStyleOptionProgressBar() + self.initStyleOption(option) + + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing) + painter.translate(0.5, 0.5) + + vertical = option.orientation == Qt.Vertical # 是否垂直 + inverted = option.invertedAppearance # 是否反转 + # 是否显示动画 + indeterminate = (option.minimum == option.maximum) or ( + option.minimum < option.progress < option.maximum) + rect = option.rect + + if vertical: + rect = QRect(rect.left(), rect.top(), rect.height(), + rect.width()) # 翻转宽度和高度 + m = QTransform.fromTranslate(rect.height(), 0) + m.rotate(90.0) + painter.setTransform(m, True) + + maxWidth = rect.width() + progress = max(option.progress, option.minimum) + totalSteps = max(1, option.maximum - option.minimum) + progressSteps = progress - option.minimum + progressBarWidth = int(progressSteps * maxWidth / totalSteps) + width = progressBarWidth # 已进行的进度宽度 + radius = max(1, (min(width, + self.width() if vertical else self.height()) // + 4) if self._radius is None else self._radius) + + reverse = (not vertical and + option.direction == Qt.RightToLeft) or vertical + if inverted: + reverse = not reverse + + # 绘制范围 + path = QPainterPath() + if not reverse: + progressBar = QRectF(rect.left(), rect.top(), width, rect.height()) + else: + progressBar = QRectF(rect.right() - width, rect.top(), width, + rect.height()) + + # 切割范围 + path.addRoundedRect(progressBar, radius, radius) + painter.setClipPath(path) + + # 绘制背景颜色 + painter.setPen(Qt.NoPen) + painter.setBrush(self._color) + painter.drawRoundedRect(progressBar, radius, radius) + + if not indeterminate: + if self._animation: + self._animation.stop() + self._animation = None + else: + # 叠加颜色覆盖后出现类似线条间隔的效果 + color = self._color.lighter(320) + color.setAlpha(80) + painter.setPen(QPen(color, self._lineWidth)) + + if self._animation: + if self._animation.state() == QProgressStyleAnimation.Stopped: + # FIXME: 最小化后动画会停止 + self._animation.start() + step = int(self._animation.animationStep() % self._lineWidth) + else: + step = 0 + self._animation = QProgressStyleAnimation(self._fps, self) + self._animation.start() + + # 动画斜线绘制 + startX = int(progressBar.left() - rect.height() - self._lineWidth) + endX = int(rect.right() + self._lineWidth) + + if (not inverted and not vertical) or (inverted and vertical): + lines = [ + QLineF(x + step, progressBar.bottom(), + x + rect.height() + step, progressBar.top()) + for x in range(startX, endX, self._lineWidth) + ] + else: + lines = [ + QLineF(x - step, progressBar.bottom(), + x + rect.height() - step, progressBar.top()) + for x in range(startX, endX, self._lineWidth) + ] + painter.drawLines(lines) + + +if __name__ == '__main__': + import cgitb + import sys + + cgitb.enable(format='text') + app = QApplication(sys.argv) + + w = QWidget() + layout = QGridLayout(w) + + w1 = ColourfulProgress(color=QColor('#85c440')) + w1.setMinimumWidth(300) + w1.setMaximumWidth(300) + w1.setRange(0, 100) + layout.addWidget(w1, 0, 0, 1, 1) + + w2 = ColourfulProgress(color=QColor('#f2b63c')) + w2.setMinimumWidth(300) + w2.setMaximumWidth(300) + w2.setInvertedAppearance(True) + w2.setRange(0, 100) + layout.addWidget(w2, 1, 0, 1, 1) + + w3 = ColourfulProgress(color=QColor('#db3a27')) + w3.setMinimumHeight(300) + w3.setMaximumHeight(300) + w3.setOrientation(Qt.Vertical) + w3.setRange(0, 100) + layout.addWidget(w3, 0, 1, 2, 1) + + w4 = ColourfulProgress(color=QColor('#5aaadb')) + w4.setMinimumHeight(300) + w4.setMaximumHeight(300) + w4.setInvertedAppearance(True) + w4.setOrientation(Qt.Vertical) + w4.setRange(0, 100) + layout.addWidget(w4, 0, 2, 2, 1) + + slider = QSlider(Qt.Horizontal) + slider.setRange(0, 100) + slider.valueChanged.connect(w1.setValue) + slider.valueChanged.connect(w2.setValue) + slider.valueChanged.connect(w3.setValue) + slider.valueChanged.connect(w4.setValue) + slider.setValue(50) + layout.addWidget(slider, 2, 0, 1, 3) + + w.show() + + sys.exit(app.exec_()) diff --git a/QProgressBar/Lib/DWaterProgress.py b/QProgressBar/Lib/DWaterProgress.py new file mode 100644 index 0000000000000000000000000000000000000000..9242ada269702f48344074cd409079ea348ab2f6 --- /dev/null +++ b/QProgressBar/Lib/DWaterProgress.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2021/1/1 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: DWaterProgress +@see https://github.com/linuxdeepin/dtkwidget/blob/master/src/widgets/dwaterprogress.cpp +@description: +""" +import math + +try: + from PyQt5.QtCore import pyqtSlot, QTimer, QSizeF, Qt, QRectF, QPointF, QRect, QPoint, QSize + from PyQt5.QtGui import QImage, QColor, QPainter, QLinearGradient, QGradient, QPainterPath, QPixmap, \ + QBrush, QPen + from PyQt5.QtSvg import QSvgRenderer + from PyQt5.QtWidgets import QProgressBar, QGraphicsDropShadowEffect +except ImportError: + from PySide2.QtCore import Slot as pyqtSlot, QTimer, QSizeF, Qt, QRectF, QPointF, QRect, QPoint, QSize + from PySide2.QtGui import QImage, QColor, QPainter, QLinearGradient, QGradient, QPainterPath, QPixmap, \ + QBrush, QPen + from PySide2.QtSvg import QSvgRenderer + from PySide2.QtWidgets import QProgressBar, QGraphicsDropShadowEffect + +WATER_FRONT = """ + + +""" +WATER_BACK = """ + + +""" + + +class Pop: + # https://github.com/linuxdeepin/dtkwidget/blob/master/src/widgets/dwaterprogress.cpp#L36 + + def __init__(self, size, xs, ys, xo=0, yo=0): + self.size = size + self.xSpeed = xs + self.ySpeed = ys + self.xOffset = xo + self.yOffset = yo + + +class DWaterProgress(QProgressBar): + + def __init__(self, *args, **kwargs): + super(DWaterProgress, self).__init__(*args, **kwargs) + self.waterFrontImage = QImage() + self.waterBackImage = QImage() + self.waterFrontSvg = QSvgRenderer(WATER_FRONT.encode()) + self.waterBackSvg = QSvgRenderer(WATER_BACK.encode()) + self.pops = [] + self.initPops() + self.setTextVisible(True) + self.interval = 33 + self.timer = QTimer(self) + self.timer.setInterval(self.interval) + self.timer.timeout.connect(self.onTimerOut) + self.resizePixmap(self.size()) + self.frontXOffset = self.width() + self.backXOffset = 0 + effect = QGraphicsDropShadowEffect(self) + effect.setOffset(0, 6) + effect.setColor(QColor(1, 153, 248, 255 * 5 / 20)) + effect.setBlurRadius(12) + self.setGraphicsEffect(effect) + + def initPops(self): + self.pops = [Pop(7, -1.8, 0.6), Pop(8, 1.2, 1.0), Pop(11, 0.8, 1.6)] + + @pyqtSlot() + def start(self): + self.timer.start() + + @pyqtSlot() + def stop(self): + self.timer.stop() + + def resizePixmap(self, sz): + # https://github.com/linuxdeepin/dtkwidget/blob/master/src/widgets/dwaterprogress.cpp#L192 + # resize water + waterWidth = 500 * sz.width() / 100 + waterHeight = 110 * sz.height() / 100 + waterSize = QSizeF(waterWidth, waterHeight).toSize() + + if self.waterFrontImage.size() != waterSize: + image = QImage(waterWidth, waterHeight, QImage.Format_ARGB32) + image.fill(Qt.transparent) + waterPainter = QPainter(image) + self.waterFrontSvg.render(waterPainter) + self.waterFrontImage = image + + if self.waterBackImage.size() != waterSize: + image = QImage(waterWidth, waterHeight, QImage.Format_ARGB32) + image.fill(Qt.transparent) # partly transparent red-ish background + waterPainter = QPainter(image) + self.waterBackSvg.render(waterPainter) + self.waterBackImage = image + + def onTimerOut(self): + # interval can not be zero, and limit to 1 + self.interval = max(1, self.interval) + # move 60% per second + frontXDeta = 40.0 / (1000.0 / self.interval) + # move 90% per second + backXDeta = 60.0 / (1000.0 / self.interval) + + canvasWidth = int(self.width() * self.devicePixelRatioF()) + self.frontXOffset -= frontXDeta * canvasWidth / 100 + self.backXOffset += backXDeta * canvasWidth / 100 + + if self.frontXOffset > canvasWidth: + self.frontXOffset = canvasWidth + + if self.frontXOffset < - (self.waterFrontImage.width() - canvasWidth): + self.frontXOffset = canvasWidth + + if self.backXOffset > self.waterBackImage.width(): + self.backXOffset = 0 + + # update pop + # move 25% per second default + speed = 25 / (1000.0 / self.interval) # 100 / self.height() + for pop in self.pops: + # yOffset 0 ~ 100 + pop.yOffset += speed * pop.ySpeed + if pop.yOffset < 0: + pass + if pop.yOffset > self.value(): + pop.yOffset = 0 + pop.xOffset = math.sin((pop.yOffset / 100) * 2 * 3.14) * 18 * pop.xSpeed + 50 + self.update() + + def paint(self, painter): + painter.setRenderHint(QPainter.Antialiasing) + + pixelRatio = self.devicePixelRatioF() + rect = QRectF(0, 0, self.width() * pixelRatio, self.height() * pixelRatio) + sz = QSizeF(self.width() * pixelRatio, self.height() * pixelRatio).toSize() + + self.resizePixmap(sz) + + yOffset = rect.toRect().topLeft().y() + (100 - self.value() - 10) * sz.height() / 100 + + # draw water + waterImage = QImage(sz, QImage.Format_ARGB32_Premultiplied) + waterPainter = QPainter() + waterPainter.begin(waterImage) + waterPainter.setRenderHint(QPainter.Antialiasing) + waterPainter.setCompositionMode(QPainter.CompositionMode_Source) + + pointStart = QPointF(sz.width() / 2, 0) + pointEnd = QPointF(sz.width() / 2, sz.height()) + linear = QLinearGradient(pointStart, pointEnd) + startColor = QColor('#1F08FF') + startColor.setAlphaF(1) + endColor = QColor('#50FFF7') + endColor.setAlphaF(0.28) + linear.setColorAt(0, startColor) + linear.setColorAt(1, endColor) + linear.setSpread(QGradient.PadSpread) + waterPainter.setPen(Qt.NoPen) + waterPainter.setBrush(linear) + waterPainter.drawEllipse(waterImage.rect().center(), sz.width() / 2 + 1, sz.height() / 2 + 1) + + waterPainter.setCompositionMode(QPainter.CompositionMode_SourceOver) + waterPainter.drawImage(int(self.backXOffset), yOffset, self.waterBackImage) + waterPainter.drawImage(int(self.backXOffset) - self.waterBackImage.width(), yOffset, + self.waterBackImage) + waterPainter.drawImage(int(self.frontXOffset), yOffset, self.waterFrontImage) + waterPainter.drawImage(int(self.frontXOffset) - self.waterFrontImage.width(), yOffset, + self.waterFrontImage) + + # draw pop + if self.value() > 30: + for pop in self.pops: + popPath = QPainterPath() + popPath.addEllipse(pop.xOffset * sz.width() / 100, (100 - pop.yOffset) * sz.height() / 100, + pop.size * sz.width() / 100, pop.size * sz.height() / 100) + waterPainter.fillPath(popPath, QColor(255, 255, 255, 255 * 0.3)) + + if self.isTextVisible(): + font = waterPainter.font() + rectValue = QRect() + progressText = self.text().strip('%') + + if progressText == '100': + font.setPixelSize(sz.height() * 35 / 100) + waterPainter.setFont(font) + + rectValue.setWidth(sz.width() * 60 / 100) + rectValue.setHeight(sz.height() * 35 / 100) + rectValue.moveCenter(rect.center().toPoint()) + waterPainter.setPen(Qt.white) + waterPainter.drawText(rectValue, Qt.AlignCenter, progressText) + else: + font.setPixelSize(sz.height() * 40 / 100) + waterPainter.setFont(font) + + rectValue.setWidth(sz.width() * 45 / 100) + rectValue.setHeight(sz.height() * 40 / 100) + rectValue.moveCenter(rect.center().toPoint()) + rectValue.moveLeft(rect.left() + rect.width() * 0.45 * 0.5) + + waterPainter.setPen(Qt.white) + waterPainter.drawText(rectValue, Qt.AlignCenter, progressText) + font.setPixelSize(font.pixelSize() / 2) + waterPainter.setFont(font) + rectPerent = QRect(QPoint(rectValue.right(), rectValue.bottom() - rect.height() * 20 / 100), + QPoint(rectValue.right() + rect.width() * 20 / 100, rectValue.bottom())) + + waterPainter.drawText(rectPerent, Qt.AlignCenter, '%') + + waterPainter.end() + + maskPixmap = QPixmap(sz) + maskPixmap.fill(Qt.transparent) + path = QPainterPath() + path.addEllipse(QRectF(0, 0, sz.width(), sz.height())) + maskPainter = QPainter() + maskPainter.begin(maskPixmap) + maskPainter.setRenderHint(QPainter.Antialiasing) + maskPainter.setPen(QPen(Qt.white, 1)) + maskPainter.fillPath(path, QBrush(Qt.white)) + maskPainter.end() + + mode = QPainter.CompositionMode_SourceIn + contentImage = QImage(sz, QImage.Format_ARGB32_Premultiplied) + contentPainter = QPainter() + contentPainter.begin(contentImage) + contentPainter.setCompositionMode(QPainter.CompositionMode_Source) + contentPainter.fillRect(contentImage.rect(), Qt.transparent) + contentPainter.setCompositionMode(QPainter.CompositionMode_SourceOver) + contentPainter.drawImage(0, 0, maskPixmap.toImage()) + contentPainter.setCompositionMode(mode) + contentPainter.drawImage(0, 0, waterImage) + contentPainter.setCompositionMode(QPainter.CompositionMode_DestinationOver) + contentPainter.end() + + contentImage.setDevicePixelRatio(pixelRatio) + painter.drawImage(self.rect(), contentImage) + + def paintEvent(self, event): + painter = QPainter(self) + self.paint(painter) + + def sizeHint(self): + return QSize(100, 100) diff --git a/QProgressBar/Lib/QStyleAnimation.py b/QProgressBar/Lib/QStyleAnimation.py new file mode 100644 index 0000000000000000000000000000000000000000..c2c91657a77b2f6964cda89f04f5231f9210a45a --- /dev/null +++ b/QProgressBar/Lib/QStyleAnimation.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Created on 2022/02/26 +@author: Irony +@site: https://pyqt.site, https://github.com/PyQt5 +@email: 892768447@qq.com +@file: QStyleAnimation.py +@description: +""" +from enum import IntEnum + +try: + from PyQt5.QtCore import (QAbstractAnimation, QCoreApplication, QEvent, + QTime) +except ImportError: + from PySide2.QtCore import (QAbstractAnimation, QCoreApplication, QEvent, + QTime) + +ScrollBarFadeOutDuration = 200.0 +ScrollBarFadeOutDelay = 450.0 + +StyleAnimationUpdate = 213 + + +class QStyleAnimation(QAbstractAnimation): + FrameRate = IntEnum( + 'FrameRate', + ['DefaultFps', 'SixtyFps', 'ThirtyFps', 'TwentyFps', 'FifteenFps']) + + def __init__(self, *args, **kwargs): + super(QStyleAnimation, self).__init__(*args, **kwargs) + self._delay = 0 + self._duration = -1 + self._startTime = QTime.currentTime() + self._fps = self.FrameRate.ThirtyFps + self._skip = 0 + + def target(self): + return self.parent() + + def duration(self): + return self._duration + + def setDuration(self, duration): + self._duration = duration + + def delay(self): + return self._delay + + def setDelay(self, delay): + self._delay = delay + + def startTime(self): + return self._startTime + + def setStartTime(self, time): + self._startTime = time + + def frameRate(self): + return self._fps + + def setFrameRate(self, fps): + self._fps = fps + + def updateTarget(self): + event = QEvent(QEvent.Type(StyleAnimationUpdate)) + event.setAccepted(False) + QCoreApplication.sendEvent(self.target(), event) + if not event.isAccepted(): + self.stop() + + def start(self): + self._skip = 0 + super(QStyleAnimation, self).start(QAbstractAnimation.KeepWhenStopped) + + def isUpdateNeeded(self): + return self.currentTime() > self._delay + + def updateCurrentTime(self, _): + self._skip += 1 + if self._skip >= self._fps: + self._skip = 0 + if self.parent() and self.isUpdateNeeded(): + self.updateTarget() + + +class QProgressStyleAnimation(QStyleAnimation): + + def __init__(self, speed, *args, **kwargs): + super(QProgressStyleAnimation, self).__init__(*args, **kwargs) + self._speed = speed + self._step = -1 + + def animationStep(self): + return self.currentTime() / (1000.0 / self._speed) + + def progressStep(self, width): + step = self.animationStep() + progress = (step * width / self._speed) % width + if ((step * width / self._speed) % (2 * width)) >= width: + progress = width - progress + return progress + + def speed(self): + return self._speed + + def setSpeed(self, speed): + self._speed = speed + + def isUpdateNeeded(self): + if super(QProgressStyleAnimation, self).isUpdateNeeded(): + current = self.animationStep() + if self._step == -1 or self._step != current: + self._step = current + return True + return False diff --git a/QProgressBar/Lib/WaterRippleProgressBar.py b/QProgressBar/Lib/WaterRippleProgressBar.py index 3673a712ce59e7ab0d3e3274a69da0587913249d..605e56dd865eb31a8b18ae6e862c704536f95e7d 100644 --- a/QProgressBar/Lib/WaterRippleProgressBar.py +++ b/QProgressBar/Lib/WaterRippleProgressBar.py @@ -1,28 +1,28 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Created on 2018年4月1日 -# author: Irony -# site: https://pyqt5.com , https://github.com/892768447 -# email: 892768447@qq.com -# file: WaterRippleProgressBar -# description: - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 +""" +Created on 2018年4月1日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: WaterRippleProgressBar +@description: +""" import math -from PyQt5.QtCore import QTimer, Qt, QRectF, QSize -from PyQt5.QtGui import QPainter, QPainterPath, QColor, QFont -from PyQt5.QtWidgets import QProgressBar +try: + from PyQt5.QtCore import QTimer, Qt, QRectF, QSize + from PyQt5.QtGui import QPainter, QPainterPath, QColor, QFont + from PyQt5.QtWidgets import QProgressBar +except ImportError: + from PySide2.QtCore import QTimer, Qt, QRectF, QSize + from PySide2.QtGui import QPainter, QPainterPath, QColor, QFont + from PySide2.QtWidgets import QProgressBar class WaterRippleProgressBar(QProgressBar): - # 浪高百分比 waterHeight = 1 # 密度 @@ -84,7 +84,7 @@ class WaterRippleProgressBar(QProgressBar): # 正弦曲线公式 y = A * sin(ωx + φ) + k # 当前值所占百分比 percent = 1 - (self.value() - self.minimum()) / \ - (self.maximum() - self.minimum()) + (self.maximum() - self.minimum()) # w表示周期,6为人为定义 w = 6 * self.waterDensity * math.pi / self.width() # A振幅 高度百分比,1/26为人为定义 diff --git a/QProgressBar/MetroCircleProgress.py b/QProgressBar/MetroCircleProgress.py index 3b1fe1f64657e22a5be0b5806eab14421d78b94f..58f4f4649aa5781d97770cdcb82cda190959b26b 100644 --- a/QProgressBar/MetroCircleProgress.py +++ b/QProgressBar/MetroCircleProgress.py @@ -4,27 +4,25 @@ """ Created on 2018年9月日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: MetroCircleProgress @description: """ -from PyQt5.QtCore import QSequentialAnimationGroup, pyqtProperty,\ - QPauseAnimation, QPropertyAnimation, QParallelAnimationGroup,\ - QObject, QSize, Qt, pyqtSignal, QRectF -from PyQt5.QtGui import QPainter, QColor -from PyQt5.QtWidgets import QWidget, QVBoxLayout - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 +try: + from PyQt5.QtCore import QSequentialAnimationGroup, QPauseAnimation, QPropertyAnimation, \ + QParallelAnimationGroup, QObject, QSize, Qt, QRectF, pyqtSignal, pyqtProperty + from PyQt5.QtGui import QPainter, QColor + from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout +except ImportError: + from PySide2.QtCore import QSequentialAnimationGroup, QPauseAnimation, QPropertyAnimation, \ + QParallelAnimationGroup, QObject, QSize, Qt, QRectF, Signal as pyqtSignal, Property as pyqtProperty + from PySide2.QtGui import QPainter, QColor + from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout class CircleItem(QObject): - X = 0 # x坐标 Opacity = 1 # 透明度0~1 valueChanged = pyqtSignal() @@ -52,7 +50,6 @@ def qBound(miv, cv, mxv): class MetroCircleProgress(QWidget): - Radius = 5 # 半径 Color = QColor(24, 189, 155) # 圆圈颜色 BackgroundColor = QColor(Qt.transparent) # 背景颜色 @@ -189,7 +186,7 @@ class Window(QWidget): if __name__ == '__main__': import sys - from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) w = Window() w.show() diff --git a/QProgressBar/PercentProgressBar.py b/QProgressBar/PercentProgressBar.py index 80f5d6c7ab5ad910d9f22478685f91c58d3ec2c1..8e469103682f078d55875b2e420383f05a911b17 100644 --- a/QProgressBar/PercentProgressBar.py +++ b/QProgressBar/PercentProgressBar.py @@ -4,25 +4,23 @@ """ Created on 2018年9月4日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: PercentProgressBar @description: """ -from PyQt5.QtCore import pyqtProperty, QSize, Qt, QRectF, QTimer -from PyQt5.QtGui import QColor, QPainter, QFont -from PyQt5.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QSlider - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" +try: + from PyQt5.QtCore import pyqtProperty, QSize, Qt, QRectF, QTimer + from PyQt5.QtGui import QColor, QPainter, QFont + from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QVBoxLayout, QSlider +except ImportError: + from PySide2.QtCore import Property as pyqtProperty, QSize, Qt, QRectF, QTimer + from PySide2.QtGui import QColor, QPainter, QFont + from PySide2.QtWidgets import QApplication, QWidget, QHBoxLayout, QVBoxLayout, QSlider class PercentProgressBar(QWidget): - MinValue = 0 MaxValue = 100 Value = 0 @@ -300,8 +298,10 @@ class Window(QWidget): self.staticPercentProgressBar.showFreeArea = True self.staticPercentProgressBar.ShowSmallCircle = True vlayout.addWidget(self.staticPercentProgressBar) - vlayout.addWidget(QSlider(self, minimum=0, maximum=100, orientation=Qt.Horizontal, - valueChanged=self.staticPercentProgressBar.setValue)) + + self.slider = QSlider(self, minimum=0, maximum=100, orientation=Qt.Horizontal) + self.slider.valueChanged.connect(self.staticPercentProgressBar.setValue) + vlayout.addWidget(self.slider) self._timer.start(100) @@ -316,8 +316,9 @@ class Window(QWidget): if __name__ == '__main__': import sys import cgitb - sys.excepthook = cgitb.Hook(1, None, 5, sys.stderr, 'text') - from PyQt5.QtWidgets import QApplication + + cgitb.enable(format='text') + app = QApplication(sys.argv) w = Window() w.show() diff --git a/QProgressBar/README.md b/QProgressBar/README.md index 2c64728bc8924adfaef100cf91d10059ddbb8606..b7197dc8688a0b4bd6a6c65aac174d73d802e938 100644 --- a/QProgressBar/README.md +++ b/QProgressBar/README.md @@ -1,5 +1,14 @@ # QProgressBar +- 目录 + - [常规样式美化](#1常规样式美化) + - [圆圈进度条](#2圆圈进度条) + - [百分比进度条](#3百分比进度条) + - [Metro进度条](#4Metro进度条) + - [水波纹进度条](#5水波纹进度条) + - [圆形水位进度条](#6圆形水位进度条) + - [多彩动画进度条](#7多彩动画进度条) + ## 1、常规样式美化 [运行 SimpleStyle.py](SimpleStyle.py) @@ -29,4 +38,18 @@ 2. 利用 `QPainterPath` 矩形或者圆形作为背景 3. 用 `QPainterPath` 把y坐标用 `lineTo` 连接起来形成一个U字形+上方波浪的闭合区间 -![WaterProgressBar](ScreenShot/WaterProgressBar.gif) \ No newline at end of file +![WaterProgressBar](ScreenShot/WaterProgressBar.gif) + +## 6、圆形水位进度条 +[运行 WaterProgress.py](WaterProgress.py) + +参考 https://github.com/linuxdeepin/dtkwidget/blob/master/src/widgets/dwaterprogress.cpp + +![WaterProgressBar](ScreenShot/WaterProgress.gif) + +## 7、多彩动画进度条 +[运行 ColourfulProgress.py](ColourfulProgress.py) + +动画实现参考 qfusionstyle.cpp 中的 CE_ProgressBarContents 绘制方法 + +![ColourfulProgress](ScreenShot/ColourfulProgress.gif) diff --git a/QProgressBar/RoundProgressBar.py b/QProgressBar/RoundProgressBar.py index d406a6672134a304fa0b9393008352259595d36e..a52cef198697f28261753fb94d36d29749fd4ed6 100644 --- a/QProgressBar/RoundProgressBar.py +++ b/QProgressBar/RoundProgressBar.py @@ -4,25 +4,23 @@ """ Created on 2018年9月4日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: 界面美化.圆形进度条.CircleProgressBar @description: """ -from PyQt5.QtCore import QSize, pyqtProperty, QTimer, Qt -from PyQt5.QtGui import QColor, QPainter -from PyQt5.QtWidgets import QWidget, QHBoxLayout - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" +try: + from PyQt5.QtCore import QSize, pyqtProperty, QTimer, Qt + from PyQt5.QtGui import QColor, QPainter + from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout +except ImportError: + from PySide2.QtCore import QSize, Property as pyqtProperty, QTimer, Qt + from PySide2.QtGui import QColor, QPainter + from PySide2.QtWidgets import QApplication, QWidget, QHBoxLayout class CircleProgressBar(QWidget): - Color = QColor(24, 189, 155) # 圆圈颜色 Clockwise = True # 顺时针还是逆时针 Delta = 36 @@ -105,7 +103,7 @@ class Window(QWidget): if __name__ == '__main__': import sys - from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) w = Window() w.show() diff --git a/QProgressBar/ScreenShot/ColourfulProgress.gif b/QProgressBar/ScreenShot/ColourfulProgress.gif new file mode 100644 index 0000000000000000000000000000000000000000..3489cd96cd5419930e2a78db5dc95fd99ddd1784 Binary files /dev/null and b/QProgressBar/ScreenShot/ColourfulProgress.gif differ diff --git a/QProgressBar/ScreenShot/WaterProgress.gif b/QProgressBar/ScreenShot/WaterProgress.gif new file mode 100644 index 0000000000000000000000000000000000000000..f434c17290089053933c5c9c397c53f22f42d0ee Binary files /dev/null and b/QProgressBar/ScreenShot/WaterProgress.gif differ diff --git a/QProgressBar/SimpleStyle.py b/QProgressBar/SimpleStyle.py index 2ad17c8e7e4e18804c7bc433f820ed9cb60f2a98..b8aa5b73b92b843c7674b2caab12b8b0fa03bc65 100644 --- a/QProgressBar/SimpleStyle.py +++ b/QProgressBar/SimpleStyle.py @@ -1,26 +1,26 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -''' +""" Created on 2018年1月30日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: SimpleStyle @description: -''' -from random import randint -import sys - -from PyQt5.QtCore import QTimer -from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QProgressBar +""" +import sys +from random import randint -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" -__Version__ = "Version 1.0" +try: + from PyQt5.QtCore import QTimer + from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QProgressBar +except ImportError: + from PySide2.QtCore import QTimer + from PySide2.QtWidgets import QWidget, QApplication, QVBoxLayout, QProgressBar -StyleSheet = ''' +StyleSheet = """ /*设置红色进度条*/ #RedProgressBar { text-align: center; /*进度值居中*/ @@ -50,7 +50,7 @@ StyleSheet = ''' width: 10px; /*区块宽度*/ margin: 0.5px; } -''' +""" class ProgressBar(QProgressBar): diff --git a/QProgressBar/WaterProgress.py b/QProgressBar/WaterProgress.py new file mode 100644 index 0000000000000000000000000000000000000000..97bbc20178a005846bd017715fcdf9ab9e8d8e54 --- /dev/null +++ b/QProgressBar/WaterProgress.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2021/1/1 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: WaterProgress +@description: +""" +import sys + +try: + from PyQt5.QtCore import QTimer + from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout +except ImportError: + from PySide2.QtCore import QTimer + from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout + +from Lib.DWaterProgress import DWaterProgress + + +class WaterProgressWindow(QWidget): + + def __init__(self, *args, **kwargs): + super(WaterProgressWindow, self).__init__(*args, **kwargs) + layout = QVBoxLayout(self) + + self.progress = DWaterProgress(self) + self.progress.setFixedSize(100, 100) + self.progress.setValue(0) + self.progress.start() + + layout.addWidget(self.progress) + + self.timer = QTimer(self, timeout=self.updateProgress) + self.timer.start(50) + + def updateProgress(self): + value = self.progress.value() + if value == 100: + self.progress.setValue(0) + else: + self.progress.setValue(value + 1) + + +if __name__ == '__main__': + import cgitb + + cgitb.enable(format='text') + app = QApplication(sys.argv) + w = WaterProgressWindow() + w.show() + sys.exit(app.exec_()) diff --git a/QProgressBar/WaterProgressBar.py b/QProgressBar/WaterProgressBar.py index 5704fe06839bdfdaac65424a38113a6dee144958..30aa6d43f49427e008f3ca0e8805e96c12abe79d 100644 --- a/QProgressBar/WaterProgressBar.py +++ b/QProgressBar/WaterProgressBar.py @@ -1,27 +1,27 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Created on 2018年4月1日 -# author: Irony -# site: https://pyqt5.com , https://github.com/892768447 -# email: 892768447@qq.com -# file: WaterProgressBar -# description: - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 - +""" +Created on 2018年4月1日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: WaterProgressBar +@description: +""" from random import randint -from PyQt5.Qt import QSpinBox -from PyQt5.QtCore import QTimer -from PyQt5.QtGui import QPixmap, QIcon -from PyQt5.QtWidgets import QWidget, QFormLayout, QRadioButton, QPushButton,\ - QColorDialog +try: + from PyQt5.QtCore import QTimer + from PyQt5.QtGui import QPixmap, QIcon + from PyQt5.QtWidgets import QApplication, QWidget, QFormLayout, QRadioButton, QPushButton, QColorDialog, \ + QSpinBox +except ImportError: + from PySide2.QtCore import QTimer + from PySide2.QtGui import QPixmap, QIcon + from PySide2.QtWidgets import QApplication, QWidget, QFormLayout, QRadioButton, QPushButton, QColorDialog, \ + QSpinBox from Lib.WaterRippleProgressBar import WaterRippleProgressBar # @UnresolvedImport @@ -53,11 +53,13 @@ class Window(QWidget): layout.addWidget( QPushButton('设置随机0-100固定值', self, clicked=self.setRandomValue)) - layout.addRow('振幅(浪高)', - QSpinBox(self, value=1, valueChanged=self.bar.setWaterHeight)) + spb1 = QSpinBox(self, value=1) + spb1.valueChanged.connect(self.bar.setWaterHeight) + layout.addRow('振幅(浪高)', spb1) - layout.addRow('周期(密度)', - QSpinBox(self, value=1, valueChanged=self.bar.setWaterDensity)) + spb2 = QSpinBox(self, value=1) + spb2.valueChanged.connect(self.bar.setWaterDensity) + layout.addRow('周期(密度)', spb2) layout.addWidget(self.bar) @@ -116,9 +118,10 @@ class Window(QWidget): value = 0 self.bar.setValue(value) + if __name__ == '__main__': import sys - from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) w = Window() w.show() diff --git a/QPropertyAnimation/Data/1.png b/QPropertyAnimation/Data/1.png new file mode 100644 index 0000000000000000000000000000000000000000..f53f32a98d5f00dd28c05c42c2a5c7599b2a7108 Binary files /dev/null and b/QPropertyAnimation/Data/1.png differ diff --git a/QPropertyAnimation/Data/2.png b/QPropertyAnimation/Data/2.png new file mode 100644 index 0000000000000000000000000000000000000000..b6a92262815c100b59ed38d45bc65ebc326d9510 Binary files /dev/null and b/QPropertyAnimation/Data/2.png differ diff --git a/QPropertyAnimation/Data/pointtool.pyx b/QPropertyAnimation/Data/pointtool.pyx index bd47b6678072cc784648eb7b662f1caefc36280a..3300cc6679144116ddbdf29f3aaa625b123d9908 100644 --- a/QPropertyAnimation/Data/pointtool.pyx +++ b/QPropertyAnimation/Data/pointtool.pyx @@ -1,10 +1,8 @@ from libc.math cimport pow - def getDistance(p1, p2): return pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2) - def findClose(points): cdef int plen = len(points) cdef int i = 0 diff --git a/QPropertyAnimation/Data/setup.py b/QPropertyAnimation/Data/setup.py index 09839f6ee37088b4c389cc97e751fe7e305d304b..3112b44c03ec2be395ba690ad17528ea53a03a06 100644 --- a/QPropertyAnimation/Data/setup.py +++ b/QPropertyAnimation/Data/setup.py @@ -10,8 +10,9 @@ # ) from distutils.core import setup + from Cython.Build import cythonize setup( ext_modules=cythonize("pointtool.pyx"), -) \ No newline at end of file +) diff --git a/QPropertyAnimation/FadeInOut.py b/QPropertyAnimation/FadeInOut.py index edf30eae42a743fac1e0f7b535d1ef189d1389ce..024c65c27346d703081aaa408219b0652270b90e 100644 --- a/QPropertyAnimation/FadeInOut.py +++ b/QPropertyAnimation/FadeInOut.py @@ -1,20 +1,22 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from PyQt5.QtCore import QPropertyAnimation -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton -# Created on 2018年6月14日 -# author: Irony -# site: https://pyqt5.com , https://github.com/892768447 -# email: 892768447@qq.com -# file: FadeInOut -# description: -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 +""" +Created on 2018年6月14日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: FadeInOut +@description: +""" + +try: + from PyQt5.QtCore import QPropertyAnimation + from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton +except ImportError: + from PySide2.QtCore import QPropertyAnimation + from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton class Window(QWidget): @@ -55,7 +57,7 @@ class Window(QWidget): if __name__ == '__main__': import sys - from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) w = Window() w.show() diff --git a/QPropertyAnimation/FlipWidgetAnimation.py b/QPropertyAnimation/FlipWidgetAnimation.py new file mode 100644 index 0000000000000000000000000000000000000000..1adc2648fe282f9de3ed7dc587bc965568ab7816 --- /dev/null +++ b/QPropertyAnimation/FlipWidgetAnimation.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2019年5月15日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: 翻转动画 +@description: +""" + +try: + from PyQt5.QtCore import Qt, pyqtSignal, QTimer + from PyQt5.QtGui import QPixmap + from PyQt5.QtWidgets import QApplication, QStackedWidget, QLabel +except ImportError: + from PySide2.QtCore import Qt, Signal as pyqtSignal, QTimer + from PySide2.QtGui import QPixmap + from PySide2.QtWidgets import QApplication, QStackedWidget, QLabel + +from Lib.FlipWidget import FlipWidget + + +class LoginWidget(QLabel): + # 只是显示登录界面截图 + + windowClosed = pyqtSignal() + windowChanged = pyqtSignal() + + def __init__(self, *args, **kwargs): + super(LoginWidget, self).__init__(*args, **kwargs) + self.setPixmap(QPixmap('Data/1.png')) + + def mousePressEvent(self, event): + super(LoginWidget, self).mousePressEvent(event) + pos = event.pos() + if pos.y() <= 40: + if pos.x() > self.width() - 30: + # 点击关闭按钮的地方 + self.windowClosed.emit() + elif self.width() - 90 <= pos.x() <= self.width() - 60: + # 点击切换按钮 + self.windowChanged.emit() + + +class SettingWidget(QLabel): + # 只是显示设置界面截图 + + windowClosed = pyqtSignal() + windowChanged = pyqtSignal() + + def __init__(self, *args, **kwargs): + super(SettingWidget, self).__init__(*args, **kwargs) + self.setPixmap(QPixmap('Data/2.png')) + + def mousePressEvent(self, event): + super(SettingWidget, self).mousePressEvent(event) + pos = event.pos() + if pos.y() >= self.height() - 30: + if self.width() - 95 <= pos.x() <= self.width() - 10: + # 点击切换按钮 + self.windowChanged.emit() + elif pos.y() <= 40: + if pos.x() > self.width() - 30: + # 点击关闭按钮的地方 + self.windowClosed.emit() + + +class Window(QStackedWidget): + # 主窗口 + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + self.resize(428, 329) + self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint) + + # 这个是动画窗口,先创建不显示 + self.flipWidget = FlipWidget() + self.flipWidget.finished.connect(self.showWidget) + + # 登录窗口 + self.loginWidget = LoginWidget(self) + self.loginWidget.windowClosed.connect(self.close) + self.loginWidget.windowChanged.connect(self.jumpSettingWidget) + self.addWidget(self.loginWidget) + + # 设置窗口 + self.settingWidget = SettingWidget(self) + self.settingWidget.windowClosed.connect(self.close) + self.settingWidget.windowChanged.connect(self.jumpLoginWidget) + self.addWidget(self.settingWidget) + + def showWidget(self): + # 显示主窗口隐藏动画窗口 + self.setWindowOpacity(1) + QTimer.singleShot(100, self.flipWidget.hide) + + def jumpLoginWidget(self): + # 翻转到登录界面 + self.setWindowOpacity(0) # 类似隐藏,但是保留了任务栏 + self.setCurrentWidget(self.loginWidget) # 很重要,一定要先切换过去,不然会导致第一次截图有误 + image1 = self.loginWidget.grab() # 截图1 + image2 = self.settingWidget.grab() # 截图2 + padding = 100 # 扩大边距 @UnusedVariable + self.flipWidget.setGeometry(self.geometry()) + # .adjusted(-padding, -padding, padding, padding)) + self.flipWidget.updateImages(FlipWidget.Right, image2, image1) + + def jumpSettingWidget(self): + # 翻转到设置界面 + self.setWindowOpacity(0) # 类似隐藏,但是保留了任务栏 + self.setCurrentWidget(self.settingWidget) # 很重要,一定要先切换过去,不然会导致第一次截图有误 + image1 = self.loginWidget.grab() # 截图1 + image2 = self.settingWidget.grab() # 截图2 + padding = 100 # 扩大边距 @UnusedVariable + self.flipWidget.setGeometry(self.geometry()) + # .adjusted(-padding, -padding, padding, padding)) + self.flipWidget.updateImages(FlipWidget.Left, image1, image2) + + +if __name__ == '__main__': + import sys + + app = QApplication(sys.argv) + w = Window() + w.show() + sys.exit(app.exec_()) diff --git a/QPropertyAnimation/Lib/FlipWidget.py b/QPropertyAnimation/Lib/FlipWidget.py new file mode 100644 index 0000000000000000000000000000000000000000..6878e632626a86af096b7e780cbd259eb900a4c6 --- /dev/null +++ b/QPropertyAnimation/Lib/FlipWidget.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2019年5月15日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: FlipWidget +@description: 动画翻转窗口 +""" + +try: + from PyQt5.QtCore import pyqtSignal, pyqtProperty, Qt, QPropertyAnimation, QEasingCurve, QPointF + from PyQt5.QtGui import QPainter, QTransform + from PyQt5.QtWidgets import QWidget +except ImportError: + from PySide2.QtCore import Signal as pyqtSignal, Property as pyqtProperty, Qt, QPropertyAnimation, \ + QEasingCurve, QPointF + from PySide2.QtGui import QPainter, QTransform + from PySide2.QtWidgets import QWidget + + +class FlipWidget(QWidget): + Left = 0 # 从右往左 + Right = 1 # 从左往右 + Scale = 3 # 图片缩放比例 + finished = pyqtSignal() + + def __init__(self, *args, **kwargs): + super(FlipWidget, self).__init__(*args, **kwargs) + # 无边框无任务栏 + self.setWindowFlags(self.windowFlags() | + Qt.FramelessWindowHint | Qt.SubWindow) + # 背景透明 + self.setAttribute(Qt.WA_TranslucentBackground, True) + # 翻转角度 + self._angle = 0 + # 属性动画针对自定义属性`angle` + self._animation = QPropertyAnimation(self, b'angle', self) + self._animation.setDuration(550) + self._animation.setEasingCurve(QEasingCurve.OutInQuad) + self._animation.finished.connect(self.finished.emit) + + @pyqtProperty(int) + def angle(self): + return self._angle + + @angle.setter + def angle(self, angle): + self._angle = angle + self.update() + + def updateImages(self, direction, image1, image2): + """设置两张切换图 + :param direction: 方向 + :param image1: 图片1 + :param image2: 图片2 + """ + self.image1 = image1 + self.image2 = image2 + self.show() + self._angle = 0 + # 根据方向设置动画的初始和结束值 + if direction == self.Right: + self._animation.setStartValue(1) + self._animation.setEndValue(-180) + elif direction == self.Left: + self._animation.setStartValue(1) + self._animation.setEndValue(180) + self._animation.start() + + def paintEvent(self, event): + super(FlipWidget, self).paintEvent(event) + + if hasattr(self, 'image1') and hasattr(self, 'image2') and self.isVisible(): + + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing, True) + painter.setRenderHint(QPainter.SmoothPixmapTransform, True) + + # 变换 + transform = QTransform() + # 把圆心设置为矩形中心 + transform.translate(self.width() / 2, self.height() / 2) + + if self._angle >= -90 and self._angle <= 90: + # 当翻转角度在90范围内显示第一张图,且从大图缩放到小图的过程 + painter.save() + # 设置翻转角度 + transform.rotate(self._angle, Qt.YAxis) + painter.setTransform(transform) + # 缩放图片高度 + width = self.image1.width() / 2 + height = int(self.image1.height() * + (1 - abs(self._angle / self.Scale) / 100)) + image = self.image1.scaled( + self.image1.width(), height, + Qt.IgnoreAspectRatio, Qt.SmoothTransformation) + painter.drawPixmap( + QPointF(-width, -height / 2), image) + painter.restore() + else: + # 当翻转角度在90范围内显示第二张图,且从小图缩放到原图的过程 + painter.save() + if self._angle > 0: + angle = 180 + self._angle + else: + angle = self._angle - 180 + # 设置翻转角度, 注意这里角度有差异 + transform.rotate(angle, Qt.YAxis) + painter.setTransform(transform) + # 缩放图片高度 + width = self.image2.width() / 2 + height = int(self.image2.height() * + (1 - ((360 - abs(angle)) / self.Scale / 100))) + image = self.image2.scaled( + self.image2.width(), height, + Qt.IgnoreAspectRatio, Qt.SmoothTransformation) + painter.drawPixmap( + QPointF(-width, -height / 2), image) + painter.restore() diff --git a/QPropertyAnimation/Lib/SlidingStackedWidget.py b/QPropertyAnimation/Lib/SlidingStackedWidget.py index aae37a147668b41e40ee3c707b6ca881f6d456db..b10bb341dde439061a8c01eb0a92fd4a4a43a1fe 100644 --- a/QPropertyAnimation/Lib/SlidingStackedWidget.py +++ b/QPropertyAnimation/Lib/SlidingStackedWidget.py @@ -4,25 +4,23 @@ """ Created on 2018年11月24日 author: Irony -site: https://pyqt5.com , https://github.com/892768447 +site: https://pyqt.site , https://github.com/PyQt5 email: 892768447@qq.com file: description: 参考 http://qt.shoutwiki.com/wiki/Extending_QStackedWidget_for_sliding_page_animations_in_Qt """ -from PyQt5.QtCore import Qt, pyqtProperty, QEasingCurve, QPoint, \ - QPropertyAnimation, QParallelAnimationGroup, QTimer -from PyQt5.QtWidgets import QStackedWidget - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 +try: + from PyQt5.QtCore import Qt, pyqtProperty, QEasingCurve, QPoint, QPropertyAnimation, \ + QParallelAnimationGroup, QTimer + from PyQt5.QtWidgets import QStackedWidget +except ImportError: + from PySide2.QtCore import Qt, Property as pyqtProperty, QEasingCurve, QPoint, QPropertyAnimation, \ + QParallelAnimationGroup, QTimer + from PySide2.QtWidgets import QStackedWidget class SlidingStackedWidget(QStackedWidget): - LEFT2RIGHT, RIGHT2LEFT, TOP2BOTTOM, BOTTOM2TOP, AUTOMATIC = range(5) def __init__(self, *args, **kwargs): @@ -119,7 +117,7 @@ class SlidingStackedWidget(QStackedWidget): self._active = 1 _now = self.currentIndex() _next = self.indexOf(widget) - if _now == next: + if _now == _next: self._active = 0 return @@ -210,7 +208,7 @@ class SlidingStackedWidget(QStackedWidget): def animationDoneSlot(self): """动画结束处理函数""" # 由于重写了setCurrentIndex方法所以这里要用父类本身的方法 -# self.setCurrentIndex(self._next) + # self.setCurrentIndex(self._next) QStackedWidget.setCurrentIndex(self, self._next) w = self.widget(self._now) w.hide() diff --git a/QPropertyAnimation/Lib/UiImageSlider.py b/QPropertyAnimation/Lib/UiImageSlider.py index a47fb819e19702287b366dd9ffd08a863cfba1d7..78be1306ca98d90d8e283000bb1135f6a0f2da6a 100644 --- a/QPropertyAnimation/Lib/UiImageSlider.py +++ b/QPropertyAnimation/Lib/UiImageSlider.py @@ -6,7 +6,11 @@ # # WARNING! All changes made in this file will be lost! -from PyQt5 import QtCore, QtGui, QtWidgets +try: + from PyQt5 import QtCore, QtWidgets +except ImportError: + from PySide2 import QtCore, QtWidgets + class Ui_Form(object): def setupUi(self, Form): @@ -89,14 +93,15 @@ class Ui_Form(object): self.pushButtonStart.setText(_translate("Form", "轮播开始")) self.pushButtonStop.setText(_translate("Form", "轮播停止")) + from Lib.SlidingStackedWidget import SlidingStackedWidget if __name__ == "__main__": import sys + app = QtWidgets.QApplication(sys.argv) Form = QtWidgets.QWidget() ui = Ui_Form() ui.setupUi(Form) Form.show() sys.exit(app.exec_()) - diff --git a/QPropertyAnimation/MenuAnimation.py b/QPropertyAnimation/MenuAnimation.py index e8a84c5629776b6b17194c779aea193cc01da47d..4e8c7cdc1af9cfb592609ad8d38018128d8d4695 100644 --- a/QPropertyAnimation/MenuAnimation.py +++ b/QPropertyAnimation/MenuAnimation.py @@ -4,20 +4,18 @@ """ Created on 2018年8月22日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: MenuAnimation @description: """ -from PyQt5.QtCore import QPropertyAnimation, QEasingCurve, QRect -from PyQt5.QtWidgets import QWidget, QMenu, QApplication - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" +try: + from PyQt5.QtCore import QPropertyAnimation, QEasingCurve, QRect + from PyQt5.QtWidgets import QWidget, QMenu, QApplication +except ImportError: + from PySide2.QtCore import QPropertyAnimation, QEasingCurve, QRect + from PySide2.QtWidgets import QWidget, QMenu, QApplication class Window(QWidget): @@ -61,7 +59,8 @@ class Window(QWidget): if __name__ == '__main__': import sys import cgitb - sys.excepthook = cgitb.Hook(1, None, 5, sys.stderr, 'text') + + cgitb.enable(format='text') app = QApplication(sys.argv) w = Window() w.show() diff --git a/QPropertyAnimation/PageSwitching.py b/QPropertyAnimation/PageSwitching.py index 739f4040f93c9abe87d2e9b6d3ff9647321dddcd..9477eb4edff849e29cb86825615b123ef05dfa64 100644 --- a/QPropertyAnimation/PageSwitching.py +++ b/QPropertyAnimation/PageSwitching.py @@ -4,27 +4,25 @@ """ Created on 2018年11月24日 author: Irony -site: https://pyqt5.com , https://github.com/892768447 +site: https://pyqt.site , https://github.com/PyQt5 email: 892768447@qq.com file: PageSwitching description: """ import os -from PyQt5.QtCore import QEasingCurve, Qt -from PyQt5.QtGui import QPixmap -from PyQt5.QtWidgets import QWidget, QLabel +try: + from PyQt5.QtCore import QEasingCurve, Qt + from PyQt5.QtGui import QPixmap + from PyQt5.QtWidgets import QWidget, QLabel, QApplication +except ImportError: + from PySide2.QtCore import QEasingCurve, Qt + from PySide2.QtGui import QPixmap + from PySide2.QtWidgets import QWidget, QLabel, QApplication from Lib.UiImageSlider import Ui_Form # @UnresolvedImport -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 - - class ImageSliderWidget(QWidget, Ui_Form): def __init__(self, *args, **kwargs): @@ -76,7 +74,7 @@ class ImageSliderWidget(QWidget, Ui_Form): if __name__ == '__main__': import sys - from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) w = ImageSliderWidget() w.show() diff --git a/QPropertyAnimation/README.md b/QPropertyAnimation/README.md index 6becc907869ade4bb2300223138474b3e506d962..25ecc4df4389e885a29e2fef532dc22eec8401c4 100644 --- a/QPropertyAnimation/README.md +++ b/QPropertyAnimation/README.md @@ -1,6 +1,14 @@ # QPropertyAnimation -# 1、窗口淡入淡出 +- 目录 + - [窗口淡入淡出](#1窗口淡入淡出) + - [右键菜单动画](#2右键菜单动画) + - [点阵特效](#3点阵特效) + - [页面切换/图片轮播动画](#4页面切换图片轮播动画) + - [窗口抖动](#5窗口抖动) + - [窗口翻转动画(仿QQ)](#6窗口翻转动画仿QQ) + +## 1、窗口淡入淡出 [运行 FadeInOut.py](FadeInOut.py) 1. 使用`QPropertyAnimation`对窗口的`windowOpacity`透明度属性进行修改 @@ -13,7 +21,7 @@ ![FadeInOut](ScreenShot/FadeInOut.gif) -# 2、右键菜单动画 +## 2、右键菜单动画 [运行 MenuAnimation.py](MenuAnimation.py) 1. 使用`QPropertyAnimation`对菜单控件的`geometry`属性进行修改 @@ -96,8 +104,8 @@ def findClose(points): ![RlatticeEffect](ScreenShot/RlatticeEffect.gif) -## 5、页面切换/图片轮播动画 -[运行 PageSwitching.py](PageSwitching.py) +## 4、页面切换/图片轮播动画 +[运行 PageSwitching.py](PageSwitching.py) | [查看 UiImageSlider.ui](Data/UiImageSlider.ui) 1. 使用`QPropertyAnimation`对`QStackedWidget`中的子控件进行pos位移操作实现动画切换特效 1. 主要代码参考http://qt.shoutwiki.com/wiki/Extending_QStackedWidget_for_sliding_page_animations_in_Qt @@ -110,4 +118,22 @@ def findClose(points): 1. `setCurrentIndex` 切换到指定页 1. `autoStart(msec)` 轮播模式, 默认是3000毫秒 -![PageSwitching](ScreenShot/PageSwitching.gif) \ No newline at end of file +![PageSwitching](ScreenShot/PageSwitching.gif) + +## 5、窗口抖动 +[运行 ShakeWindow.py](ShakeWindow.py) + +通过`QPropertyAnimation`对控件的pos属性进行死去活来的修改 + +![ShakeWindow](ScreenShot/ShakeWindow.gif) + +## 6、窗口翻转动画(仿QQ) +[运行 FlipWidgetAnimation.py](FlipWidgetAnimation.py) + +1. 用了两个`QLabel`来显示模拟的图片界面,并实现鼠标点击模拟真实的窗口对应位置点击 +2. 用了`QStackedWidget`来存放上面的两个界面`QLabel` +3. 点击切换时主要是对上面的两个界面进行截图并传递给翻转动画窗口 +4. 通过`setWindowOpacity`控制主窗口的显示隐藏(保留任务栏),当然也可以用`hide` +5. 动画窗口`FlipWidget.py`主要实现两张图片的翻转显示,考虑到0-90和90-180之前的情况,以及图片的缩放动画 + +![FlipWidgetAnimation](ScreenShot/FlipWidgetAnimation.gif) \ No newline at end of file diff --git a/QPropertyAnimation/RlatticeEffect.py b/QPropertyAnimation/RlatticeEffect.py index 7c4433740ed71176f192b546b6c53bbce940071b..a57c37b1c2a2f69dc1a9ca38d21b6e69a3449c47 100644 --- a/QPropertyAnimation/RlatticeEffect.py +++ b/QPropertyAnimation/RlatticeEffect.py @@ -4,7 +4,7 @@ """ Created on 2018年11月22日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: RlatticeEffect @description: @@ -12,29 +12,29 @@ Created on 2018年11月22日 from random import random from time import time -from PyQt5.QtCore import QPropertyAnimation, QObject, pyqtProperty, QEasingCurve,\ - Qt, QRectF, pyqtSignal -from PyQt5.QtGui import QColor, QPainterPath, QPainter -from PyQt5.QtWidgets import QWidget - - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 - +try: + from PyQt5.QtCore import QPropertyAnimation, QObject, QEasingCurve, Qt, QRectF, pyqtSignal, pyqtProperty + from PyQt5.QtGui import QColor, QPainterPath, QPainter + from PyQt5.QtWidgets import QApplication, QWidget +except ImportError: + from PySide2.QtCore import QPropertyAnimation, QObject, QEasingCurve, Qt, QRectF, Signal as pyqtSignal, \ + Property as pyqtProperty + from PySide2.QtGui import QColor, QPainterPath, QPainter + from PySide2.QtWidgets import QApplication, QWidget try: from Lib import pointtool # @UnusedImport @UnresolvedImport + getDistance = pointtool.getDistance findClose = pointtool.findClose except: import math + def getDistance(p1, p2): return math.pow(p1.x - p2.x, 2) + math.pow(p1.y - p2.y, 2) + def findClose(points): plen = len(points) for i in range(plen): @@ -66,8 +66,7 @@ class Target: class Point(QObject): - - valueChanged = pyqtSignal() + valueChanged = pyqtSignal(int) def __init__(self, x, ox, y, oy, *args, **kwargs): super(Point, self).__init__(*args, **kwargs) @@ -90,12 +89,12 @@ class Point(QObject): # 属性动画 if not hasattr(self, 'xanimation'): self.xanimation = QPropertyAnimation( - self, b'x', self, valueChanged=self.valueChanged.emit, - easingCurve=QEasingCurve.InOutSine) + self, b'x', self, easingCurve=QEasingCurve.InOutSine) + self.xanimation.valueChanged.connect(self.valueChanged.emit) self.yanimation = QPropertyAnimation( - self, b'y', self, valueChanged=self.valueChanged.emit, - easingCurve=QEasingCurve.InOutSine, - finished=self.updateAnimation) + self, b'y', self, easingCurve=QEasingCurve.InOutSine) + self.yanimation.valueChanged.connect(self.valueChanged.emit) + self.yanimation.finished.connect(self.updateAnimation) self.updateAnimation() def updateAnimation(self): @@ -138,6 +137,9 @@ class Window(QWidget): self.target = Target(self.width() / 2, self.height() / 2) self.initPoints() + def update(self, *args): + super(Window, self).update() + def paintEvent(self, event): super(Window, self).paintEvent(event) painter = QPainter() @@ -220,8 +222,9 @@ class Window(QWidget): if __name__ == '__main__': import sys import cgitb - sys.excepthook = cgitb.enable(1, None, 5, '') - from PyQt5.QtWidgets import QApplication + + cgitb.enable(format='text') + app = QApplication(sys.argv) w = Window() w.show() diff --git a/QPropertyAnimation/ScreenShot/FlipWidgetAnimation.gif b/QPropertyAnimation/ScreenShot/FlipWidgetAnimation.gif new file mode 100644 index 0000000000000000000000000000000000000000..106f90106fb77bb34d0fd8ac98f9d7b30b829795 Binary files /dev/null and b/QPropertyAnimation/ScreenShot/FlipWidgetAnimation.gif differ diff --git a/QPropertyAnimation/ScreenShot/ShakeWindow.gif b/QPropertyAnimation/ScreenShot/ShakeWindow.gif new file mode 100644 index 0000000000000000000000000000000000000000..3100a51a450b8493eb6b3403c0993f2ebe2a1185 Binary files /dev/null and b/QPropertyAnimation/ScreenShot/ShakeWindow.gif differ diff --git a/QPropertyAnimation/ShakeWindow.py b/QPropertyAnimation/ShakeWindow.py new file mode 100644 index 0000000000000000000000000000000000000000..6c7d9c95325dbb7889d307a29cd3b64c1b2b5223 --- /dev/null +++ b/QPropertyAnimation/ShakeWindow.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2019年5月8日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: ShakeWindow +@description: 抖动动画 +""" + +try: + from PyQt5.QtCore import QPropertyAnimation, QPoint + from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton +except ImportError: + from PySide2.QtCore import QPropertyAnimation, QPoint + from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton + + +class Window(QWidget): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + self.resize(400, 400) + layout = QVBoxLayout(self) + layout.addWidget(QPushButton('抖动', self, clicked=self.doShake)) + + def doShake(self): + self.doShakeWindow(self) + + # 下面这个方法可以做成这样的封装给任何控件 + def doShakeWindow(self, target): + """窗口抖动动画 + :param target: 目标控件 + """ + if hasattr(target, '_shake_animation'): + # 如果已经有该对象则跳过 + return + + animation = QPropertyAnimation(target, b'pos', target) + target._shake_animation = animation + animation.finished.connect(lambda: delattr(target, '_shake_animation')) + + pos = target.pos() + x, y = pos.x(), pos.y() + + animation.setDuration(200) + animation.setLoopCount(2) + animation.setKeyValueAt(0, QPoint(x, y)) + animation.setKeyValueAt(0.09, QPoint(x + 2, y - 2)) + animation.setKeyValueAt(0.18, QPoint(x + 4, y - 4)) + animation.setKeyValueAt(0.27, QPoint(x + 2, y - 6)) + animation.setKeyValueAt(0.36, QPoint(x + 0, y - 8)) + animation.setKeyValueAt(0.45, QPoint(x - 2, y - 10)) + animation.setKeyValueAt(0.54, QPoint(x - 4, y - 8)) + animation.setKeyValueAt(0.63, QPoint(x - 6, y - 6)) + animation.setKeyValueAt(0.72, QPoint(x - 8, y - 4)) + animation.setKeyValueAt(0.81, QPoint(x - 6, y - 2)) + animation.setKeyValueAt(0.90, QPoint(x - 4, y - 0)) + animation.setKeyValueAt(0.99, QPoint(x - 2, y + 2)) + animation.setEndValue(QPoint(x, y)) + + animation.start(animation.DeleteWhenStopped) + + +if __name__ == '__main__': + import sys + + app = QApplication(sys.argv) + w = Window() + w.show() + sys.exit(app.exec_()) diff --git a/QProxyStyle/Lib/TabBarStyle.py b/QProxyStyle/Lib/TabBarStyle.py index 12d7c8ab2bd2e9ad8db89d4e29ab2ff83bf26f47..59406ccfb4e204beb27453f7d7c4ce2c69280cf6 100644 --- a/QProxyStyle/Lib/TabBarStyle.py +++ b/QProxyStyle/Lib/TabBarStyle.py @@ -4,7 +4,7 @@ """ Created on 2018年12月27日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: TabBarStyle @description: @@ -13,13 +13,6 @@ from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QProxyStyle -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" - - class TabBarStyle(QProxyStyle): def sizeFromContents(self, types, option, size, widget): diff --git a/QProxyStyle/Lib/TabCornerStyle.py b/QProxyStyle/Lib/TabCornerStyle.py new file mode 100644 index 0000000000000000000000000000000000000000..fe7935402fcac4bc0253b6205ab87d4fc09e4395 --- /dev/null +++ b/QProxyStyle/Lib/TabCornerStyle.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2021年06月23日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: TabCornerStyle +@description: +""" + +try: + from PyQt5.QtCore import QRect + from PyQt5.QtWidgets import QProxyStyle, QStyle +except ImportError: + from PySide2.QtCore import QRect + from PySide2.QtWidgets import QProxyStyle, QStyle + + +class TabCornerStyle(QProxyStyle): + + def subElementRect(self, element, option, widget): + try: + rect = super(TabCornerStyle, self).subElementRect(element, option, widget) + if element == QStyle.SE_TabWidgetRightCorner and rect.isValid(): + # 标签tab的矩形范围 + tab_rect = self.subElementRect(QStyle.SE_TabWidgetTabBar, option, widget) + # 内容面板的矩形范围 + panel_rect = self.subElementRect(QStyle.SE_TabWidgetTabPane, option, widget) + ext_height = 2 * self.pixelMetric(QStyle.PM_TabBarBaseHeight, option, widget) + # 修正过后填充的矩形范围 + cor_rect = QRect(tab_rect.x() + tab_rect.width() + ext_height, tab_rect.y() + ext_height, + panel_rect.width() - tab_rect.width() - 2 * ext_height, + tab_rect.height() - 2 * ext_height) + return cor_rect + return rect + except Exception as e: + print(e) + return QRect() diff --git a/QProxyStyle/README.md b/QProxyStyle/README.md index bf6f59812d69d1fca9f1d45a1acf44a6cbc63349..d64072bf2d4ac26e040d0e3954acbf89ab14b883 100644 --- a/QProxyStyle/README.md +++ b/QProxyStyle/README.md @@ -1,5 +1,9 @@ # QProxyStyle +- 目录 + - [QTabWidget Tab文字方向](#1qtabwidget-tab文字方向) + - [QTabWidget 角落控件位置](#2qtabwidget-角落控件位置) + ## 1、QTabWidget Tab文字方向 [运行 TabTextDirection.py](TabTextDirection.py) @@ -7,4 +11,15 @@ 2. `sizeFromContents` 转置size 3. `drawControl` 绘制文字 -![TabTextDirection](ScreenShot/TabTextDirection.png) \ No newline at end of file +![TabTextDirection](ScreenShot/TabTextDirection.png) + +## 2、QTabWidget 角落控件位置 +[运行 TabCornerWidget.py](TabCornerWidget.py) + +1. 通过 `app.setStyle(TabCornerStyle())` 设置代理样式 +2. `setCornerWidget` 设置自定义角落控件 + +原理是通过代理样式中对 `SE_TabWidgetRightCorner` 计算的结果进行校正,使得角落控件占满右边空白位置, +然后再配合自定义控件中使用 `QSpacerItem` 占据右边位置使得 + 号按钮居左,表现效果为 + 号按钮跟随标签的增加和减少 + +![TabCornerStyle](ScreenShot/TabCornerStyle.png) \ No newline at end of file diff --git a/QProxyStyle/ScreenShot/TabCornerStyle.png b/QProxyStyle/ScreenShot/TabCornerStyle.png new file mode 100644 index 0000000000000000000000000000000000000000..4133f625cd29132ec3625750e75220a6d8280323 Binary files /dev/null and b/QProxyStyle/ScreenShot/TabCornerStyle.png differ diff --git a/QProxyStyle/TabCornerWidget.py b/QProxyStyle/TabCornerWidget.py new file mode 100644 index 0000000000000000000000000000000000000000..39d71462158256136e1a2e62e6b2f2d30cd4dbcc --- /dev/null +++ b/QProxyStyle/TabCornerWidget.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2021年06月23日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: TabCornerWidget +@description: +""" + +try: + from PyQt5.QtCore import pyqtSignal, Qt + from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QHBoxLayout, QSpacerItem, QSizePolicy, \ + QTabWidget +except ImportError: + from PySide2.QtCore import Signal as pyqtSignal, Qt + from PySide2.QtWidgets import QApplication, QWidget, QPushButton, QHBoxLayout, QSpacerItem, QSizePolicy, \ + QTabWidget + +from QProxyStyle.Lib.TabCornerStyle import TabCornerStyle + + +class TabCornerWidget(QWidget): + signalTabAdd = pyqtSignal() + + def __init__(self, *args, **kwargs): + super(TabCornerWidget, self).__init__(*args, **kwargs) + layout = QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + self.buttonAdd = QPushButton('+', self, toolTip='添加新标签页', clicked=self.signalTabAdd.emit) + layout.addWidget(self.buttonAdd) + layout.addItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) + + def resizeEvent(self, event): + super(TabCornerWidget, self).resizeEvent(event) + # 更新按钮高度 + if hasattr(self, 'buttonAdd'): + self.buttonAdd.setFixedSize(self.height(), self.height()) + + +if __name__ == '__main__': + import sys + + app = QApplication(sys.argv) + app.setStyle(TabCornerStyle()) + + tab1 = QTabWidget() + cor1 = TabCornerWidget(tab1) + cor1.signalTabAdd.connect(lambda: tab1.addTab(QWidget(tab1), 'tab' + str(tab1.count() + 1))) + tab1.setCornerWidget(cor1, Qt.TopRightCorner) + tab1.show() + + tab2 = QTabWidget() + tab2.setTabPosition(QTabWidget.South) # tab 标签方向 + cor2 = TabCornerWidget(tab2) + cor2.signalTabAdd.connect(lambda: tab2.addTab(QWidget(tab2), 'tab' + str(tab2.count() + 1))) + tab2.setCornerWidget(cor2, Qt.BottomRightCorner) + tab2.show() + + for i in range(10): + tab1.addTab(QWidget(tab1), 'tab' + str(i + 1)) + tab2.addTab(QWidget(tab1), 'tab' + str(i + 1)) + + sys.exit(app.exec_()) diff --git a/QProxyStyle/TabTextDirection.py b/QProxyStyle/TabTextDirection.py index 22dc4d869180c0e8b3c42c8d2115b07cc95a3760..52ba56872a17268f11f010c1f00708b87e364861 100644 --- a/QProxyStyle/TabTextDirection.py +++ b/QProxyStyle/TabTextDirection.py @@ -4,7 +4,7 @@ """ Created on 2018年12月27日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: TestTabWidget @description: @@ -14,13 +14,6 @@ from PyQt5.QtWidgets import QTabWidget, QLabel, QWidget, QGridLayout from Lib.TabBarStyle import TabBarStyle -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" - - class TabWidget(QTabWidget): def __init__(self, *args, **kwargs): @@ -44,6 +37,7 @@ class Window(QWidget): if __name__ == '__main__': import sys from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) app.setStyle(TabBarStyle()) w = Window() diff --git a/QPushButton/BottomLineProgress.py b/QPushButton/BottomLineProgress.py index 491124f90634d738ec917ce6028b6ac6f5d5f5f4..00352f5447cea143f60f17ee040964ddeaf97d5f 100644 --- a/QPushButton/BottomLineProgress.py +++ b/QPushButton/BottomLineProgress.py @@ -1,25 +1,26 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -''' +""" Created on 2018年2月1日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: PushButtonLine @description: -''' -from random import randint -import sys - -from PyQt5.QtCore import QTimer, QThread, pyqtSignal -from PyQt5.QtGui import QPainter, QColor, QPen -from PyQt5.QtWidgets import QPushButton, QApplication, QWidget, QVBoxLayout +""" +import sys +from random import randint -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" -__Version__ = "Version 1.0" +try: + from PyQt5.QtCore import QTimer, QThread, pyqtSignal + from PyQt5.QtGui import QPainter, QColor, QPen + from PyQt5.QtWidgets import QPushButton, QApplication, QWidget, QVBoxLayout +except ImportError: + from PySide2.QtCore import QTimer, QThread, Signal as pyqtSignal + from PySide2.QtGui import QPainter, QColor, QPen + from PySide2.QtWidgets import QPushButton, QApplication, QWidget, QVBoxLayout StyleSheet = ''' PushButtonLine { @@ -32,7 +33,6 @@ PushButtonLine { class LoadingThread(QThread): - valueChanged = pyqtSignal(float) # 当前值/最大值 def __init__(self, *args, **kwargs): @@ -41,12 +41,13 @@ class LoadingThread(QThread): def run(self): for i in range(self.totalValue + 1): + if self.isInterruptionRequested(): + break self.valueChanged.emit(i / self.totalValue) - QThread.msleep(randint(300, 600)) + QThread.msleep(randint(50, 100)) class PushButtonLine(QPushButton): - lineColor = QColor(0, 150, 136) def __init__(self, *args, **kwargs): @@ -57,6 +58,9 @@ class PushButtonLine(QPushButton): self._timer = QTimer(self, timeout=self.update) self.clicked.connect(self.start) + def __del__(self): + self.stop() + def paintEvent(self, event): super(PushButtonLine, self).paintEvent(event) if not self._timer.isActive(): @@ -79,13 +83,21 @@ class PushButtonLine(QPushButton): self.setText(self._waitText) def stop(self): - self.loadingThread.valueChanged.disconnect(self.setPercent) - self.loadingThread.terminate() - self.loadingThread.deleteLater() - del self.loadingThread - self._percent = 0 - self._timer.stop() - self.setText(self._text) + try: + if hasattr(self, "loadingThread"): + if self.loadingThread.isRunning(): + self.loadingThread.requestInterruption() + self.loadingThread.quit() + self.loadingThread.wait(2000) + del self.loadingThread + except RuntimeError: + pass + try: + self._percent = 0 + self._timer.stop() + self.setText(self._text) + except RuntimeError: + pass def setPercent(self, v): self._percent = v diff --git a/QPushButton/FontRotate.py b/QPushButton/FontRotate.py index c9cae277512dc9c4fabfe6d322ff9aac41da5c62..e8a728a2c7594793bb8ce6a8b3a8cd409a3266e8 100644 --- a/QPushButton/FontRotate.py +++ b/QPushButton/FontRotate.py @@ -1,31 +1,30 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -''' +""" Created on 2018年2月1日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: PushButtonFont @description: -''' - +""" import sys -from PyQt5.QtCore import QPropertyAnimation, Qt, QRectF -from PyQt5.QtGui import QFontDatabase -from PyQt5.QtWidgets import QPushButton, QApplication, QStyleOptionButton,\ - QStylePainter, QStyle - - -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" -__Version__ = "Version 1.0" +try: + from PyQt5.QtCore import QPropertyAnimation, Qt, QRectF + from PyQt5.QtGui import QFontDatabase + from PyQt5.QtWidgets import QPushButton, QApplication, QStyleOptionButton, \ + QStylePainter, QStyle +except ImportError: + from PySide2.QtCore import QPropertyAnimation, Qt, QRectF + from PySide2.QtGui import QFontDatabase + from PySide2.QtWidgets import QPushButton, QApplication, QStyleOptionButton, \ + QStylePainter, QStyle class PushButtonFont(QPushButton): - LoadingText = "\uf110" def __init__(self, *args, **kwargs): diff --git a/QPushButton/NormalStyle.py b/QPushButton/NormalStyle.py index 831064b48110ce9caa5fc0a9310ee26ba8dca2be..ec36a89fbdea7e2b88eb127f0494ba81e0cd6817 100644 --- a/QPushButton/NormalStyle.py +++ b/QPushButton/NormalStyle.py @@ -1,23 +1,23 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -''' +""" Created on 2018年1月29日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: NormalStyle @description: -''' -import sys -from PyQt5.QtWidgets import QWidget, QHBoxLayout, QPushButton, QApplication +""" +import sys -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" -__Version__ = "Version 1.0" +try: + from PyQt5.QtWidgets import QWidget, QHBoxLayout, QPushButton, QApplication +except ImportError: + from PySide2.QtWidgets import QWidget, QHBoxLayout, QPushButton, QApplication -StyleSheet = ''' +StyleSheet = """ /*这里是通用设置,所有按钮都有效,不过后面的可以覆盖这个*/ QPushButton { border: none; /*去掉边框*/ @@ -85,7 +85,7 @@ QPushButton[text="purple button"] { color: white; /*文字颜色*/ background-color: #9c27b0; } -''' +""" class Window(QWidget): diff --git a/QPushButton/README.md b/QPushButton/README.md index 0697824e8aabaaac501acefffbab737a8841bb03..2328a51eacd564e9a4aa7007952d90a205e66e7e 100644 --- a/QPushButton/README.md +++ b/QPushButton/README.md @@ -1,5 +1,11 @@ # QPushButton +- 目录 + - [普通样式](#1普通样式) + - [按钮底部线条进度](#2按钮底部线条进度) + - [按钮文字旋转进度](#3按钮文字旋转进度) + - [按钮常用信号](#4按钮常用信号) + ## 1、普通样式 [运行 NormalStyle.py](NormalStyle.py) @@ -19,4 +25,12 @@ 利用字体,使用FontAwesome字体来显示一个圆形进度条,然后利用旋转动画 -![FontRotate](ScreenShot/FontRotate.gif) \ No newline at end of file +![FontRotate](ScreenShot/FontRotate.gif) + +## 4、按钮常用信号 +[运行 SignalsExample.py](SignalsExample.py) + +根据官网文档 https://doc.qt.io/qt-5/qabstractbutton.html#signals 中的信号介绍编写 +按钮的点击、按下、释放、选中信号演示 + +![SignalsExample](ScreenShot/SignalsExample.gif) \ No newline at end of file diff --git a/QPushButton/ScreenShot/SignalsExample.gif b/QPushButton/ScreenShot/SignalsExample.gif new file mode 100644 index 0000000000000000000000000000000000000000..49420031b01fbfb2af5a926c8bfcfa342d63d533 Binary files /dev/null and b/QPushButton/ScreenShot/SignalsExample.gif differ diff --git a/QPushButton/SignalsExample.py b/QPushButton/SignalsExample.py new file mode 100644 index 0000000000000000000000000000000000000000..35163e814e3e07a58a406e0da7cd6279a9961da1 --- /dev/null +++ b/QPushButton/SignalsExample.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2019年7月2日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: QPushButton.SignalsExample +@description: 按钮信号例子 +""" + +try: + from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QPlainTextEdit +except ImportError: + from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QPlainTextEdit + + +class Window(QWidget): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + layout = QVBoxLayout(self) + + btn1 = QPushButton('按钮点击信号', self) + btn1.setObjectName('ClickBtn') + btn1.clicked.connect(self.onClicked) + layout.addWidget(btn1) + + btn2 = QPushButton('按钮按下信号', self) + btn2.setObjectName('PressBtn') + btn2.pressed.connect(self.onPressed) + layout.addWidget(btn2) + + btn3 = QPushButton('按钮释放信号', self) + btn3.setObjectName('ReleaseBtn') + btn3.released.connect(self.onReleased) + layout.addWidget(btn3) + + btn4 = QPushButton('按钮释放信号', self) + btn4.setObjectName('ToggleBtn') + btn4.setCheckable(True) + btn4.toggled.connect(self.onToggled) + layout.addWidget(btn4) + + self.resultView = QPlainTextEdit(self) + self.resultView.setReadOnly(True) + layout.addWidget(self.resultView) + + def onClicked(self): + self.resultView.appendPlainText( + '按钮{0}被点击'.format(self.sender().objectName())) + + def onPressed(self): + self.resultView.appendPlainText( + '按钮{0}被按下'.format(self.sender().objectName())) + + def onReleased(self): + self.resultView.appendPlainText( + '按钮{0}被释放'.format(self.sender().objectName())) + + def onToggled(self, checked): + self.resultView.appendPlainText( + '按钮{0}被选中:{1}'.format(self.sender().objectName(), checked)) + + +if __name__ == '__main__': + import sys + + app = QApplication(sys.argv) + w = Window() + w.show() + sys.exit(app.exec_()) diff --git a/QScrollArea/Lib/SettingUi.py b/QScrollArea/Lib/SettingUi.py index a9bfdffc653bc1922cc3a9e416bff3a1d128a228..d25f3ef2a9e6561a16c99f934e82373ea6da9805 100644 --- a/QScrollArea/Lib/SettingUi.py +++ b/QScrollArea/Lib/SettingUi.py @@ -6,7 +6,11 @@ # # WARNING! All changes made in this file will be lost! -from PyQt5 import QtCore, QtGui, QtWidgets +try: + from PyQt5 import QtCore, QtWidgets +except ImportError: + from PySide2 import QtCore, QtWidgets + class Ui_Setting(object): def setupUi(self, Setting): @@ -151,7 +155,8 @@ class Ui_Setting(object): self.comboBox.addItem("") self.comboBox.addItem("") self.horizontalLayout_2.addWidget(self.comboBox) - spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem) self.formLayout_9.setLayout(0, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_2) self.checkBox_30 = QtWidgets.QCheckBox(self.widget_2) @@ -171,7 +176,8 @@ class Ui_Setting(object): self.pushButton_4 = QtWidgets.QPushButton(self.widget_2) self.pushButton_4.setObjectName("pushButton_4") self.horizontalLayout_3.addWidget(self.pushButton_4) - spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_3.addItem(spacerItem1) self.formLayout_9.setLayout(4, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_3) self.verticalLayout.addWidget(self.widget_2) @@ -469,7 +475,8 @@ class Ui_Setting(object): self.checkBox_23.setText(_translate("Setting", "启用一声问候消息")) self.checkBox_24.setText(_translate("Setting", "启用设备连接提醒")) self.right5.setText(_translate("Setting", "当插入安卓设备时,提示安装或者更新QQ手机版")) - self.label_3.setText(_translate("Setting", "

您可以设置是否在屏幕右下角收到来自QQ空间的通知,进入设置

")) + self.label_3.setText(_translate("Setting", + "

您可以设置是否在屏幕右下角收到来自QQ空间的通知,进入设置

")) self.label_4.setText(_translate("Setting", "好友上线提醒")) self.radioButton.setText(_translate("Setting", "关闭好友上线提醒")) self.radioButton_2.setText(_translate("Setting", "全部好友上线提醒")) @@ -485,10 +492,10 @@ class Ui_Setting(object): if __name__ == "__main__": import sys + app = QtWidgets.QApplication(sys.argv) Setting = QtWidgets.QWidget() ui = Ui_Setting() ui.setupUi(Setting) Setting.show() sys.exit(app.exec_()) - diff --git a/QScrollArea/QQSettingPanel.py b/QScrollArea/QQSettingPanel.py index 6dccd7c00044f1b84b082d5183f85745bff8c4da..2a5236aa32acf0370b9e340988b61e545aaddead 100644 --- a/QScrollArea/QQSettingPanel.py +++ b/QScrollArea/QQSettingPanel.py @@ -1,22 +1,21 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from PyQt5.QtWidgets import QWidget - -from Lib.SettingUi import Ui_Setting # @UnresolvedImport +""" +Created on 2018年3月28日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: QQSettingPanel +@description: +""" -# Created on 2018年3月28日 -# author: Irony -# site: https://pyqt5.com , https://github.com/892768447 -# email: 892768447@qq.com -# file: QQSettingPanel -# description: +try: + from PyQt5.QtWidgets import QApplication, QWidget +except ImportError: + from PySide2.QtWidgets import QApplication, QWidget -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 +from Lib.SettingUi import Ui_Setting # @UnresolvedImport class Window(QWidget, Ui_Setting): @@ -59,9 +58,9 @@ class Window(QWidget, Ui_Setting): if __name__ == '__main__': import sys - from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) - app.setStyleSheet(open("Data/style.qss", "rb").read().decode("utf-8")) + app.setStyleSheet(open('Data/style.qss', 'rb').read().decode('utf-8')) w = Window() w.show() sys.exit(app.exec_()) diff --git a/QScrollArea/README.md b/QScrollArea/README.md index 7591a342d23ebcb016e35dca7726768074be63b1..810730955f529f1300705a54ace8c9b1b5bccd81 100644 --- a/QScrollArea/README.md +++ b/QScrollArea/README.md @@ -1,7 +1,10 @@ # QScrollArea +- 目录 + - [仿QQ设置面板](#1仿QQ设置面板) + ## 1、仿QQ设置面板 -[运行 QQSettingPanel.py](QQSettingPanel.py) +[运行 QQSettingPanel.py](QQSettingPanel.py) | [查看 setting.ui](Data/setting.ui) 1. 左侧为`QListWidget`,右侧使用`QScrollArea`设置`QVBoxLayout`,然后依次往里面添加QWidget 2. 右侧添加`QWidget`的时候有两种方案 diff --git a/QScrollBar/README.md b/QScrollBar/README.md index 190ff2a809426abe65c95f05434b668ad6d0e35b..1de1b8b0148fb6016f93a71520d186c00496717b 100644 --- a/QScrollBar/README.md +++ b/QScrollBar/README.md @@ -1,6 +1,10 @@ # QScrollBar +- 目录 + - [滚动条样式美化](#1滚动条样式美化) + ## 1、滚动条样式美化 +[运行 StyleScrollBar.py](StyleScrollBar.py) 使用QSS和图片对滚动条进行美化(horizontal 横向、vertical 纵向) diff --git a/QScrollBar/StyleScrollBar.py b/QScrollBar/StyleScrollBar.py index 96c591d201d7f29b94b55126609af3157f4e3a6c..f5d4d347f30723158b01d371a378d1728640fd71 100644 --- a/QScrollBar/StyleScrollBar.py +++ b/QScrollBar/StyleScrollBar.py @@ -1,22 +1,22 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -''' +""" Created on 2018年1月20日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: ScrollBar @description: -''' -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QTextEdit, QApplication +""" import chardet - -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" -__Version__ = "Version 1.0" +try: + from PyQt5.QtCore import Qt + from PyQt5.QtWidgets import QTextEdit, QApplication +except ImportError: + from PySide2.QtCore import Qt + from PySide2.QtWidgets import QTextEdit, QApplication class Window(QTextEdit): @@ -38,6 +38,7 @@ class Window(QTextEdit): if __name__ == "__main__": import sys + app = QApplication(sys.argv) app.setApplicationName("滚动条样式") app.setApplicationDisplayName("滚动条样式") diff --git a/QSerialPort/Lib/UiSerialPort.py b/QSerialPort/Lib/UiSerialPort.py index 0da8c3512e604936a945aecc477da3b242461d6b..934e2156c1e9d5d1d7ded463b717bf1ba65965a5 100644 --- a/QSerialPort/Lib/UiSerialPort.py +++ b/QSerialPort/Lib/UiSerialPort.py @@ -6,19 +6,20 @@ # # WARNING! All changes made in this file will be lost! -from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5 import QtCore, QtWidgets + class Ui_FormSerialPort(object): def setupUi(self, FormSerialPort): FormSerialPort.setObjectName("FormSerialPort") FormSerialPort.resize(721, 597) FormSerialPort.setStyleSheet("#labelStatus {\n" -" border-radius: 13px;\n" -" background-color: gray;\n" -"}\n" -"#labelStatus[isOn=\"true\"] {\n" -" background-color: green;\n" -"}") + " border-radius: 13px;\n" + " background-color: gray;\n" + "}\n" + "#labelStatus[isOn=\"true\"] {\n" + " background-color: green;\n" + "}") self.gridLayout = QtWidgets.QGridLayout(FormSerialPort) self.gridLayout.setObjectName("gridLayout") self.groupBox = QtWidgets.QGroupBox(FormSerialPort) @@ -86,7 +87,8 @@ class Ui_FormSerialPort(object): self.labelStatus.setProperty("isOn", False) self.labelStatus.setObjectName("labelStatus") self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.labelStatus) - spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, + QtWidgets.QSizePolicy.Expanding) self.formLayout.setItem(6, QtWidgets.QFormLayout.FieldRole, spacerItem) self.gridLayout.addWidget(self.groupBox, 0, 0, 1, 1) self.textBrowser = QtWidgets.QTextBrowser(FormSerialPort) @@ -166,10 +168,10 @@ class Ui_FormSerialPort(object): if __name__ == "__main__": import sys + app = QtWidgets.QApplication(sys.argv) FormSerialPort = QtWidgets.QWidget() ui = Ui_FormSerialPort() ui.setupUi(FormSerialPort) FormSerialPort.show() sys.exit(app.exec_()) - diff --git a/QSerialPort/README.md b/QSerialPort/README.md index 6e97a66f141772cc25f74c196acfd57e3019efbe..a0418b793119fbcb12978701921ae9cbe8ed0f71 100644 --- a/QSerialPort/README.md +++ b/QSerialPort/README.md @@ -1,7 +1,10 @@ -# +# QSerialPort + +- 目录 + - [串口调试小助手](#1串口调试小助手) ## 1、串口调试小助手 -[运行 SerialDebugAssistant.py](SerialDebugAssistant.py) +[运行 SerialDebugAssistant.py](SerialDebugAssistant.py) | [查看 UiSerialPort.ui](Data/UiSerialPort.ui) 用`QSerialPort`写了个类似串口调试小助手的工具, 这个类的官方资料: http://doc.qt.io/qt-5/qserialport.html diff --git a/QSerialPort/SerialDebugAssistant.py b/QSerialPort/SerialDebugAssistant.py index 0f87638f79eb085fd6e7ec8ea7d8e3d6ea2677bb..5f5c8e415300aa687d74f0f04db2a576bae3906e 100644 --- a/QSerialPort/SerialDebugAssistant.py +++ b/QSerialPort/SerialDebugAssistant.py @@ -4,7 +4,7 @@ """ Created on 2018年11月6日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: SerialDebugAssistant @description: 串口调试小助手 @@ -16,13 +16,6 @@ from PyQt5.QtWidgets import QWidget, QMessageBox from Lib.UiSerialPort import Ui_FormSerialPort # @UnresolvedImport -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 - - class Window(QWidget, Ui_FormSerialPort): def __init__(self, *args, **kwargs): @@ -51,7 +44,7 @@ class Window(QWidget, Ui_FormSerialPort): QMessageBox.critical(self, '错误', '没有选择串口') return port = self._ports[name] -# self._serial.setPort(port) + # self._serial.setPort(port) # 根据名字设置串口(也可以用上面的函数) self._serial.setPortName(port.systemLocation()) # 设置波特率 @@ -139,8 +132,10 @@ class Window(QWidget, Ui_FormSerialPort): if __name__ == '__main__': import sys import cgitb - sys.excepthook = cgitb.enable(1, None, 5, '') + + cgitb.enable(format='text') from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) w = Window() w.show() diff --git a/QSlider/ClickJumpSlider.py b/QSlider/ClickJumpSlider.py index 3e7f00a330ecd8dbe50f884807e51cf640c7975d..d12f97a55117f4586a0dd0f9d2eb5a1b9dc0b29d 100644 --- a/QSlider/ClickJumpSlider.py +++ b/QSlider/ClickJumpSlider.py @@ -4,21 +4,20 @@ """ Created on 2018年11月5日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: ClickJumpSlider @description: """ -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QSlider, QStyleOptionSlider, QStyle, QWidget,\ - QFormLayout, QLabel - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" +try: + from PyQt5.QtCore import Qt + from PyQt5.QtWidgets import QApplication, QSlider, QStyleOptionSlider, QStyle, QWidget, QFormLayout, \ + QLabel +except ImportError: + from PySide2.QtCore import Qt + from PySide2.QtWidgets import QApplication, QSlider, QStyleOptionSlider, QStyle, QWidget, QFormLayout, \ + QLabel class ClickJumpSlider(QSlider): @@ -55,31 +54,34 @@ class DemoWindow(QWidget): layout = QFormLayout(self) self.label1 = QLabel('0', self) - layout.addRow(self.label1, ClickJumpSlider( - Qt.Horizontal, valueChanged=lambda v: self.label1.setText(str(v)))) + self.slider1 = ClickJumpSlider(Qt.Horizontal) + self.slider1.valueChanged.connect(lambda v: self.label1.setText(str(v))) + layout.addRow(self.label1, self.slider1) # 横向-反向显示 self.label2 = QLabel('0', self) - layout.addRow(self.label2, ClickJumpSlider( - Qt.Horizontal, invertedAppearance=True, - valueChanged=lambda v: self.label2.setText(str(v)))) + self.slider2 = ClickJumpSlider(Qt.Horizontal, invertedAppearance=True) + self.slider2.valueChanged.connect(lambda v: self.label2.setText(str(v))) + layout.addRow(self.label2, self.slider2) self.label3 = QLabel('0', self) - layout.addRow(self.label3, ClickJumpSlider( - Qt.Vertical, minimumHeight=200, valueChanged=lambda v: self.label3.setText(str(v)))) + self.slider3 = ClickJumpSlider(Qt.Vertical, minimumHeight=200) + self.slider3.valueChanged.connect(lambda v: self.label3.setText(str(v))) + layout.addRow(self.label3, self.slider3) # 纵向反向显示 self.label4 = QLabel('0', self) - layout.addRow(self.label4, ClickJumpSlider( - Qt.Vertical, invertedAppearance=True, - minimumHeight=200, valueChanged=lambda v: self.label4.setText(str(v)))) + self.slider4 = ClickJumpSlider(Qt.Vertical, invertedAppearance=True, minimumHeight=200) + self.slider4.valueChanged.connect(lambda v: self.label4.setText(str(v))) + layout.addRow(self.label4, self.slider4) if __name__ == '__main__': import sys import cgitb - sys.excepthook = cgitb.enable(1, None, 5, '') - from PyQt5.QtWidgets import QApplication + + cgitb.enable(format='text') + app = QApplication(sys.argv) w = DemoWindow() w.show() diff --git a/QSlider/LfSlider.py b/QSlider/LfSlider.py new file mode 100644 index 0000000000000000000000000000000000000000..2c900cfa9d29598fbd1ef0a76e738d1d1039a27b --- /dev/null +++ b/QSlider/LfSlider.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2021/4/9 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: LfSlider +@description: 降低值变化频率 +""" +from datetime import datetime + +try: + from PyQt5.QtCore import pyqtSignal, QTimer, Qt + from PyQt5.QtWidgets import QApplication, QSlider, QWidget, QVBoxLayout, QPlainTextEdit, QHBoxLayout, \ + QGroupBox +except ImportError: + from PySide2.QtCore import Signal as pyqtSignal, QTimer, Qt + from PySide2.QtWidgets import QApplication, QSlider, QWidget, QVBoxLayout, QPlainTextEdit, QHBoxLayout, \ + QGroupBox + + +class LfSlider(QSlider): + valueChanged = pyqtSignal(int) + + def __init__(self, *args, **kwargs): + delay = kwargs.pop('delay', 500) + super(LfSlider, self).__init__(*args, **kwargs) + self.lastValue = self.value() + self.uTimer = QTimer(self) + self.uTimer.timeout.connect(self.onValueChanged) + self.uTimer.start(delay) + + def onValueChanged(self): + if self.lastValue != self.value(): + self.lastValue = self.value() + self.valueChanged.emit(self.lastValue) + + +class Window(QWidget): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + layout = QHBoxLayout(self) + + # 左侧原始 + left_group = QGroupBox('原始QSlider', self) + left_layout = QVBoxLayout(left_group) + self.leftLabel = QPlainTextEdit(self) + left_layout.addWidget(self.leftLabel) + + self.leftSlider = QSlider(Qt.Horizontal, self) + self.leftSlider.valueChanged.connect(self.onLeftChanged) + left_layout.addWidget(self.leftSlider) + + layout.addWidget(left_group) + + # 右侧低频率变化 + right_group = QGroupBox('LfSlider', self) + right_layout = QVBoxLayout(right_group) + self.rightLabel = QPlainTextEdit(self) + right_layout.addWidget(self.rightLabel) + + self.rightSlider = LfSlider(Qt.Horizontal, self) + self.rightSlider.valueChanged.connect(self.onRightChanged) + right_layout.addWidget(self.rightSlider) + + layout.addWidget(right_group) + + def onLeftChanged(self, value): + self.leftLabel.appendPlainText(datetime.now().strftime("[%H:%M:%S.%f] ") + str(value)) + + def onRightChanged(self, value): + self.rightLabel.appendPlainText(datetime.now().strftime("[%H:%M:%S.%f] ") + str(value)) + + +if __name__ == '__main__': + import sys + import cgitb + + cgitb.enable(format='text') + + app = QApplication(sys.argv) + w = Window() + w.show() + sys.exit(app.exec_()) diff --git a/QSlider/PaintQSlider.py b/QSlider/PaintQSlider.py index 0d154e8b51faf7c751ea2028b53c3894e8951682..d72a757484c96cb1f46a3a88a0e1f94a2790bdd0 100644 --- a/QSlider/PaintQSlider.py +++ b/QSlider/PaintQSlider.py @@ -4,24 +4,18 @@ """ Created on 2018年5月15日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: PaintQSlider @description: """ + from PyQt5.QtCore import Qt, QRect, QPointF from PyQt5.QtGui import QPainter, QColor -from PyQt5.QtWidgets import QSlider, QWidget, QVBoxLayout, QProxyStyle, QStyle,\ +from PyQt5.QtWidgets import QSlider, QWidget, QVBoxLayout, QProxyStyle, QStyle, \ QStyleOptionSlider -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" - - class SliderStyle(QProxyStyle): def subControlRect(self, control, option, subControl, widget=None): @@ -124,6 +118,7 @@ class Window(QWidget): if __name__ == '__main__': import sys from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) w = Window() w.setStyleSheet('QWidget {background: gray;}') diff --git a/QSlider/QssQSlider.py b/QSlider/QssQSlider.py index af65e4916134998d9a770621b3b3a94a7fc34576..7b3a0f33958701edcdeee33630de29b3f1dce3c4 100644 --- a/QSlider/QssQSlider.py +++ b/QSlider/QssQSlider.py @@ -4,20 +4,18 @@ """ Created on 2018年5月15日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: QssQSlider @description: 通过QSS美化QSlider """ -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QSlider - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" +try: + from PyQt5.QtCore import Qt + from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QSlider +except ImportError: + from PySide2.QtCore import Qt + from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout, QSlider StyleSheet = """ QWidget { @@ -76,7 +74,7 @@ class Window(QWidget): if __name__ == '__main__': import sys - from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) app.setStyleSheet(StyleSheet) w = Window() diff --git a/QSlider/README.md b/QSlider/README.md index b03d8a0c32993cbd87ddb1c91b414659c75406b3..1dc8803a37d8fabb70ffe0cdecfc7b513b8486e8 100644 --- a/QSlider/README.md +++ b/QSlider/README.md @@ -1,5 +1,10 @@ # QSlider +- 目录 + - [滑动条点击定位](#1滑动条点击定位) + - [双层圆环样式](#2双层圆环样式) + - [低频率值变化](#3低频率值变化) + ## 1、滑动条点击定位 [运行 ClickJumpSlider.py](ClickJumpSlider.py) @@ -39,4 +44,11 @@ def mousePressEvent(self, event): [运行 QssQSlider.py](QssQSlider.py) | [运行 PaintQSlider.py](PaintQSlider.py) ![QssQSlider](ScreenShot/QssQSlider.gif) -![PaintQSlider](ScreenShot/PaintQSlider.gif) \ No newline at end of file +![PaintQSlider](ScreenShot/PaintQSlider.gif) + +## 3、低频率值变化 +[运行 LfSlider.py](LfSlider.py) + +覆盖了`valueChanged`信号,通过使用定时器来延迟发送值变化,如果无法覆盖信号则可以自定义一个新的信号 + +![LfSlider](ScreenShot/LfSlider.gif) diff --git a/QSlider/ScreenShot/LfSlider.gif b/QSlider/ScreenShot/LfSlider.gif new file mode 100644 index 0000000000000000000000000000000000000000..035f31184212d02da3b6be6ebaef6f7148be9772 Binary files /dev/null and b/QSlider/ScreenShot/LfSlider.gif differ diff --git a/QSplashScreen/Data/splash.gif b/QSplashScreen/Data/splash.gif new file mode 100644 index 0000000000000000000000000000000000000000..8553388e94d3a15dc09b0f1390d7b02006d2d1a8 Binary files /dev/null and b/QSplashScreen/Data/splash.gif differ diff --git a/QSplashScreen/GifSplashScreen.py b/QSplashScreen/GifSplashScreen.py new file mode 100644 index 0000000000000000000000000000000000000000..d409dd04fac34a5817dc1a13348980d67777568c --- /dev/null +++ b/QSplashScreen/GifSplashScreen.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2020/6/11 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: +@description: +""" + +from time import sleep + +try: + from PyQt5.QtCore import Qt + from PyQt5.QtGui import QMovie + from PyQt5.QtWidgets import QApplication, QSplashScreen, QWidget +except ImportError: + from PySide2.QtCore import Qt + from PySide2.QtGui import QMovie + from PySide2.QtWidgets import QApplication, QSplashScreen, QWidget + + +class GifSplashScreen(QSplashScreen): + + def __init__(self, *args, **kwargs): + super(GifSplashScreen, self).__init__(*args, **kwargs) + self.movie = QMovie('Data/splash.gif') + self.movie.frameChanged.connect(self.onFrameChanged) + self.movie.start() + + def onFrameChanged(self, _): + self.setPixmap(self.movie.currentPixmap()) + + def finish(self, widget): + self.movie.stop() + super(GifSplashScreen, self).finish(widget) + + +class BusyWindow(QWidget): + + def __init__(self, *args, **kwargs): + super(BusyWindow, self).__init__(*args, **kwargs) + # 模拟耗时操作,一般来说耗时的加载数据应该放到线程 + for i in range(5): + sleep(1) + splash.showMessage('加载进度: %d' % i, Qt.AlignHCenter | Qt.AlignBottom, Qt.white) + QApplication.instance().processEvents() + + splash.showMessage('初始化完成', Qt.AlignHCenter | Qt.AlignBottom, Qt.white) + splash.finish(self) + + +if __name__ == '__main__': + import sys + import cgitb + + cgitb.enable(format='text') + + app = QApplication(sys.argv) + + global splash + splash = GifSplashScreen() + splash.show() + + w = BusyWindow() + w.show() + + # 测试二 + # def createWindow(): + # app.w = QWidget() + # # 模拟初始5秒后再显示 + # splash.showMessage('等待界面显示', Qt.AlignHCenter | Qt.AlignBottom, Qt.white) + # QTimer.singleShot(3000, lambda: ( + # splash.showMessage('初始化完成', Qt.AlignHCenter | Qt.AlignBottom, Qt.white), app.w.show(), + # splash.finish(app.w))) + + # 模拟耗时5秒。但是不能用sleep + # 可以使用子线程加载耗时的数据 + # 主线程中循环设置UI可以配合QApplication.instance().processEvents() + # QTimer.singleShot(3000, createWindow) + + splash.showMessage('等待创建界面', Qt.AlignHCenter | Qt.AlignBottom, Qt.white) + + sys.exit(app.exec_()) diff --git a/QSplashScreen/README.en.md b/QSplashScreen/README.en.md new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/QSplashScreen/README.md b/QSplashScreen/README.md new file mode 100644 index 0000000000000000000000000000000000000000..51da420e76ac818e4576db305fa77707eca745b4 --- /dev/null +++ b/QSplashScreen/README.md @@ -0,0 +1,11 @@ +# QSplashScreen + +- 目录 + - [启动画面动画](#1启动画面动画) + +## 1、启动画面动画 +[运行 GifSplashScreen.py](GifSplashScreen.py) + +结合 `QMovie` 的 `frameChanged` 信号 不停地设置新的pixmap图片 + +![GifSplashScreen](ScreenShot/GifSplashScreen.gif) \ No newline at end of file diff --git a/QSplashScreen/ScreenShot/GifSplashScreen.gif b/QSplashScreen/ScreenShot/GifSplashScreen.gif new file mode 100644 index 0000000000000000000000000000000000000000..0c22635058ffac4df5a38e558df1b72463866744 Binary files /dev/null and b/QSplashScreen/ScreenShot/GifSplashScreen.gif differ diff --git a/QSplitter/README.en.md b/QSplitter/README.en.md new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/QSplitter/README.md b/QSplitter/README.md index b19695b2476502d7c02a7da19bc31ab9723d3964..f52a7ca3bfca45797d4b9636e2e7c2031ab1fa4b 100644 --- a/QSplitter/README.md +++ b/QSplitter/README.md @@ -1,5 +1,8 @@ # QSplitter +- 目录 + - [分割窗口的分割条重绘](#1分割窗口的分割条重绘) + ## 1、分割窗口的分割条重绘 [运行 RewriteHandle.py](RewriteHandle.py) diff --git a/QSplitter/RewriteHandle.py b/QSplitter/RewriteHandle.py index 241f6c28e01bb5a12d977b57027980c499c0692f..d69ab630d92d080ee250c3531a5f3a11b0c57a04 100644 --- a/QSplitter/RewriteHandle.py +++ b/QSplitter/RewriteHandle.py @@ -4,25 +4,23 @@ """ Created on 2018年3月21日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: Splitter @description: """ import sys -from PyQt5.QtCore import Qt, QPointF, pyqtSignal -from PyQt5.QtGui import QPainter, QPolygonF -from PyQt5.QtWidgets import QTextEdit, QListWidget,\ - QTreeWidget, QSplitter, QApplication, QMainWindow, QSplitterHandle - - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0"\ - +try: + from PyQt5.QtCore import Qt, QPointF, pyqtSignal + from PyQt5.QtGui import QPainter, QPolygonF + from PyQt5.QtWidgets import QTextEdit, QListWidget, \ + QTreeWidget, QSplitter, QApplication, QMainWindow, QSplitterHandle +except ImportError: + from PySide2.QtCore import Qt, QPointF, Signal as pyqtSignal + from PySide2.QtGui import QPainter, QPolygonF + from PySide2.QtWidgets import QTextEdit, QListWidget, \ + QTreeWidget, QSplitter, QApplication, QMainWindow, QSplitterHandle class SplitterHandle(QSplitterHandle): @@ -49,7 +47,7 @@ class SplitterHandle(QSplitterHandle): else: # 设置默认的鼠标样式并可以移动 self.setCursor(Qt.SplitHCursor if self.orientation() - == Qt.Horizontal else Qt.SplitVCursor) + == Qt.Horizontal else Qt.SplitVCursor) super(SplitterHandle, self).mouseMoveEvent(event) def paintEvent(self, event): diff --git a/QStackedWidget/LeftTabStacked.py b/QStackedWidget/LeftTabStacked.py index 298b8494319e762bb4e04508a1214b6e5c2559c9..6c8257d06ed7a63fad4b0ac61a56b9b8dd12875c 100644 --- a/QStackedWidget/LeftTabStacked.py +++ b/QStackedWidget/LeftTabStacked.py @@ -1,24 +1,27 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from random import randint -from PyQt5.QtCore import Qt, QSize -from PyQt5.QtGui import QIcon -from PyQt5.QtWidgets import QWidget, QListWidget, QStackedWidget, QHBoxLayout,\ - QListWidgetItem, QLabel +""" +Created on 2018年5月29日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: LeftTabWidget +@description: +""" +from random import randint -# Created on 2018年5月29日 -# author: Irony -# site: https://pyqt5.com , https://github.com/892768447 -# email: 892768447@qq.com -# file: LeftTabWidget -# description: -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 +try: + from PyQt5.QtCore import Qt, QSize + from PyQt5.QtGui import QIcon + from PyQt5.QtWidgets import QApplication, QWidget, QListWidget, QStackedWidget, QHBoxLayout, \ + QListWidgetItem, QLabel +except ImportError: + from PySide2.QtCore import Qt, QSize + from PySide2.QtGui import QIcon + from PySide2.QtWidgets import QApplication, QWidget, QListWidget, QStackedWidget, QHBoxLayout, \ + QListWidgetItem, QLabel class LeftTabWidget(QWidget): @@ -26,7 +29,7 @@ class LeftTabWidget(QWidget): def __init__(self, *args, **kwargs): super(LeftTabWidget, self).__init__(*args, **kwargs) self.resize(800, 600) - #左右布局(左边一个QListWidget + 右边QStackedWidget) + # 左右布局(左边一个QListWidget + 右边QStackedWidget) layout = QHBoxLayout(self, spacing=0) layout.setContentsMargins(0, 0, 0, 0) # 左侧列表 @@ -102,7 +105,7 @@ QLabel { if __name__ == '__main__': import sys - from PyQt5.QtWidgets import QApplicationr + app = QApplication(sys.argv) app.setStyleSheet(Stylesheet) w = LeftTabWidget() diff --git a/QStackedWidget/README.md b/QStackedWidget/README.md index 0ad655eac70f4bd5ffd09c269e100f6927b0e589..2329fc3ed460075577e8d04f63039c1118b460b8 100644 --- a/QStackedWidget/README.md +++ b/QStackedWidget/README.md @@ -1,5 +1,8 @@ # QStackedWidget +- 目录 + - [左侧选项卡](#1左侧选项卡) + ## 1、左侧选项卡 [运行 LeftTabStacked.py](LeftTabStacked.py) @@ -11,5 +14,7 @@ 2. 右侧添加`QWidget`的时候有两种方案 1. 左侧list根据序号来索引,右侧添加widget时给定带序号的变量名,如widget_0,widget_1,widget_2之类的,这样可以直接根据`QListWidget`的序号关联起来 2. 左侧list添加item时给定右侧对应的widget变量值 + +PS: 用设计设的做法 : https://www.jianshu.com/p/dac62b5c225c -![LeftTabStacked](ScreenShot/LeftTabStacked.gif) \ No newline at end of file +![LeftTabStacked](ScreenShot/LeftTabStacked.gif) diff --git a/QSystemTrayIcon/MinimizeToTray.py b/QSystemTrayIcon/MinimizeToTray.py new file mode 100755 index 0000000000000000000000000000000000000000..f4325838511199f7a4057b5d834d182ff64eecc4 --- /dev/null +++ b/QSystemTrayIcon/MinimizeToTray.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys + +try: + from PyQt5.QtCore import QSize + from PyQt5.QtWidgets import ( + QApplication, QMainWindow, + QLabel, QGridLayout, QWidget, + QCheckBox, QSystemTrayIcon, + QSpacerItem, QSizePolicy, QMenu, QAction, QStyle) +except ImportError: + from PySide2.QtCore import QSize + from PySide2.QtWidgets import ( + QApplication, QMainWindow, + QLabel, QGridLayout, QWidget, + QCheckBox, QSystemTrayIcon, + QSpacerItem, QSizePolicy, QMenu, QAction, QStyle) + + +class MainWindow(QMainWindow): + """ + Сheckbox and system tray icons. + Will initialize in the constructor. + """ + check_box = None + tray_icon = None + + # Override the class constructor + def __init__(self): + # Be sure to call the super class method + QMainWindow.__init__(self) + + self.setMinimumSize(QSize(480, 80)) # Set sizes + self.setWindowTitle("System Tray Application") # Set a title + # Create a central widget + central_widget = QWidget(self) + # Set the central widget + self.setCentralWidget(central_widget) + + grid_layout = QGridLayout(self) # Create a QGridLayout + # Set the layout into the central widget + central_widget.setLayout(grid_layout) + grid_layout.addWidget( + QLabel("Application, which can minimize to Tray", self), 0, 0) + + # Add a checkbox, which will depend on the behavior of the program when the window is closed + self.check_box = QCheckBox('Minimize to Tray') + grid_layout.addWidget(self.check_box, 1, 0) + grid_layout.addItem(QSpacerItem( + 0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding), 2, 0) + + # Init QSystemTrayIcon + self.tray_icon = QSystemTrayIcon(self) + self.tray_icon.setIcon( + self.style().standardIcon(QStyle.SP_ComputerIcon)) + + ''' + Define and add steps to work with the system tray icon + show - show window + hide - hide window + exit - exit from application + ''' + show_action = QAction("Show", self) + quit_action = QAction("Exit", self) + hide_action = QAction("Hide", self) + show_action.triggered.connect(self.show) + hide_action.triggered.connect(self.hide) + quit_action.triggered.connect(QApplication.instance().quit) + tray_menu = QMenu() + tray_menu.addAction(show_action) + tray_menu.addAction(hide_action) + tray_menu.addAction(quit_action) + self.tray_icon.setContextMenu(tray_menu) + self.tray_icon.show() + + # Override closeEvent, to intercept the window closing event + # The window will be closed only if there is no check mark in the check box + def closeEvent(self, event): + if self.check_box.isChecked(): + event.ignore() + self.hide() + self.tray_icon.showMessage( + "Tray Program", + "Application was minimized to Tray", + QSystemTrayIcon.Information, + 2000 + ) + + +if __name__ == "__main__": + app = QApplication(sys.argv) + mw = MainWindow() + mw.show() + sys.exit(app.exec_()) diff --git a/QSystemTrayIcon/README.md b/QSystemTrayIcon/README.md new file mode 100755 index 0000000000000000000000000000000000000000..b333ea5030aa50635efdaf38ee8af1167423a45e --- /dev/null +++ b/QSystemTrayIcon/README.md @@ -0,0 +1,21 @@ +# QSystemTrayIcon + +- 目录 + - [最小化到系统托盘](#1最小化到系统托盘) + - [系统托盘闪烁](#2系统托盘闪烁) + +## 1、最小化到系统托盘 + +[运行 MinimizeToTray.py](MinimizeToTray.py) + +选择 Minimize to Tray 在关闭窗口时最小化到系统托盘。 + +> Reference: https://evileg.com/en/post/68/ + +![MinimizeToTray](ScreenShot/MinimizeToTray.gif) + +## 2、系统托盘闪烁 + +[运行 TrayNotify.py](TrayNotify.py) + +通过定时器设置不同图标来实现闪烁。 \ No newline at end of file diff --git a/QSystemTrayIcon/ScreenShot/MinimizeToTray.gif b/QSystemTrayIcon/ScreenShot/MinimizeToTray.gif new file mode 100755 index 0000000000000000000000000000000000000000..29d322bf1d6ba46b57debe83de88668d4b6600a7 Binary files /dev/null and b/QSystemTrayIcon/ScreenShot/MinimizeToTray.gif differ diff --git a/QSystemTrayIcon/TrayNotify.py b/QSystemTrayIcon/TrayNotify.py new file mode 100644 index 0000000000000000000000000000000000000000..914a064defcb624eebda2bed6415a0e8084ead80 --- /dev/null +++ b/QSystemTrayIcon/TrayNotify.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Created on 2021年12月09日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: TrayNotify +@description: 托盘闪烁 +""" + +try: + from PyQt5.QtCore import QTimer + from PyQt5.QtWidgets import (QApplication, QHBoxLayout, QPushButton, + QStyle, QSystemTrayIcon, QWidget) +except ImportError: + from PySide2.QtCore import QTimer + from PySide2.QtWidgets import (QApplication, QHBoxLayout, QPushButton, + QStyle, QSystemTrayIcon, QWidget) + + +class Window(QWidget): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + layout = QHBoxLayout(self) + layout.addWidget(QPushButton('开始闪烁', self, clicked=self.start_flash)) + layout.addWidget(QPushButton('停止闪烁', self, clicked=self.stop_flash)) + # 创建托盘图标 + self.tray_icon = QSystemTrayIcon(self) + self.tray_icon.setIcon(self.style().standardIcon( + QStyle.SP_ComputerIcon)) + self.tray_icon.show() + # 图标闪烁定时器 + self.tray_visible = True + self.flash_timer = QTimer(self, timeout=self.flash_icon) + + def closeEvent(self, event): + self.stop_flash() + self.tray_icon.hide() + super(Window, self).closeEvent(event) + + def start_flash(self): + """开始闪烁""" + if not self.flash_timer.isActive(): + self.flash_timer.start(500) + + def stop_flash(self): + """停止闪烁后需要显示图标""" + if self.flash_timer.isActive(): + self.flash_timer.stop() + self.tray_icon.setIcon(self.style().standardIcon( + QStyle.SP_ComputerIcon)) + + def flash_icon(self): + """根据当前图标是否可见切换图标""" + if self.tray_visible: + self.tray_icon.setIcon(self.style().standardIcon( + QStyle.SP_TrashIcon)) + else: + self.tray_icon.setIcon(self.style().standardIcon( + QStyle.SP_ComputerIcon)) + self.tray_visible = not self.tray_visible + + +if __name__ == '__main__': + import sys + app = QApplication(sys.argv) + w = Window() + w.show() + sys.exit(app.exec_()) diff --git a/QTableView/CopyContent.py b/QTableView/CopyContent.py index b5c82943fd416c6c6a0cf5986ee712e45273c1f7..abeabe436cf4dbd181ef821cecb7f05f7e2b798e 100644 --- a/QTableView/CopyContent.py +++ b/QTableView/CopyContent.py @@ -1,22 +1,23 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -''' +""" Created on 2017年4月6日 -@author: Irony."[讽刺] -@site: https://pyqt5.com , https://github.com/892768447 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: CopyContent @description: -''' -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QStandardItemModel, QStandardItem -from PyQt5.QtWidgets import QTableView, QApplication, QAction, QMessageBox +""" - -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]" -__Version__ = "Version 1.0" +try: + from PyQt5.QtCore import Qt + from PyQt5.QtGui import QStandardItemModel, QStandardItem + from PyQt5.QtWidgets import QTableView, QApplication, QAction, QMessageBox +except ImportError: + from PySide2.QtCore import Qt + from PySide2.QtGui import QStandardItemModel, QStandardItem + from PySide2.QtWidgets import QTableView, QApplication, QAction, QMessageBox class TableView(QTableView): @@ -98,8 +99,10 @@ class TableView(QTableView): self.myModel.setItem( row, col, QStandardItem("row: {row},col: {col}".format(row=row + 1, col=col + 1))) + if __name__ == "__main__": import sys + app = QApplication(sys.argv) app.setApplicationName("TableView") w = TableView() diff --git a/QTableView/README.md b/QTableView/README.md index 171ad4a6ccdf89090b2f93f58db08ee6449e2123..1d79c7a79960aa6cafeec0096b6c0dc218ee7e86 100644 --- a/QTableView/README.md +++ b/QTableView/README.md @@ -1,5 +1,8 @@ # QTableView +- 目录 + - [表格内容复制](#1表格内容复制) + ## 1、表格内容复制 [运行 CopyContent.py](CopyContent.py) diff --git a/QTableWidget/Lib/mainui.py b/QTableWidget/Lib/mainui.py index d5f7041a0f575c420c1fe74e59658bd5044265a5..c8f46651c71dcf79fdbc451a91c2723f9567939f 100644 --- a/QTableWidget/Lib/mainui.py +++ b/QTableWidget/Lib/mainui.py @@ -6,7 +6,11 @@ # # WARNING! All changes made in this file will be lost! -from PyQt5 import QtCore, QtGui, QtWidgets +try: + from PyQt5 import QtCore, QtWidgets +except ImportError: + from PySide2 import QtCore, QtWidgets + class Ui_Form(object): def setupUi(self, Form): @@ -102,10 +106,10 @@ class Ui_Form(object): if __name__ == "__main__": import sys + app = QtWidgets.QApplication(sys.argv) Form = QtWidgets.QWidget() ui = Ui_Form() ui.setupUi(Form) Form.show() sys.exit(app.exec_()) - diff --git a/QTableWidget/README.md b/QTableWidget/README.md index a8bcbb901aaec7793360d62f94a4bfea880356aa..7ed8033751f4bc10488ccb2895244835b712b668 100644 --- a/QTableWidget/README.md +++ b/QTableWidget/README.md @@ -1,8 +1,17 @@ # QTableWidget +- 目录 + - [Sqlalchemy动态拼接字段查询显示表格](#1Sqlalchemy动态拼接字段查询显示表格) + - [表格嵌入日历,下拉框,进度条,按钮](#2表格嵌入) + ## 1、Sqlalchemy动态拼接字段查询显示表格 -[运行 SqlQuery.py](SqlQuery.py) +[运行 SqlQuery.py](SqlQuery.py) | [查看 mainui.ui](Data/mainui.ui) 通过判断界面中选择的条件对`Sqlalchemy`的`model`进行字段拼接从而实现按条件查询 -![SqlQuery](ScreenShot/SqlQuery.png) \ No newline at end of file +![SqlQuery](ScreenShot/SqlQuery.png) + +## 2、TableWidget嵌入部件 +[运行 TableWidget.py](TableWidget.py) +点击开始按钮,进度条开始 +![嵌入小部件](ScreenShot/table.png) diff --git a/QTableWidget/ScreenShot/table.png b/QTableWidget/ScreenShot/table.png new file mode 100644 index 0000000000000000000000000000000000000000..f050ee7330a7761cc497b35aae507c218c6ed861 Binary files /dev/null and b/QTableWidget/ScreenShot/table.png differ diff --git a/QTableWidget/SqlQuery.py b/QTableWidget/SqlQuery.py index 0e417f759c2d91c32000850e4e8f801de1943975..c40581e0aeb301292df20ebdf5c4e1e751c4006e 100644 --- a/QTableWidget/SqlQuery.py +++ b/QTableWidget/SqlQuery.py @@ -4,26 +4,27 @@ """ Created on 2018年5月15日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: SqlQuery @description: """ -from PyQt5.QtCore import pyqtSlot -from PyQt5.QtWidgets import QWidget, QMessageBox, QTableWidgetItem + +try: + from PyQt5.QtCore import pyqtSlot + from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox, QTableWidgetItem +except ImportError: + from PySide2.QtCore import Slot as pyqtSlot + from PySide2.QtWidgets import QApplication, QWidget, QMessageBox, QTableWidgetItem + from sqlalchemy.engine import create_engine from sqlalchemy.ext.declarative.api import declarative_base from sqlalchemy.orm.session import sessionmaker from sqlalchemy.sql.expression import and_ from sqlalchemy.sql.schema import Column from sqlalchemy.sql.sqltypes import Integer, Text -from Lib.mainui import Ui_Form -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" +from Lib.mainui import Ui_Form # engine = create_engine('mysql+mysqldb://root@localhost:3306/tourist?charset=utf8') engine = create_engine('sqlite:///Data/data.sqlite3', echo=True) # echo 表示开启命令显示 @@ -31,7 +32,6 @@ Base = declarative_base() class Tourist(Base): - __tablename__ = 'tourist' id = Column(Integer, primary_key=True) @@ -141,8 +141,9 @@ class Window(QWidget, Ui_Form): if __name__ == '__main__': import sys import cgitb - sys.excepthook = cgitb.Hook(1, None, 5, sys.stderr, 'text') - from PyQt5.QtWidgets import QApplication + + cgitb.enable(format='text') + app = QApplication(sys.argv) w = Window() w.show() diff --git a/QTableWidget/TableWidget.py b/QTableWidget/TableWidget.py new file mode 100644 index 0000000000000000000000000000000000000000..a052566b03d2382f99d36da70eb48833f94c46e6 --- /dev/null +++ b/QTableWidget/TableWidget.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Created on 2017年4月21日 +@author: weike32 +@site: https://pyqt.site ,https://github.com/weike32 +@email: 394967319@qq.com +@file: CopyContent +@description: 查阅了很多博客,如果有异,可以联系作者邮箱。本Demo仅作学习参考用,保有后续相关权益。 +""" +import sys + +try: + from PyQt5 import QtWidgets + from PyQt5.QtCore import * + from PyQt5.QtGui import * + from PyQt5.QtWidgets import * +except ImportError: + from PySide2 import QtWidgets + from PySide2.QtCore import * + from PySide2.QtGui import * + from PySide2.QtWidgets import * + + +class MyTable(QTableWidget): + def __init__(self, parent=None): + super(MyTable, self).__init__(parent) + self.setWindowTitle("我是一个表格") + self.setWindowIcon(QIcon("male.png")) + self.resize(920, 240) + self.setColumnCount(6) + self.setRowCount(2) + # 设置表格有两行五列。 + self.setColumnWidth(0, 200) + self.setColumnWidth(4, 200) + self.setRowHeight(0, 100) + # 设置第一行高度为100px,第一列宽度为200px。 + + self.table() + + def table(self): + self.setItem(0, 0, QTableWidgetItem("你的名字")) + self.setItem(0, 1, QTableWidgetItem("性别")) + self.setItem(0, 2, QTableWidgetItem("出生日期")) + self.setItem(0, 3, QTableWidgetItem("职业")) + self.setItem(0, 4, QTableWidgetItem("收入")) + self.setItem(0, 5, QTableWidgetItem("进度条")) + # 添加表格的文字内容. + self.setHorizontalHeaderLabels(["第一行", "第二行", "第三行", "第四行", "第五行", "第六行"]) + self.setVerticalHeaderLabels(["第一列", "第二列"]) + # 设置表头 + lbp = QLabel() + lbp.setPixmap(QPixmap("youPicture.png")) + self.setCellWidget(1, 1, lbp) + # 在表中添加一张图片 + twi = QTableWidgetItem("Graph") + twi.setFont(QFont("Times", 10, )) + self.setItem(1, 0, twi) + + # 添加一个自己设置了大小和类型的文字。 + dte = QDateTimeEdit() + dte.setDateTime(QDateTime.currentDateTime()) + dte.setDisplayFormat("yyyy/MM/dd") + dte.setCalendarPopup(True) + self.setCellWidget(1, 2, dte) + # 添加一个弹出的日期选择,设置默认值为当前日期,显示格式为年月日。 + cbw = QComboBox() + cbw.addItem("医生") + cbw.addItem("老师") + cbw.addItem("律师") + self.setCellWidget(1, 3, cbw) + # 添加了一个下拉选择框 + sb = QSpinBox() + sb.setRange(1000, 10000) + sb.setValue(5000) # 设置最开始显示的数字 + sb.setDisplayIntegerBase(10) # 这个是显示数字的进制,默认是十进制。 + sb.setSuffix("元") # 设置后辍 + sb.setPrefix("RMB: ") # 设置前辍 + sb.setSingleStep(100) + self.setCellWidget(1, 4, sb) + # 添加一个进度条 + + self.progressBar = QtWidgets.QProgressBar(self) + self.progressBar.setProperty("value", 0) + self.progressBar.setObjectName("progressBar") + self.setCellWidget(1, 5, self.progressBar) + self.step = 0 + self.timer = QTimer() + self.timer.setInterval(1000) + self.timer.start() + # 信号连接到槽 + self.timer.timeout.connect(self.onTimerOut) + self.count = 0 + + def onTimerOut(self): # 重写timerEvent + self.count += 1 + if self.count >= 100: # value >= 100时,停止计时器 + self.timer.stop() + print("结束") + # self.progressBar.setValue(self.step) + else: + print(self.count) + self.progressBar.setValue(self.count) + # return + # self.step += 1 + + +if __name__ == '__main__': + app = QApplication(sys.argv) + myTable = MyTable() + myTable.show() + app.exit(app.exec_()) diff --git a/QTextBrowser/DynamicRes.py b/QTextBrowser/DynamicRes.py new file mode 100644 index 0000000000000000000000000000000000000000..2c229fe5ddddc39c6fb550e9e1a9947f67724c81 --- /dev/null +++ b/QTextBrowser/DynamicRes.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2020/6/3 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: DynamicRes +@description: +""" +from threading import Thread + +import requests + +try: + from PyQt5.QtCore import QUrl, QByteArray + from PyQt5.QtGui import QImage, QTextDocument + from PyQt5.QtWidgets import QApplication, QTextBrowser, QWidget, QVBoxLayout, QPushButton +except ImportError: + from PySide2.QtCore import QUrl, QByteArray + from PySide2.QtGui import QImage, QTextDocument + from PySide2.QtWidgets import QApplication, QTextBrowser, QWidget, QVBoxLayout, QPushButton + + +class TextBrowser(QTextBrowser): + NetImages = {} + + def __init__(self, *args, **kwargs): + super(TextBrowser, self).__init__(*args, **kwargs) + self.setOpenLinks(False) # 禁止打开URL + + def downloadImage(self, url): + try: + self.NetImages[url] = [QByteArray(requests.get(url.toString()).content), 1] + print('下载完成', url) + except Exception as e: + print('下载失败', url, e) + self.NetImages[url] = [QByteArray(), 1] + + def loadResource(self, rtype, url): + ret = super(TextBrowser, self).loadResource(rtype, url) + # 加载图片资源 + if rtype == QTextDocument.ImageResource: + if ret: + return ret + if url.toString().startswith('irony'): # 自定义的协议头 + print('加载本地', '../Donate/zhifubao.png', url) + return QImage( + '../Donate/zhifubao.png') # 或者 QByteArray(open('../Donate/zhifubao.png', 'rb').read()) + elif url.toString().startswith('http'): # 加载网络图片 + img, status = self.NetImages.get(url, [None, None]) + if url not in self.NetImages or status is None: + # 子线程下载 + self.NetImages[url] = [None, 1] + print('download ', url) + Thread(target=self.downloadImage, args=(url,), daemon=True).start() + elif img: + return img + return ret + + def mouseDoubleClickEvent(self, event): + # 双击图片得到图片的URL,也可以用来放大显示 + super(TextBrowser, self).mouseDoubleClickEvent(event) + url = self.anchorAt(event.pos()) + if url: + print('url:', url, self.document().resource(QTextDocument.ImageResource, QUrl(url))) + + +class Window(QWidget): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + layout = QVBoxLayout(self) + + self.textBrowser = TextBrowser(self) + self.downButton = QPushButton('加载网络图片', self) + + layout.addWidget(self.textBrowser) + layout.addWidget(self.downButton) + + # 加载本地图片 + img = QImage('../Donate/weixin.png') + # 第二个参数为任意唯一的url类似于qrc方式 + self.textBrowser.document().addResource(QTextDocument.ImageResource, + QUrl('dynamic:/images/weixin.png'), img) + + # 设置html + # 需要注意里面的图片地址 + self.textBrowser.setHtml( + '

' # 方式一直接加载本地图片 + '

' # 方式二通过addResource添加资源 + '

' # 方式三定义自定义的协议头通过loadResource动态加载 + '

') + + +if __name__ == '__main__': + import sys + import cgitb + + cgitb.enable(format='text') + + app = QApplication(sys.argv) + w = Window() + w.show() + sys.exit(app.exec_()) diff --git a/QTextBrowser/README.md b/QTextBrowser/README.md index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..dee5984cc93cd54cb9bf16f686efd0ad065b1075 100644 --- a/QTextBrowser/README.md +++ b/QTextBrowser/README.md @@ -0,0 +1,14 @@ +# QTextBrowser + +- 目录 + - [动态加载图片](#1动态加载图片) + +## 1、动态加载图片 +[运行 DynamicRes.py](DynamicRes.py) + +动态加载资源有多种方式,这里主要介绍 [addResource](https://doc.qt.io/qt-5/qtextdocument.html#addResource) 和 [loadResource](https://doc.qt.io/qt-5/qtextbrowser.html#loadResource) 函数 + +1、通过 `self.textBrowser.document().addResource(QTextDocument.ImageResource, QUrl('dynamic:/images/weixin.png'), img)` 向文档中注册新的资源索引,类似QRC +2、通过重载 `loadResource` 函数可以监听到所有的资源加载,然后动态返回内容 + +![DynamicRes](ScreenShot/DynamicRes.gif) \ No newline at end of file diff --git a/QTextBrowser/ScreenShot/DynamicRes.gif b/QTextBrowser/ScreenShot/DynamicRes.gif new file mode 100644 index 0000000000000000000000000000000000000000..9c923b58a50c56707b092957dbfd3d74c8f090af Binary files /dev/null and b/QTextBrowser/ScreenShot/DynamicRes.gif differ diff --git a/QTextEdit/HighlightText.py b/QTextEdit/HighlightText.py index d1c8c485d75b24ef4bd15bf1cd4b033b6b7e1867..ca7398fdfaef271b745c28173cccef83feccc30c 100644 --- a/QTextEdit/HighlightText.py +++ b/QTextEdit/HighlightText.py @@ -1,7 +1,27 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2019年5月22日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: +@description: +""" + import sys -from PyQt5.QtGui import QTextCharFormat, QTextDocument, QTextCursor -from PyQt5.QtWidgets import (QApplication, QMainWindow, QTextEdit, - QToolBar, QLineEdit, QPushButton, QColorDialog, QHBoxLayout, QWidget) + +try: + from PyQt5.QtCore import QRegExp + from PyQt5.QtGui import QTextCharFormat, QTextCursor + from PyQt5.QtWidgets import (QApplication, QMainWindow, QTextEdit, + QToolBar, QLineEdit, QPushButton, QColorDialog, QHBoxLayout, QWidget) +except ImportError: + from PySide2.QtCore import QRegExp + from PySide2.QtGui import QTextCharFormat, QTextCursor + from PySide2.QtWidgets import (QApplication, QMainWindow, QTextEdit, + QToolBar, QLineEdit, QPushButton, QColorDialog, QHBoxLayout, QWidget) class TextEdit(QMainWindow): @@ -22,30 +42,46 @@ class TextEdit(QMainWindow): tb = QToolBar(self) tb.addWidget(widget) + self.addToolBar(tb) def setText(self, text): self.textEdit.setPlainText(text) - def mergeFormatOnWordOrSelection(self, format): - cursor = self.textEdit.textCursor() - if not cursor.hasSelection(): - cursor.select(QTextCursor.WordUnderCursor) - cursor.mergeCharFormat(format) - self.textEdit.mergeCurrentCharFormat(format) - def highlight(self): text = self.findText.text() # 输入框中的文字 if not text: return + col = QColorDialog.getColor(self.textEdit.textColor(), self) if not col.isValid(): return + + # 恢复默认的颜色 + cursor = self.textEdit.textCursor() + cursor.select(QTextCursor.Document) + cursor.setCharFormat(QTextCharFormat()) + cursor.clearSelection() + self.textEdit.setTextCursor(cursor) + + # 文字颜色 fmt = QTextCharFormat() fmt.setForeground(col) - # 先把光标移动到开头 + + # 正则 + expression = QRegExp(text) self.textEdit.moveCursor(QTextCursor.Start) - while self.textEdit.find(text, QTextDocument.FindWholeWords): # 查找所有文字 - self.mergeFormatOnWordOrSelection(fmt) + cursor = self.textEdit.textCursor() + + # 循环查找设置颜色 + pos = 0 + index = expression.indexIn(self.textEdit.toPlainText(), pos) + while index >= 0: + cursor.setPosition(index) + cursor.movePosition(QTextCursor.Right, + QTextCursor.KeepAnchor, len(text)) + cursor.mergeCharFormat(fmt) + pos = index + expression.matchedLength() + index = expression.indexIn(self.textEdit.toPlainText(), pos) if __name__ == '__main__': diff --git a/QTextEdit/README.md b/QTextEdit/README.md index 68780a4cdeb78883cff0a264c5e75f5d70997d76..98bec9d8f1dc1533c0d45b8c1cef8049cffc5a40 100644 --- a/QTextEdit/README.md +++ b/QTextEdit/README.md @@ -1,5 +1,8 @@ # QTextEdit +- 目录 + - [文本查找高亮](#1文本查找高亮) + ## 1、文本查找高亮 [运行 HighlightText.py](HighlightText.py) diff --git a/QThread/InheritQThread.py b/QThread/InheritQThread.py index ea10801818974cc3067d1182b7524b8295446ec9..051f5f6b2b5e4014c54386f95f20e83211410449 100644 --- a/QThread/InheritQThread.py +++ b/QThread/InheritQThread.py @@ -4,27 +4,28 @@ """ Created on 2018年3月9日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: InheritQThread @description: 继承QThread """ -from PyQt5.QtCore import QThread, pyqtSignal -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QProgressBar, QPushButton - -__Author__ = 'By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com' -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 +try: + from PyQt5.QtCore import QThread, pyqtSignal + from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QProgressBar, QPushButton +except ImportError: + from PySide2.QtCore import QThread, Signal as pyqtSignal + from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout, QProgressBar, QPushButton class Worker(QThread): - valueChanged = pyqtSignal(int) # 值变化信号 def run(self): - print('thread id', int(QThread.currentThreadId())) + print('thread id', QThread.currentThread()) for i in range(1, 101): + if self.isInterruptionRequested(): + break print('value', i) self.valueChanged.emit(i) QThread.sleep(1) @@ -41,7 +42,7 @@ class Window(QWidget): layout.addWidget(QPushButton('开启线程', self, clicked=self.onStart)) # 当前线程id - print('main id', int(QThread.currentThreadId())) + print('main id', QThread.currentThread()) # 子线程 self._thread = Worker(self) @@ -49,21 +50,26 @@ class Window(QWidget): self._thread.valueChanged.connect(self.progressBar.setValue) def onStart(self): - print('main id', int(QThread.currentThreadId())) - self._thread.start() # 启动线程 + if not self._thread.isRunning(): + print('main id', QThread.currentThread()) + self._thread.start() # 启动线程 def closeEvent(self, event): if self._thread.isRunning(): + self._thread.requestInterruption() self._thread.quit() + self._thread.wait() # 强制 # self._thread.terminate() - del self._thread + self._thread.deleteLater() super(Window, self).closeEvent(event) if __name__ == '__main__': import sys - from PyQt5.QtWidgets import QApplication + import cgitb + + cgitb.enable(format='text') app = QApplication(sys.argv) w = Window() w.show() diff --git a/QThread/QuitThread.py b/QThread/QuitThread.py new file mode 100644 index 0000000000000000000000000000000000000000..35b7a8e9eb331471f785922ac8a6c07436d7a9d7 --- /dev/null +++ b/QThread/QuitThread.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2020/11/27 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: QuitThread +@description: +""" + +import sys +from time import time + +try: + from PyQt5.QtCore import QThread, QCoreApplication, QTimer +except ImportError: + from PySide2.QtCore import QThread, QCoreApplication, QTimer + + +class Thread(QThread): + + def run(self): + print('thread id', QThread.currentThread()) + i = 0 + while i < 101 and not self.isInterruptionRequested(): + print('value', i, time()) + i += 1 + QThread.msleep(500) + print('thread quit') + + +if __name__ == '__main__': + app = QCoreApplication(sys.argv) + t = Thread() + t.finished.connect(app.quit) + t.start() + # 3秒后退出 + print('will quit 3s latter') + QTimer.singleShot(3000, t.requestInterruption) + sys.exit(app.exec_()) diff --git a/QThread/README.md b/QThread/README.md index 6f52c7c94b4736c75af090bbb16114f3d5bc10ad..b9661c21c64559ea041b749eea945ea1c51a9cb9 100644 --- a/QThread/README.md +++ b/QThread/README.md @@ -1,6 +1,11 @@ # QThread -PyQt多线程的简单使用例子 +- 目录 + - [继承QThread](#1继承QThread) + - [moveToThread](#2moveToThread) + - [线程挂起恢复](#3线程挂起恢复) + - [线程休眠唤醒](#4线程休眠唤醒) + - [线程退出](#5线程退出) ## 1、继承QThread [运行 InheritQThread.py](InheritQThread.py) @@ -30,4 +35,11 @@ PyQt多线程的简单使用例子 使用 `QWaitCondition` 的 `wait` 和 `wakeAll` 方法 -![WakeupThread](ScreenShot/WakeupThread.gif) \ No newline at end of file +![WakeupThread](ScreenShot/WakeupThread.gif) + +## 5、线程退出 +[运行 QuitThread.py](QuitThread.py) + +`isInterruptionRequested` 和 `requestInterruption` 函数作为退出标识调用 + +![QuitThread](ScreenShot/QuitThread.jpg) \ No newline at end of file diff --git a/QThread/ScreenShot/QuitThread.jpg b/QThread/ScreenShot/QuitThread.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2c96dee2508aea759382d501574cf5583c55357e Binary files /dev/null and b/QThread/ScreenShot/QuitThread.jpg differ diff --git a/QThread/SuspendThread.py b/QThread/SuspendThread.py index 707324e629a69e981c5815c75537fbac78550ccc..32c3601835114feb1a2c6ef40bc85cab5eb64165 100644 --- a/QThread/SuspendThread.py +++ b/QThread/SuspendThread.py @@ -1,26 +1,24 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- + +""" +Created on 2018年3月13日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: +@description: +""" + import ctypes +import win32con from PyQt5.QtCore import QThread, pyqtSignal from PyQt5.QtWidgets import QWidget, QVBoxLayout, QProgressBar, QPushButton -import win32con from win32process import SuspendThread, ResumeThread -# Created on 2018年3月13日 -# author: Irony -# site: https://pyqt5.com , https://github.com/892768447 -# email: 892768447@qq.com -# file: 多线程使用.a -# description: -__Author__ = 'By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com' -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 - - class Worker(QThread): - valueChanged = pyqtSignal(int) # 值变化信号 handle = -1 @@ -109,8 +107,10 @@ class Window(QWidget): if __name__ == '__main__': import sys import os + print('pid', os.getpid()) from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) w = Window() w.show() diff --git a/QThread/WakeupThread.py b/QThread/WakeupThread.py index 2f11a8065ed07a6d4de06fd0416021792c761565..e9350371fee85a759a54c3bf23d30903e933ff8c 100644 --- a/QThread/WakeupThread.py +++ b/QThread/WakeupThread.py @@ -4,7 +4,7 @@ """ Created on 2018年11月11日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: @description: @@ -13,15 +13,7 @@ from PyQt5.QtCore import QThread, QWaitCondition, QMutex, pyqtSignal from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QProgressBar -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 - - class Thread(QThread): - valueChange = pyqtSignal(int) def __init__(self, *args, **kwargs): @@ -75,8 +67,10 @@ class Window(QWidget): if __name__ == '__main__': import sys import cgitb - sys.excepthook = cgitb.enable(1, None, 5, '') + + cgitb.enable(format='text') from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) w = Window() w.show() diff --git a/QThread/moveToThread.py b/QThread/moveToThread.py index ea1602de6269c2f7a08b3b5b7d93b50e0dd4ea52..77214df1f7823b70e33c64a867416e25814f035a 100644 --- a/QThread/moveToThread.py +++ b/QThread/moveToThread.py @@ -4,27 +4,28 @@ """ Created on 2018年3月9日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: moveToThread @description: moveToThread """ -from PyQt5.QtCore import QObject, pyqtSignal, QThread -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QProgressBar, QPushButton - -__Author__ = 'By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com' -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 +try: + from PyQt5.QtCore import QObject, pyqtSignal, QThread + from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QProgressBar, QPushButton +except ImportError: + from PySide2.QtCore import QObject, Signal as pyqtSignal, QThread + from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout, QProgressBar, QPushButton class Worker(QObject): - valueChanged = pyqtSignal(int) # 值变化信号 def run(self): - print('thread id', int(QThread.currentThreadId())) + print('thread id', ) for i in range(1, 101): + if QThread.currentThread().isInterruptionRequested(): + break print('value', i) self.valueChanged.emit(i) QThread.sleep(1) @@ -41,33 +42,38 @@ class Window(QWidget): layout.addWidget(QPushButton('开启线程', self, clicked=self.onStart)) # 当前线程id - print('main id', int(QThread.currentThreadId())) + print('main id', QThread.currentThread()) # 启动线程更新进度条值 self._thread = QThread(self) self._worker = Worker() self._worker.moveToThread(self._thread) # 移动到线程中执行 self._thread.finished.connect(self._worker.deleteLater) + self._thread.started.connect(self._worker.run) self._worker.valueChanged.connect(self.progressBar.setValue) def onStart(self): - print('main id', int(QThread.currentThreadId())) - self._thread.started.connect(self._worker.run) - self._thread.start() # 启动线程 + if not self._thread.isRunning(): + print('main id', QThread.currentThread()) + self._thread.start() # 启动线程 def closeEvent(self, event): if self._thread.isRunning(): + self._thread.requestInterruption() self._thread.quit() + self._thread.wait() # 强制 # self._thread.terminate() - del self._thread - del self._worker + self._thread.deleteLater() super(Window, self).closeEvent(event) if __name__ == '__main__': import sys - from PyQt5.QtWidgets import QApplication + import cgitb + + cgitb.enable(format='text') + app = QApplication(sys.argv) w = Window() w.show() diff --git a/QTreeWidget/Data/testTree.ui b/QTreeWidget/Data/testTree.ui new file mode 100644 index 0000000000000000000000000000000000000000..8ceab90f82dd962cac0a365f01407211c2ee8a48 --- /dev/null +++ b/QTreeWidget/Data/testTree.ui @@ -0,0 +1,82 @@ + + + Form + + + + 0 + 0 + 719 + 544 + + + + Form + + + + + 80 + 80 + 256 + 192 + + + + + 测试 + + + + + 测试1 + + + Unchecked + + + + 子节点1 + + + Unchecked + + + + + 字节点2 + + + Unchecked + + + + + 字节点3 + + + Unchecked + + + + + 字节点4 + + + Unchecked + + + + + 字节点5 + + + Unchecked + + + + + + + + diff --git a/QTreeWidget/Lib/__init__.py b/QTreeWidget/Lib/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/QTreeWidget/Lib/testTree.py b/QTreeWidget/Lib/testTree.py new file mode 100644 index 0000000000000000000000000000000000000000..2745b729f1d4c48b0e6829d3e0ae58198ebb01a9 --- /dev/null +++ b/QTreeWidget/Lib/testTree.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'testTree.ui' +# +# Created by: PyQt5 UI code generator 5.10.1 +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(719, 544) + self.treeWidget = QtWidgets.QTreeWidget(Form) + self.treeWidget.setGeometry(QtCore.QRect(80, 80, 256, 192)) + self.treeWidget.setObjectName("treeWidget") + item_0 = QtWidgets.QTreeWidgetItem(self.treeWidget) + item_0.setCheckState(0, QtCore.Qt.Unchecked) + item_1 = QtWidgets.QTreeWidgetItem(item_0) + item_1.setCheckState(0, QtCore.Qt.Unchecked) + item_1 = QtWidgets.QTreeWidgetItem(item_0) + item_1.setCheckState(0, QtCore.Qt.Unchecked) + item_1 = QtWidgets.QTreeWidgetItem(item_0) + item_1.setCheckState(0, QtCore.Qt.Unchecked) + item_1 = QtWidgets.QTreeWidgetItem(item_0) + item_1.setCheckState(0, QtCore.Qt.Unchecked) + item_1 = QtWidgets.QTreeWidgetItem(item_0) + item_1.setCheckState(0, QtCore.Qt.Unchecked) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Form")) + self.treeWidget.headerItem().setText(0, _translate("Form", "测试")) + __sortingEnabled = self.treeWidget.isSortingEnabled() + self.treeWidget.setSortingEnabled(False) + self.treeWidget.topLevelItem(0).setText(0, _translate("Form", "测试1")) + self.treeWidget.topLevelItem(0).child(0).setText(0, _translate("Form", "子节点1")) + self.treeWidget.topLevelItem(0).child(1).setText(0, _translate("Form", "字节点2")) + self.treeWidget.topLevelItem(0).child(2).setText(0, _translate("Form", "字节点3")) + self.treeWidget.topLevelItem(0).child(3).setText(0, _translate("Form", "字节点4")) + self.treeWidget.topLevelItem(0).child(4).setText(0, _translate("Form", "字节点5")) + self.treeWidget.setSortingEnabled(__sortingEnabled) + + +if __name__ == "__main__": + import sys + + app = QtWidgets.QApplication(sys.argv) + Form = QtWidgets.QWidget() + ui = Ui_Form() + ui.setupUi(Form) + Form.show() + sys.exit(app.exec_()) diff --git a/QTreeWidget/ParentNodeForbid.py b/QTreeWidget/ParentNodeForbid.py new file mode 100644 index 0000000000000000000000000000000000000000..1a139e7bacc39d326936c437132f79d3b06ef2ad --- /dev/null +++ b/QTreeWidget/ParentNodeForbid.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2019年11月8日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: QTreeWidget.ParentNodeForbid +@description: 父节点不可选中 +""" + +try: + from PyQt5.QtCore import Qt + from PyQt5.QtWidgets import QApplication, QTreeWidget, QTreeWidgetItem, QStyledItemDelegate, \ + QStyle +except ImportError: + from PySide2.QtCore import Qt + from PySide2.QtWidgets import QApplication, QTreeWidget, QTreeWidgetItem, QStyledItemDelegate, \ + QStyle + + +class NoColorItemDelegate(QStyledItemDelegate): + + def paint(self, painter, option, index): + if option.state & QStyle.State_HasFocus: + # 取消虚线框 + option.state = option.state & ~ QStyle.State_HasFocus + if option.state & QStyle.State_MouseOver and index.data(Qt.UserRole + 1): + # 不显示鼠标悬停颜色 + option.state = option.state & ~ QStyle.State_MouseOver + super(NoColorItemDelegate, self).paint(painter, option, index) + + +class Window(QTreeWidget): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + self.setItemDelegateForColumn(0, NoColorItemDelegate(self)) + + # 父节点(不可选中) + pitem1 = QTreeWidgetItem(self, ['parent item 1']) + # 设置不可选中 + pitem1.setFlags(pitem1.flags() & ~Qt.ItemIsSelectable) + # 设置一个标识用于屏蔽鼠标事件 + pitem1.setData(0, Qt.UserRole + 1, True) + + pitem2 = QTreeWidgetItem(self, ['parent item 2']) + pitem2.setFlags(pitem2.flags() & ~Qt.ItemIsSelectable) + pitem2.setData(0, Qt.UserRole + 1, True) + + # 子节点(可选) + citem1 = QTreeWidgetItem(pitem1, ['child item 1']) + citem2 = QTreeWidgetItem(pitem2, ['child item 2']) + + self.expandAll() + + # 信号槽 + self.itemActivated.connect(self.onItemActivated) + self.itemClicked.connect(self.onItemClicked) + self.itemDoubleClicked.connect(self.onItemDoubleClicked) + self.itemPressed.connect(self.onItemPressed) + + def mousePressEvent(self, event): + # 鼠标点击事件,判断当前点击位置是否有item且满足标志则拦截鼠标事件 + item = self.itemAt(event.pos()) + if item and item.data(0, Qt.UserRole + 1): + event.accept() + return + super(Window, self).mousePressEvent(event) + + def onItemActivated(self, item, column): + print('Activated', item.text(0), item, column) + + def onItemClicked(self, item, column): + print('Clicked', item.text(0), item, column) + + def onItemDoubleClicked(self, item, column): + print('DoubleClicked', item.text(0), item, column) + + def onItemPressed(self, item, column): + print('Pressed', item.text(0), item, column) + + +if __name__ == '__main__': + import sys + import cgitb + + cgitb.enable(format='text') + + app = QApplication(sys.argv) + w = Window() + w.show() + sys.exit(app.exec_()) diff --git a/QTreeWidget/ParsingJson.py b/QTreeWidget/ParsingJson.py index dbbc088c368bca6b84669349a15652ca95753538..c32480ce840bca2c8d5b21a714f78ef40bbb052c 100644 --- a/QTreeWidget/ParsingJson.py +++ b/QTreeWidget/ParsingJson.py @@ -4,7 +4,7 @@ """ Created on 2018年4月8日 @author: Irony -@site: https://pyqt5.com , https://github.com/892768447 +@site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: ParsingJson @description: @@ -12,18 +12,18 @@ Created on 2018年4月8日 import json import webbrowser -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QIcon -from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QWidget,\ - QLabel, QSpacerItem, QSizePolicy, QHBoxLayout import chardet - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" +try: + from PyQt5.QtCore import Qt + from PyQt5.QtGui import QIcon + from PyQt5.QtWidgets import QApplication, QTreeWidget, QTreeWidgetItem, QWidget, \ + QLabel, QSpacerItem, QSizePolicy, QHBoxLayout +except ImportError: + from PySide2.QtCore import Qt + from PySide2.QtGui import QIcon + from PySide2.QtWidgets import QApplication, QTreeWidget, QTreeWidgetItem, QWidget, \ + PySide2, QSpacerItem, QSizePolicy, QHBoxLayout class ItemWidget(QWidget): @@ -94,7 +94,7 @@ class JsonTreeWidget(QTreeWidget): if __name__ == '__main__': import sys - from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) app.setStyleSheet("""QTreeView { outline: 0px; diff --git a/QTreeWidget/README.md b/QTreeWidget/README.md index 5317d970b9a301ded042fea7b462adc70743f71d..5194650a93e39d71b2682af9db917a2821a9d39f 100644 --- a/QTreeWidget/README.md +++ b/QTreeWidget/README.md @@ -1,8 +1,29 @@ # QTreeWidget +- 目录 + - [通过json数据生成树形结构](#1通过json数据生成树形结构) + - [点击父节点全选/取消全选子节点](#2点击父节点全选取消全选子节点) + - [禁止父节点/禁止父节点](#3禁止父节点) + ## 1、通过json数据生成树形结构 [运行 ParsingJson.py](ParsingJson.py) 解析每一层json数据中的list -![ParsingJson](ScreenShot/ParsingJson.png) \ No newline at end of file + +![ParsingJson](ScreenShot/ParsingJson.png) + +## 2、点击父节点全选/取消全选子节点 +[运行 testTreeWidget.py](testTreeWidget.py) | [查看 testTree.ui](Data/testTree.ui) + +点击父节点全选/取消全选子节点 + +![testTreeWidget](ScreenShot/allSelectNode.png) + +## 3、禁止父节点 +[运行 ParentNodeForbid.py](ParentNodeForbid.py) + + 1. 父节点通过设置`pitem1.setFlags(pitem1.flags() & ~Qt.ItemIsSelectable)`为不可选 + 2. 完全禁用点击等需要重写`mousePressEvent`事件并结合item的标志来判断 + +![ParentNodeForbid](ScreenShot/ParentNodeForbid.gif) \ No newline at end of file diff --git a/QTreeWidget/ScreenShot/ParentNodeForbid.gif b/QTreeWidget/ScreenShot/ParentNodeForbid.gif new file mode 100644 index 0000000000000000000000000000000000000000..272309932ba2136e3c1778115cd5eaf099344f20 Binary files /dev/null and b/QTreeWidget/ScreenShot/ParentNodeForbid.gif differ diff --git a/QTreeWidget/ScreenShot/allSelectNode.png b/QTreeWidget/ScreenShot/allSelectNode.png new file mode 100644 index 0000000000000000000000000000000000000000..fac534f0e25fa4902508c9a9f52ad2942c83da78 Binary files /dev/null and b/QTreeWidget/ScreenShot/allSelectNode.png differ diff --git a/QTreeWidget/__init__.py b/QTreeWidget/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/QTreeWidget/testTreeWidget.py b/QTreeWidget/testTreeWidget.py new file mode 100644 index 0000000000000000000000000000000000000000..3bdb95682210c22ed5f6d2db9c02da1407d25d0a --- /dev/null +++ b/QTreeWidget/testTreeWidget.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Created on 2017年4月20日 +@author: weike32 +@site: https://pyqt.site , https://github.com/weike32 +@email: 394967319@qq.com +@file: CopyContent +@description: +""" +import sys + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QDialog, QApplication + +from Lib.testTree import Ui_Form # @UnresolvedImport + + +class graphAnalysis(QDialog, Ui_Form): + def __init__(self): + super(graphAnalysis, self).__init__() + self.setupUi(self) + # 点击父节点 + self.treeWidget.itemChanged.connect(self.handleChanged) + + def handleChanged(self, item, column): + count = item.childCount() + if item.checkState(column) == Qt.Checked: + for index in range(count): + item.child(index).setCheckState(0, Qt.Checked) + if item.checkState(column) == Qt.Unchecked: + for index in range(count): + item.child(index).setCheckState(0, Qt.Unchecked) + + +if __name__ == "__main__": + app = QApplication(sys.argv) + w = graphAnalysis() + w.show() + sys.exit(app.exec_()) diff --git a/QVBoxLayout/Data/BaseVerticalLayout.ui b/QVBoxLayout/Data/BaseVerticalLayout.ui new file mode 100644 index 0000000000000000000000000000000000000000..77c5b148ad2500287ca8c1003bb80bd1ed1e7239 --- /dev/null +++ b/QVBoxLayout/Data/BaseVerticalLayout.ui @@ -0,0 +1,38 @@ + + + BaseVerticalLayout + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + 通过 右键 -> 布局 -> 垂直布局 + + + Qt::AlignCenter + + + + + + + 底部按钮 + + + + + + + + diff --git a/QVBoxLayout/Data/VerticalLayoutMargin.ui b/QVBoxLayout/Data/VerticalLayoutMargin.ui new file mode 100644 index 0000000000000000000000000000000000000000..6045d80906bf7be1493d06efc615d73c4469c88a --- /dev/null +++ b/QVBoxLayout/Data/VerticalLayoutMargin.ui @@ -0,0 +1,62 @@ + + + VerticalLayoutMargin + + + + 0 + 0 + 400 + 300 + + + + Form + + + #VerticalLayoutMargin { + background: #5aaadb; +} +#label { + background: #85c440; +} + + + + 50 + + + 20 + + + 20 + + + + + 通过设置Margin和Spacing设置边距以及两个控件之间的间隔距离 + + + Qt::AlignCenter + + + + + + + Spcasing 为 50 + + + + + + + Spcasing 为 50 + + + + + + + + diff --git a/QVBoxLayout/Data/VerticalLayoutStretch.ui b/QVBoxLayout/Data/VerticalLayoutStretch.ui new file mode 100644 index 0000000000000000000000000000000000000000..bf198c9f3649bf70580b9721006b3ec62607f596 --- /dev/null +++ b/QVBoxLayout/Data/VerticalLayoutStretch.ui @@ -0,0 +1,62 @@ + + + VerticalLayoutStretch + + + + 0 + 0 + 400 + 300 + + + + Form + + + #label { + background: #5aaadb; +} +#label_2 { + background: #85c440; +} +#label_3 { + background: #f2b63c; +} + + + + + + 通过设置Stretch设置每部分的占比(1,2,3) 1/6 + + + Qt::AlignCenter + + + + + + + 2/6 + + + Qt::AlignCenter + + + + + + + 3/6 + + + Qt::AlignCenter + + + + + + + + diff --git a/QVBoxLayout/README.md b/QVBoxLayout/README.md index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0067952222ef89db5dab91c5ed7f9a79a6ec5772 100644 --- a/QVBoxLayout/README.md +++ b/QVBoxLayout/README.md @@ -0,0 +1,32 @@ +# QVBoxLayout + +- 目录 + - [垂直布局](#1垂直布局) + - [边距和间隔](#2边距和间隔) + - [比例分配](#3比例分配) + +## 1、垂直布局 +[查看 BaseVerticalLayout.ui](Data/BaseVerticalLayout.ui) + +![BaseVerticalLayout](ScreenShot/BaseVerticalLayout.png) + +## 2、边距和间隔 +[查看 VerticalLayoutMargin.ui](Data/VerticalLayoutMargin.ui) + +1. 通过`setContentsMargins(20, 20, -1, -1)`设置左上右下的边距,-1表示默认值 +2. 通过`setSpacing`设置控件之间的间隔 + +![VerticalLayoutMargin](ScreenShot/VerticalLayoutMargin.png) + +## 3、比例分配 +[查看 VerticalLayoutStretch.ui](Data/VerticalLayoutStretch.ui) + +通过`setStretch`设置各个部分的占比 分别为:1/6 2/6 3/6 + +```python +self.verticalLayout.setStretch(0, 1) +self.verticalLayout.setStretch(1, 2) +self.verticalLayout.setStretch(2, 3) +``` + +![VerticalLayoutStretch](ScreenShot/VerticalLayoutStretch.png) \ No newline at end of file diff --git a/QVBoxLayout/ScreenShot/BaseVerticalLayout.png b/QVBoxLayout/ScreenShot/BaseVerticalLayout.png new file mode 100644 index 0000000000000000000000000000000000000000..944a73a7a32c9522603f8c1fe0957074b11fdfd3 Binary files /dev/null and b/QVBoxLayout/ScreenShot/BaseVerticalLayout.png differ diff --git a/QVBoxLayout/ScreenShot/VerticalLayoutMargin.png b/QVBoxLayout/ScreenShot/VerticalLayoutMargin.png new file mode 100644 index 0000000000000000000000000000000000000000..2208490cea21175507f6259c4b266b93e387e5ce Binary files /dev/null and b/QVBoxLayout/ScreenShot/VerticalLayoutMargin.png differ diff --git a/QVBoxLayout/ScreenShot/VerticalLayoutStretch.png b/QVBoxLayout/ScreenShot/VerticalLayoutStretch.png new file mode 100644 index 0000000000000000000000000000000000000000..ff3573234678233e4afef7ba0c8edaa5102c8819 Binary files /dev/null and b/QVBoxLayout/ScreenShot/VerticalLayoutStretch.png differ diff --git a/QWebChannel/CallEachWithJs.py b/QWebChannel/CallEachWithJs.py new file mode 100644 index 0000000000000000000000000000000000000000..238b0625f7e0a34ce32e20ea7b52ce16a6e1cc5a --- /dev/null +++ b/QWebChannel/CallEachWithJs.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Created on 2021/12/15 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: CallEachWithJs.py +@description: 与JS之间的互相调用 +""" + +import os + +from PyQt5.QtCore import QUrl, pyqtSlot +from PyQt5.QtGui import QDesktopServices +from PyQt5.QtWidgets import (QApplication, QLineEdit, QPushButton, QVBoxLayout, + QWidget) + +from Lib.WebChannelObject import WebChannelObject + + +class Window(QWidget): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + self.m_obj = WebChannelObject(self) + # 注册该窗口,可以访问该窗口的属性,槽函数,信号 + # https://doc.qt.io/qt-5/qwidget.html#properties + # https://doc.qt.io/qt-5/qwidget.html#signals + # https://doc.qt.io/qt-5/qwidget.html#public-slots + self.m_obj.registerObject('qtwindow', self) + self.m_obj.start() + + layout = QVBoxLayout(self) + self.editTitle = QLineEdit(self, placeholderText='输入标题') + layout.addWidget(self.editTitle) + layout.addWidget(QPushButton('修改标题', self, clicked=self.onChangeTitle)) + + QDesktopServices.openUrl( + QUrl.fromLocalFile( + os.path.join(os.path.dirname(sys.argv[0] or __file__), + 'Data/CallEachWithJs.html'))) + + def onChangeTitle(self): + self.setWindowTitle(self.editTitle.text()) + + # ------- 把非槽函数通过pyqtSlot重新暴露 ------- + @pyqtSlot(int, int) + def resize(self, width, height): + super().resize(width, height) + + +if __name__ == '__main__': + import cgitb + import sys + + cgitb.enable(format='text') + app = QApplication(sys.argv) + w = Window() + w.show() + sys.exit(app.exec_()) diff --git a/QWebChannel/Data/CallEachWithJs.html b/QWebChannel/Data/CallEachWithJs.html new file mode 100644 index 0000000000000000000000000000000000000000..fb5752be5aab70b9c55e8826515902f5f0aa9797 --- /dev/null +++ b/QWebChannel/Data/CallEachWithJs.html @@ -0,0 +1,76 @@ + + + + + + + + + + + + 输出:


+ + +

+ + +

+ + +

+ + +

+ + + +

+ + + + + \ No newline at end of file diff --git a/QWebChannel/Data/qwebchannel.js b/QWebChannel/Data/qwebchannel.js new file mode 100644 index 0000000000000000000000000000000000000000..32ad51eb7de524d5b7221e32711882d6d3d4957e --- /dev/null +++ b/QWebChannel/Data/qwebchannel.js @@ -0,0 +1,448 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebChannel module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +"use strict"; + +var QWebChannelMessageTypes = { + signal: 1, + propertyUpdate: 2, + init: 3, + idle: 4, + debug: 5, + invokeMethod: 6, + connectToSignal: 7, + disconnectFromSignal: 8, + setProperty: 9, + response: 10, +}; + +var QWebChannel = function(transport, initCallback) +{ + if (typeof transport !== "object" || typeof transport.send !== "function") { + console.error("The QWebChannel expects a transport object with a send function and onmessage callback property." + + " Given is: transport: " + typeof(transport) + ", transport.send: " + typeof(transport.send)); + return; + } + + var channel = this; + this.transport = transport; + + this.send = function(data) + { + if (typeof(data) !== "string") { + data = JSON.stringify(data); + } + channel.transport.send(data); + } + + this.transport.onmessage = function(message) + { + var data = message.data; + if (typeof data === "string") { + data = JSON.parse(data); + } + switch (data.type) { + case QWebChannelMessageTypes.signal: + channel.handleSignal(data); + break; + case QWebChannelMessageTypes.response: + channel.handleResponse(data); + break; + case QWebChannelMessageTypes.propertyUpdate: + channel.handlePropertyUpdate(data); + break; + default: + console.error("invalid message received:", message.data); + break; + } + } + + this.execCallbacks = {}; + this.execId = 0; + this.exec = function(data, callback) + { + if (!callback) { + // if no callback is given, send directly + channel.send(data); + return; + } + if (channel.execId === Number.MAX_VALUE) { + // wrap + channel.execId = Number.MIN_VALUE; + } + if (data.hasOwnProperty("id")) { + console.error("Cannot exec message with property id: " + JSON.stringify(data)); + return; + } + data.id = channel.execId++; + channel.execCallbacks[data.id] = callback; + channel.send(data); + }; + + this.objects = {}; + + this.handleSignal = function(message) + { + var object = channel.objects[message.object]; + if (object) { + object.signalEmitted(message.signal, message.args); + } else { + console.warn("Unhandled signal: " + message.object + "::" + message.signal); + } + } + + this.handleResponse = function(message) + { + if (!message.hasOwnProperty("id")) { + console.error("Invalid response message received: ", JSON.stringify(message)); + return; + } + channel.execCallbacks[message.id](message.data); + delete channel.execCallbacks[message.id]; + } + + this.handlePropertyUpdate = function(message) + { + message.data.forEach(data => { + var object = channel.objects[data.object]; + if (object) { + object.propertyUpdate(data.signals, data.properties); + } else { + console.warn("Unhandled property update: " + data.object + "::" + data.signal); + } + }); + channel.exec({type: QWebChannelMessageTypes.idle}); + } + + this.debug = function(message) + { + channel.send({type: QWebChannelMessageTypes.debug, data: message}); + }; + + channel.exec({type: QWebChannelMessageTypes.init}, function(data) { + for (const objectName of Object.keys(data)) { + new QObject(objectName, data[objectName], channel); + } + + // now unwrap properties, which might reference other registered objects + for (const objectName of Object.keys(channel.objects)) { + channel.objects[objectName].unwrapProperties(); + } + + if (initCallback) { + initCallback(channel); + } + channel.exec({type: QWebChannelMessageTypes.idle}); + }); +}; + +function QObject(name, data, webChannel) +{ + this.__id__ = name; + webChannel.objects[name] = this; + + // List of callbacks that get invoked upon signal emission + this.__objectSignals__ = {}; + + // Cache of all properties, updated when a notify signal is emitted + this.__propertyCache__ = {}; + + var object = this; + + // ---------------------------------------------------------------------- + + this.unwrapQObject = function(response) + { + if (response instanceof Array) { + // support list of objects + return response.map(qobj => object.unwrapQObject(qobj)) + } + if (!(response instanceof Object)) + return response; + + if (!response["__QObject*__"] || response.id === undefined) { + var jObj = {}; + for (const propName of Object.keys(response)) { + jObj[propName] = object.unwrapQObject(response[propName]); + } + return jObj; + } + + var objectId = response.id; + if (webChannel.objects[objectId]) + return webChannel.objects[objectId]; + + if (!response.data) { + console.error("Cannot unwrap unknown QObject " + objectId + " without data."); + return; + } + + var qObject = new QObject( objectId, response.data, webChannel ); + qObject.destroyed.connect(function() { + if (webChannel.objects[objectId] === qObject) { + delete webChannel.objects[objectId]; + // reset the now deleted QObject to an empty {} object + // just assigning {} though would not have the desired effect, but the + // below also ensures all external references will see the empty map + // NOTE: this detour is necessary to workaround QTBUG-40021 + Object.keys(qObject).forEach(name => delete qObject[name]); + } + }); + // here we are already initialized, and thus must directly unwrap the properties + qObject.unwrapProperties(); + return qObject; + } + + this.unwrapProperties = function() + { + for (const propertyIdx of Object.keys(object.__propertyCache__)) { + object.__propertyCache__[propertyIdx] = object.unwrapQObject(object.__propertyCache__[propertyIdx]); + } + } + + function addSignal(signalData, isPropertyNotifySignal) + { + var signalName = signalData[0]; + var signalIndex = signalData[1]; + object[signalName] = { + connect: function(callback) { + if (typeof(callback) !== "function") { + console.error("Bad callback given to connect to signal " + signalName); + return; + } + + object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || []; + object.__objectSignals__[signalIndex].push(callback); + + // only required for "pure" signals, handled separately for properties in propertyUpdate + if (isPropertyNotifySignal) + return; + + // also note that we always get notified about the destroyed signal + if (signalName === "destroyed" || signalName === "destroyed()" || signalName === "destroyed(QObject*)") + return; + + // and otherwise we only need to be connected only once + if (object.__objectSignals__[signalIndex].length == 1) { + webChannel.exec({ + type: QWebChannelMessageTypes.connectToSignal, + object: object.__id__, + signal: signalIndex + }); + } + }, + disconnect: function(callback) { + if (typeof(callback) !== "function") { + console.error("Bad callback given to disconnect from signal " + signalName); + return; + } + object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || []; + var idx = object.__objectSignals__[signalIndex].indexOf(callback); + if (idx === -1) { + console.error("Cannot find connection of signal " + signalName + " to " + callback.name); + return; + } + object.__objectSignals__[signalIndex].splice(idx, 1); + if (!isPropertyNotifySignal && object.__objectSignals__[signalIndex].length === 0) { + // only required for "pure" signals, handled separately for properties in propertyUpdate + webChannel.exec({ + type: QWebChannelMessageTypes.disconnectFromSignal, + object: object.__id__, + signal: signalIndex + }); + } + } + }; + } + + /** + * Invokes all callbacks for the given signalname. Also works for property notify callbacks. + */ + function invokeSignalCallbacks(signalName, signalArgs) + { + var connections = object.__objectSignals__[signalName]; + if (connections) { + connections.forEach(function(callback) { + callback.apply(callback, signalArgs); + }); + } + } + + this.propertyUpdate = function(signals, propertyMap) + { + // update property cache + for (const propertyIndex of Object.keys(propertyMap)) { + var propertyValue = propertyMap[propertyIndex]; + object.__propertyCache__[propertyIndex] = this.unwrapQObject(propertyValue); + } + + for (const signalName of Object.keys(signals)) { + // Invoke all callbacks, as signalEmitted() does not. This ensures the + // property cache is updated before the callbacks are invoked. + invokeSignalCallbacks(signalName, signals[signalName]); + } + } + + this.signalEmitted = function(signalName, signalArgs) + { + invokeSignalCallbacks(signalName, this.unwrapQObject(signalArgs)); + } + + function addMethod(methodData) + { + var methodName = methodData[0]; + var methodIdx = methodData[1]; + + // Fully specified methods are invoked by id, others by name for host-side overload resolution + var invokedMethod = methodName[methodName.length - 1] === ')' ? methodIdx : methodName + + object[methodName] = function() { + var args = []; + var callback; + var errCallback; + for (var i = 0; i < arguments.length; ++i) { + var argument = arguments[i]; + if (typeof argument === "function") + callback = argument; + else if (argument instanceof QObject && webChannel.objects[argument.__id__] !== undefined) + args.push({ + "id": argument.__id__ + }); + else + args.push(argument); + } + + var result; + // during test, webChannel.exec synchronously calls the callback + // therefore, the promise must be constucted before calling + // webChannel.exec to ensure the callback is set up + if (!callback && (typeof(Promise) === 'function')) { + result = new Promise(function(resolve, reject) { + callback = resolve; + errCallback = reject; + }); + } + + webChannel.exec({ + "type": QWebChannelMessageTypes.invokeMethod, + "object": object.__id__, + "method": invokedMethod, + "args": args + }, function(response) { + if (response !== undefined) { + var result = object.unwrapQObject(response); + if (callback) { + (callback)(result); + } + } else if (errCallback) { + (errCallback)(); + } + }); + + return result; + }; + } + + function bindGetterSetter(propertyInfo) + { + var propertyIndex = propertyInfo[0]; + var propertyName = propertyInfo[1]; + var notifySignalData = propertyInfo[2]; + // initialize property cache with current value + // NOTE: if this is an object, it is not directly unwrapped as it might + // reference other QObject that we do not know yet + object.__propertyCache__[propertyIndex] = propertyInfo[3]; + + if (notifySignalData) { + if (notifySignalData[0] === 1) { + // signal name is optimized away, reconstruct the actual name + notifySignalData[0] = propertyName + "Changed"; + } + addSignal(notifySignalData, true); + } + + Object.defineProperty(object, propertyName, { + configurable: true, + get: function () { + var propertyValue = object.__propertyCache__[propertyIndex]; + if (propertyValue === undefined) { + // This shouldn't happen + console.warn("Undefined value in property cache for property \"" + propertyName + "\" in object " + object.__id__); + } + + return propertyValue; + }, + set: function(value) { + if (value === undefined) { + console.warn("Property setter for " + propertyName + " called with undefined value!"); + return; + } + object.__propertyCache__[propertyIndex] = value; + var valueToSend = value; + if (valueToSend instanceof QObject && webChannel.objects[valueToSend.__id__] !== undefined) + valueToSend = { "id": valueToSend.__id__ }; + webChannel.exec({ + "type": QWebChannelMessageTypes.setProperty, + "object": object.__id__, + "property": propertyIndex, + "value": valueToSend + }); + } + }); + + } + + // ---------------------------------------------------------------------- + + data.methods.forEach(addMethod); + + data.properties.forEach(bindGetterSetter); + + data.signals.forEach(function(signal) { addSignal(signal, false); }); + + Object.assign(object, data.enums); +} + +//required for use with nodejs +if (typeof module === 'object') { + module.exports = { + QWebChannel: QWebChannel + }; +} diff --git a/QWebChannel/Lib/WebChannelObject.py b/QWebChannel/Lib/WebChannelObject.py new file mode 100644 index 0000000000000000000000000000000000000000..7fe5e9edb3d9936000cd6ce4bdab048ecc56771c --- /dev/null +++ b/QWebChannel/Lib/WebChannelObject.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Created on 2021/12/15 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: WebChannelObject.py +@description: 交互对象,需要继承QObject并暴露接口 +""" + +from PyQt5.QtCore import (QJsonDocument, QJsonParseError, QObject, + pyqtProperty, pyqtSlot) +from PyQt5.QtNetwork import QHostAddress +from PyQt5.QtWebChannel import QWebChannel, QWebChannelAbstractTransport +from PyQt5.QtWebSockets import QWebSocketServer + + +class WebSocketTransport(QWebChannelAbstractTransport): + + def __init__(self, socket, *args, **kwargs): + super(WebSocketTransport, self).__init__(*args, **kwargs) + self.m_socket = socket + self.m_socket.textMessageReceived.connect(self.textMessageReceived) + self.m_socket.disconnected.connect(self.deleteLater) + + def sendMessage(self, message): + print('sendMessage:', message) + self.m_socket.sendTextMessage( + QJsonDocument(message).toJson(QJsonDocument.Compact).data().decode( + 'utf-8', errors='ignore')) + + def textMessageReceived(self, message): + print('textMessageReceived:', message) + error = QJsonParseError() + json = QJsonDocument.fromJson(message.encode('utf-8', errors='ignore'), + error) + if error.error: + print('Failed to parse message:{}, Error is:{}'.format( + message, error.errorString())) + return + if not json.isObject(): + print('Received JSON message that is not an object:{}'.format( + message)) + return + self.messageReceived.emit(json.object(), self) + + +class WebChannelObject(QObject): + + def __init__(self, *args, **kwargs): + super(WebChannelObject, self).__init__(*args, **kwargs) + # 内部属性供外部调用 + self._intValue = 0 + self._floatValue = 0.0 + self._boolValue = False + self._strValue = '' + # 设置数组或者字典有一定问题 + # self._listValue = [] + # self._mapValue = {} + + # webchannel对象 + self.m_webchannel = QWebChannel(self) + # 这里默认注册自己,这里使用了类名作为名称 + self.registerObject(self.__class__.__name__, self) + # websocket服务 + self.m_clients = {} + self.m_server = QWebSocketServer(self.__class__.__name__, + QWebSocketServer.NonSecureMode, self) + + def registerObject(self, name, obj): + """注册对象 + @param name: 名称 + @type name: str + @param obj: 对象 + @type obj: QObject + """ + self.m_webchannel.registerObject(name, obj) + + def registerObjects(self, objects): + """注册多个对象 + @param objects: 对象列表 + @type objects: list + """ + for name, obj in objects: + self.registerObject(name, obj) + + def deregisterObject(self, obj): + """注销对象 + @param obj: 对象 + @type obj: QObject + """ + self.m_webchannel.deregisterObject(obj) + + def deregisterObjects(self, objects): + """注销多个对象 + @param objects: 对象列表 + @type objects: list + """ + for obj in objects: + self.deregisterObject(obj) + + def start(self, port=12345): + """启动服务 + @param port: 端口 + @type port: int + """ + if not self.m_server.listen(QHostAddress.Any, port): + raise Exception( + 'Failed to create WebSocket server on port {}'.format(port)) + + print('WebSocket server listening on port {}'.format(port)) + # 新连接信号 + self.m_server.newConnection.connect(self._handleNewConnection) + + def stop(self): + """停止服务""" + self.m_server.close() + + def _handleNewConnection(self): + """新连接""" + socket = self.m_server.nextPendingConnection() + print('New WebSocket connection from {}'.format( + socket.peerAddress().toString())) + # 连接关闭信号 + socket.disconnected.connect(self._handleDisconnected) + transport = WebSocketTransport(socket) + self.m_clients[socket] = transport + self.m_webchannel.connectTo(transport) + + def _handleDisconnected(self): + """连接关闭""" + socket = self.sender() + print('WebSocket connection from {} closed'.format( + socket.peerAddress())) + if socket in self.m_clients: + self.m_clients.pop(socket) + socket.deleteLater() + + # ------- 下面是注册属性的方法 ------- + + @pyqtProperty(int) + def intValue(self): + return self._intValue + + @intValue.setter + def intValue(self, value): + self._intValue = value + + @pyqtProperty(float) + def floatValue(self): + return self._floatValue + + @floatValue.setter + def floatValue(self, value): + self._floatValue = value + + @pyqtProperty(bool) + def boolValue(self): + return self._boolValue + + @boolValue.setter + def boolValue(self, value): + self._boolValue = value + + @pyqtProperty(str) + def strValue(self): + return self._strValue + + @strValue.setter + def strValue(self, value): + self._strValue = value + + # @pyqtProperty(list) + # def listValue(self): + # return self._listValue + + # @listValue.setter + # def listValue(self, value): + # self._listValue = value + + # @pyqtProperty(dict) + # def mapValue(self): + # return self._mapValue + + # @mapValue.setter + # def mapValue(self, value): + # self._mapValue = value + + # ------- 下面是注册函数的方法 ------- + # ------- 如果有返回值一定要注明 result=返回类型 ------- + + @pyqtSlot(int, int, result=int) + def testAdd(self, a, b): + return a + b diff --git a/QWebChannel/Lib/__init__.py b/QWebChannel/Lib/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/QWebChannel/README.en.md b/QWebChannel/README.en.md new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/QWebChannel/README.md b/QWebChannel/README.md new file mode 100644 index 0000000000000000000000000000000000000000..51618de783a868a6e0aeba5a3953a19a18fb88fd --- /dev/null +++ b/QWebChannel/README.md @@ -0,0 +1,12 @@ +# QWebChannel + +- 目录 + - [和Js互相调用](#1和Js互相调用) + +## 1、和Js互相调用 +[运行 CallEachWithJs.py](CallEachWithJs.py) + +通过`qwebchannel.js`和`QWebChannel.registerObject`通过中间件`WebSocket`进行对象和Javascript的交互(类似于json rpc) +该方法类似与`QWebEngineView`中的例子,同时该demo也适用与nodejs。 + +![CallEachWithJs](ScreenShot/CallEachWithJs.gif) \ No newline at end of file diff --git a/QWebChannel/ScreenShot/CallEachWithJs.gif b/QWebChannel/ScreenShot/CallEachWithJs.gif new file mode 100644 index 0000000000000000000000000000000000000000..8ff3a88e3c72509139b877dbdb047d597c5fe090 Binary files /dev/null and b/QWebChannel/ScreenShot/CallEachWithJs.gif differ diff --git a/QWebEngineView/BlockRequest.py b/QWebEngineView/BlockRequest.py new file mode 100644 index 0000000000000000000000000000000000000000..27b86d786a582bf91877919cea6b5f4cf1dcf834 --- /dev/null +++ b/QWebEngineView/BlockRequest.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2019年9月24日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: BlockRequest +@description: 拦截请求 +""" + +try: + from PyQt5.QtCore import QUrl + from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInterceptor + from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile + from PyQt5.QtWidgets import QApplication +except ImportError: + from PySide2.QtCore import QUrl + from PySide2.QtWebEngineCore import QWebEngineUrlRequestInterceptor + from PySide2.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile + from PySide2.QtWidgets import QApplication + + +class RequestInterceptor(QWebEngineUrlRequestInterceptor): + + def interceptRequest(self, info): + url = info.requestUrl().toString() + if url.find('pos.baidu.com') > -1 and url.find('ltu=') > -1: + # 拦截百度联盟的广告 + print('block:', url) + info.block(True) + + +class Window(QWebEngineView): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + self.resize(800, 600) + QWebEngineProfile.defaultProfile().setRequestInterceptor(RequestInterceptor(self)) + + +if __name__ == '__main__': + import sys + + app = QApplication(sys.argv) + w = Window() + w.show() + w.load(QUrl('https://so.csdn.net/so/search/s.do?q=Qt&t=blog')) + sys.exit(app.exec_()) diff --git a/QWebEngineView/BlockRequestData.py b/QWebEngineView/BlockRequestData.py new file mode 100644 index 0000000000000000000000000000000000000000..eea7a02413b82992e8436fb9cde068f7db3f40e1 --- /dev/null +++ b/QWebEngineView/BlockRequestData.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2020年2月18日 +@author: Irony +@site: https://pyqt.site , https://github.com/PyQt5 +@email: 892768447@qq.com +@file: BlockRequestData +@description: 拦截请求内容 +""" + +try: + from PyQt5.QtCore import QUrl, QFile, QIODevice, QByteArray + from PyQt5.QtWidgets import QApplication + from PyQt5.QtWebEngineCore import QWebEngineUrlSchemeHandler, \ + QWebEngineUrlRequestInterceptor, QWebEngineUrlScheme # @UnresolvedImport + from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile +except ImportError: + from PySide2.QtCore import QUrl, QFile, QIODevice, QByteArray + from PySide2.QtWidgets import QApplication + from PySide2.QtWebEngineCore import QWebEngineUrlSchemeHandler, \ + QWebEngineUrlRequestInterceptor, QWebEngineUrlScheme # @UnresolvedImport + from PySide2.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile + + +# 自定义url协议头 +class UrlSchemeHandler(QWebEngineUrlSchemeHandler): + + def requestStarted(self, job): + url = job.requestUrl().toString() + if url == 'myurl://png': + file = QFile('Data/app.png', job) + file.open(QIODevice.ReadOnly) + job.reply(b'image/png', file) + + +# 请求拦截器 + + +class RequestInterceptor(QWebEngineUrlRequestInterceptor): + + def interceptRequest(self, info): + url = info.requestUrl().toString() + # 这里演示只是拦截所有png图片,可自由发挥比如拦截js文件,修改后再返回 + if url.endswith('.png'): + # 原理在于重定向到自己的url协议里 + info.redirect(QUrl('myurl://png')) + + +class Window(QWebEngineView): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + self.resize(800, 600) + + # 首先获取默认的url协议 + h1 = QWebEngineUrlScheme.schemeByName(QByteArray(b'http')) + h2 = QWebEngineUrlScheme.schemeByName(QByteArray(b'https')) + + # 这里需要修改增加本地文件和跨域支持 + CorsEnabled = 0x80 # 5.14才增加 + h1.setFlags(h1.flags() | + QWebEngineUrlScheme.SecureScheme | + QWebEngineUrlScheme.LocalScheme | + QWebEngineUrlScheme.LocalAccessAllowed | + CorsEnabled) + h2.setFlags(h2.flags() | + QWebEngineUrlScheme.SecureScheme | + QWebEngineUrlScheme.LocalScheme | + QWebEngineUrlScheme.LocalAccessAllowed | + CorsEnabled) + + # 安装url拦截器和自定义url协议处理 + de = QWebEngineProfile.defaultProfile() # @UndefinedVariable + de.setRequestInterceptor(RequestInterceptor(self)) + de.installUrlSchemeHandler(QByteArray(b'myurl'), UrlSchemeHandler(self)) + + +if __name__ == '__main__': + import sys + + app = QApplication(sys.argv) + w = Window() + w.show() + w.load(QUrl('https://www.baidu.com/')) + sys.exit(app.exec_()) diff --git a/QWebEngineView/Data/JsSignals.html b/QWebEngineView/Data/JsSignals.html new file mode 100644 index 0000000000000000000000000000000000000000..9fb0bb32a6ed918c89ae666cdbcf855f1025d460 --- /dev/null +++ b/QWebEngineView/Data/JsSignals.html @@ -0,0 +1,46 @@ + + + + 测试 + + + + +

1、测试修改窗口属性

+ windowTitle 是Qt窗口本身的属性, 当标题被修改后会触发本身的windowTitleChanged信号
+ +

+ + +

2、调用Python中的方法callFromJs

+ callFromJs(str) 函数是由Python代码中定义, 通过@pyqtSlot(str)装饰器暴露出来
+ + +

3、发送自定义信号

+ 点击底部的按钮将会发送customSignal信号出来,网页中收到该信号将会在下面日志框输出内容
+ +

日志

+ + + diff --git a/QWebEngineView/Data/app.png b/QWebEngineView/Data/app.png new file mode 100644 index 0000000000000000000000000000000000000000..3f50561f192ef269b0bd0b8d270ca89802937118 Binary files /dev/null and b/QWebEngineView/Data/app.png differ diff --git a/QWebEngineView/Data/html2canvas.min.js b/QWebEngineView/Data/html2canvas.min.js new file mode 100644 index 0000000000000000000000000000000000000000..c1b8cc998eb989ed8e9c3c561ae9c04c43f5f1a8 --- /dev/null +++ b/QWebEngineView/Data/html2canvas.min.js @@ -0,0 +1,20 @@ +/*! + * html2canvas 1.0.0-rc.3 + * Copyright (c) 2019 Niklas von Hertzen + * Released under MIT License + */ +!function(A,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(A=A||self).html2canvas=e();window.html2canvas = e()}(this,function(){"use strict"; +/*! ***************************************************************************** + Copyright (c) Microsoft Corporation. All rights reserved. + Licensed under the Apache License, Version 2.0 (the "License"); you may not use + this file except in compliance with the License. You may obtain a copy of the + License at http://www.apache.org/licenses/LICENSE-2.0 + + THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED + WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, + MERCHANTABLITY OR NON-INFRINGEMENT. + + See the Apache Version 2.0 License for specific language governing permissions + and limitations under the License. + ***************************************************************************** */var r=function(A,e){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(A,e){A.__proto__=e}||function(A,e){for(var t in e)e.hasOwnProperty(t)&&(A[t]=e[t])})(A,e)};function A(A,e){function t(){this.constructor=A}r(A,e),A.prototype=null===e?Object.create(e):(t.prototype=e.prototype,new t)}var K=function(){return(K=Object.assign||function(A){for(var e,t=1,r=arguments.length;ts[0]&&e[1]>10),s%1024+56320)),(B+1===t||16384>5])<<2)+(31&A),this.data[e];if(A<=65535)return e=((e=this.index[2048+(A-55296>>5)])<<2)+(31&A),this.data[e];if(A>11),e=this.index[e],e+=A>>5&63,e=((e=this.index[e])<<2)+(31&A),this.data[e];if(A<=1114111)return this.data[this.highValueIndex]}return this.errorValue},i);function i(A,e,t,r,B,n){this.initialValue=A,this.errorValue=e,this.highStart=t,this.highValueIndex=r,this.index=B,this.data=n}function C(A,e,t,r){var B=r[t];if(Array.isArray(A)?-1!==A.indexOf(B):A===B)for(var n=t;n<=r.length;){if((i=r[++n])===e)return!0;if(i!==H)break}if(B===H)for(n=t;0>4,c[i++]=(15&r)<<4|B>>2,c[i++]=(3&B)<<6|63&n;return a}("KwAAAAAAAAAACA4AIDoAAPAfAAACAAAAAAAIABAAGABAAEgAUABYAF4AZgBeAGYAYABoAHAAeABeAGYAfACEAIAAiACQAJgAoACoAK0AtQC9AMUAXgBmAF4AZgBeAGYAzQDVAF4AZgDRANkA3gDmAOwA9AD8AAQBDAEUARoBIgGAAIgAJwEvATcBPwFFAU0BTAFUAVwBZAFsAXMBewGDATAAiwGTAZsBogGkAawBtAG8AcIBygHSAdoB4AHoAfAB+AH+AQYCDgIWAv4BHgImAi4CNgI+AkUCTQJTAlsCYwJrAnECeQKBAk0CiQKRApkCoQKoArACuALAAsQCzAIwANQC3ALkAjAA7AL0AvwCAQMJAxADGAMwACADJgMuAzYDPgOAAEYDSgNSA1IDUgNaA1oDYANiA2IDgACAAGoDgAByA3YDfgOAAIQDgACKA5IDmgOAAIAAogOqA4AAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAK8DtwOAAIAAvwPHA88D1wPfAyAD5wPsA/QD/AOAAIAABAQMBBIEgAAWBB4EJgQuBDMEIAM7BEEEXgBJBCADUQRZBGEEaQQwADAAcQQ+AXkEgQSJBJEEgACYBIAAoASoBK8EtwQwAL8ExQSAAIAAgACAAIAAgACgAM0EXgBeAF4AXgBeAF4AXgBeANUEXgDZBOEEXgDpBPEE+QQBBQkFEQUZBSEFKQUxBTUFPQVFBUwFVAVcBV4AYwVeAGsFcwV7BYMFiwWSBV4AmgWgBacFXgBeAF4AXgBeAKsFXgCyBbEFugW7BcIFwgXIBcIFwgXQBdQF3AXkBesF8wX7BQMGCwYTBhsGIwYrBjMGOwZeAD8GRwZNBl4AVAZbBl4AXgBeAF4AXgBeAF4AXgBeAF4AXgBeAGMGXgBqBnEGXgBeAF4AXgBeAF4AXgBeAF4AXgB5BoAG4wSGBo4GkwaAAIADHgR5AF4AXgBeAJsGgABGA4AAowarBrMGswagALsGwwbLBjAA0wbaBtoG3QbaBtoG2gbaBtoG2gblBusG8wb7BgMHCwcTBxsHCwcjBysHMAc1BzUHOgdCB9oGSgdSB1oHYAfaBloHaAfaBlIH2gbaBtoG2gbaBtoG2gbaBjUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHbQdeAF4ANQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQd1B30HNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1B4MH2gaKB68EgACAAIAAgACAAIAAgACAAI8HlwdeAJ8HpweAAIAArwe3B14AXgC/B8UHygcwANAH2AfgB4AA6AfwBz4B+AcACFwBCAgPCBcIogEYAR8IJwiAAC8INwg/CCADRwhPCFcIXwhnCEoDGgSAAIAAgABvCHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIhAiLCI4IMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwAJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlggwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAANQc1BzUHNQc1BzUHNQc1BzUHNQc1B54INQc1B6II2gaqCLIIugiAAIAAvgjGCIAAgACAAIAAgACAAIAAgACAAIAAywiHAYAA0wiAANkI3QjlCO0I9Aj8CIAAgACAAAIJCgkSCRoJIgknCTYHLwk3CZYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiAAIAAAAFAAXgBeAGAAcABeAHwAQACQAKAArQC9AJ4AXgBeAE0A3gBRAN4A7AD8AMwBGgEAAKcBNwEFAUwBXAF4QkhCmEKnArcCgAHHAsABz4LAAcABwAHAAd+C6ABoAG+C/4LAAcABwAHAAc+DF4MAAcAB54M3gweDV4Nng3eDaABoAGgAaABoAGgAaABoAGgAaABoAGgAaABoAGgAaABoAGgAaABoAEeDqABVg6WDqABoQ6gAaABoAHXDvcONw/3DvcO9w73DvcO9w73DvcO9w73DvcO9w73DvcO9w73DvcO9w73DvcO9w73DvcO9w73DvcO9w73DvcO9w73DncPAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcAB7cPPwlGCU4JMACAAIAAgABWCV4JYQmAAGkJcAl4CXwJgAkwADAAMAAwAIgJgACLCZMJgACZCZ8JowmrCYAAswkwAF4AXgB8AIAAuwkABMMJyQmAAM4JgADVCTAAMAAwADAAgACAAIAAgACAAIAAgACAAIAAqwYWBNkIMAAwADAAMADdCeAJ6AnuCR4E9gkwAP4JBQoNCjAAMACAABUK0wiAAB0KJAosCjQKgAAwADwKQwqAAEsKvQmdCVMKWwowADAAgACAALcEMACAAGMKgABrCjAAMAAwADAAMAAwADAAMAAwADAAMAAeBDAAMAAwADAAMAAwADAAMAAwADAAMAAwAIkEPQFzCnoKiQSCCooKkAqJBJgKoAqkCokEGAGsCrQKvArBCjAAMADJCtEKFQHZCuEK/gHpCvEKMAAwADAAMACAAIwE+QowAIAAPwEBCzAAMAAwADAAMACAAAkLEQswAIAAPwEZCyELgAAOCCkLMAAxCzkLMAAwADAAMAAwADAAXgBeAEELMAAwADAAMAAwADAAMAAwAEkLTQtVC4AAXAtkC4AAiQkwADAAMAAwADAAMAAwADAAbAtxC3kLgAuFC4sLMAAwAJMLlwufCzAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAApwswADAAMACAAIAAgACvC4AAgACAAIAAgACAALcLMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAvwuAAMcLgACAAIAAgACAAIAAyguAAIAAgACAAIAA0QswADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAANkLgACAAIAA4AswADAAMAAwADAAMAAwADAAMAAwADAAMAAwAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACJCR4E6AswADAAhwHwC4AA+AsADAgMEAwwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMACAAIAAGAwdDCUMMAAwAC0MNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQw1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHPQwwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADUHNQc1BzUHNQc1BzUHNQc2BzAAMAA5DDUHNQc1BzUHNQc1BzUHNQc1BzUHNQdFDDAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAgACAAIAATQxSDFoMMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwAF4AXgBeAF4AXgBeAF4AYgxeAGoMXgBxDHkMfwxeAIUMXgBeAI0MMAAwADAAMAAwAF4AXgCVDJ0MMAAwADAAMABeAF4ApQxeAKsMswy7DF4Awgy9DMoMXgBeAF4AXgBeAF4AXgBeAF4AXgDRDNkMeQBqCeAM3Ax8AOYM7Az0DPgMXgBeAF4AXgBeAF4AXgBeAF4AXgBeAF4AXgBeAF4AXgCgAAANoAAHDQ4NFg0wADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAeDSYNMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwAIAAgACAAIAAgACAAC4NMABeAF4ANg0wADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwAD4NRg1ODVYNXg1mDTAAbQ0wADAAMAAwADAAMAAwADAA2gbaBtoG2gbaBtoG2gbaBnUNeg3CBYANwgWFDdoGjA3aBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gaUDZwNpA2oDdoG2gawDbcNvw3HDdoG2gbPDdYN3A3fDeYN2gbsDfMN2gbaBvoN/g3aBgYODg7aBl4AXgBeABYOXgBeACUG2gYeDl4AJA5eACwO2w3aBtoGMQ45DtoG2gbaBtoGQQ7aBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gZJDjUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1B1EO2gY1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQdZDjUHNQc1BzUHNQc1B2EONQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHaA41BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1B3AO2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gY1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1B2EO2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gZJDtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBtoG2gbaBkkOeA6gAKAAoAAwADAAMAAwAKAAoACgAKAAoACgAKAAgA4wADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAD//wQABAAEAAQABAAEAAQABAAEAA0AAwABAAEAAgAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAKABMAFwAeABsAGgAeABcAFgASAB4AGwAYAA8AGAAcAEsASwBLAEsASwBLAEsASwBLAEsAGAAYAB4AHgAeABMAHgBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAFgAbABIAHgAeAB4AUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQABYADQARAB4ABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsABAAEAAQABAAEAAUABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAkAFgAaABsAGwAbAB4AHQAdAB4ATwAXAB4ADQAeAB4AGgAbAE8ATwAOAFAAHQAdAB0ATwBPABcATwBPAE8AFgBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAHQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB0AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgBQAB4AHgAeAB4AUABQAFAAUAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAeAB4AHgAeAFAATwBAAE8ATwBPAEAATwBQAFAATwBQAB4AHgAeAB4AHgAeAB0AHQAdAB0AHgAdAB4ADgBQAFAAUABQAFAAHgAeAB4AHgAeAB4AHgBQAB4AUAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4ABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAJAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAkACQAJAAkACQAJAAkABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAeAB4AHgAeAFAAHgAeAB4AKwArAFAAUABQAFAAGABQACsAKwArACsAHgAeAFAAHgBQAFAAUAArAFAAKwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4ABAAEAAQABAAEAAQABAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAUAAeAB4AHgAeAB4AHgArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwAYAA0AKwArAB4AHgAbACsABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQADQAEAB4ABAAEAB4ABAAEABMABAArACsAKwArACsAKwArACsAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAKwArACsAKwArAFYAVgBWAB4AHgArACsAKwArACsAKwArACsAKwArACsAHgAeAB4AHgAeAB4AHgAeAB4AGgAaABoAGAAYAB4AHgAEAAQABAAEAAQABAAEAAQABAAEAAQAEwAEACsAEwATAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABABLAEsASwBLAEsASwBLAEsASwBLABoAGQAZAB4AUABQAAQAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQABMAUAAEAAQABAAEAAQABAAEAB4AHgAEAAQABAAEAAQABABQAFAABAAEAB4ABAAEAAQABABQAFAASwBLAEsASwBLAEsASwBLAEsASwBQAFAAUAAeAB4AUAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwAeAFAABABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAAEAAQABAAEAFAAKwArACsAKwArACsAKwArACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAAEAAQAUABQAB4AHgAYABMAUAArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAFAABAAEAAQABAAEAFAABAAEAAQAUAAEAAQABAAEAAQAKwArAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAArACsAHgArAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAeAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABABQAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAFAABAAEAAQABAAEAAQABABQAFAAUABQAFAAUABQAFAAUABQAAQABAANAA0ASwBLAEsASwBLAEsASwBLAEsASwAeAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQAKwBQAFAAUABQAFAAUABQAFAAKwArAFAAUAArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAUABQAFAAUABQAFAAUAArAFAAKwArACsAUABQAFAAUAArACsABABQAAQABAAEAAQABAAEAAQAKwArAAQABAArACsABAAEAAQAUAArACsAKwArACsAKwArACsABAArACsAKwArAFAAUAArAFAAUABQAAQABAArACsASwBLAEsASwBLAEsASwBLAEsASwBQAFAAGgAaAFAAUABQAFAAUABMAB4AGwBQAB4AKwArACsABAAEAAQAKwBQAFAAUABQAFAAUAArACsAKwArAFAAUAArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAUABQAFAAUABQAFAAUAArAFAAUAArAFAAUAArAFAAUAArACsABAArAAQABAAEAAQABAArACsAKwArAAQABAArACsABAAEAAQAKwArACsABAArACsAKwArACsAKwArAFAAUABQAFAAKwBQACsAKwArACsAKwArACsASwBLAEsASwBLAEsASwBLAEsASwAEAAQAUABQAFAABAArACsAKwArACsAKwArACsAKwArACsABAAEAAQAKwBQAFAAUABQAFAAUABQAFAAUAArAFAAUABQACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAUABQAFAAUABQAFAAUAArAFAAUAArAFAAUABQAFAAUAArACsABABQAAQABAAEAAQABAAEAAQABAArAAQABAAEACsABAAEAAQAKwArAFAAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAUABQAAQABAArACsASwBLAEsASwBLAEsASwBLAEsASwAeABsAKwArACsAKwArACsAKwBQAAQABAAEAAQABAAEACsABAAEAAQAKwBQAFAAUABQAFAAUABQAFAAKwArAFAAUAArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQAKwArAAQABAArACsABAAEAAQAKwArACsAKwArACsAKwArAAQABAArACsAKwArAFAAUAArAFAAUABQAAQABAArACsASwBLAEsASwBLAEsASwBLAEsASwAeAFAAUABQAFAAUABQAFAAKwArACsAKwArACsAKwArACsAKwAEAFAAKwBQAFAAUABQAFAAUAArACsAKwBQAFAAUAArAFAAUABQAFAAKwArACsAUABQACsAUAArAFAAUAArACsAKwBQAFAAKwArACsAUABQAFAAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwAEAAQABAAEAAQAKwArACsABAAEAAQAKwAEAAQABAAEACsAKwBQACsAKwArACsAKwArAAQAKwArACsAKwArACsAKwArACsAKwBLAEsASwBLAEsASwBLAEsASwBLAFAAUABQAB4AHgAeAB4AHgAeABsAHgArACsAKwArACsABAAEAAQABAArAFAAUABQAFAAUABQAFAAUAArAFAAUABQACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArAFAABAAEAAQABAAEAAQABAArAAQABAAEACsABAAEAAQABAArACsAKwArACsAKwArAAQABAArAFAAUABQACsAKwArACsAKwBQAFAABAAEACsAKwBLAEsASwBLAEsASwBLAEsASwBLACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAB4AUAAEAAQABAArAFAAUABQAFAAUABQAFAAUAArAFAAUABQACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQACsAUABQAFAAUABQACsAKwAEAFAABAAEAAQABAAEAAQABAArAAQABAAEACsABAAEAAQABAArACsAKwArACsAKwArAAQABAArACsAKwArACsAKwArAFAAKwBQAFAABAAEACsAKwBLAEsASwBLAEsASwBLAEsASwBLACsAUABQACsAKwArACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAFAABAAEAAQABAAEAAQABAArAAQABAAEACsABAAEAAQABABQAB4AKwArACsAKwBQAFAAUAAEAFAAUABQAFAAUABQAFAAUABQAFAABAAEACsAKwBLAEsASwBLAEsASwBLAEsASwBLAFAAUABQAFAAUABQAFAAUABQABoAUABQAFAAUABQAFAAKwArAAQABAArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAUABQACsAUAArACsAUABQAFAAUABQAFAAUAArACsAKwAEACsAKwArACsABAAEAAQABAAEAAQAKwAEACsABAAEAAQABAAEAAQABAAEACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArAAQABAAeACsAKwArACsAKwArACsAKwArACsAKwArAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXAAqAFwAXAAqACoAKgAqACoAKgAqACsAKwArACsAGwBcAFwAXABcAFwAXABcACoAKgAqACoAKgAqACoAKgAeAEsASwBLAEsASwBLAEsASwBLAEsADQANACsAKwArACsAKwBcAFwAKwBcACsAKwBcAFwAKwBcACsAKwBcACsAKwArACsAKwArAFwAXABcAFwAKwBcAFwAXABcAFwAXABcACsAXABcAFwAKwBcACsAXAArACsAXABcACsAXABcAFwAXAAqAFwAXAAqACoAKgAqACoAKgArACoAKgBcACsAKwBcAFwAXABcAFwAKwBcACsAKgAqACoAKgAqACoAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArAFwAXABcAFwAUAAOAA4ADgAOAB4ADgAOAAkADgAOAA0ACQATABMAEwATABMACQAeABMAHgAeAB4ABAAEAB4AHgAeAB4AHgAeAEsASwBLAEsASwBLAEsASwBLAEsAUABQAFAAUABQAFAAUABQAFAAUAANAAQAHgAEAB4ABAAWABEAFgARAAQABABQAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAANAAQABAAEAAQABAANAAQABABQAFAAUABQAFAABAAEAAQABAAEAAQABAAEAAQABAAEACsABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEACsADQANAB4AHgAeAB4AHgAeAAQAHgAeAB4AHgAeAB4AKwAeAB4ADgAOAA0ADgAeAB4AHgAeAB4ACQAJACsAKwArACsAKwBcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqAFwASwBLAEsASwBLAEsASwBLAEsASwANAA0AHgAeAB4AHgBcAFwAXABcAFwAXAAqACoAKgAqAFwAXABcAFwAKgAqACoAXAAqACoAKgBcAFwAKgAqACoAKgAqACoAKgBcAFwAXAAqACoAKgAqAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAKgAqACoAKgAqACoAKgAqACoAKgAqACoAXAAqAEsASwBLAEsASwBLAEsASwBLAEsAKgAqACoAKgAqACoAUABQAFAAUABQAFAAKwBQACsAKwArACsAKwBQACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAeAFAAUABQAFAAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAUABQACsAKwBQAFAAUABQAFAAUABQACsAUAArAFAAUABQAFAAKwArAFAAUABQAFAAUABQAFAAUABQACsAUABQAFAAUAArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAUABQAFAAUAArACsAUABQAFAAUABQAFAAUAArAFAAKwBQAFAAUABQACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwAEAAQABAAeAA0AHgAeAB4AHgAeAB4AHgBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAeAB4AHgAeAB4AHgAeAB4AHgAeACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArAFAAUABQAFAAUABQACsAKwANAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAeAB4AUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAA0AUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQABYAEQArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAADQANAA0AUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAABAAEAAQAKwArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAEAA0ADQArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQAKwArACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUABQACsABAAEACsAKwArACsAKwArACsAKwArACsAKwArAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXAAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoADQANABUAXAANAB4ADQAbAFwAKgArACsASwBLAEsASwBLAEsASwBLAEsASwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwArAB4AHgATABMADQANAA4AHgATABMAHgAEAAQABAAJACsASwBLAEsASwBLAEsASwBLAEsASwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAUABQAFAAUABQAAQABABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABABQACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAAQABAAEAAQABAAEAAQABAAEAAQABAAEACsAKwArACsABAAEAAQABAAEAAQABAAEAAQABAAEAAQAKwArACsAKwAeACsAKwArABMAEwBLAEsASwBLAEsASwBLAEsASwBLAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcACsAKwBcAFwAXABcAFwAKwArACsAKwArACsAKwArACsAKwArAFwAXABcAFwAXABcAFwAXABcAFwAXABcACsAKwArACsAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAKwArACsAKwArACsASwBLAEsASwBLAEsASwBLAEsASwBcACsAKwArACoAKgBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEACsAKwAeAB4AXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAKgAqACoAKgAqACoAKgAqACoAKgArACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgArACsABABLAEsASwBLAEsASwBLAEsASwBLACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsAKgAqACoAKgAqACoAKgBcACoAKgAqACoAKgAqACsAKwAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAArAAQABAAEAAQABABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQAUABQAFAAUABQAFAAUAArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsADQANAB4ADQANAA0ADQAeAB4AHgAeAB4AHgAeAB4AHgAeAAQABAAEAAQABAAEAAQABAAEAB4AHgAeAB4AHgAeAB4AHgAeACsAKwArAAQABAAEAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAUABQAEsASwBLAEsASwBLAEsASwBLAEsAUABQAFAAUABQAFAAUABQAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAArACsAKwArACsAKwArACsAHgAeAB4AHgBQAFAAUABQAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAArACsAKwANAA0ADQANAA0ASwBLAEsASwBLAEsASwBLAEsASwArACsAKwBQAFAAUABLAEsASwBLAEsASwBLAEsASwBLAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAANAA0AUABQAFAAUABQAFAAUABQAFAAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAB4AHgAeAB4AHgAeAB4AHgArACsAKwArACsAKwArACsABAAEAAQAHgAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAFAAUABQAFAABABQAFAAUABQAAQABAAEAFAAUAAEAAQABAArACsAKwArACsAKwAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAKwAEAAQABAAEAAQAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArACsAUABQAFAAUABQAFAAKwArAFAAUABQAFAAUABQAFAAUAArAFAAKwBQACsAUAArAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwArAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeACsAHgAeAB4AHgAeAB4AHgAeAFAAHgAeAB4AUABQAFAAKwAeAB4AHgAeAB4AHgAeAB4AHgAeAFAAUABQAFAAKwArAB4AHgAeAB4AHgAeACsAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArACsAUABQAFAAKwAeAB4AHgAeAB4AHgAeAA4AHgArAA0ADQANAA0ADQANAA0ACQANAA0ADQAIAAQACwAEAAQADQAJAA0ADQAMAB0AHQAeABcAFwAWABcAFwAXABYAFwAdAB0AHgAeABQAFAAUAA0AAQABAAQABAAEAAQABAAJABoAGgAaABoAGgAaABoAGgAeABcAFwAdABUAFQAeAB4AHgAeAB4AHgAYABYAEQAVABUAFQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgANAB4ADQANAA0ADQAeAA0ADQANAAcAHgAeAB4AHgArAAQABAAEAAQABAAEAAQABAAEAAQAUABQACsAKwBPAFAAUABQAFAAUAAeAB4AHgAWABEATwBQAE8ATwBPAE8AUABQAFAAUABQAB4AHgAeABYAEQArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAGwAbABsAGwAbABsAGwAaABsAGwAbABsAGwAbABsAGwAbABsAGwAbABsAGwAaABsAGwAbABsAGgAbABsAGgAbABsAGwAbABsAGwAbABsAGwAbABsAGwAbABsAGwAbABsABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAB4AHgBQABoAHgAdAB4AUAAeABoAHgAeAB4AHgAeAB4AHgAeAB4ATwAeAFAAGwAeAB4AUABQAFAAUABQAB4AHgAeAB0AHQAeAFAAHgBQAB4AUAAeAFAATwBQAFAAHgAeAB4AHgAeAB4AHgBQAFAAUABQAFAAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgBQAB4AUABQAFAAUABPAE8AUABQAFAAUABQAE8AUABQAE8AUABPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBQAFAAUABQAE8ATwBPAE8ATwBPAE8ATwBPAE8AUABQAFAAUABQAFAAUABQAFAAHgAeAFAAUABQAFAATwAeAB4AKwArACsAKwAdAB0AHQAdAB0AHQAdAB0AHQAdAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAdAB4AHQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHQAeAB0AHQAeAB4AHgAdAB0AHgAeAB0AHgAeAB4AHQAeAB0AGwAbAB4AHQAeAB4AHgAeAB0AHgAeAB0AHQAdAB0AHgAeAB0AHgAdAB4AHQAdAB0AHQAdAB0AHgAdAB4AHgAeAB4AHgAdAB0AHQAdAB4AHgAeAB4AHQAdAB4AHgAeAB4AHgAeAB4AHgAeAB4AHQAeAB4AHgAdAB4AHgAeAB4AHgAdAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHQAdAB4AHgAdAB0AHQAdAB4AHgAdAB0AHgAeAB0AHQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAdAB0AHgAeAB0AHQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB0AHgAeAB4AHQAeAB4AHgAeAB4AHgAeAB0AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAdAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeABQAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAWABEAFgARAB4AHgAeAB4AHgAeAB0AHgAeAB4AHgAeAB4AHgAlACUAHgAeAB4AHgAeAB4AHgAeAB4AFgARAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeACUAJQAlACUAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBQAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB4AHgAeAB4AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHgAeAB0AHQAdAB0AHgAeAB4AHgAeAB4AHgAeAB4AHgAdAB0AHgAdAB0AHQAdAB0AHQAdAB4AHgAeAB4AHgAeAB4AHgAdAB0AHgAeAB0AHQAeAB4AHgAeAB0AHQAeAB4AHgAeAB0AHQAdAB4AHgAdAB4AHgAdAB0AHQAdAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHQAdAB0AHQAeAB4AHgAeAB4AHgAeAB4AHgAdAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AJQAlACUAJQAeAB0AHQAeAB4AHQAeAB4AHgAeAB0AHQAeAB4AHgAeACUAJQAdAB0AJQAeACUAJQAlACAAJQAlAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AJQAlACUAHgAeAB4AHgAdAB4AHQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHQAdAB4AHQAdAB0AHgAdACUAHQAdAB4AHQAdAB4AHQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAlAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB0AHQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AJQAlACUAJQAlACUAJQAlACUAJQAlACUAHQAdAB0AHQAlAB4AJQAlACUAHQAlACUAHQAdAB0AJQAlAB0AHQAlAB0AHQAlACUAJQAeAB0AHgAeAB4AHgAdAB0AJQAdAB0AHQAdAB0AHQAlACUAJQAlACUAHQAlACUAIAAlAB0AHQAlACUAJQAlACUAJQAlACUAHgAeAB4AJQAlACAAIAAgACAAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAdAB4AHgAeABcAFwAXABcAFwAXAB4AEwATACUAHgAeAB4AFgARABYAEQAWABEAFgARABYAEQAWABEAFgARAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAWABEAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AFgARABYAEQAWABEAFgARABYAEQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeABYAEQAWABEAFgARABYAEQAWABEAFgARABYAEQAWABEAFgARABYAEQAWABEAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AFgARABYAEQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeABYAEQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHQAdAB0AHQAdAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwArAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwArACsAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwAeAB4AHgAeAB4AHgAeAB4AHgArACsAKwArACsAKwArACsAKwArACsAKwArAB4AHgAeAB4AKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAEAAQABAAeAB4AKwArACsAKwArABMADQANAA0AUAATAA0AUABQAFAAUABQAFAAUABQACsAKwArACsAKwArACsAUAANACsAKwArACsAKwArACsAKwArACsAKwArACsAKwAEAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQACsAUABQAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQACsAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXAA0ADQANAA0ADQANAA0ADQAeAA0AFgANAB4AHgAXABcAHgAeABcAFwAWABEAFgARABYAEQAWABEADQANAA0ADQATAFAADQANAB4ADQANAB4AHgAeAB4AHgAMAAwADQANAA0AHgANAA0AFgANAA0ADQANAA0ADQANACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACsAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAKwArACsAKwArACsAKwArACsAKwArACsAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwAlACUAJQAlACUAJQAlACUAJQAlACUAJQArACsAKwArAA0AEQARACUAJQBHAFcAVwAWABEAFgARABYAEQAWABEAFgARACUAJQAWABEAFgARABYAEQAWABEAFQAWABEAEQAlAFcAVwBXAFcAVwBXAFcAVwBXAAQABAAEAAQABAAEACUAVwBXAFcAVwA2ACUAJQBXAFcAVwBHAEcAJQAlACUAKwBRAFcAUQBXAFEAVwBRAFcAUQBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFEAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBRAFcAUQBXAFEAVwBXAFcAVwBXAFcAUQBXAFcAVwBXAFcAVwBRAFEAKwArAAQABAAVABUARwBHAFcAFQBRAFcAUQBXAFEAVwBRAFcAUQBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFEAVwBRAFcAUQBXAFcAVwBXAFcAVwBRAFcAVwBXAFcAVwBXAFEAUQBXAFcAVwBXABUAUQBHAEcAVwArACsAKwArACsAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAKwArAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwArACUAJQBXAFcAVwBXACUAJQAlACUAJQAlACUAJQAlACUAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAKwArACsAKwArACUAJQAlACUAKwArACsAKwArACsAKwArACsAKwArACsAUQBRAFEAUQBRAFEAUQBRAFEAUQBRAFEAUQBRAFEAUQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACsAVwBXAFcAVwBXAFcAVwBXAFcAVwAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlAE8ATwBPAE8ATwBPAE8ATwAlAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXACUAJQAlACUAJQAlACUAJQAlACUAVwBXAFcAVwBXAFcAVwBXAFcAVwBXACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAEcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAKwArACsAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAADQATAA0AUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABLAEsASwBLAEsASwBLAEsASwBLAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAFAABAAEAAQABAAeAAQABAAEAAQABAAEAAQABAAEAAQAHgBQAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AUABQAAQABABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAeAA0ADQANAA0ADQArACsAKwArACsAKwArACsAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAFAAUABQAFAAUABQAFAAUABQAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AUAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgBQAB4AHgAeAB4AHgAeAFAAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArAB4AHgAeAB4AHgAeAB4AHgArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAAQAUABQAFAABABQAFAAUABQAAQAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAEAAQABAAeAB4AHgAeACsAKwArACsAUABQAFAAUABQAFAAHgAeABoAHgArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAADgAOABMAEwArACsAKwArACsAKwArACsABAAEAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAEAAQABAAEACsAKwArACsAKwArACsAKwANAA0ASwBLAEsASwBLAEsASwBLAEsASwArACsAKwArACsAKwAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABABQAFAAUABQAFAAUAAeAB4AHgBQAA4AUAArACsAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAAEAA0ADQBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAKwArACsAKwArACsAKwArACsAKwArAB4AWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYACsAKwArAAQAHgAeAB4AHgAeAB4ADQANAA0AHgAeAB4AHgArAFAASwBLAEsASwBLAEsASwBLAEsASwArACsAKwArAB4AHgBcAFwAXABcAFwAKgBcAFwAXABcAFwAXABcAFwAXABcAEsASwBLAEsASwBLAEsASwBLAEsAXABcAFwAXABcACsAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEACsAKwArACsAKwArACsAKwArAFAAUABQAAQAUABQAFAAUABQAFAAUABQAAQABAArACsASwBLAEsASwBLAEsASwBLAEsASwArACsAHgANAA0ADQBcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAKgAqACoAXAAqACoAKgBcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXAAqAFwAKgAqACoAXABcACoAKgBcAFwAXABcAFwAKgAqAFwAKgBcACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAFwAXABcACoAKgBQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEAA0ADQBQAFAAUAAEAAQAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUAArACsAUABQAFAAUABQAFAAKwArAFAAUABQAFAAUABQACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAAQADQAEAAQAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsAVABVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBUAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVACsAKwArACsAKwArACsAKwArACsAKwArAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAKwArACsAKwBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAKwArACsAKwAGAAYABgAGAAYABgAGAAYABgAGAAYABgAGAAYABgAGAAYABgAGAAYABgAGAAYABgAGAAYABgAGAAYABgAGAAYAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXACUAJQBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAJQAlACUAJQAlACUAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAKwArACsAKwArAFYABABWAFYAVgBWAFYAVgBWAFYAVgBWAB4AVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgArAFYAVgBWAFYAVgArAFYAKwBWAFYAKwBWAFYAKwBWAFYAVgBWAFYAVgBWAFYAVgBWAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAEQAWAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUAAaAB4AKwArAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAGAARABEAGAAYABMAEwAWABEAFAArACsAKwArACsAKwAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEACUAJQAlACUAJQAWABEAFgARABYAEQAWABEAFgARABYAEQAlACUAFgARACUAJQAlACUAJQAlACUAEQAlABEAKwAVABUAEwATACUAFgARABYAEQAWABEAJQAlACUAJQAlACUAJQAlACsAJQAbABoAJQArACsAKwArAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArAAcAKwATACUAJQAbABoAJQAlABYAEQAlACUAEQAlABEAJQBXAFcAVwBXAFcAVwBXAFcAVwBXABUAFQAlACUAJQATACUAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXABYAJQARACUAJQAlAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwAWACUAEQAlABYAEQARABYAEQARABUAVwBRAFEAUQBRAFEAUQBRAFEAUQBRAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAEcARwArACsAVwBXAFcAVwBXAFcAKwArAFcAVwBXAFcAVwBXACsAKwBXAFcAVwBXAFcAVwArACsAVwBXAFcAKwArACsAGgAbACUAJQAlABsAGwArAB4AHgAeAB4AHgAeAB4AKwArACsAKwArACsAKwArACsAKwAEAAQABAAQAB0AKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsADQANAA0AKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArAB4AHgAeAB4AHgAeAB4AHgAeAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgBQAFAAHgAeAB4AKwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArACsAKwArAB4AKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4ABAArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAAQAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsADQBQAFAAUABQACsAKwArACsAUABQAFAAUABQAFAAUABQAA0AUABQAFAAUABQACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwArACsAKwArACsAKwArAB4AKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUAArACsAUAArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAUABQACsAKwArAFAAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAA0AUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAB4AHgBQAFAAUABQAFAAUABQACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAUABQACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsADQBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwArAB4AUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwBQAFAAUABQAFAABAAEAAQAKwAEAAQAKwArACsAKwArAAQABAAEAAQAUABQAFAAUAArAFAAUABQACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsABAAEAAQAKwArACsAKwAEAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsADQANAA0ADQANAA0ADQANAB4AKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAB4AUABQAFAAUABQAFAAUABQAB4AUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEACsAKwArACsAUABQAFAAUABQAA0ADQANAA0ADQANABQAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwANAA0ADQANAA0ADQANAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwArACsAHgAeAB4AHgArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwArACsAKwBQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAA0ADQAeAB4AHgAeAB4AKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsABABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAEAAQABAAEAAQABAAEAAQABAAeAB4AHgANAA0ADQANACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwArACsAKwBLAEsASwBLAEsASwBLAEsASwBLACsAKwArACsAKwArAFAAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEACsASwBLAEsASwBLAEsASwBLAEsASwANAA0ADQANACsAKwArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAeAA4AUAArACsAKwArACsAKwArACsAKwAEAFAAUABQAFAADQANAB4ADQAeAAQABAAEAB4AKwArAEsASwBLAEsASwBLAEsASwBLAEsAUAAOAFAADQANAA0AKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAAQABAAEAAQABAANAA0AHgANAA0AHgAEACsAUABQAFAAUABQAFAAUAArAFAAKwBQAFAAUABQACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAA0AKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAAQABAAEAAQAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsABAAEAAQABAArAFAAUABQAFAAUABQAFAAUAArACsAUABQACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAEAAQABAArACsABAAEACsAKwAEAAQABAArACsAUAArACsAKwArACsAKwAEACsAKwArACsAKwBQAFAAUABQAFAABAAEACsAKwAEAAQABAAEAAQABAAEACsAKwArAAQABAAEAAQABAArACsAKwArACsAKwArACsAKwArACsABAAEAAQABAAEAAQABABQAFAAUABQAA0ADQANAA0AHgBLAEsASwBLAEsASwBLAEsASwBLACsADQArAB4AKwArAAQABAAEAAQAUABQAB4AUAArACsAKwArACsAKwArACsASwBLAEsASwBLAEsASwBLAEsASwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEACsAKwAEAAQABAAEAAQABAAEAAQABAAOAA0ADQATABMAHgAeAB4ADQANAA0ADQANAA0ADQANAA0ADQANAA0ADQANAA0AUABQAFAAUAAEAAQAKwArAAQADQANAB4AUAArACsAKwArACsAKwArACsAKwArACsASwBLAEsASwBLAEsASwBLAEsASwArACsAKwArACsAKwAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsASwBLAEsASwBLAEsASwBLAEsASwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXAArACsAKwAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAXABcAA0ADQANACoASwBLAEsASwBLAEsASwBLAEsASwBQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwBQAFAABAAEAAQABAAEAAQABAAEAAQABABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAFAABAAEAAQABAAOAB4ADQANAA0ADQAOAB4ABAArACsAKwArACsAKwArACsAUAAEAAQABAAEAAQABAAEAAQABAAEAAQAUABQAFAAUAArACsAUABQAFAAUAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAA0ADQANACsADgAOAA4ADQANACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAAEACsABAAEAAQABAAEAAQABAAEAFAADQANAA0ADQANACsAKwArACsAKwArACsAKwArACsASwBLAEsASwBLAEsASwBLAEsASwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwAOABMAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAArAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQACsAUABQACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAArACsAKwAEACsABAAEACsABAAEAAQABAAEAAQABABQAAQAKwArACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsADQANAA0ADQANACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAASABIAEgAQwBDAEMAUABQAFAAUABDAFAAUABQAEgAQwBIAEMAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAASABDAEMAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABIAEMAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwANAA0AKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArAAQABAAEAAQABAANACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAA0ADQANAB4AHgAeAB4AHgAeAFAAUABQAFAADQAeACsAKwArACsAKwArACsAKwArACsASwBLAEsASwBLAEsASwBLAEsASwArAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAUAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsABAAEAAQABABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAEcARwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwArACsAKwArACsAKwArACsAKwArACsAKwArAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQACsAKwAeAAQABAANAAQABAAEAAQAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeACsAKwArACsAKwArACsAKwArACsAHgAeAB4AHgAeAB4AHgArACsAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4ABAAEAAQABAAEAB4AHgAeAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAHgAeAAQABAAEAAQABAAEAAQAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAEAAQABAAEAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAB4AHgAEAAQABAAeACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwArACsAKwArACsAKwArACsAKwArAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeACsAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArAFAAUAArACsAUAArACsAUABQACsAKwBQAFAAUABQACsAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwBQACsAUABQAFAAUABQAFAAUAArAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArAFAAUABQAFAAKwArAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAKwAeAB4AUABQAFAAUABQACsAUAArACsAKwBQAFAAUABQAFAAUABQACsAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAeAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAFAAUABQAFAAUABQAFAAUABQAFAAUAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAHgAeAB4AHgAeAB4AHgAeAB4AKwArAEsASwBLAEsASwBLAEsASwBLAEsASwBLAEsASwBLAEsASwBLAEsASwBLAEsASwBLAEsASwBLAEsASwBLAEsASwBLAEsABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAB4AHgAeAB4ABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAB4AHgAeAB4AHgAeAB4AHgAEAB4AHgAeAB4AHgAeAB4AHgAeAB4ABAAeAB4ADQANAA0ADQAeACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAAQABAAEAAQABAArAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsABAAEAAQABAAEAAQABAArAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAArACsABAAEAAQABAAEAAQABAArAAQABAArAAQABAAEAAQABAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAKwArAFAAUABQAFAAUABQAFAAUABQAAQABAAEAAQABAAEAAQAKwArACsAKwArACsAKwArACsAHgAeAB4AHgAEAAQABAAEAAQABAAEACsAKwArACsAKwBLAEsASwBLAEsASwBLAEsASwBLACsAKwArACsAFgAWAFAAUABQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUAArAFAAKwArAFAAKwBQAFAAUABQAFAAUABQAFAAUABQACsAUABQAFAAUAArAFAAKwBQACsAKwArACsAKwArAFAAKwArACsAKwBQACsAUAArAFAAKwBQAFAAUAArAFAAUAArAFAAKwArAFAAKwBQACsAUAArAFAAKwBQACsAUABQACsAUAArACsAUABQAFAAUAArAFAAUABQAFAAUABQAFAAKwBQAFAAUABQACsAUABQAFAAUAArAFAAKwBQAFAAUABQAFAAUABQAFAAUABQACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwBQAFAAUAArAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAB4AHgArACsAKwArACsAKwArACsAKwArACsAKwArACsATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwAlACUAJQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAeACUAHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHgAeACUAJQAlACUAHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACkAKQApACkAKQApACkAKQApACkAKQApACkAKQApACkAKQApACkAKQApACkAKQApACkAKQAlACUAJQAlACUAIAAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlAB4AHgAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAHgAeACUAJQAlACUAJQAeACUAJQAlACUAJQAgACAAIAAlACUAIAAlACUAIAAgACAAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAIQAhACEAIQAhACUAJQAgACAAJQAlACAAIAAgACAAIAAgACAAIAAgACAAIAAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAIAAgACAAIAAlACUAJQAlACAAJQAgACAAIAAgACAAIAAgACAAIAAlACUAJQAgACUAJQAlACUAIAAgACAAJQAgACAAIAAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAeACUAHgAlAB4AJQAlACUAJQAlACAAJQAlACUAJQAeACUAHgAeACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAHgAeAB4AHgAeAB4AHgAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlAB4AHgAeAB4AHgAeAB4AHgAeAB4AJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAIAAgACUAJQAlACUAIAAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAIAAlACUAJQAlACAAIAAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAeAB4AHgAeAB4AHgAeAB4AJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlAB4AHgAeAB4AHgAeACUAJQAlACUAJQAlACUAIAAgACAAJQAlACUAIAAgACAAIAAgAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AFwAXABcAFQAVABUAHgAeAB4AHgAlACUAJQAgACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAIAAgACAAJQAlACUAJQAlACUAJQAlACUAIAAlACUAJQAlACUAJQAlACUAJQAlACUAIAAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAlACUAJQAlACUAJQAlACUAJQAlACUAJQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAlACUAJQAlAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AJQAlACUAJQAlACUAJQAlAB4AHgAeAB4AHgAeAB4AHgAeAB4AJQAlACUAJQAlACUAHgAeAB4AHgAeAB4AHgAeACUAJQAlACUAJQAlACUAJQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeACUAJQAlACUAJQAlACUAJQAlACUAJQAlACAAIAAgACAAIAAlACAAIAAlACUAJQAlACUAJQAgACUAJQAlACUAJQAlACUAJQAlACAAIAAgACAAIAAgACAAIAAgACAAJQAlACUAIAAgACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACsAKwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAJQAlACUAJQAlACUAJQAlACUAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAJQAlACUAJQAlACUAJQAlACUAJQAlAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQArAAQAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsA"),U=Array.isArray(a)?function(A){for(var e=A.length,t=[],r=0;r=this._value.length?-1:this._value[A]},yA.prototype.consumeUnicodeRangeToken=function(){for(var A=[],e=this.consumeCodePoint();aA(e)&&A.length<6;)A.push(e),e=this.consumeCodePoint();for(var t=!1;63===e&&A.length<6;)A.push(e),e=this.consumeCodePoint(),t=!0;if(t){var r=parseInt(l.apply(void 0,A.map(function(A){return 63===A?48:A})),16),B=parseInt(l.apply(void 0,A.map(function(A){return 63===A?70:A})),16);return{type:sA.UNICODE_RANGE_TOKEN,start:r,end:B}}var n=parseInt(l.apply(void 0,A),16);if(45===this.peekCodePoint(0)&&aA(this.peekCodePoint(1))){this.consumeCodePoint(),e=this.consumeCodePoint();for(var s=[];aA(e)&&s.length<6;)s.push(e),e=this.consumeCodePoint();return B=parseInt(l.apply(void 0,s),16),{type:sA.UNICODE_RANGE_TOKEN,start:n,end:B}}return{type:sA.UNICODE_RANGE_TOKEN,start:n,end:n}},yA.prototype.consumeIdentLikeToken=function(){var A=this.consumeName();return"url"===A.toLowerCase()&&40===this.peekCodePoint(0)?(this.consumeCodePoint(),this.consumeUrlToken()):40===this.peekCodePoint(0)?(this.consumeCodePoint(),{type:sA.FUNCTION_TOKEN,value:A}):{type:sA.IDENT_TOKEN,value:A}},yA.prototype.consumeUrlToken=function(){var A=[];if(this.consumeWhiteSpace(),-1===this.peekCodePoint(0))return{type:sA.URL_TOKEN,value:""};var e,t=this.peekCodePoint(0);if(39===t||34===t){var r=this.consumeStringToken(this.consumeCodePoint());return r.type===sA.STRING_TOKEN&&(this.consumeWhiteSpace(),-1===this.peekCodePoint(0)||41===this.peekCodePoint(0))?(this.consumeCodePoint(),{type:sA.URL_TOKEN,value:r.value}):(this.consumeBadUrlRemnants(),IA)}for(;;){var B=this.consumeCodePoint();if(-1===B||41===B)return{type:sA.URL_TOKEN,value:l.apply(void 0,A)};if(cA(B))return this.consumeWhiteSpace(),-1===this.peekCodePoint(0)||41===this.peekCodePoint(0)?(this.consumeCodePoint(),{type:sA.URL_TOKEN,value:l.apply(void 0,A)}):(this.consumeBadUrlRemnants(),IA);if(34===B||39===B||40===B||0<=(e=B)&&e<=8||11===e||14<=e&&e<=31||127===e)return this.consumeBadUrlRemnants(),IA;if(92===B){if(!uA(B,this.peekCodePoint(0)))return this.consumeBadUrlRemnants(),IA;A.push(this.consumeEscapedCodePoint())}else A.push(B)}},yA.prototype.consumeWhiteSpace=function(){for(;cA(this.peekCodePoint(0));)this.consumeCodePoint()},yA.prototype.consumeBadUrlRemnants=function(){for(;;){var A=this.consumeCodePoint();if(41===A||-1===A)return;uA(A,this.peekCodePoint(0))&&this.consumeEscapedCodePoint()}},yA.prototype.consumeStringSlice=function(A){for(var e="";0>8,r=255&A>>16,B=255&A>>24;return e<255?"rgba("+B+","+r+","+t+","+e/255+")":"rgb("+B+","+r+","+t+")"}function re(A,e){if(A.type===sA.NUMBER_TOKEN)return A.number;if(A.type!==sA.PERCENTAGE_TOKEN)return 0;var t=3===e?1:255;return 3===e?A.number/100*t:Math.round(A.number/100*t)}function Be(A){var e=A.filter(kA);if(3===e.length){var t=e.map(re),r=t[0],B=t[1],n=t[2];return ue(r,B,n,1)}if(4!==e.length)return 0;var s=e.map(re),o=(r=s[0],B=s[1],n=s[2],s[3]);return ue(r,B,n,o)}var ne=function(A,e){return e===sA.LEFT_CURLY_BRACKET_TOKEN&&A.type===sA.RIGHT_CURLY_BRACKET_TOKEN||(e===sA.LEFT_SQUARE_BRACKET_TOKEN&&A.type===sA.RIGHT_SQUARE_BRACKET_TOKEN||e===sA.LEFT_PARENTHESIS_TOKEN&&A.type===sA.RIGHT_PARENTHESIS_TOKEN)},se={type:sA.NUMBER_TOKEN,number:0,flags:4},oe={type:sA.PERCENTAGE_TOKEN,number:50,flags:4},ie={type:sA.PERCENTAGE_TOKEN,number:100,flags:4},ae=function(A,e){if(A.type===sA.PERCENTAGE_TOKEN)return A.number/100*e;if(xA(A))switch(A.unit){case"rem":case"em":return 16*A.number;case"px":default:return A.number}return A.number},ce=function(A){if(A.type===sA.DIMENSION_TOKEN)switch(A.unit){case"deg":return Math.PI*A.number/180;case"grad":return Math.PI/200*A.number;case"rad":return A.number;case"turn":return 2*Math.PI*A.number}throw new Error("Unsupported angle type")},Qe=function(A){return Math.PI*A/180},we=function(A){if(A.type===sA.FUNCTION){var e=he[A.name];if(void 0===e)throw new Error('Attempting to parse an unsupported color function "'+A.name+'"');return e(A.values)}if(A.type===sA.HASH_TOKEN){if(3===A.value.length){var t=A.value.substring(0,1),r=A.value.substring(1,2),B=A.value.substring(2,3);return ue(parseInt(t+t,16),parseInt(r+r,16),parseInt(B+B,16),1)}if(4===A.value.length){t=A.value.substring(0,1),r=A.value.substring(1,2),B=A.value.substring(2,3);var n=A.value.substring(3,4);return ue(parseInt(t+t,16),parseInt(r+r,16),parseInt(B+B,16),parseInt(n+n,16)/255)}if(6===A.value.length){t=A.value.substring(0,2),r=A.value.substring(2,4),B=A.value.substring(4,6);return ue(parseInt(t,16),parseInt(r,16),parseInt(B,16),1)}if(8===A.value.length){t=A.value.substring(0,2),r=A.value.substring(2,4),B=A.value.substring(4,6),n=A.value.substring(6,8);return ue(parseInt(t,16),parseInt(r,16),parseInt(B,16),parseInt(n,16)/255)}}if(A.type===sA.IDENT_TOKEN){var s=He[A.value.toUpperCase()];if(void 0!==s)return s}return He.TRANSPARENT},ue=function(A,e,t,r){return(A<<24|e<<16|t<<8|Math.round(255*r)<<0)>>>0};function Ue(A,e,t){return t<0&&(t+=1),1<=t&&(t-=1),t<1/6?(e-A)*t*6+A:t<.5?e:t<2/3?6*(e-A)*(2/3-t)+A:A}function le(A){var e=A.filter(kA),t=e[0],r=e[1],B=e[2],n=e[3],s=(t.type===sA.NUMBER_TOKEN?Qe(t.number):ce(t))/(2*Math.PI),o=qA(r)?r.number/100:0,i=qA(B)?B.number/100:0,a=void 0!==n&&qA(n)?ae(n,1):1;if(0==o)return ue(255*i,255*i,255*i,1);var c=i<=.5?i*(1+o):i+o-i*o,Q=2*i-c,w=Ue(Q,c,s+1/3),u=Ue(Q,c,s),U=Ue(Q,c,s-1/3);return ue(255*w,255*u,255*U,a)}var Ce,ge,Ee,Fe,he={hsl:le,hsla:le,rgb:Be,rgba:Be},He={ALICEBLUE:4042850303,ANTIQUEWHITE:4209760255,AQUA:16777215,AQUAMARINE:2147472639,AZURE:4043309055,BEIGE:4126530815,BISQUE:4293182719,BLACK:255,BLANCHEDALMOND:4293643775,BLUE:65535,BLUEVIOLET:2318131967,BROWN:2771004159,BURLYWOOD:3736635391,CADETBLUE:1604231423,CHARTREUSE:2147418367,CHOCOLATE:3530104575,CORAL:4286533887,CORNFLOWERBLUE:1687547391,CORNSILK:4294499583,CRIMSON:3692313855,CYAN:16777215,DARKBLUE:35839,DARKCYAN:9145343,DARKGOLDENROD:3095837695,DARKGRAY:2846468607,DARKGREEN:6553855,DARKGREY:2846468607,DARKKHAKI:3182914559,DARKMAGENTA:2332068863,DARKOLIVEGREEN:1433087999,DARKORANGE:4287365375,DARKORCHID:2570243327,DARKRED:2332033279,DARKSALMON:3918953215,DARKSEAGREEN:2411499519,DARKSLATEBLUE:1211993087,DARKSLATEGRAY:793726975,DARKSLATEGREY:793726975,DARKTURQUOISE:13554175,DARKVIOLET:2483082239,DEEPPINK:4279538687,DEEPSKYBLUE:12582911,DIMGRAY:1768516095,DIMGREY:1768516095,DODGERBLUE:512819199,FIREBRICK:2988581631,FLORALWHITE:4294635775,FORESTGREEN:579543807,FUCHSIA:4278255615,GAINSBORO:3705462015,GHOSTWHITE:4177068031,GOLD:4292280575,GOLDENROD:3668254975,GRAY:2155905279,GREEN:8388863,GREENYELLOW:2919182335,GREY:2155905279,HONEYDEW:4043305215,HOTPINK:4285117695,INDIANRED:3445382399,INDIGO:1258324735,IVORY:4294963455,KHAKI:4041641215,LAVENDER:3873897215,LAVENDERBLUSH:4293981695,LAWNGREEN:2096890111,LEMONCHIFFON:4294626815,LIGHTBLUE:2916673279,LIGHTCORAL:4034953471,LIGHTCYAN:3774873599,LIGHTGOLDENRODYELLOW:4210742015,LIGHTGRAY:3553874943,LIGHTGREEN:2431553791,LIGHTGREY:3553874943,LIGHTPINK:4290167295,LIGHTSALMON:4288707327,LIGHTSEAGREEN:548580095,LIGHTSKYBLUE:2278488831,LIGHTSLATEGRAY:2005441023,LIGHTSLATEGREY:2005441023,LIGHTSTEELBLUE:2965692159,LIGHTYELLOW:4294959359,LIME:16711935,LIMEGREEN:852308735,LINEN:4210091775,MAGENTA:4278255615,MAROON:2147483903,MEDIUMAQUAMARINE:1724754687,MEDIUMBLUE:52735,MEDIUMORCHID:3126187007,MEDIUMPURPLE:2473647103,MEDIUMSEAGREEN:1018393087,MEDIUMSLATEBLUE:2070474495,MEDIUMSPRINGGREEN:16423679,MEDIUMTURQUOISE:1221709055,MEDIUMVIOLETRED:3340076543,MIDNIGHTBLUE:421097727,MINTCREAM:4127193855,MISTYROSE:4293190143,MOCCASIN:4293178879,NAVAJOWHITE:4292783615,NAVY:33023,OLDLACE:4260751103,OLIVE:2155872511,OLIVEDRAB:1804477439,ORANGE:4289003775,ORANGERED:4282712319,ORCHID:3664828159,PALEGOLDENROD:4008225535,PALEGREEN:2566625535,PALETURQUOISE:2951671551,PALEVIOLETRED:3681588223,PAPAYAWHIP:4293907967,PEACHPUFF:4292524543,PERU:3448061951,PINK:4290825215,PLUM:3718307327,POWDERBLUE:2967529215,PURPLE:2147516671,REBECCAPURPLE:1714657791,RED:4278190335,ROSYBROWN:3163525119,ROYALBLUE:1097458175,SADDLEBROWN:2336560127,SALMON:4202722047,SANDYBROWN:4104413439,SEAGREEN:780883967,SEASHELL:4294307583,SIENNA:2689740287,SILVER:3233857791,SKYBLUE:2278484991,SLATEBLUE:1784335871,SLATEGRAY:1887473919,SLATEGREY:1887473919,SNOW:4294638335,SPRINGGREEN:16744447,STEELBLUE:1182971135,TAN:3535047935,TEAL:8421631,THISTLE:3636451583,TOMATO:4284696575,TRANSPARENT:0,TURQUOISE:1088475391,VIOLET:4001558271,WHEAT:4125012991,WHITE:4294967295,WHITESMOKE:4126537215,YELLOW:4294902015,YELLOWGREEN:2597139199};(ge=Ce||(Ce={}))[ge.VALUE=0]="VALUE",ge[ge.LIST=1]="LIST",ge[ge.IDENT_VALUE=2]="IDENT_VALUE",ge[ge.TYPE_VALUE=3]="TYPE_VALUE",ge[ge.TOKEN_VALUE=4]="TOKEN_VALUE",(Fe=Ee||(Ee={}))[Fe.BORDER_BOX=0]="BORDER_BOX",Fe[Fe.PADDING_BOX=1]="PADDING_BOX";function de(A){var e=we(A[0]),t=A[1];return t&&qA(t)?{color:e,stop:t}:{color:e,stop:null}}function fe(A,t){var e=A[0],r=A[A.length-1];null===e.stop&&(e.stop=se),null===r.stop&&(r.stop=ie);for(var B=[],n=0,s=0;sA.optimumDistance)?{optimumCorner:e,optimumDistance:B}:A},{optimumDistance:o?1/0:-1/0,optimumCorner:null}).optimumCorner}function Ie(A){var B=Qe(180),n=[];return WA(A).forEach(function(A,e){if(0===e){var t=A[0];if(t.type===sA.IDENT_TOKEN&&-1!==["top","left","right","bottom"].indexOf(t.value))return void(B=Ae(A));if($A(t))return void(B=(ce(t)+Qe(270))%Qe(360))}var r=de(A);n.push(r)}),{angle:B,stops:n,type:xe.LINEAR_GRADIENT}}function Te(A){return 0===A[0]&&255===A[1]&&0===A[2]&&255===A[3]}var me={name:"background-clip",initialValue:"border-box",prefix:!(Fe[Fe.CONTENT_BOX=2]="CONTENT_BOX"),type:Ce.LIST,parse:function(A){return A.map(function(A){if(zA(A))switch(A.value){case"padding-box":return Ee.PADDING_BOX;case"content-box":return Ee.CONTENT_BOX}return Ee.BORDER_BOX})}},Re={name:"background-color",initialValue:"transparent",prefix:!1,type:Ce.TYPE_VALUE,format:"color"},Le=function(A,e,t,r,B){var n="http://www.w3.org/2000/svg",s=document.createElementNS(n,"svg"),o=document.createElementNS(n,"foreignObject");return s.setAttributeNS(null,"width",A.toString()),s.setAttributeNS(null,"height",e.toString()),o.setAttributeNS(null,"width","100%"),o.setAttributeNS(null,"height","100%"),o.setAttributeNS(null,"x",t.toString()),o.setAttributeNS(null,"y",r.toString()),o.setAttributeNS(null,"externalResourcesRequired","true"),s.appendChild(o),o.appendChild(B),s},Oe=function(r){return new Promise(function(A,e){var t=new Image;t.onload=function(){return A(t)},t.onerror=e,t.src="data:image/svg+xml;charset=utf-8,"+encodeURIComponent((new XMLSerializer).serializeToString(r))})},ve={get SUPPORT_RANGE_BOUNDS(){var A=function(A){if(A.createRange){var e=A.createRange();if(e.getBoundingClientRect){var t=A.createElement("boundtest");t.style.height="123px",t.style.display="block",A.body.appendChild(t),e.selectNode(t);var r=e.getBoundingClientRect(),B=Math.round(r.height);if(A.body.removeChild(t),123===B)return!0}}return!1}(document);return Object.defineProperty(ve,"SUPPORT_RANGE_BOUNDS",{value:A}),A},get SUPPORT_SVG_DRAWING(){var A=function(A){var e=new Image,t=A.createElement("canvas"),r=t.getContext("2d");if(!r)return!1;e.src="data:image/svg+xml,";try{r.drawImage(e,0,0),t.toDataURL()}catch(A){return!1}return!0}(document);return Object.defineProperty(ve,"SUPPORT_SVG_DRAWING",{value:A}),A},get SUPPORT_FOREIGNOBJECT_DRAWING(){var A="function"==typeof Array.from&&"function"==typeof window.fetch?function(r){var A=r.createElement("canvas"),B=100;A.width=B,A.height=B;var n=A.getContext("2d");if(!n)return Promise.reject(!1);n.fillStyle="rgb(0, 255, 0)",n.fillRect(0,0,B,B);var e=new Image,s=A.toDataURL();e.src=s;var t=Le(B,B,0,0,e);return n.fillStyle="red",n.fillRect(0,0,B,B),Oe(t).then(function(A){n.drawImage(A,0,0);var e=n.getImageData(0,0,B,B).data;n.fillStyle="red",n.fillRect(0,0,B,B);var t=r.createElement("div");return t.style.backgroundImage="url("+s+")",t.style.height="100px",Te(e)?Oe(Le(B,B,0,0,t)):Promise.reject(!1)}).then(function(A){return n.drawImage(A,0,0),Te(n.getImageData(0,0,B,B).data)}).catch(function(){return!1})}(document):Promise.resolve(!1);return Object.defineProperty(ve,"SUPPORT_FOREIGNOBJECT_DRAWING",{value:A}),A},get SUPPORT_CORS_IMAGES(){var A=void 0!==(new Image).crossOrigin;return Object.defineProperty(ve,"SUPPORT_CORS_IMAGES",{value:A}),A},get SUPPORT_RESPONSE_TYPE(){var A="string"==typeof(new XMLHttpRequest).responseType;return Object.defineProperty(ve,"SUPPORT_RESPONSE_TYPE",{value:A}),A},get SUPPORT_CORS_XHR(){var A="withCredentials"in new XMLHttpRequest;return Object.defineProperty(ve,"SUPPORT_CORS_XHR",{value:A}),A}},De=(Se.prototype.debug=function(){for(var A=[],e=0;eA.height?new I(A.left+(A.width-A.height)/2,A.top,A.height,A.height):A.width"),Wn(this.referenceElement.ownerDocument,B,n),o.replaceChild(o.adoptNode(this.documentElement),o.documentElement),o.close(),i},xn.prototype.createElementClone=function(A){return Fn(A)?this.createCanvasClone(A):rn(A)?this.createStyleClone(A):A.cloneNode(!1)},xn.prototype.createStyleClone=function(A){try{var e=A.sheet;if(e&&e.cssRules){var t=[].slice.call(e.cssRules,0).reduce(function(A,e){return e&&"string"==typeof e.cssText?A+e.cssText:A},""),r=A.cloneNode(!1);return r.textContent=t,r}}catch(A){if(De.getInstance(this.options.id).error("Unable to access cssRules property",A),"SecurityError"!==A.name)throw A}return A.cloneNode(!1)},xn.prototype.createCanvasClone=function(A){if(this.options.inlineImages&&A.ownerDocument){var e=A.ownerDocument.createElement("img");try{return e.src=A.toDataURL(),e}catch(A){De.getInstance(this.options.id).info("Unable to clone canvas contents, canvas is tainted")}}var t=A.cloneNode(!1);try{t.width=A.width,t.height=A.height;var r=A.getContext("2d"),B=t.getContext("2d");return B&&(r?B.putImageData(r.getImageData(0,0,A.width,A.height),0,0):B.drawImage(A,0,0)),t}catch(A){}return t},xn.prototype.cloneNode=function(A){if(Qn(A))return document.createTextNode(A.data);if(!A.ownerDocument)return A.cloneNode(!1);var e=A.ownerDocument.defaultView;if(un(A)&&e){var t=this.createElementClone(A),r=e.getComputedStyle(A),B=e.getComputedStyle(A,":before"),n=e.getComputedStyle(A,":after");this.referenceElement===A&&(this.clonedReferenceElement=t),En(t)&&$n(t);for(var s=this.counters.parse(new wB(r)),o=this.resolvePseudoContent(A,t,B,Ln.BEFORE),i=A.firstChild;i;i=i.nextSibling)wn(i)&&("SCRIPT"===i.tagName||i.hasAttribute(_n)||"function"==typeof this.options.ignoreElements&&this.options.ignoreElements(i))||this.options.copyStyles&&wn(i)&&rn(i)||t.appendChild(this.cloneNode(i));o&&t.insertBefore(o,t.firstChild);var a=this.resolvePseudoContent(A,t,n,Ln.AFTER);return a&&t.appendChild(a),this.counters.pop(s),r&&this.options.copyStyles&&!Hn(A)&&Gn(r,t),0===A.scrollTop&&0===A.scrollLeft||this.scrolledElements.push([t,A.scrollLeft,A.scrollTop]),(dn(A)||fn(A))&&(dn(t)||fn(t))&&(t.value=A.value),t}return A.cloneNode(!1)},xn.prototype.resolvePseudoContent=function(U,A,e,t){var l=this;if(e){var r=e.content,C=A.ownerDocument;if(C&&r&&"none"!==r&&"-moz-alt-content"!==r&&"none"!==e.display){this.counters.parse(new wB(e));var g=new QB(e),E=C.createElement("html2canvaspseudoelement");return Gn(e,E),g.content.forEach(function(A){if(A.type===sA.STRING_TOKEN)E.appendChild(C.createTextNode(A.value));else if(A.type===sA.URL_TOKEN){var e=C.createElement("img");e.src=A.value,e.style.opacity="1",E.appendChild(e)}else if(A.type===sA.FUNCTION){if("attr"===A.name){var t=A.values.filter(zA);t.length&&E.appendChild(C.createTextNode(U.getAttribute(t[0].value)||""))}else if("counter"===A.name){var r=A.values.filter(kA),B=r[0],n=r[1];if(B&&zA(B)){var s=l.counters.getCounterValue(B.value),o=n&&zA(n)?ir.parse(n.value):tr.DECIMAL;E.appendChild(C.createTextNode(yn(s,o,!1)))}}else if("counters"===A.name){var i=A.values.filter(kA),a=(B=i[0],i[1]);if(n=i[2],B&&zA(B)){var c=l.counters.getCounterValues(B.value),Q=n&&zA(n)?ir.parse(n.value):tr.DECIMAL,w=a&&a.type===sA.STRING_TOKEN?a.value:"",u=c.map(function(A){return yn(A,Q,!1)}).join(w);E.appendChild(C.createTextNode(u))}}}else if(A.type===sA.IDENT_TOKEN)switch(A.value){case"open-quote":E.appendChild(C.createTextNode(eB(g.quotes,l.quoteDepth++,!0)));break;case"close-quote":E.appendChild(C.createTextNode(eB(g.quotes,--l.quoteDepth,!1)))}}),E.className=qn+" "+Zn,A.className+=t===Ln.BEFORE?" "+qn:" "+Zn,E}}},xn);function xn(A,e){if(this.options=e,this.scrolledElements=[],this.referenceElement=A,this.counters=new pn,this.quoteDepth=0,!A.ownerDocument)throw new Error("Cloned element does not have an owner document");this.documentElement=this.cloneNode(A.ownerDocument.documentElement)}(On=Ln||(Ln={}))[On.BEFORE=0]="BEFORE",On[On.AFTER=1]="AFTER";var Vn,zn,Xn=function(A,e){var t=A.createElement("iframe");return t.className="html2canvas-container",t.style.visibility="hidden",t.style.position="fixed",t.style.left="-10000px",t.style.top="0px",t.style.border="0",t.width=e.width.toString(),t.height=e.height.toString(),t.scrolling="no",t.setAttribute(_n,"true"),A.body.appendChild(t),t},Jn=function(B){return new Promise(function(e,A){var t=B.contentWindow;if(!t)return A("No window assigned for iframe");var r=t.document;t.onload=B.onload=r.onreadystatechange=function(){t.onload=B.onload=r.onreadystatechange=null;var A=setInterval(function(){0"),e},Wn=function(A,e,t){A&&A.defaultView&&(e!==A.defaultView.pageXOffset||t!==A.defaultView.pageYOffset)&&A.defaultView.scrollTo(e,t)},Yn=function(A){var e=A[0],t=A[1],r=A[2];e.scrollLeft=t,e.scrollTop=r},qn="___html2canvas___pseudoelement_before",Zn="___html2canvas___pseudoelement_after",jn='{\n content: "" !important;\n display: none !important;\n}',$n=function(A){As(A,"."+qn+":before"+jn+"\n ."+Zn+":after"+jn)},As=function(A,e){var t=A.ownerDocument;if(t){var r=t.createElement("style");r.textContent=e,A.appendChild(r)}};(zn=Vn||(Vn={}))[zn.VECTOR=0]="VECTOR",zn[zn.BEZIER_CURVE=1]="BEZIER_CURVE";function es(A,t){return A.length===t.length&&A.some(function(A,e){return A===t[e]})}var ts=(rs.prototype.add=function(A,e){return new rs(this.x+A,this.y+e)},rs);function rs(A,e){this.type=Vn.VECTOR,this.x=A,this.y=e}function Bs(A,e,t){return new ts(A.x+(e.x-A.x)*t,A.y+(e.y-A.y)*t)}var ns=(ss.prototype.subdivide=function(A,e){var t=Bs(this.start,this.startControl,A),r=Bs(this.startControl,this.endControl,A),B=Bs(this.endControl,this.end,A),n=Bs(t,r,A),s=Bs(r,B,A),o=Bs(n,s,A);return e?new ss(this.start,t,n,o):new ss(o,s,B,this.end)},ss.prototype.add=function(A,e){return new ss(this.start.add(A,e),this.startControl.add(A,e),this.endControl.add(A,e),this.end.add(A,e))},ss.prototype.reverse=function(){return new ss(this.end,this.endControl,this.startControl,this.start)},ss);function ss(A,e,t,r){this.type=Vn.BEZIER_CURVE,this.start=A,this.startControl=e,this.endControl=t,this.end=r}function os(A){return A.type===Vn.BEZIER_CURVE}var is,as,cs=function(A){var e=A.styles,t=A.bounds,r=jA(e.borderTopLeftRadius,t.width,t.height),B=r[0],n=r[1],s=jA(e.borderTopRightRadius,t.width,t.height),o=s[0],i=s[1],a=jA(e.borderBottomRightRadius,t.width,t.height),c=a[0],Q=a[1],w=jA(e.borderBottomLeftRadius,t.width,t.height),u=w[0],U=w[1],l=[];l.push((B+o)/t.width),l.push((u+c)/t.width),l.push((n+U)/t.height),l.push((i+Q)/t.height);var C=Math.max.apply(Math,l);1t.width+p?0:o-p,i-H,is.TOP_RIGHT):new ts(t.left+t.width-d,t.top+H),this.bottomRightPaddingBox=0t.width+p+T?0:o-p+T,i-(H+N),is.TOP_RIGHT):new ts(t.left+t.width-(d+K),t.top+H+N),this.bottomRightContentBox=0A.element.container.styles.zIndex.order&&(i=e,!0)}),n.negativeZIndex.splice(i,0,s)}else if(0A.element.container.styles.zIndex.order&&(a=e+1,!0)}),n.positiveZIndex.splice(a,0,s)}else n.zeroOrAutoZIndexOrTransformedOrOpacity.push(s)}else A.styles.isFloating()?n.nonPositionedFloats.push(s):n.nonPositionedInlineLevel.push(s);ps(r,s,e?s:w,B)}else A.styles.isInlineLevel()?Q.inlineLevel.push(r):Q.nonInlineLevel.push(r),ps(r,Q,w,B);AB(A.flags,8)&&Ns(A,B)})},Ns=function(A,e){for(var t=A instanceof bB?A.start:1,r=A instanceof bB&&A.reversed,B=0;Ba?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(l.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:k&&!k.call("\ufeff\xa0")?function(a){return null==a?"":k.call(a)}:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||n.guid++,e):void 0},now:function(){return+new Date},support:l}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s="sizzle"+-new Date,t=a.document,u=0,v=0,w=eb(),x=eb(),y=eb(),z=function(a,b){return a===b&&(j=!0),0},A="undefined",B=1<<31,C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=D.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",M=L.replace("w","w#"),N="\\["+K+"*("+L+")"+K+"*(?:([*^$|!~]?=)"+K+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+M+")|)|)"+K+"*\\]",O=":("+L+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+N.replace(3,8)+")*)|.*)\\)|)",P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(O),U=new RegExp("^"+M+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L.replace("w","w*")+")"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=/'|\\/g,ab=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),bb=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{G.apply(D=H.call(t.childNodes),t.childNodes),D[t.childNodes.length].nodeType}catch(cb){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function db(a,b,d,e){var f,g,h,i,j,m,p,q,u,v;if((b?b.ownerDocument||b:t)!==l&&k(b),b=b||l,d=d||[],!a||"string"!=typeof a)return d;if(1!==(i=b.nodeType)&&9!==i)return[];if(n&&!e){if(f=Z.exec(a))if(h=f[1]){if(9===i){if(g=b.getElementById(h),!g||!g.parentNode)return d;if(g.id===h)return d.push(g),d}else if(b.ownerDocument&&(g=b.ownerDocument.getElementById(h))&&r(b,g)&&g.id===h)return d.push(g),d}else{if(f[2])return G.apply(d,b.getElementsByTagName(a)),d;if((h=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(h)),d}if(c.qsa&&(!o||!o.test(a))){if(q=p=s,u=b,v=9===i&&a,1===i&&"object"!==b.nodeName.toLowerCase()){m=ob(a),(p=b.getAttribute("id"))?q=p.replace(_,"\\$&"):b.setAttribute("id",q),q="[id='"+q+"'] ",j=m.length;while(j--)m[j]=q+pb(m[j]);u=$.test(a)&&mb(b.parentNode)||b,v=m.join(",")}if(v)try{return G.apply(d,u.querySelectorAll(v)),d}catch(w){}finally{p||b.removeAttribute("id")}}}return xb(a.replace(P,"$1"),b,d,e)}function eb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function fb(a){return a[s]=!0,a}function gb(a){var b=l.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function hb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function ib(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||B)-(~a.sourceIndex||B);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function jb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function kb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function lb(a){return fb(function(b){return b=+b,fb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function mb(a){return a&&typeof a.getElementsByTagName!==A&&a}c=db.support={},f=db.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},k=db.setDocument=function(a){var b,e=a?a.ownerDocument||a:t,g=e.defaultView;return e!==l&&9===e.nodeType&&e.documentElement?(l=e,m=e.documentElement,n=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){k()},!1):g.attachEvent&&g.attachEvent("onunload",function(){k()})),c.attributes=gb(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=gb(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(e.getElementsByClassName)&&gb(function(a){return a.innerHTML="
",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=gb(function(a){return m.appendChild(a).id=s,!e.getElementsByName||!e.getElementsByName(s).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==A&&n){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){var c=typeof a.getAttributeNode!==A&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==A?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==A&&n?b.getElementsByClassName(a):void 0},p=[],o=[],(c.qsa=Y.test(e.querySelectorAll))&&(gb(function(a){a.innerHTML="",a.querySelectorAll("[t^='']").length&&o.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||o.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll(":checked").length||o.push(":checked")}),gb(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&o.push("name"+K+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||o.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),o.push(",.*:")})),(c.matchesSelector=Y.test(q=m.webkitMatchesSelector||m.mozMatchesSelector||m.oMatchesSelector||m.msMatchesSelector))&&gb(function(a){c.disconnectedMatch=q.call(a,"div"),q.call(a,"[s!='']:x"),p.push("!=",O)}),o=o.length&&new RegExp(o.join("|")),p=p.length&&new RegExp(p.join("|")),b=Y.test(m.compareDocumentPosition),r=b||Y.test(m.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},z=b?function(a,b){if(a===b)return j=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===t&&r(t,a)?-1:b===e||b.ownerDocument===t&&r(t,b)?1:i?I.call(i,a)-I.call(i,b):0:4&d?-1:1)}:function(a,b){if(a===b)return j=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],k=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:i?I.call(i,a)-I.call(i,b):0;if(f===g)return ib(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)k.unshift(c);while(h[d]===k[d])d++;return d?ib(h[d],k[d]):h[d]===t?-1:k[d]===t?1:0},e):l},db.matches=function(a,b){return db(a,null,null,b)},db.matchesSelector=function(a,b){if((a.ownerDocument||a)!==l&&k(a),b=b.replace(S,"='$1']"),!(!c.matchesSelector||!n||p&&p.test(b)||o&&o.test(b)))try{var d=q.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return db(b,l,null,[a]).length>0},db.contains=function(a,b){return(a.ownerDocument||a)!==l&&k(a),r(a,b)},db.attr=function(a,b){(a.ownerDocument||a)!==l&&k(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!n):void 0;return void 0!==f?f:c.attributes||!n?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},db.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},db.uniqueSort=function(a){var b,d=[],e=0,f=0;if(j=!c.detectDuplicates,i=!c.sortStable&&a.slice(0),a.sort(z),j){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return i=null,a},e=db.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=db.selectors={cacheLength:50,createPseudo:fb,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ab,bb),a[3]=(a[4]||a[5]||"").replace(ab,bb),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||db.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&db.error(a[0]),a},PSEUDO:function(a){var b,c=!a[5]&&a[2];return V.CHILD.test(a[0])?null:(a[3]&&void 0!==a[4]?a[2]=a[4]:c&&T.test(c)&&(b=ob(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ab,bb).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=w[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&w(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==A&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=db.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),t=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&t){k=q[s]||(q[s]={}),j=k[a]||[],n=j[0]===u&&j[1],m=j[0]===u&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[u,n,m];break}}else if(t&&(j=(b[s]||(b[s]={}))[a])&&j[0]===u)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(t&&((l[s]||(l[s]={}))[a]=[u,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||db.error("unsupported pseudo: "+a);return e[s]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?fb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:fb(function(a){var b=[],c=[],d=g(a.replace(P,"$1"));return d[s]?fb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:fb(function(a){return function(b){return db(a,b).length>0}}),contains:fb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:fb(function(a){return U.test(a||"")||db.error("unsupported lang: "+a),a=a.replace(ab,bb).toLowerCase(),function(b){var c;do if(c=n?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===m},focus:function(a){return a===l.activeElement&&(!l.hasFocus||l.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:lb(function(){return[0]}),last:lb(function(a,b){return[b-1]}),eq:lb(function(a,b,c){return[0>c?c+b:c]}),even:lb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:lb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:lb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:lb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function qb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=v++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[u,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[s]||(b[s]={}),(h=i[d])&&h[0]===u&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function rb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function sb(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function tb(a,b,c,d,e,f){return d&&!d[s]&&(d=tb(d)),e&&!e[s]&&(e=tb(e,f)),fb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||wb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:sb(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=sb(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=sb(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ub(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],i=g||d.relative[" "],j=g?1:0,k=qb(function(a){return a===b},i,!0),l=qb(function(a){return I.call(b,a)>-1},i,!0),m=[function(a,c,d){return!g&&(d||c!==h)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>j;j++)if(c=d.relative[a[j].type])m=[qb(rb(m),c)];else{if(c=d.filter[a[j].type].apply(null,a[j].matches),c[s]){for(e=++j;f>e;e++)if(d.relative[a[e].type])break;return tb(j>1&&rb(m),j>1&&pb(a.slice(0,j-1).concat({value:" "===a[j-2].type?"*":""})).replace(P,"$1"),c,e>j&&ub(a.slice(j,e)),f>e&&ub(a=a.slice(e)),f>e&&pb(a))}m.push(c)}return rb(m)}function vb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,i,j,k){var m,n,o,p=0,q="0",r=f&&[],s=[],t=h,v=f||e&&d.find.TAG("*",k),w=u+=null==t?1:Math.random()||.1,x=v.length;for(k&&(h=g!==l&&g);q!==x&&null!=(m=v[q]);q++){if(e&&m){n=0;while(o=a[n++])if(o(m,g,i)){j.push(m);break}k&&(u=w)}c&&((m=!o&&m)&&p--,f&&r.push(m))}if(p+=q,c&&q!==p){n=0;while(o=b[n++])o(r,s,g,i);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=E.call(j));s=sb(s)}G.apply(j,s),k&&!f&&s.length>0&&p+b.length>1&&db.uniqueSort(j)}return k&&(u=w,h=t),r};return c?fb(f):f}g=db.compile=function(a,b){var c,d=[],e=[],f=y[a+" "];if(!f){b||(b=ob(a)),c=b.length;while(c--)f=ub(b[c]),f[s]?d.push(f):e.push(f);f=y(a,vb(e,d))}return f};function wb(a,b,c){for(var d=0,e=b.length;e>d;d++)db(a,b[d],c);return c}function xb(a,b,e,f){var h,i,j,k,l,m=ob(a);if(!f&&1===m.length){if(i=m[0]=m[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&c.getById&&9===b.nodeType&&n&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(ab,bb),b)||[])[0],!b)return e;a=a.slice(i.shift().value.length)}h=V.needsContext.test(a)?0:i.length;while(h--){if(j=i[h],d.relative[k=j.type])break;if((l=d.find[k])&&(f=l(j.matches[0].replace(ab,bb),$.test(i[0].type)&&mb(b.parentNode)||b))){if(i.splice(h,1),a=f.length&&pb(i),!a)return G.apply(e,f),e;break}}}return g(a,m)(f,b,!n,e,$.test(a)&&mb(b.parentNode)||b),e}return c.sortStable=s.split("").sort(z).join("")===s,c.detectDuplicates=!!j,k(),c.sortDetached=gb(function(a){return 1&a.compareDocumentPosition(l.createElement("div"))}),gb(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||hb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&gb(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||hb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),gb(function(a){return null==a.getAttribute("disabled")})||hb(J,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),db}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return n.inArray(a,b)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;e>b;b++)if(n.contains(d[b],this))return!0}));for(b=0;e>b;b++)n.find(a,d[b],c);return c=this.pushStack(e>1?n.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=a.document,A=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,B=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:A.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:z,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=z.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return y.find(a);this.length=1,this[0]=d}return this.context=z,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};B.prototype=n.fn,y=n(z);var C=/^(?:parents|prev(?:Until|All))/,D={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!n(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b,c=n(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(n.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?n.inArray(this[0],n(a)):n.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function E(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return E(a,"nextSibling")},prev:function(a){return E(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return n.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(D[a]||(e=n.unique(e)),C.test(a)&&(e=e.reverse())),this.pushStack(e)}});var F=/\S+/g,G={};function H(a){var b=G[a]={};return n.each(a.match(F)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?G[a]||H(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&n.each(arguments,function(a,c){var d;while((d=n.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){if(a===!0?!--n.readyWait:!n.isReady){if(!z.body)return setTimeout(n.ready);n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(z,[n]),n.fn.trigger&&n(z).trigger("ready").off("ready"))}}});function J(){z.addEventListener?(z.removeEventListener("DOMContentLoaded",K,!1),a.removeEventListener("load",K,!1)):(z.detachEvent("onreadystatechange",K),a.detachEvent("onload",K))}function K(){(z.addEventListener||"load"===event.type||"complete"===z.readyState)&&(J(),n.ready())}n.ready.promise=function(b){if(!I)if(I=n.Deferred(),"complete"===z.readyState)setTimeout(n.ready);else if(z.addEventListener)z.addEventListener("DOMContentLoaded",K,!1),a.addEventListener("load",K,!1);else{z.attachEvent("onreadystatechange",K),a.attachEvent("onload",K);var c=!1;try{c=null==a.frameElement&&z.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!n.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}J(),n.ready()}}()}return I.promise(b)};var L="undefined",M;for(M in n(l))break;l.ownLast="0"!==M,l.inlineBlockNeedsLayout=!1,n(function(){var a,b,c=z.getElementsByTagName("body")[0];c&&(a=z.createElement("div"),a.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",b=z.createElement("div"),c.appendChild(a).appendChild(b),typeof b.style.zoom!==L&&(b.style.cssText="border:0;margin:0;width:1px;padding:1px;display:inline;zoom:1",(l.inlineBlockNeedsLayout=3===b.offsetWidth)&&(c.style.zoom=1)),c.removeChild(a),a=b=null)}),function(){var a=z.createElement("div");if(null==l.deleteExpando){l.deleteExpando=!0;try{delete a.test}catch(b){l.deleteExpando=!1}}a=null}(),n.acceptData=function(a){var b=n.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(O,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}n.data(a,b,c)}else c=void 0}return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function R(a,b,d,e){if(n.acceptData(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f +}}function S(a,b,c){if(n.acceptData(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d]));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},X=/^(?:checkbox|radio)$/i;!function(){var a=z.createDocumentFragment(),b=z.createElement("div"),c=z.createElement("input");if(b.setAttribute("className","t"),b.innerHTML="
a",l.leadingWhitespace=3===b.firstChild.nodeType,l.tbody=!b.getElementsByTagName("tbody").length,l.htmlSerialize=!!b.getElementsByTagName("link").length,l.html5Clone="<:nav>"!==z.createElement("nav").cloneNode(!0).outerHTML,c.type="checkbox",c.checked=!0,a.appendChild(c),l.appendChecked=c.checked,b.innerHTML="",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,a.appendChild(b),b.innerHTML="",l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,l.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){l.noCloneEvent=!1}),b.cloneNode(!0).click()),null==l.deleteExpando){l.deleteExpando=!0;try{delete b.test}catch(d){l.deleteExpando=!1}}a=b=c=null}(),function(){var b,c,d=z.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(l[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),l[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var Y=/^(?:input|select|textarea)$/i,Z=/^key/,$=/^(?:mouse|contextmenu)|click/,_=/^(?:focusinfocus|focusoutblur)$/,ab=/^([^.]*)(?:\.(.+)|)$/;function bb(){return!0}function cb(){return!1}function db(){try{return z.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=n.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof n===L||a&&n.event.triggered===a.type?void 0:n.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(F)||[""],h=b.length;while(h--)f=ab.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=n.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=n.event.special[o]||{},l=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},i),(m=g[o])||(m=g[o]=[],m.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,l):m.push(l),n.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n.hasData(a)&&n._data(a);if(r&&(k=r.events)){b=(b||"").match(F)||[""],j=b.length;while(j--)if(h=ab.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=m.length;while(f--)g=m[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(m.splice(f,1),g.selector&&m.delegateCount--,l.remove&&l.remove.call(a,g));i&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(k)&&(delete r.handle,n._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,m,o=[d||z],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||z,3!==d.nodeType&&8!==d.nodeType&&!_.test(p+n.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[n.expando]?b:new n.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),k=n.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!n.isWindow(d)){for(i=k.delegateType||p,_.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||z)&&o.push(l.defaultView||l.parentWindow||a)}m=0;while((h=o[m++])&&!b.isPropagationStopped())b.type=m>1?i:k.bindType||p,f=(n._data(h,"events")||{})[b.type]&&n._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&n.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&n.acceptData(d)&&g&&d[p]&&!n.isWindow(d)){l=d[g],l&&(d[g]=null),n.event.triggered=p;try{d[p]()}catch(r){}n.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(n._data(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((n.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?n(c,this).index(i)>=0:n.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),ib=/^\s+/,jb=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,kb=/<([\w:]+)/,lb=/\s*$/g,sb={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:l.htmlSerialize?[0,"",""]:[1,"X
","
"]},tb=eb(z),ub=tb.appendChild(z.createElement("div"));sb.optgroup=sb.option,sb.tbody=sb.tfoot=sb.colgroup=sb.caption=sb.thead,sb.th=sb.td;function vb(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==L?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==L?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||n.nodeName(d,b)?f.push(d):n.merge(f,vb(d,b));return void 0===b||b&&n.nodeName(a,b)?n.merge([a],f):f}function wb(a){X.test(a.type)&&(a.defaultChecked=a.checked)}function xb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function yb(a){return a.type=(null!==n.find.attr(a,"type"))+"/"+a.type,a}function zb(a){var b=qb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ab(a,b){for(var c,d=0;null!=(c=a[d]);d++)n._data(c,"globalEval",!b||n._data(b[d],"globalEval"))}function Bb(a,b){if(1===b.nodeType&&n.hasData(a)){var c,d,e,f=n._data(a),g=n._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)n.event.add(b,c,h[c][d])}g.data&&(g.data=n.extend({},g.data))}}function Cb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!l.noCloneEvent&&b[n.expando]){e=n._data(b);for(d in e.events)n.removeEvent(b,d,e.handle);b.removeAttribute(n.expando)}"script"===c&&b.text!==a.text?(yb(b).text=a.text,zb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),l.html5Clone&&a.innerHTML&&!n.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&X.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}n.extend({clone:function(a,b,c){var d,e,f,g,h,i=n.contains(a.ownerDocument,a);if(l.html5Clone||n.isXMLDoc(a)||!hb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(ub.innerHTML=a.outerHTML,ub.removeChild(f=ub.firstChild)),!(l.noCloneEvent&&l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(d=vb(f),h=vb(a),g=0;null!=(e=h[g]);++g)d[g]&&Cb(e,d[g]);if(b)if(c)for(h=h||vb(a),d=d||vb(f),g=0;null!=(e=h[g]);g++)Bb(e,d[g]);else Bb(a,f);return d=vb(f,"script"),d.length>0&&Ab(d,!i&&vb(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k,m=a.length,o=eb(b),p=[],q=0;m>q;q++)if(f=a[q],f||0===f)if("object"===n.type(f))n.merge(p,f.nodeType?[f]:f);else if(mb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(kb.exec(f)||["",""])[1].toLowerCase(),k=sb[i]||sb._default,h.innerHTML=k[1]+f.replace(jb,"<$1>")+k[2],e=k[0];while(e--)h=h.lastChild;if(!l.leadingWhitespace&&ib.test(f)&&p.push(b.createTextNode(ib.exec(f)[0])),!l.tbody){f="table"!==i||lb.test(f)?""!==k[1]||lb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)n.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}n.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),l.appendChecked||n.grep(vb(p,"input"),wb),q=0;while(f=p[q++])if((!d||-1===n.inArray(f,d))&&(g=n.contains(f.ownerDocument,f),h=vb(o.appendChild(f),"script"),g&&Ab(h),c)){e=0;while(f=h[e++])pb.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=n.expando,j=n.cache,k=l.deleteExpando,m=n.event.special;null!=(d=a[h]);h++)if((b||n.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)m[e]?n.event.remove(d,e):n.removeEvent(d,e,g.handle);j[f]&&(delete j[f],k?delete d[i]:typeof d.removeAttribute!==L?d.removeAttribute(i):d[i]=null,c.push(f))}}}),n.fn.extend({text:function(a){return W(this,function(a){return void 0===a?n.text(this):this.empty().append((this[0]&&this[0].ownerDocument||z).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=xb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=xb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(vb(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&Ab(vb(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&n.cleanData(vb(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&n.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return W(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(gb,""):void 0;if(!("string"!=typeof a||nb.test(a)||!l.htmlSerialize&&hb.test(a)||!l.leadingWhitespace&&ib.test(a)||sb[(kb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(jb,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(vb(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(vb(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,k=this.length,m=this,o=k-1,p=a[0],q=n.isFunction(p);if(q||k>1&&"string"==typeof p&&!l.checkClone&&ob.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(k&&(i=n.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=n.map(vb(i,"script"),yb),f=g.length;k>j;j++)d=i,j!==o&&(d=n.clone(d,!0,!0),f&&n.merge(g,vb(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,n.map(g,zb),j=0;f>j;j++)d=g[j],pb.test(d.type||"")&&!n._data(d,"globalEval")&&n.contains(h,d)&&(d.src?n._evalUrl&&n._evalUrl(d.src):n.globalEval((d.text||d.textContent||d.innerHTML||"").replace(rb,"")));i=c=null}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=0,e=[],g=n(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),n(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Db,Eb={};function Fb(b,c){var d=n(c.createElement(b)).appendTo(c.body),e=a.getDefaultComputedStyle?a.getDefaultComputedStyle(d[0]).display:n.css(d[0],"display");return d.detach(),e}function Gb(a){var b=z,c=Eb[a];return c||(c=Fb(a,b),"none"!==c&&c||(Db=(Db||n("