summaryrefslogtreecommitdiff
path: root/tidal_dl_ng
diff options
context:
space:
mode:
authorexislow <exislow@users.noreply.github.com>2024-02-01 07:42:08 +0100
committerGitHub <noreply@github.com>2024-02-01 07:42:08 +0100
commit2ec3314b0714bc938b068084723d31ff1a854f16 (patch)
treef84ec117c3ef57f7513ceb434c9e55835c0e441c /tidal_dl_ng
parent7c0d2ab2eabfc8574f569752465035557baa2875 (diff)
parentce9afc1629c5fdb7ef74f2c5fcd9f9bdde527efc (diff)
downloadtidal-dl-ng-2ec3314b0714bc938b068084723d31ff1a854f16.tar.gz
Merge pull request #25 from exislow/pkce
Pkce
Diffstat (limited to 'tidal_dl_ng')
-rw-r--r--tidal_dl_ng/cli.py2
-rw-r--r--tidal_dl_ng/config.py19
-rw-r--r--tidal_dl_ng/dialog.py31
-rw-r--r--tidal_dl_ng/download.py18
-rw-r--r--tidal_dl_ng/gui.py29
-rw-r--r--tidal_dl_ng/helper/path.py20
-rw-r--r--tidal_dl_ng/ui/dialog_login.py110
-rw-r--r--tidal_dl_ng/ui/dialog_login.ui194
-rw-r--r--tidal_dl_ng/ui/dummy_register.py33
-rw-r--r--tidal_dl_ng/ui/dummy_wiggly.py68
10 files changed, 488 insertions, 36 deletions
diff --git a/tidal_dl_ng/cli.py b/tidal_dl_ng/cli.py
index 1266f98..e84eafc 100644
--- a/tidal_dl_ng/cli.py
+++ b/tidal_dl_ng/cli.py
@@ -95,7 +95,7 @@ def login(ctx: typer.Context) -> bool:
settings = Settings()
tidal = Tidal(settings)
- result = tidal.login(print)
+ result = tidal.login(fn_print=print)
ctx.obj[CTX_TIDAL] = tidal
return result
diff --git a/tidal_dl_ng/config.py b/tidal_dl_ng/config.py
index 12fa061..92eea3b 100644
--- a/tidal_dl_ng/config.py
+++ b/tidal_dl_ng/config.py
@@ -8,7 +8,7 @@ import tidalapi
from requests import HTTPError
from tidal_dl_ng.helper.decorator import SingletonMeta
-from tidal_dl_ng.helper.path import path_base, path_file_settings, path_file_token
+from tidal_dl_ng.helper.path import path_config_base, path_file_settings, path_file_token
from tidal_dl_ng.model.cfg import Settings as ModelSettings
from tidal_dl_ng.model.cfg import Token as ModelToken
@@ -17,7 +17,7 @@ class BaseConfig:
data: ModelSettings | ModelToken
file_path: str
cls_model: ModelSettings | ModelToken
- path_base: str = path_base()
+ path_base: str = path_config_base()
def save(self, config_to_compare: str = None) -> None:
data_json = self.data.to_json()
@@ -36,7 +36,8 @@ class BaseConfig:
setattr(self.data, key, value)
def read(self, path: str) -> bool:
- result = False
+ result: bool = False
+ settings_json: str = ""
try:
with open(path, encoding="utf-8") as f:
@@ -116,10 +117,7 @@ class Tidal(BaseConfig, metaclass=SingletonMeta):
return result
- def login_oauth_start(self, function=print) -> None:
- self.session.login_oauth_simple(function)
-
- def login_oauth_finish(self) -> bool:
+ def login_finalize(self) -> bool:
result = self.session.check_login()
if result:
@@ -145,9 +143,12 @@ class Tidal(BaseConfig, metaclass=SingletonMeta):
elif not is_token:
fn_print("You either do not have a token or your token is invalid.")
fn_print("No worries, we will handle this...")
- self.login_oauth_start(fn_print)
+ # Login method: Device linking
+ # self.session.login_oauth_simple(fn_print)
+ # Login method: PKCE authorization (enables HiRes streaming)
+ self.session.login_pkce(fn_print)
- is_login = self.login_oauth_finish()
+ is_login = self.login_finalize()
if is_login:
fn_print("The login was successful. I have stored your credentials (token).")
diff --git a/tidal_dl_ng/dialog.py b/tidal_dl_ng/dialog.py
index 5370136..788aeba 100644
--- a/tidal_dl_ng/dialog.py
+++ b/tidal_dl_ng/dialog.py
@@ -1,6 +1,7 @@
from PySide6.QtWidgets import QDialog
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
@@ -9,11 +10,39 @@ class DialogVersion(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
+
# Create an instance of the GUI
self.ui = Ui_DialogVersion()
+
# Run the .setupUi() method to show the GUI
self.ui.setupUi(self)
# Set the version.
self.ui.l_version.setText("v" + __version__)
-
+ # Show
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
diff --git a/tidal_dl_ng/download.py b/tidal_dl_ng/download.py
index 24720fb..5d2fe86 100644
--- a/tidal_dl_ng/download.py
+++ b/tidal_dl_ng/download.py
@@ -198,7 +198,9 @@ class Download:
# Create file name and path
file_name_relative = format_path_media(file_template, media)
- path_file = os.path.abspath(os.path.normpath(os.path.join(self.path_base, file_name_relative)))
+ path_file = os.path.abspath(
+ os.path.normpath(os.path.join(os.path.expanduser(self.path_base), file_name_relative))
+ )
# Populate StreamManifest for further download.
if isinstance(media, Track):
@@ -366,18 +368,14 @@ class Download:
if AudioExtensions.FLAC.value in stream_url:
result: str = AudioExtensions.FLAC.value
elif AudioExtensions.MP4.value in stream_url:
- # TODO: Need to investigate, what the correct extension is.
- # if "ac4" in stream_codec or "mha1" in stream_codec:
- # result = ".mp4"
- # elif "flac" in stream_codec:
- # result = ".flac"
- # else:
- # result = ".m4a"
- result: str = AudioExtensions.MP4.value
+ if "ac4" in stream_codec or "mha1" in stream_codec or "flac" in stream_codec or "mp4a" in stream_codec:
+ result: str = AudioExtensions.M4A.value
+ else:
+ result: str = AudioExtensions.MP4.value
elif VideoExtensions.TS.value in stream_url:
result: str = VideoExtensions.TS.value
else:
- result: str = AudioExtensions.M4A.value
+ result: str = AudioExtensions.MP4.value
return result
diff --git a/tidal_dl_ng/gui.py b/tidal_dl_ng/gui.py
index a12cd82..2a1b2d1 100644
--- a/tidal_dl_ng/gui.py
+++ b/tidal_dl_ng/gui.py
@@ -2,8 +2,10 @@ import math
import sys
from collections.abc import Callable
+from requests.exceptions import HTTPError
+
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.tidal import items_results_all, search_results_all, user_media_lists
@@ -85,12 +87,29 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
result = True
else:
self.tidal = Tidal(self.settings)
+ result = self.tidal.login_token()
+
+ if not result:
+ 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
- while True:
- result = self.tidal.login(logger_gui.info)
+ if d_login.return_code == 1:
+ 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()
- if result:
- break
+ 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:
# Init `Download` object.
diff --git a/tidal_dl_ng/helper/path.py b/tidal_dl_ng/helper/path.py
index a2ce98c..373f78c 100644
--- a/tidal_dl_ng/helper/path.py
+++ b/tidal_dl_ng/helper/path.py
@@ -23,7 +23,7 @@ def path_home() -> str:
return os.path.abspath("./")
-def path_base() -> str:
+def path_config_base() -> str:
path_config: str = ".config"
path_base: str = os.path.join(path_home(), path_config, __name_display__)
@@ -34,30 +34,30 @@ def path_file_log() -> str:
# TODO: Remove this soon. Only for migration to new dir.
old = os.path.join(path_home(), ".tidal-dl-ng.log")
if os.path.isfile(old):
- os.makedirs(path_base(), exist_ok=True)
- os.rename(old, os.path.join(path_base(), "app.log"))
+ os.makedirs(path_config_base(), exist_ok=True)
+ os.rename(old, os.path.join(path_config_base(), "app.log"))
- return os.path.join(path_base(), "app.log")
+ return os.path.join(path_config_base(), "app.log")
def path_file_token() -> str:
# TODO: Remove this soon. Only for migration to new dir.
old = os.path.join(path_home(), ".tidal-dl-ng_token.json")
if os.path.isfile(old):
- os.makedirs(path_base(), exist_ok=True)
- os.rename(old, os.path.join(path_base(), "token.json"))
+ os.makedirs(path_config_base(), exist_ok=True)
+ os.rename(old, os.path.join(path_config_base(), "token.json"))
- return os.path.join(path_base(), "token.json")
+ return os.path.join(path_config_base(), "token.json")
def path_file_settings() -> str:
# TODO: Remove this soon. Only for migration to new dir.
old = os.path.join(path_home(), ".tidal-dl-ng_settings.json")
if os.path.isfile(old):
- os.makedirs(path_base(), exist_ok=True)
- os.rename(old, os.path.join(path_base(), "settings.json"))
+ os.makedirs(path_config_base(), exist_ok=True)
+ os.rename(old, os.path.join(path_config_base(), "settings.json"))
- return os.path.join(path_base(), "settings.json")
+ return os.path.join(path_config_base(), "settings.json")
def format_path_media(fmt_template: str, media: Track | Album | Playlist | UserPlaylist | Video | Mix) -> str:
diff --git a/tidal_dl_ng/ui/dialog_login.py b/tidal_dl_ng/ui/dialog_login.py
new file mode 100644
index 0000000..ffb8e76
--- /dev/null
+++ b/tidal_dl_ng/ui/dialog_login.py
@@ -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
diff --git a/tidal_dl_ng/ui/dialog_login.ui b/tidal_dl_ng/ui/dialog_login.ui
new file mode 100644
index 0000000..ae0a25a
--- /dev/null
+++ b/tidal_dl_ng/ui/dialog_login.ui
@@ -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 &quot;Ooops&quot; page after a successful login. Copy the URL of this &quot;Ooops&quot; 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>
diff --git a/tidal_dl_ng/ui/dummy_register.py b/tidal_dl_ng/ui/dummy_register.py
new file mode 100644
index 0000000..dfffdc2
--- /dev/null
+++ b/tidal_dl_ng/ui/dummy_register.py
@@ -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
+ )
diff --git a/tidal_dl_ng/ui/dummy_wiggly.py b/tidal_dl_ng/ui/dummy_wiggly.py
new file mode 100644
index 0000000..e6eb3d3
--- /dev/null
+++ b/tidal_dl_ng/ui/dummy_wiggly.py
@@ -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)