✨ Implemented login dialog for PKCE login method. Fixes #22.
This commit is contained in:
@@ -5,8 +5,7 @@
|
|||||||
[](https://img.shields.io/github/commit-activity/m/exislow/tidal-dl-ng)
|
[](https://img.shields.io/github/commit-activity/m/exislow/tidal-dl-ng)
|
||||||
[](https://img.shields.io/github/license/exislow/tidal-dl-ng)
|
[](https://img.shields.io/github/license/exislow/tidal-dl-ng)
|
||||||
|
|
||||||
This tool allows to download songs and videos from TIDAL (a paid plan is required!). You can use the command line or GUI
|
This tool allows to download songs and videos from TIDAL. A paid plan is required! Audio quality varies up to HiRes / TIDAL MAX 24 Bit, 192 kHz depending on the song and your TIDAL plan. You can use the command line or GUI version of this tool.
|
||||||
version of this tool.
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -93,7 +92,7 @@ tidal_ng_dl/gui.py
|
|||||||
The GUI is build with `PySide6` using the [Qt Designer](https://doc.qt.io/qt-6/qtdesigner-manual.html):
|
The GUI is build with `PySide6` using the [Qt Designer](https://doc.qt.io/qt-6/qtdesigner-manual.html):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pyside6-designer
|
PYSIDE_DESIGNER_PLUGINS=tidal_dl_ng/ui pyside6-designer
|
||||||
```
|
```
|
||||||
|
|
||||||
After all changes are saved you need to translate the Qt Designer `*.ui` file into Python code:
|
After all changes are saved you need to translate the Qt Designer `*.ui` file into Python code:
|
||||||
|
|||||||
+1
-1
@@ -95,7 +95,7 @@ def login(ctx: typer.Context) -> bool:
|
|||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
tidal = Tidal(settings)
|
tidal = Tidal(settings)
|
||||||
result = tidal.login(print)
|
result = tidal.login(fn_print=print)
|
||||||
ctx.obj[CTX_TIDAL] = tidal
|
ctx.obj[CTX_TIDAL] = tidal
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ class Tidal(BaseConfig, metaclass=SingletonMeta):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _login_finalize(self) -> bool:
|
def login_finalize(self) -> bool:
|
||||||
result = self.session.check_login()
|
result = self.session.check_login()
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
@@ -151,7 +151,7 @@ class Tidal(BaseConfig, metaclass=SingletonMeta):
|
|||||||
# Login method: PKCE authorization (enables HiRes streaming)
|
# Login method: PKCE authorization (enables HiRes streaming)
|
||||||
self.session.login_pkce(fn_print)
|
self.session.login_pkce(fn_print)
|
||||||
|
|
||||||
is_login = self._login_finalize()
|
is_login = self.login_finalize()
|
||||||
|
|
||||||
if is_login:
|
if is_login:
|
||||||
fn_print("The login was successful. I have stored your credentials (token).")
|
fn_print("The login was successful. I have stored your credentials (token).")
|
||||||
|
|||||||
+30
-1
@@ -1,6 +1,7 @@
|
|||||||
from PySide6.QtWidgets import QDialog
|
from PySide6.QtWidgets import QDialog
|
||||||
|
|
||||||
from tidal_dl_ng import __version__
|
from tidal_dl_ng import __version__
|
||||||
|
from tidal_dl_ng.ui.dialog_login import Ui_DialogLogin
|
||||||
from tidal_dl_ng.ui.dialog_version import Ui_DialogVersion
|
from tidal_dl_ng.ui.dialog_version import Ui_DialogVersion
|
||||||
|
|
||||||
|
|
||||||
@@ -9,11 +10,39 @@ class DialogVersion(QDialog):
|
|||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
# Create an instance of the GUI
|
# Create an instance of the GUI
|
||||||
self.ui = Ui_DialogVersion()
|
self.ui = Ui_DialogVersion()
|
||||||
|
|
||||||
# Run the .setupUi() method to show the GUI
|
# Run the .setupUi() method to show the GUI
|
||||||
self.ui.setupUi(self)
|
self.ui.setupUi(self)
|
||||||
# Set the version.
|
# Set the version.
|
||||||
self.ui.l_version.setText("v" + __version__)
|
self.ui.l_version.setText("v" + __version__)
|
||||||
|
# Show
|
||||||
self.exec()
|
self.exec()
|
||||||
|
|
||||||
|
|
||||||
|
class DialogLogin(QDialog):
|
||||||
|
"""Version dialog."""
|
||||||
|
|
||||||
|
url_redirect: str
|
||||||
|
return_code: int
|
||||||
|
|
||||||
|
def __init__(self, url_login: str, hint: str, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
# Create an instance of the GUI
|
||||||
|
self.ui = Ui_DialogLogin()
|
||||||
|
|
||||||
|
# Run the .setupUi() method to show the GUI
|
||||||
|
self.ui.setupUi(self)
|
||||||
|
# Set data.
|
||||||
|
self.ui.l_url_login.setText(f'<a href="{url_login}">{url_login}</a>')
|
||||||
|
self.ui.l_hint.setText(hint)
|
||||||
|
# Show
|
||||||
|
self.return_code = self.exec()
|
||||||
|
|
||||||
|
self.url_redirect = self.ui.te_url_redirect.toPlainText()
|
||||||
|
|
||||||
|
def accept1(self):
|
||||||
|
pass
|
||||||
|
|||||||
+24
-5
@@ -2,8 +2,10 @@ import math
|
|||||||
import sys
|
import sys
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
|
|
||||||
|
from requests.exceptions import HTTPError
|
||||||
|
|
||||||
from tidal_dl_ng import __version__
|
from tidal_dl_ng import __version__
|
||||||
from tidal_dl_ng.dialog import DialogVersion
|
from tidal_dl_ng.dialog import DialogLogin, DialogVersion
|
||||||
from tidal_dl_ng.helper.path import get_format_template
|
from tidal_dl_ng.helper.path import get_format_template
|
||||||
from tidal_dl_ng.helper.tidal import items_results_all, search_results_all, user_media_lists
|
from tidal_dl_ng.helper.tidal import items_results_all, search_results_all, user_media_lists
|
||||||
|
|
||||||
@@ -85,12 +87,29 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
|||||||
result = True
|
result = True
|
||||||
else:
|
else:
|
||||||
self.tidal = Tidal(self.settings)
|
self.tidal = Tidal(self.settings)
|
||||||
|
result = self.tidal.login_token()
|
||||||
|
|
||||||
while True:
|
if not result:
|
||||||
result = self.tidal.login(logger_gui.info)
|
hint: str = "Watiting for user input."
|
||||||
|
while not result:
|
||||||
|
url_login = self.tidal.session.pkce_login_url()
|
||||||
|
d_login: DialogLogin = DialogLogin(url_login=url_login, hint=hint, parent=self)
|
||||||
|
url_redirect: str = d_login.url_redirect
|
||||||
|
|
||||||
if result:
|
if d_login.return_code == 1:
|
||||||
break
|
try:
|
||||||
|
token: dict[str, str | int] = self.tidal.session.pkce_get_auth_token(url_redirect)
|
||||||
|
self.tidal.session.process_auth_token(token)
|
||||||
|
self.tidal.login_finalize()
|
||||||
|
|
||||||
|
result = True
|
||||||
|
logger_gui.info("Login not successful. Have fun!")
|
||||||
|
except (HTTPError, Exception):
|
||||||
|
hint = "Something was wrong with your redirect url. Please try again!"
|
||||||
|
logger_gui.warning("Login not successful. Try again...")
|
||||||
|
else:
|
||||||
|
# If user has pressed cancel.
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
# Init `Download` object.
|
# Init `Download` object.
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
################################################################################
|
||||||
|
## Form generated from reading UI file 'dialog_login.ui'
|
||||||
|
##
|
||||||
|
## Created by: Qt User Interface Compiler version 6.6.1
|
||||||
|
##
|
||||||
|
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
from PySide6.QtCore import QCoreApplication, QMetaObject, QRect, QSize, Qt
|
||||||
|
from PySide6.QtGui import QFont
|
||||||
|
from PySide6.QtWidgets import QDialogButtonBox, QLabel, QSizePolicy, QTextEdit, QVBoxLayout, QWidget
|
||||||
|
|
||||||
|
|
||||||
|
class Ui_DialogLogin:
|
||||||
|
def setupUi(self, DialogLogin):
|
||||||
|
if not DialogLogin.objectName():
|
||||||
|
DialogLogin.setObjectName("DialogLogin")
|
||||||
|
DialogLogin.resize(451, 400)
|
||||||
|
sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||||
|
sizePolicy.setHorizontalStretch(0)
|
||||||
|
sizePolicy.setVerticalStretch(0)
|
||||||
|
sizePolicy.setHeightForWidth(DialogLogin.sizePolicy().hasHeightForWidth())
|
||||||
|
DialogLogin.setSizePolicy(sizePolicy)
|
||||||
|
self.bb_dialog = QDialogButtonBox(DialogLogin)
|
||||||
|
self.bb_dialog.setObjectName("bb_dialog")
|
||||||
|
self.bb_dialog.setGeometry(QRect(20, 350, 411, 32))
|
||||||
|
sizePolicy.setHeightForWidth(self.bb_dialog.sizePolicy().hasHeightForWidth())
|
||||||
|
self.bb_dialog.setSizePolicy(sizePolicy)
|
||||||
|
self.bb_dialog.setStyleSheet("")
|
||||||
|
self.bb_dialog.setOrientation(Qt.Horizontal)
|
||||||
|
self.bb_dialog.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok)
|
||||||
|
self.verticalLayoutWidget = QWidget(DialogLogin)
|
||||||
|
self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
|
||||||
|
self.verticalLayoutWidget.setGeometry(QRect(20, 20, 411, 321))
|
||||||
|
self.lv_main = QVBoxLayout(self.verticalLayoutWidget)
|
||||||
|
self.lv_main.setObjectName("lv_main")
|
||||||
|
self.lv_main.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.l_header = QLabel(self.verticalLayoutWidget)
|
||||||
|
self.l_header.setObjectName("l_header")
|
||||||
|
sizePolicy.setHeightForWidth(self.l_header.sizePolicy().hasHeightForWidth())
|
||||||
|
self.l_header.setSizePolicy(sizePolicy)
|
||||||
|
font = QFont()
|
||||||
|
font.setPointSize(23)
|
||||||
|
font.setBold(True)
|
||||||
|
self.l_header.setFont(font)
|
||||||
|
|
||||||
|
self.lv_main.addWidget(self.l_header)
|
||||||
|
|
||||||
|
self.l_description = QLabel(self.verticalLayoutWidget)
|
||||||
|
self.l_description.setObjectName("l_description")
|
||||||
|
sizePolicy.setHeightForWidth(self.l_description.sizePolicy().hasHeightForWidth())
|
||||||
|
self.l_description.setSizePolicy(sizePolicy)
|
||||||
|
font1 = QFont()
|
||||||
|
font1.setItalic(True)
|
||||||
|
self.l_description.setFont(font1)
|
||||||
|
self.l_description.setWordWrap(True)
|
||||||
|
|
||||||
|
self.lv_main.addWidget(self.l_description)
|
||||||
|
|
||||||
|
self.l_url_login = QLabel(self.verticalLayoutWidget)
|
||||||
|
self.l_url_login.setObjectName("l_url_login")
|
||||||
|
sizePolicy.setHeightForWidth(self.l_url_login.sizePolicy().hasHeightForWidth())
|
||||||
|
self.l_url_login.setSizePolicy(sizePolicy)
|
||||||
|
self.l_url_login.setMinimumSize(QSize(0, 50))
|
||||||
|
self.l_url_login.setBaseSize(QSize(0, 0))
|
||||||
|
self.l_url_login.setStyleSheet("border: 2px solid red; padding: 6px")
|
||||||
|
self.l_url_login.setWordWrap(True)
|
||||||
|
self.l_url_login.setOpenExternalLinks(True)
|
||||||
|
|
||||||
|
self.lv_main.addWidget(self.l_url_login)
|
||||||
|
|
||||||
|
self.te_url_redirect = QTextEdit(self.verticalLayoutWidget)
|
||||||
|
self.te_url_redirect.setObjectName("te_url_redirect")
|
||||||
|
|
||||||
|
self.lv_main.addWidget(self.te_url_redirect)
|
||||||
|
|
||||||
|
self.l_hint = QLabel(self.verticalLayoutWidget)
|
||||||
|
self.l_hint.setObjectName("l_hint")
|
||||||
|
sizePolicy.setHeightForWidth(self.l_hint.sizePolicy().hasHeightForWidth())
|
||||||
|
self.l_hint.setSizePolicy(sizePolicy)
|
||||||
|
self.l_hint.setFont(font1)
|
||||||
|
|
||||||
|
self.lv_main.addWidget(self.l_hint)
|
||||||
|
|
||||||
|
self.retranslateUi(DialogLogin)
|
||||||
|
self.bb_dialog.accepted.connect(DialogLogin.accept)
|
||||||
|
self.bb_dialog.rejected.connect(DialogLogin.reject)
|
||||||
|
|
||||||
|
QMetaObject.connectSlotsByName(DialogLogin)
|
||||||
|
|
||||||
|
# setupUi
|
||||||
|
|
||||||
|
def retranslateUi(self, DialogLogin):
|
||||||
|
DialogLogin.setWindowTitle(QCoreApplication.translate("DialogLogin", "Dialog", None))
|
||||||
|
self.l_header.setText(QCoreApplication.translate("DialogLogin", "TIDAL PKCE Login", None))
|
||||||
|
self.l_description.setText(
|
||||||
|
QCoreApplication.translate(
|
||||||
|
"DialogLogin",
|
||||||
|
'Click the link below and login with your TIDAL credentials. You will be redirected to an "Ooops" page'
|
||||||
|
' after a successful login. Copy the URL of this "Ooops" page and post it in the box below.',
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.l_url_login.setText(QCoreApplication.translate("DialogLogin", "PLACEHOLDER URL...", None))
|
||||||
|
self.te_url_redirect.setPlaceholderText(
|
||||||
|
QCoreApplication.translate("DialogLogin", "Copy and paste the redirect URL after login here...", None)
|
||||||
|
)
|
||||||
|
self.l_hint.setText(QCoreApplication.translate("DialogLogin", "Waiting...", None))
|
||||||
|
|
||||||
|
# retranslateUi
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>DialogLogin</class>
|
||||||
|
<widget class="QDialog" name="DialogLogin">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>451</width>
|
||||||
|
<height>400</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Dialog</string>
|
||||||
|
</property>
|
||||||
|
<widget class="QDialogButtonBox" name="bb_dialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>20</x>
|
||||||
|
<y>350</y>
|
||||||
|
<width>411</width>
|
||||||
|
<height>32</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true"/>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="verticalLayoutWidget">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>20</x>
|
||||||
|
<y>20</y>
|
||||||
|
<width>411</width>
|
||||||
|
<height>321</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="lv_main">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="l_header">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>23</pointsize>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>TIDAL PKCE Login</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="l_description">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<italic>true</italic>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Click the link below and login with your TIDAL credentials. You will be redirected to an "Ooops" page after a successful login. Copy the URL of this "Ooops" page and post it in the box below.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="l_url_login">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>50</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="baseSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">border: 2px solid red; padding: 6px</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>PLACEHOLDER URL...</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="openExternalLinks">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QTextEdit" name="te_url_redirect">
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Copy and paste the redirect URL after login here...</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="l_hint">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<italic>true</italic>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Waiting...</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>bb_dialog</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>DialogLogin</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>248</x>
|
||||||
|
<y>254</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>157</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>bb_dialog</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>DialogLogin</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>316</x>
|
||||||
|
<y>260</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||||
|
|
||||||
|
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
||||||
|
|
||||||
|
from .dummy_wiggly import WigglyWidget
|
||||||
|
|
||||||
|
# Set PYSIDE_DESIGNER_PLUGINS to point to this directory and load the plugin
|
||||||
|
|
||||||
|
|
||||||
|
TOOLTIP = "A cool wiggly widget (Python)"
|
||||||
|
DOM_XML = """
|
||||||
|
<ui language='c++'>
|
||||||
|
<widget class='WigglyWidget' name='wigglyWidget'>
|
||||||
|
<property name='geometry'>
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>400</width>
|
||||||
|
<height>200</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name='text'>
|
||||||
|
<string>Hello, world</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</ui>
|
||||||
|
"""
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
QPyDesignerCustomWidgetCollection.registerCustomWidget(
|
||||||
|
WigglyWidget, module="wigglywidget", tool_tip=TOOLTIP, xml=DOM_XML
|
||||||
|
)
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||||
|
|
||||||
|
from PySide6.QtCore import Property, QBasicTimer
|
||||||
|
from PySide6.QtGui import QColor, QFontMetrics, QPainter, QPalette
|
||||||
|
from PySide6.QtWidgets import QWidget
|
||||||
|
|
||||||
|
|
||||||
|
class WigglyWidget(QWidget):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self._step = 0
|
||||||
|
self._text = ""
|
||||||
|
self.setBackgroundRole(QPalette.Midlight)
|
||||||
|
self.setAutoFillBackground(True)
|
||||||
|
|
||||||
|
new_font = self.font()
|
||||||
|
new_font.setPointSize(new_font.pointSize() + 20)
|
||||||
|
self.setFont(new_font)
|
||||||
|
self._timer = QBasicTimer()
|
||||||
|
|
||||||
|
def isRunning(self):
|
||||||
|
return self._timer.isActive()
|
||||||
|
|
||||||
|
def setRunning(self, r):
|
||||||
|
if r == self.isRunning():
|
||||||
|
return
|
||||||
|
if r:
|
||||||
|
self._timer.start(60, self)
|
||||||
|
else:
|
||||||
|
self._timer.stop()
|
||||||
|
|
||||||
|
def paintEvent(self, event):
|
||||||
|
if not self._text:
|
||||||
|
return
|
||||||
|
|
||||||
|
sineTable = [0, 38, 71, 92, 100, 92, 71, 38, 0, -38, -71, -92, -100, -92, -71, -38]
|
||||||
|
|
||||||
|
metrics = QFontMetrics(self.font())
|
||||||
|
x = (self.width() - metrics.horizontalAdvance(self.text)) / 2
|
||||||
|
y = (self.height() + metrics.ascent() - metrics.descent()) / 2
|
||||||
|
color = QColor()
|
||||||
|
|
||||||
|
with QPainter(self) as painter:
|
||||||
|
for i in range(len(self.text)):
|
||||||
|
index = (self._step + i) % 16
|
||||||
|
color.setHsv((15 - index) * 16, 255, 191)
|
||||||
|
painter.setPen(color)
|
||||||
|
dy = (sineTable[index] * metrics.height()) / 400
|
||||||
|
c = self._text[i]
|
||||||
|
painter.drawText(x, y - dy, str(c))
|
||||||
|
x += metrics.horizontalAdvance(c)
|
||||||
|
|
||||||
|
def timerEvent(self, event):
|
||||||
|
if event.timerId() == self._timer.timerId():
|
||||||
|
self._step += 1
|
||||||
|
self.update()
|
||||||
|
else:
|
||||||
|
QWidget.timerEvent(event)
|
||||||
|
|
||||||
|
def text(self):
|
||||||
|
return self._text
|
||||||
|
|
||||||
|
def setText(self, text):
|
||||||
|
self._text = text
|
||||||
|
|
||||||
|
running = Property(bool, isRunning, setRunning)
|
||||||
|
text = Property(str, text, setText)
|
||||||
Reference in New Issue
Block a user