Reset directory
@@ -0,0 +1,16 @@
|
||||
from setuptools import setup, find_packages
|
||||
setup(
|
||||
name = 'tidal-gui',
|
||||
version = '1.0.0.4',
|
||||
license = "Apache2",
|
||||
description = "Tidal Music Downloader.",
|
||||
|
||||
author = 'YaronH',
|
||||
author_email = "yaronhuang@foxmail.com",
|
||||
|
||||
packages = find_packages(),
|
||||
include_package_data = True,
|
||||
platforms = "any",
|
||||
install_requires=["aigpy", "PyQt5", "requests>=2.22.0", "pycryptodome", "pydub", "prettytable", "lyricsgenius"],
|
||||
entry_points={'console_scripts': [ 'tidal-gui = tidal_gui:main', ]}
|
||||
)
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : __init__.py
|
||||
@Date : 2021/05/08
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
import sys
|
||||
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
from tidal_gui import theme
|
||||
from tidal_gui.viewModel.mainModel import MainModel
|
||||
|
||||
|
||||
def main():
|
||||
qss = theme.getThemeQssContent()
|
||||
app = QApplication(sys.argv)
|
||||
app.setStyleSheet(qss)
|
||||
|
||||
mainView = MainModel()
|
||||
mainView.show()
|
||||
|
||||
app.exec_()
|
||||
mainView.uninit()
|
||||
|
||||
sys.exit()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : __init__.py
|
||||
@Date : 2021/05/08
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : checkBox.py
|
||||
@Date : 2021/05/08
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
|
||||
from PyQt5.QtWidgets import QCheckBox
|
||||
|
||||
|
||||
class CheckBox(QCheckBox):
|
||||
def __init__(self, text: str = "", checked: bool = False):
|
||||
super(CheckBox, self).__init__()
|
||||
self.setChecked(checked)
|
||||
self.setText(text)
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : comboBox.py
|
||||
@Date : 2021/8/17
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
from PyQt5.QtWidgets import QComboBox, QListView
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
|
||||
class ComboBox(QComboBox):
|
||||
def __init__(self, items: list, width: int=200):
|
||||
super(ComboBox, self).__init__()
|
||||
self.setItems(items)
|
||||
self.setFixedWidth(width)
|
||||
self.setView(QListView())
|
||||
# remove shadow
|
||||
self.view().window().setWindowFlags(Qt.Popup | Qt.FramelessWindowHint | Qt.NoDropShadowWindowHint)
|
||||
self.view().window().setAttribute(Qt.WA_TranslucentBackground)
|
||||
|
||||
def setItems(self, items):
|
||||
for item in items:
|
||||
self.addItem(str(item))
|
||||
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : framelessWidget.py
|
||||
@Date : 2021/05/08
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
import ctypes.wintypes
|
||||
from ctypes.wintypes import POINT
|
||||
import typing
|
||||
|
||||
import PyQt5
|
||||
import win32api
|
||||
import win32con
|
||||
from PyQt5.QtCore import Qt, QPoint
|
||||
from PyQt5.QtGui import QMouseEvent
|
||||
from PyQt5.QtWidgets import QWidget, QGridLayout, QHBoxLayout
|
||||
|
||||
from tidal_gui.control.pushButton import PushButton
|
||||
from tidal_gui.style import ButtonStyle
|
||||
|
||||
|
||||
class FramelessWidget(QWidget):
|
||||
def __init__(self):
|
||||
super(FramelessWidget, self).__init__()
|
||||
self.setWindowFlags(Qt.FramelessWindowHint)
|
||||
self.BorderWidth = 5
|
||||
self.borderWidget = QWidget()
|
||||
self.borderWidget.setObjectName("widgetMain")
|
||||
self.borderWidget.setStyleSheet("QWidget#widgetMain{border: 1px solid #000000;};")
|
||||
|
||||
self.contentGrid = QGridLayout()
|
||||
self.contentGrid.setContentsMargins(1, 1, 1, 1)
|
||||
|
||||
self.windowBtnGrid = self.__createWindowsButtonLayout__()
|
||||
self.enableMove = True
|
||||
self.validMoveWidget = None
|
||||
self.clickPos = None
|
||||
|
||||
self.grid = QGridLayout()
|
||||
self.grid.setSpacing(0)
|
||||
self.grid.setContentsMargins(0, 0, 0, 0)
|
||||
self.grid.addWidget(self.borderWidget, 0, 0)
|
||||
self.grid.addLayout(self.contentGrid, 0, 0)
|
||||
self.setLayout(self.grid)
|
||||
|
||||
def __showMaxWindows__(self):
|
||||
if self.windowState() == Qt.WindowMaximized:
|
||||
self.showNormal()
|
||||
else:
|
||||
self.showMaximized()
|
||||
|
||||
def __createWindowsButtonLayout__(self):
|
||||
self.closeBtn = PushButton('', ButtonStyle.CloseWindow)
|
||||
self.maxBtn = PushButton('', ButtonStyle.MaxWindow)
|
||||
self.minBtn = PushButton('', ButtonStyle.MinWindow)
|
||||
|
||||
self.closeBtn.clicked.connect(self.close)
|
||||
self.minBtn.clicked.connect(self.showMinimized)
|
||||
self.maxBtn.clicked.connect(self.__showMaxWindows__)
|
||||
|
||||
layout = QHBoxLayout()
|
||||
layout.setSpacing(0)
|
||||
layout.setContentsMargins(1, 1, 1, 1)
|
||||
layout.addWidget(self.minBtn)
|
||||
layout.addWidget(self.maxBtn)
|
||||
layout.addWidget(self.closeBtn)
|
||||
return layout
|
||||
|
||||
def __clickInValidMoveWidget__(self, x=-1, y=-1) -> bool:
|
||||
if self.validMoveWidget is None:
|
||||
return True
|
||||
if x == -1 and y == -1:
|
||||
x = self.clickPos.x()
|
||||
y = self.clickPos.y()
|
||||
|
||||
pos = self.validMoveWidget.pos()
|
||||
if x < pos.x() or x > pos.x() + self.validMoveWidget.width():
|
||||
return False
|
||||
if y < pos.y() or y > pos.y() + self.validMoveWidget.height():
|
||||
return False
|
||||
return True
|
||||
|
||||
def nativeEvent(self, eventType, message):
|
||||
retVal, result = super(FramelessWidget, self).nativeEvent(eventType, message)
|
||||
if eventType == "windows_generic_MSG":
|
||||
msg = ctypes.wintypes.MSG.from_address(message.__int__())
|
||||
if msg.message != win32con.WM_NCHITTEST:
|
||||
return retVal, result
|
||||
|
||||
# 获取鼠标移动经过时的坐标
|
||||
x = win32api.LOWORD(msg.lParam) - self.frameGeometry().x()
|
||||
y = win32api.HIWORD(msg.lParam) - self.frameGeometry().y()
|
||||
|
||||
w, h = self.width(), self.height()
|
||||
lx = x < self.BorderWidth
|
||||
rx = x > w - self.BorderWidth
|
||||
ty = y < self.BorderWidth
|
||||
by = y > h - self.BorderWidth
|
||||
if (lx and ty):# 左上角
|
||||
return True, win32con.HTTOPLEFT
|
||||
elif (rx and by):# 右下角
|
||||
return True, win32con.HTBOTTOMRIGHT
|
||||
elif (rx and ty):# 右上角
|
||||
return True, win32con.HTTOPRIGHT
|
||||
elif (lx and by):# 左下角
|
||||
return True, win32con.HTBOTTOMLEFT
|
||||
elif ty:# 上
|
||||
return True, win32con.HTTOP
|
||||
elif by:# 下
|
||||
return True, win32con.HTBOTTOM
|
||||
elif lx:# 左
|
||||
return True, win32con.HTLEFT
|
||||
elif rx:# 右
|
||||
return True, win32con.HTRIGHT
|
||||
return retVal, result
|
||||
|
||||
def mousePressEvent(self, e: QMouseEvent):
|
||||
if e.button() == Qt.LeftButton:
|
||||
self.clickPos = e.pos()
|
||||
|
||||
def mouseReleaseEvent(self, e: QMouseEvent):
|
||||
if e.button() == Qt.LeftButton:
|
||||
self.clickPos = QPoint(-1, -1)
|
||||
|
||||
def mouseMoveEvent(self, e: QMouseEvent):
|
||||
if not self.enableMove:
|
||||
return
|
||||
if Qt.LeftButton & e.buttons():
|
||||
if self.__clickInValidMoveWidget__() and self.clickPos:
|
||||
self.move(e.pos() + self.pos() - self.clickPos)
|
||||
|
||||
def mouseDoubleClickEvent(self, e: QMouseEvent):
|
||||
if self.maxBtn.isHidden():
|
||||
return
|
||||
if Qt.LeftButton & e.buttons():
|
||||
if self.__clickInValidMoveWidget__(e.x(), e.y()):
|
||||
self.__showMaxWindows__()
|
||||
|
||||
def getGrid(self):
|
||||
return self.contentGrid
|
||||
|
||||
def disableMove(self):
|
||||
self.enableMove = False
|
||||
|
||||
def setValidMoveWidget(self, widget):
|
||||
self.validMoveWidget = widget
|
||||
|
||||
def setWindowButton(self, showClose=True, showMin=True, showMax=True):
|
||||
if not showMax:
|
||||
self.maxBtn.hide()
|
||||
if not showMin:
|
||||
self.minBtn.hide()
|
||||
if not showClose:
|
||||
self.closeBtn.hide()
|
||||
self.grid.addLayout(self.windowBtnGrid, 0, 0, Qt.AlignTop | Qt.AlignRight)
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : label.py
|
||||
@Date : 2021/05/08
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
from PyQt5.QtWidgets import QLabel
|
||||
|
||||
from tidal_gui.style import LabelStyle
|
||||
|
||||
|
||||
class Label(QLabel):
|
||||
def __init__(self, text: str = "", style: LabelStyle = LabelStyle.Default):
|
||||
super(Label, self).__init__()
|
||||
self.setText(text)
|
||||
self.setObjectName(style.name + "Label")
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : layout.py
|
||||
@Date : 2021/8/13
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout
|
||||
|
||||
|
||||
def createHBoxLayout(widgets):
|
||||
layout = QHBoxLayout()
|
||||
for item in widgets:
|
||||
layout.addWidget(item)
|
||||
return layout
|
||||
|
||||
|
||||
def createVBoxLayout(widgets):
|
||||
layout = QVBoxLayout()
|
||||
for item in widgets:
|
||||
layout.addWidget(item)
|
||||
return layout
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : line.py
|
||||
@Date : 2021/8/17
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
from PyQt5.QtWidgets import QFrame
|
||||
|
||||
|
||||
class Line(QFrame):
|
||||
def __init__(self, shape: str = 'V'):
|
||||
super(Line, self).__init__()
|
||||
self.setFrameShape(QFrame.VLine if shape == 'V' else QFrame.HLine)
|
||||
self.setFrameShadow(QFrame.Sunken)
|
||||
|
||||
self.setObjectName('VLineQFrame' if shape == 'V' else 'HLineQFrame')
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : lineEdit.py
|
||||
@Date : 2021/05/08
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QLineEdit, QAction
|
||||
|
||||
|
||||
class LineEdit(QLineEdit):
|
||||
def __init__(self, placeholderText: str = "", iconUrl: str = ''):
|
||||
super(LineEdit, self).__init__()
|
||||
self.setPlaceholderText(placeholderText)
|
||||
|
||||
if iconUrl != '':
|
||||
action = QAction(self)
|
||||
action.setIcon(QIcon(iconUrl))
|
||||
self.addAction(action, QLineEdit.LeadingPosition)
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : listWidget.py
|
||||
@Date : 2021/8/17
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
from PyQt5.QtCore import QSize
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QListWidget, QListWidgetItem, QWidget
|
||||
|
||||
from tidal_gui.style import ListWidgetStyle
|
||||
|
||||
|
||||
class ListWidget(QListWidget):
|
||||
def __init__(self, style: ListWidgetStyle = ListWidgetStyle.Default):
|
||||
super(ListWidget, self).__init__()
|
||||
self.setObjectName(style.name + "ListWidget")
|
||||
|
||||
def addIConTextItem(self, iconUrl: str, text: str):
|
||||
self.addItem(QListWidgetItem(QIcon(iconUrl), text))
|
||||
|
||||
def addWidgetItem(self, widget: QWidget):
|
||||
item = QListWidgetItem(self)
|
||||
# item.setSizeHint(QSize(widget.width(), widget.height()))
|
||||
self.addItem(item)
|
||||
self.setItemWidget(item, widget)
|
||||
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : pushButton.py
|
||||
@Date : 2021/05/08
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QPushButton
|
||||
|
||||
from tidal_gui.style import ButtonStyle
|
||||
|
||||
|
||||
class PushButton(QPushButton):
|
||||
def __init__(self,
|
||||
text: str = '',
|
||||
style: ButtonStyle = ButtonStyle.Default,
|
||||
width=0,
|
||||
iconUrl=''):
|
||||
super(PushButton, self).__init__()
|
||||
self.setText(text)
|
||||
self.setObjectName(style.name + "PushButton")
|
||||
|
||||
if width > 0:
|
||||
self.setFixedWidth(width)
|
||||
|
||||
if iconUrl != '':
|
||||
self.setIcon(QIcon(iconUrl))
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : scrollWidget.py
|
||||
@Date : 2021/10/08
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
|
||||
from PyQt5.QtCore import QSize, Qt
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QListWidget, QListWidgetItem, QWidget, QScrollArea, QVBoxLayout, QPushButton
|
||||
|
||||
|
||||
class ScrollWidget(QScrollArea):
|
||||
def __init__(self):
|
||||
super(ScrollWidget, self).__init__()
|
||||
self.setWidgetResizable(True)
|
||||
|
||||
self._widget = QWidget()
|
||||
self._layout = QVBoxLayout(self._widget)
|
||||
self.setWidget(self._widget)
|
||||
|
||||
def addWidgetItem(self, widget: QWidget):
|
||||
self._layout.addWidget(widget, 0, Qt.AlignTop)
|
||||
pass
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : tableView.py
|
||||
@Date : 2021/9/10
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QStandardItemModel, QStandardItem
|
||||
from PyQt5.QtWidgets import QTableView, QTableWidgetItem, QAbstractItemView
|
||||
|
||||
|
||||
class TableView(QTableView):
|
||||
def __init__(self, columnNames: list, rowCount: int = 20):
|
||||
super(TableView, self).__init__()
|
||||
|
||||
self._model = QStandardItemModel()
|
||||
self._model.setColumnCount(len(columnNames))
|
||||
self._model.setRowCount(rowCount)
|
||||
|
||||
for index, name in enumerate(columnNames):
|
||||
self._model.setHeaderData(index, Qt.Horizontal, name)
|
||||
|
||||
self.setModel(self._model)
|
||||
# self.setHorizontalHeaderItem(index, QTableWidgetItem(name))
|
||||
# for index in range(0, rowCount):
|
||||
# self.setRowHeight(index, 50)
|
||||
|
||||
self.setShowGrid(False)
|
||||
self.verticalHeader().setVisible(False)
|
||||
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||
|
||||
self.horizontalHeader().setStretchLastSection(True)
|
||||
self.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
||||
|
||||
self.setFocusPolicy(Qt.NoFocus)
|
||||
|
||||
def addItem(self, rowIdx: int, colIdx: int, text: str):
|
||||
item = QStandardItem(text)
|
||||
item.setTextAlignment(Qt.AlignCenter)
|
||||
self._model.setItem(rowIdx, colIdx, item)
|
||||
#
|
||||
# def addWidgetItem(self, rowIdx: int, colIdx: int, widget):
|
||||
# self.setCellWidget(rowIdx, colIdx, widget)
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : tableWidget.py
|
||||
@Date : 2021/8/18
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
import aigpy
|
||||
import requests
|
||||
import threading
|
||||
from PyQt5.QtCore import Qt, QUrl
|
||||
from PyQt5.QtGui import QIcon, QPixmap
|
||||
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
|
||||
from PyQt5.QtWidgets import QTableWidget, QTableWidgetItem, QAbstractItemView, QLabel
|
||||
from tidal_gui.control.label import Label, LabelStyle
|
||||
|
||||
|
||||
class TableWidget(QTableWidget):
|
||||
def __init__(self, columnNames: list, rowCount: int = 20):
|
||||
super(TableWidget, self).__init__()
|
||||
self.setColumnCount(len(columnNames))
|
||||
self.setRowCount(rowCount)
|
||||
|
||||
self._lock = threading.Lock()
|
||||
self._netManager = QNetworkAccessManager()
|
||||
|
||||
self.columnAligns = []
|
||||
|
||||
for index, name in enumerate(columnNames):
|
||||
item = QTableWidgetItem(name)
|
||||
|
||||
align = Qt.AlignLeft | Qt.AlignVCenter
|
||||
if name == '#' or name == ' ':
|
||||
align = Qt.AlignCenter
|
||||
|
||||
self.columnAligns.append(align)
|
||||
item.setTextAlignment(align)
|
||||
self.setHorizontalHeaderItem(index, item)
|
||||
|
||||
for index in range(0, rowCount):
|
||||
self.setRowHeight(index, 50)
|
||||
|
||||
self.setShowGrid(False)
|
||||
self.verticalHeader().setVisible(False)
|
||||
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||
|
||||
self.horizontalHeader().setStretchLastSection(True)
|
||||
self.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
||||
|
||||
self.setFocusPolicy(Qt.NoFocus)
|
||||
|
||||
def changeRowCount(self, rows: int):
|
||||
if rows != self.rowCount():
|
||||
self.setRowCount(rows)
|
||||
for index in range(0, rows):
|
||||
self.setRowHeight(index, 50)
|
||||
|
||||
def addItem(self, rowIdx: int, colIdx: int, text):
|
||||
if isinstance(text, str):
|
||||
item = QTableWidgetItem(text)
|
||||
item.setTextAlignment(self.columnAligns[colIdx])
|
||||
self.setItem(rowIdx, colIdx, item)
|
||||
elif isinstance(text, QUrl):
|
||||
self.__addPicItem__(rowIdx, colIdx, text)
|
||||
|
||||
def __picDownload__(self, rowIdx, colIdx):
|
||||
reply = self.sender()
|
||||
data = reply.readAll()
|
||||
if data.size() <= 0:
|
||||
return
|
||||
|
||||
pic = QPixmap()
|
||||
pic.loadFromData(data)
|
||||
|
||||
self._lock.acquire()
|
||||
self.cellWidget(rowIdx, colIdx).setPixmap(pic.scaled(32, 32))
|
||||
self._lock.release()
|
||||
|
||||
reply.deleteLater()
|
||||
|
||||
def __addPicItem__(self, rowIdx: int, colIdx: int, url: QUrl):
|
||||
icon = Label('', LabelStyle.Icon)
|
||||
icon.setAlignment(Qt.AlignCenter)
|
||||
|
||||
self.setCellWidget(rowIdx, colIdx, icon)
|
||||
|
||||
reply = self._netManager.get(QNetworkRequest(url))
|
||||
reply.finished.connect(lambda: self.__picDownload__(rowIdx, colIdx))
|
||||
|
||||
def addWidgetItem(self, rowIdx: int, colIdx: int, widget):
|
||||
self.setCellWidget(rowIdx, colIdx, widget)
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : downloader.py
|
||||
@Date : 2021/09/15
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
from PyQt5.Qt import QThread
|
||||
from queue import Queue
|
||||
from tidal_gui.viewModel.taskModel import TaskModel
|
||||
import time
|
||||
|
||||
class DownloaderImp(QThread):
|
||||
def __init__(self):
|
||||
super(DownloaderImp, self).__init__()
|
||||
self._taskModel = None
|
||||
|
||||
def run(self):
|
||||
print('DownloadImp start...')
|
||||
while not self.isInterruptionRequested():
|
||||
if self._taskModel is not None:
|
||||
item = self._taskModel.getWaitDownloadItem()
|
||||
if item is not None:
|
||||
item.download()
|
||||
time.sleep(1)
|
||||
print('DownloadImp stop...')
|
||||
|
||||
def setTaskModel(self, model: TaskModel):
|
||||
self._taskModel = model
|
||||
|
||||
def stop(self):
|
||||
self.requestInterruption()
|
||||
self.wait()
|
||||
|
||||
downloadImp = DownloaderImp()
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1629184159878" class="icon" viewBox="0 0 1092 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="23123" xmlns:xlink="http://www.w3.org/1999/xlink" width="136.5" height="128"><defs><style type="text/css"></style></defs><path d="M538.624 1017.344L0.580267 405.162667 212.445867 0h652.356266L1076.565333 405.162667z" fill="#46F256" p-id="23124"></path><path d="M538.624 748.6464l-317.44-351.607467 67.618133-61.098666 249.787734 276.718933 249.821866-276.6848 67.618134 61.064533z" fill="#000000" p-id="23125"></path></svg>
|
||||
|
After Width: | Height: | Size: 673 B |
@@ -0,0 +1,4 @@
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg t="1629268466285" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="18338" width="128" height="128"><path d="M294.314667 0L242.432 119.424H165.418667v107.648h29.653333L225.152 418.133333H178.005333l62.293334 351.146667 40.021333-0.426667 40.192 255.146667h380.501333l2.645334-17.066667 37.546666-238.08 37.888 0.426667 62.293334-351.189333h-45.056l30.08-191.018667h32.256V119.466667h-81.834667L724.906667 0H294.314667z m22.528 34.346667h385.834666l32.896 75.946666H283.818667l33.024-75.946666z m-117.333334 119.338666H824.32v39.253334H199.509333v-39.253334z m19.328 298.581334h581.76l-50.176 282.453333-241.024-2.56-240.469333 2.56-50.090667-282.453333z" p-id="18339" fill="#ffffff"></path></svg>
|
||||
|
After Width: | Height: | Size: 743 B |
@@ -0,0 +1,5 @@
|
||||
<svg fill="#ffffff" t="1606188206696" class="icon" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="2750" width="16" height="16">
|
||||
<path d="M376.123077 836.923077L51.2 510.030769c-11.815385-11.815385-11.815385-31.507692 0-43.323077l43.323077-43.323077c11.815385-11.815385 31.507692-11.815385 43.323077 0L382.030769 669.538462c7.876923 7.876923 21.661538 7.876923 29.538462 0L890.092308 187.076923c11.815385-11.815385 31.507692-11.815385 43.323077 0l43.323077 43.323077c11.815385 11.815385 11.815385 31.507692 0 43.323077L419.446154 836.923077c-11.815385 13.784615-31.507692 13.784615-43.323077 0z"
|
||||
p-id="2751"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 672 B |
@@ -0,0 +1,6 @@
|
||||
<!-- <svg t="1605685211717" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1511"
|
||||
width="150" height="150">
|
||||
<path d="M316.16 366.506667L512 561.92l195.84-195.413333L768 426.666667l-256 256-256-256z" fill="#cbcbcb"
|
||||
p-id="1512"></path>
|
||||
</svg> -->
|
||||
<svg t="1629251448482" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1609" width="128" height="128"><path d="M179.758545 374.008242a31.030303 31.030303 0 0 1 43.876849 0L512 662.372848l288.364606-288.364606a31.030303 31.030303 0 0 1 43.876849 43.876849l-310.303031 310.30303a31.030303 31.030303 0 0 1-43.876848 0l-310.303031-310.30303a31.030303 31.030303 0 0 1 0-43.876849z" p-id="1610" fill="#cbcbcb"></path></svg>
|
||||
|
After Width: | Height: | Size: 769 B |
@@ -0,0 +1,6 @@
|
||||
<!-- <svg t="1605685211717" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1511"
|
||||
width="150" height="150">
|
||||
<path d="M316.16 366.506667L512 561.92l195.84-195.413333L768 426.666667l-256 256-256-256z" fill="#326cf3"
|
||||
p-id="1512"></path>
|
||||
</svg> -->
|
||||
<svg t="1629251448482" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1609" width="128" height="128"><path d="M179.758545 374.008242a31.030303 31.030303 0 0 1 43.876849 0L512 662.372848l288.364606-288.364606a31.030303 31.030303 0 0 1 43.876849 43.876849l-310.303031 310.30303a31.030303 31.030303 0 0 1-43.876848 0l-310.303031-310.30303a31.030303 31.030303 0 0 1 0-43.876849z" p-id="1610" fill="#326cf3"></path></svg>
|
||||
|
After Width: | Height: | Size: 769 B |
@@ -0,0 +1 @@
|
||||
<svg t="1629268481541" class="icon" viewBox="0 0 1025 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="19187" width="128" height="128"><path d="M512.032 831.904c-19.168 0-38.304-9.92-58.144-29.76-7.808-7.808-7.808-20.48 0-28.288s20.48-7.808 28.288 0C494.368 786.08 504.16 792 512.032 792s17.664-5.92 29.856-18.144c7.808-7.808 20.48-7.808 28.288 0s7.808 20.48 0 28.288c-19.84 19.84-38.976 29.76-58.144 29.76z m-512-306.4c0 49.888 4.256 95.136 12.8 135.68s20.544 75.744 36 105.536 35.008 55.904 58.656 78.336 49.344 40.928 77.056 55.456c27.744 14.528 59.456 26.304 95.2 35.264S351.84 951.04 388.8 954.624 466.496 960 510.944 960c44.448 0 85.248-1.792 122.4-5.376s73.6-9.856 109.344-18.848c35.744-8.96 67.552-20.736 95.456-35.264s53.792-33.024 77.6-55.456c23.808-22.432 43.456-48.544 58.944-78.336s27.552-64.96 36.256-105.536c8.704-40.576 13.056-85.792 13.056-135.68 0-89.376-27.744-166.368-83.2-230.976 3.2-8.608 5.952-18.496 8.256-29.6s4.544-26.816 6.656-47.104c2.144-20.288 1.344-43.712-2.4-70.272S942.56 93.888 932.256 66.24l-8-1.632c-5.344-1.088-14.048-0.704-26.144 1.088s-26.208 5.024-42.4 9.696-37.056 13.92-62.656 27.744-52.608 31.328-81.056 52.512c-48.352-14.72-115.008-30.112-200-30.112s-151.808 15.392-200.544 30.112c-28.448-21.184-55.552-38.592-81.344-52.224s-46.4-22.976-61.856-28c-15.456-5.024-29.792-8.256-42.944-9.696s-21.6-1.888-25.344-1.344c-3.744 0.544-6.496 1.152-8.256 1.888-10.304 27.648-17.408 54.752-21.344 81.312s-4.8 49.888-2.656 69.984c2.144 20.096 4.448 35.904 6.944 47.392S80 286.304 83.2 294.56C27.744 358.816 0 435.808 0 525.536z m136.544 113.888c0-58.016 21.344-110.624 64-157.856 12.8-14.4 27.648-25.312 44.544-32.704s36.096-11.616 57.6-12.608 42.048-0.8 61.6 0.608 43.744 3.296 72.544 5.696 53.696 3.616 74.656 3.616c20.96 0 45.856-1.184 74.656-3.616s52.992-4.288 72.544-5.696c19.552-1.408 40.096-1.6 61.6-0.608s40.8 5.216 57.856 12.608c17.056 7.392 32 18.304 44.8 32.704 42.656 47.232 64 99.84 64 157.856 0 34.016-3.552 64.32-10.656 90.944s-16.096 48.928-26.944 66.912c-10.848 18.016-26.048 33.216-45.6 45.632s-38.496 22.016-56.8 28.8c-18.304 6.784-41.952 12.096-70.944 15.904s-54.944 6.112-77.856 6.912c-22.944 0.8-51.808 1.216-86.656 1.216s-63.648-0.416-86.4-1.216c-22.752-0.8-48.608-3.104-77.6-6.912s-52.608-9.12-70.944-15.904c-18.304-6.816-37.248-16.416-56.8-28.8s-34.752-27.616-45.6-45.632c-10.848-18.016-19.84-40.32-26.944-66.912s-10.656-56.928-10.656-90.944zM256.032 608c0-53.024 28.64-96 64-96s64 42.976 64 96-28.64 96-64 96-64-42.976-64-96z m384 0c0-53.024 28.64-96 64-96s64 42.976 64 96-28.64 96-64 96-64-42.976-64-96z" p-id="19188"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1 @@
|
||||
<svg t="1629270772080" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1817" width="128" height="128"><path d="M658.059636 187.826424a31.030303 31.030303 0 0 1 0 43.876849l-288.364606 288.364606 288.364606 288.364606a31.030303 31.030303 0 0 1-43.876848 43.876848l-310.30303-310.30303a31.030303 31.030303 0 0 1 0-43.876848l310.30303-310.303031a31.030303 31.030303 0 0 1 43.876848 0z" p-id="1818"></path></svg>
|
||||
|
After Width: | Height: | Size: 452 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1629182866847" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="22424" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><defs><style type="text/css"></style></defs><path d="M938.666667 682.666667v170.666666a85.333333 85.333333 0 0 1-85.333334 85.333334H170.666667a85.333333 85.333333 0 0 1-85.333334-85.333334v-170.666666h85.333334v170.666666h682.666666v-170.666666h85.333334z m-384-145.664l140.501333-140.501334 60.330667 60.330667L512 700.330667l-243.498667-243.498667 60.330667-60.330667L469.333333 537.002667V85.333333h85.333334v451.669334z" p-id="22425" fill="#ffffff"></path></svg>
|
||||
|
After Width: | Height: | Size: 792 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1629183863957" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="22616" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><defs><style type="text/css"></style></defs><path d="M938.666667 682.666667v170.666666a85.333333 85.333333 0 0 1-85.333334 85.333334H170.666667a85.333333 85.333333 0 0 1-85.333334-85.333334v-170.666666h85.333334v170.666666h682.666666v-170.666666h85.333334z m-384-145.664l140.501333-140.501334 60.330667 60.330667L512 700.330667l-243.498667-243.498667 60.330667-60.330667L469.333333 537.002667V85.333333h85.333334v451.669334z" p-id="22617" fill="#e6e6e6"></path></svg>
|
||||
|
After Width: | Height: | Size: 792 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1629182847499" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="22040" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><defs><style type="text/css"></style></defs><path d="M512 981.333333C252.8 981.333333 42.666667 771.2 42.666667 512S252.8 42.666667 512 42.666667s469.333333 210.133333 469.333333 469.333333-210.133333 469.333333-469.333333 469.333333z m0-85.333333a384 384 0 1 0 0-768 384 384 0 0 0 0 768z m42.837333-298.752h42.624v85.333333h-170.666666v-85.333333h42.666666v-85.333333h-42.666666v-85.333334h128v170.666667z m-42.837333-213.333333a42.666667 42.666667 0 1 1 0-85.333334 42.666667 42.666667 0 0 1 0 85.333334z" p-id="22041" fill="#ffffff"></path></svg>
|
||||
|
After Width: | Height: | Size: 874 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1629181942259" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="21410" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><defs><style type="text/css"></style></defs><path d="M696.32 635.989333l229.845333 229.845334-60.330666 60.330666-229.845334-229.845333a341.333333 341.333333 0 1 1 60.330667-60.330667zM426.666667 682.666667a256 256 0 1 0 0-512 256 256 0 0 0 0 512z" p-id="21411" fill="#ffffff"></path></svg>
|
||||
|
After Width: | Height: | Size: 615 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1629182857650" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="22232" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><defs><style type="text/css"></style></defs><path d="M890.581333 797.013333l-94.592 94.592-121.088-33.706666-34.602666 14.250666L578.133333 981.333333h-133.76l-61.824-109.525333-34.56-14.506667-121.088 33.322667-94.549333-94.549333 33.706667-121.088-14.250667-34.602667L42.666667 578.133333v-133.76l109.568-61.824 14.506666-34.56-33.322666-121.088 94.506666-94.506666 121.088 33.749333 34.56-14.250667L445.696 42.666667h133.802667l61.824 109.568 34.56 14.506666 121.045333-33.322666 94.72 94.506666-33.792 121.130667 14.293333 34.56L981.333333 445.738667v133.802666l-109.525333 61.781334-14.506667 34.688 33.28 121.045333z m-123.392-126.805333l37.205334-88.832L896 529.664v-34.304l-91.605333-52.053333-36.650667-88.874667 28.245333-101.333333-24.277333-24.234667-101.674667 27.946667-88.746666-37.205334L529.621333 128h-34.304l-52.053333 91.605333-88.874667 36.650667-101.376-28.288-24.149333 24.149333 27.946667 101.674667-37.205334 88.746667L128 494.208v34.346667l91.52 52.138666 36.650667 88.874667-28.245334 101.376 24.192 24.192 101.674667-27.946667 88.746667 37.205334 51.626666 91.562666h34.346667l52.138667-91.52 88.874666-36.650666 101.376 28.245333 24.234667-24.234667-27.946667-101.589333zM512 682.666667a170.666667 170.666667 0 1 1 0-341.333334 170.666667 170.666667 0 0 1 0 341.333334z m0-85.333334a85.333333 85.333333 0 1 0 0-170.666666 85.333333 85.333333 0 0 0 0 170.666666z" p-id="22233" fill="#ffffff"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1 @@
|
||||
<svg t="1629268420515" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16731" width="128" height="128"><path d="M350.8 591.8c-7 38.4-34.8 217.4-43 268-0.6 3.6-2 5-6 5H152.6c-15.2 0-26.2-13.2-24.2-27.8L245.6 93.2c3-19.2 20.2-33.8 40-33.8 304.6 0 330.2-7.4 408 22.8 120.2 46.6 131.2 159 88 280.6-43 125.2-145 179-280.2 180.6-86.8 1.4-139-14-150.6 48.4zM842.2 304c-3.6-2.6-5-3.6-6 2.6-4 22.8-10.2 45-17.6 67.2-79.8 227.6-301 207.8-409 207.8-12.2 0-20.2 6.6-21.8 18.8-45.2 280.8-54.2 339.4-54.2 339.4-2 14.2 7 25.8 21.2 25.8h127c17.2 0 31.4-12.6 34.8-29.8 1.4-10.8-2.2 12.2 28.8-182.6 9.2-44 28.6-39.4 58.6-39.4 142 0 252.8-57.6 285.8-224.6 13-69.6 9.2-142.8-47.6-185.2z" p-id="16732" fill="#ffffff"></path></svg>
|
||||
|
After Width: | Height: | Size: 753 B |
@@ -0,0 +1 @@
|
||||
<svg t="1629270798317" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1968" width="128" height="128"><path d="M365.940364 187.826424a31.030303 31.030303 0 0 1 43.876848 0l310.30303 310.303031a31.030303 31.030303 0 0 1 0 43.876848l-310.30303 310.30303a31.030303 31.030303 0 0 1-43.876848-43.876848l288.364606-288.364606-288.364606-288.364606a31.030303 31.030303 0 0 1 0-43.876849z" p-id="1969"></path></svg>
|
||||
|
After Width: | Height: | Size: 451 B |
@@ -0,0 +1 @@
|
||||
<svg t="1629269306451" class="icon" viewBox="0 0 1057 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15526" width="128" height="128"><path d="M409.012356 811.965935c-108.676129 0.660645-213.058065-41.851871-289.924129-117.991225A401.242839 401.242839 0 0 1 0.006937 406.594065a401.242839 401.242839 0 0 1 119.015226-287.512775A408.344774 408.344774 0 0 1 409.012356 0.990968a408.344774 408.344774 0 0 1 289.924129 118.05729 401.275871 401.275871 0 0 1 119.08129 287.44671 401.275871 401.275871 0 0 1-119.048258 287.380645 408.344774 408.344774 0 0 1-289.891096 118.090322h-0.066065z m0-695.130838A290.617806 290.617806 0 0 0 201.63584 200.836129a285.563871 285.563871 0 0 0-84.69471 205.625806c0 162.221419 128.495484 289.626839 292.071226 289.626839 163.641806 0 292.13729-127.405419 292.13729-289.560774 0-162.221419-128.495484-289.725935-292.071225-289.725935h-0.066065zM957.942421 1005.799226l-173.980904-171.866839a55.130839 55.130839 0 0 1 0-80.235355c23.221677-22.990452 58.004645-22.990452 81.193291 0l173.947871 171.965936a55.130839 55.130839 0 0 1 0 80.235355 56.419097 56.419097 0 0 1-81.160258 0v-0.099097z" p-id="15527" fill="#ffffff"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg t="1629269306451" class="icon" viewBox="0 0 1057 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15526" width="128" height="128"><path d="M409.012356 811.965935c-108.676129 0.660645-213.058065-41.851871-289.924129-117.991225A401.242839 401.242839 0 0 1 0.006937 406.594065a401.242839 401.242839 0 0 1 119.015226-287.512775A408.344774 408.344774 0 0 1 409.012356 0.990968a408.344774 408.344774 0 0 1 289.924129 118.05729 401.275871 401.275871 0 0 1 119.08129 287.44671 401.275871 401.275871 0 0 1-119.048258 287.380645 408.344774 408.344774 0 0 1-289.891096 118.090322h-0.066065z m0-695.130838A290.617806 290.617806 0 0 0 201.63584 200.836129a285.563871 285.563871 0 0 0-84.69471 205.625806c0 162.221419 128.495484 289.626839 292.071226 289.626839 163.641806 0 292.13729-127.405419 292.13729-289.560774 0-162.221419-128.495484-289.725935-292.071225-289.725935h-0.066065zM957.942421 1005.799226l-173.980904-171.866839a55.130839 55.130839 0 0 1 0-80.235355c23.221677-22.990452 58.004645-22.990452 81.193291 0l173.947871 171.965936a55.130839 55.130839 0 0 1 0 80.235355 56.419097 56.419097 0 0 1-81.160258 0v-0.099097z" p-id="15527" fill="#0f0f0f"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48"><path fill-opacity=".01" fill="#fff" d="M0 0h48v48H0z" data-follow-fill="#fff"/><path stroke-linejoin="round" stroke-linecap="round" stroke-width="4" stroke="#333" d="m14 14 20 20M14 34l20-20" data-follow-stroke="#333"/></svg>
|
||||
|
After Width: | Height: | Size: 298 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48"><path fill-opacity=".01" fill="#fff" d="M0 0h48v48H0z" data-follow-fill="#fff"/><path stroke-linejoin="round" stroke-width="4" stroke="#333" d="M8 15h32l-3 29H11L8 15z" clip-rule="evenodd" data-follow-stroke="#333"/><path stroke-linecap="round" stroke-width="4" stroke="#333" d="M20.002 25.002v10M28.002 25v9.997" data-follow-stroke="#333"/><path stroke-linejoin="round" stroke-linecap="round" stroke-width="4" stroke="#333" d="M12 15 28.324 3 36 15" data-follow-stroke="#333"/></svg>
|
||||
|
After Width: | Height: | Size: 556 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48"><path stroke-linejoin="round" stroke-linecap="round" stroke-width="4" stroke="#333" d="M22 42H6V26M26 6h16v16" data-follow-stroke="#333"/></svg>
|
||||
|
After Width: | Height: | Size: 216 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48"><path stroke-linejoin="round" stroke-width="4" stroke="#333" d="M5 8a2 2 0 0 1 2-2h12l5 6h17a2 2 0 0 1 2 2v26a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V8z" data-follow-stroke="#333"/><path stroke-linejoin="round" stroke-linecap="round" stroke-width="4" stroke="#333" d="m30 28-6.007 6L18 28.013M24 20v14" data-follow-stroke="#333"/></svg>
|
||||
|
After Width: | Height: | Size: 398 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48"><path fill-opacity=".01" fill="#fff" d="M0 0h48v48H0z" data-follow-fill="#fff"/><path stroke-linejoin="round" stroke-linecap="round" stroke-width="4" stroke="#333" d="M36.728 36.728A17.943 17.943 0 0 1 24 42c-9.941 0-18-8.059-18-18S14.059 6 24 6c4.97 0 9.47 2.015 12.728 5.272C38.386 12.93 42 17 42 17" data-follow-stroke="#333"/><path stroke-linejoin="round" stroke-linecap="round" stroke-width="4" stroke="#333" d="M42 8v9h-9" data-follow-stroke="#333"/></svg>
|
||||
|
After Width: | Height: | Size: 534 B |
@@ -0,0 +1 @@
|
||||
<svg t="1629255942168" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8597" width="128" height="128"><path d="M512 0C229.248 0 0 229.248 0 512s229.248 512 512 512 512-229.248 512-512S794.752 0 512 0zM432.64 748.256 197.056 512.64l90.496-90.496 145.056 145.12 307.744-307.744 90.496 90.496L432.64 748.256z" p-id="8598" fill="#2DB84D"></path></svg>
|
||||
|
After Width: | Height: | Size: 391 B |
@@ -0,0 +1 @@
|
||||
<svg t="1629255998157" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8792" width="128" height="128"><path d="M512 0C229.248 0 0 229.248 0 512s229.248 512 512 512 512-229.248 512-512S794.752 0 512 0zM320 768 320 256l512.256 256L320 768z" p-id="8793" fill="#00BCD4"></path></svg>
|
||||
|
After Width: | Height: | Size: 323 B |
@@ -0,0 +1 @@
|
||||
<svg t="1629256110134" class="icon" viewBox="0 0 1185 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3025" width="128" height="128"><path d="M700.365715 60.074208l468.319709 783.01037a118.307323 118.307323 0 0 1-45.345458 164.539234 127.444603 127.444603 0 0 1-62.324434 16.297089H124.376114A122.739586 122.739586 0 0 1 0 903.567918a117.557248 117.557248 0 0 1 16.706221-60.346963L485.02593 60.210586A126.694528 126.694528 0 0 1 655.020257 16.365278a122.739586 122.739586 0 0 1 45.345458 43.845308zM592.764011 916.046443a80.803561 80.803561 0 1 0-80.80356-80.803561 80.803561 80.803561 0 0 0 80.735372 80.803561z m0-673.567572a95.464122 95.464122 0 0 0-94.373104 106.988006l34.09433 270.640786a61.369793 61.369793 0 0 0 121.103058 0l34.094329-270.640786a95.464122 95.464122 0 0 0-94.373103-106.919818z" p-id="3026" fill="#DB3340"></path></svg>
|
||||
|
After Width: | Height: | Size: 857 B |
@@ -0,0 +1 @@
|
||||
<svg t="1629251564375" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1864" width="128" height="128"><path d="M490.061576 311.947636a31.030303 31.030303 0 0 1 43.876848 0l310.303031 310.303031a31.030303 31.030303 0 0 1-43.876849 43.876848L512 377.762909l-288.364606 288.364606a31.030303 31.030303 0 0 1-43.876849-43.876848l310.303031-310.303031z" p-id="1865" fill="#326cf3"></path></svg>
|
||||
|
After Width: | Height: | Size: 432 B |
@@ -0,0 +1,5 @@
|
||||
<svg fill="#cbcbcb" t="1605778065489" class="icon" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="6682" width="16" height="16">
|
||||
<path d="M622.276923 508.061538l257.969231-257.96923c11.815385-11.815385 11.815385-29.538462 0-41.353846l-41.353846-41.353847c-11.815385-11.815385-29.538462-11.815385-41.353846 0L539.569231 425.353846c-7.876923 7.876923-19.692308 7.876923-27.569231 0L254.030769 165.415385c-11.815385-11.815385-29.538462-11.815385-41.353846 0l-41.353846 41.353846c-11.815385 11.815385-11.815385 29.538462 0 41.353846l257.969231 257.969231c7.876923 7.876923 7.876923 19.692308 0 27.56923L169.353846 793.6c-11.815385 11.815385-11.815385 29.538462 0 41.353846l41.353846 41.353846c11.815385 11.815385 29.538462 11.815385 41.353846 0L512 618.338462c7.876923-7.876923 19.692308-7.876923 27.569231 0l257.969231 257.96923c11.815385 11.815385 29.538462 11.815385 41.353846 0l41.353846-41.353846c11.815385-11.815385 11.815385-29.538462 0-41.353846L622.276923 535.630769c-5.907692-7.876923-5.907692-19.692308 0-27.569231z"
|
||||
p-id="6683"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg fill="#ffffff" t="1605778065489" class="icon" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="6682" width="16" height="16">
|
||||
<path d="M622.276923 508.061538l257.969231-257.96923c11.815385-11.815385 11.815385-29.538462 0-41.353846l-41.353846-41.353847c-11.815385-11.815385-29.538462-11.815385-41.353846 0L539.569231 425.353846c-7.876923 7.876923-19.692308 7.876923-27.569231 0L254.030769 165.415385c-11.815385-11.815385-29.538462-11.815385-41.353846 0l-41.353846 41.353846c-11.815385 11.815385-11.815385 29.538462 0 41.353846l257.969231 257.969231c7.876923 7.876923 7.876923 19.692308 0 27.56923L169.353846 793.6c-11.815385 11.815385-11.815385 29.538462 0 41.353846l41.353846 41.353846c11.815385 11.815385 29.538462 11.815385 41.353846 0L512 618.338462c7.876923-7.876923 19.692308-7.876923 27.569231 0l257.969231 257.96923c11.815385 11.815385 29.538462 11.815385 41.353846 0l41.353846-41.353846c11.815385-11.815385 11.815385-29.538462 0-41.353846L622.276923 535.630769c-5.907692-7.876923-5.907692-19.692308 0-27.569231z"
|
||||
p-id="6683"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg fill="#cbcbcb" t="1605778299751" class="icon" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="12048" width="16" height="16">
|
||||
<path d="M170.666667 85.333333h682.666666a85.333333 85.333333 0 0 1 85.333334 85.333334v682.666666a85.333333 85.333333 0 0 1-85.333334 85.333334H170.666667a85.333333 85.333333 0 0 1-85.333334-85.333334V170.666667a85.333333 85.333333 0 0 1 85.333334-85.333334z m0 85.333334v682.666666h682.666666V170.666667H170.666667z"
|
||||
p-id="12049"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 526 B |
@@ -0,0 +1,4 @@
|
||||
<svg fill="#cbcbcb" t="1605777927585" class="icon" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="5690" width="16" height="16">
|
||||
<path d="M938.666667 469.333333v85.333334H85.333333v-85.333334z" p-id="5691"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 260 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1629184159878" class="icon" viewBox="0 0 1092 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="23123" xmlns:xlink="http://www.w3.org/1999/xlink" width="136.5" height="128"><defs><style type="text/css"></style></defs><path d="M538.624 1017.344L0.580267 405.162667 212.445867 0h652.356266L1076.565333 405.162667z" fill="#46F256" p-id="23124"></path><path d="M538.624 748.6464l-317.44-351.607467 67.618133-61.098666 249.787734 276.718933 249.821866-276.6848 67.618134 61.064533z" fill="#000000" p-id="23125"></path></svg>
|
||||
|
After Width: | Height: | Size: 673 B |
@@ -0,0 +1,4 @@
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg t="1629268466285" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="18338" width="128" height="128"><path d="M294.314667 0L242.432 119.424H165.418667v107.648h29.653333L225.152 418.133333H178.005333l62.293334 351.146667 40.021333-0.426667 40.192 255.146667h380.501333l2.645334-17.066667 37.546666-238.08 37.888 0.426667 62.293334-351.189333h-45.056l30.08-191.018667h32.256V119.466667h-81.834667L724.906667 0H294.314667z m22.528 34.346667h385.834666l32.896 75.946666H283.818667l33.024-75.946666z m-117.333334 119.338666H824.32v39.253334H199.509333v-39.253334z m19.328 298.581334h581.76l-50.176 282.453333-241.024-2.56-240.469333 2.56-50.090667-282.453333z" p-id="18339" fill="#ffffff"></path></svg>
|
||||
|
After Width: | Height: | Size: 743 B |
@@ -0,0 +1,5 @@
|
||||
<svg fill="#ffffff" t="1606188206696" class="icon" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="2750" width="16" height="16">
|
||||
<path d="M376.123077 836.923077L51.2 510.030769c-11.815385-11.815385-11.815385-31.507692 0-43.323077l43.323077-43.323077c11.815385-11.815385 31.507692-11.815385 43.323077 0L382.030769 669.538462c7.876923 7.876923 21.661538 7.876923 29.538462 0L890.092308 187.076923c11.815385-11.815385 31.507692-11.815385 43.323077 0l43.323077 43.323077c11.815385 11.815385 11.815385 31.507692 0 43.323077L419.446154 836.923077c-11.815385 13.784615-31.507692 13.784615-43.323077 0z"
|
||||
p-id="2751"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 672 B |
@@ -0,0 +1,6 @@
|
||||
<!-- <svg t="1605685211717" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1511"
|
||||
width="150" height="150">
|
||||
<path d="M316.16 366.506667L512 561.92l195.84-195.413333L768 426.666667l-256 256-256-256z" fill="#cbcbcb"
|
||||
p-id="1512"></path>
|
||||
</svg> -->
|
||||
<svg t="1629251448482" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1609" width="128" height="128"><path d="M179.758545 374.008242a31.030303 31.030303 0 0 1 43.876849 0L512 662.372848l288.364606-288.364606a31.030303 31.030303 0 0 1 43.876849 43.876849l-310.303031 310.30303a31.030303 31.030303 0 0 1-43.876848 0l-310.303031-310.30303a31.030303 31.030303 0 0 1 0-43.876849z" p-id="1610" fill="#cbcbcb"></path></svg>
|
||||
|
After Width: | Height: | Size: 769 B |
@@ -0,0 +1,6 @@
|
||||
<!-- <svg t="1605685211717" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1511"
|
||||
width="150" height="150">
|
||||
<path d="M316.16 366.506667L512 561.92l195.84-195.413333L768 426.666667l-256 256-256-256z" fill="#326cf3"
|
||||
p-id="1512"></path>
|
||||
</svg> -->
|
||||
<svg t="1629251448482" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1609" width="128" height="128"><path d="M179.758545 374.008242a31.030303 31.030303 0 0 1 43.876849 0L512 662.372848l288.364606-288.364606a31.030303 31.030303 0 0 1 43.876849 43.876849l-310.303031 310.30303a31.030303 31.030303 0 0 1-43.876848 0l-310.303031-310.30303a31.030303 31.030303 0 0 1 0-43.876849z" p-id="1610" fill="#326cf3"></path></svg>
|
||||
|
After Width: | Height: | Size: 769 B |
@@ -0,0 +1 @@
|
||||
<svg t="1629268481541" class="icon" viewBox="0 0 1025 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="19187" width="128" height="128"><path d="M512.032 831.904c-19.168 0-38.304-9.92-58.144-29.76-7.808-7.808-7.808-20.48 0-28.288s20.48-7.808 28.288 0C494.368 786.08 504.16 792 512.032 792s17.664-5.92 29.856-18.144c7.808-7.808 20.48-7.808 28.288 0s7.808 20.48 0 28.288c-19.84 19.84-38.976 29.76-58.144 29.76z m-512-306.4c0 49.888 4.256 95.136 12.8 135.68s20.544 75.744 36 105.536 35.008 55.904 58.656 78.336 49.344 40.928 77.056 55.456c27.744 14.528 59.456 26.304 95.2 35.264S351.84 951.04 388.8 954.624 466.496 960 510.944 960c44.448 0 85.248-1.792 122.4-5.376s73.6-9.856 109.344-18.848c35.744-8.96 67.552-20.736 95.456-35.264s53.792-33.024 77.6-55.456c23.808-22.432 43.456-48.544 58.944-78.336s27.552-64.96 36.256-105.536c8.704-40.576 13.056-85.792 13.056-135.68 0-89.376-27.744-166.368-83.2-230.976 3.2-8.608 5.952-18.496 8.256-29.6s4.544-26.816 6.656-47.104c2.144-20.288 1.344-43.712-2.4-70.272S942.56 93.888 932.256 66.24l-8-1.632c-5.344-1.088-14.048-0.704-26.144 1.088s-26.208 5.024-42.4 9.696-37.056 13.92-62.656 27.744-52.608 31.328-81.056 52.512c-48.352-14.72-115.008-30.112-200-30.112s-151.808 15.392-200.544 30.112c-28.448-21.184-55.552-38.592-81.344-52.224s-46.4-22.976-61.856-28c-15.456-5.024-29.792-8.256-42.944-9.696s-21.6-1.888-25.344-1.344c-3.744 0.544-6.496 1.152-8.256 1.888-10.304 27.648-17.408 54.752-21.344 81.312s-4.8 49.888-2.656 69.984c2.144 20.096 4.448 35.904 6.944 47.392S80 286.304 83.2 294.56C27.744 358.816 0 435.808 0 525.536z m136.544 113.888c0-58.016 21.344-110.624 64-157.856 12.8-14.4 27.648-25.312 44.544-32.704s36.096-11.616 57.6-12.608 42.048-0.8 61.6 0.608 43.744 3.296 72.544 5.696 53.696 3.616 74.656 3.616c20.96 0 45.856-1.184 74.656-3.616s52.992-4.288 72.544-5.696c19.552-1.408 40.096-1.6 61.6-0.608s40.8 5.216 57.856 12.608c17.056 7.392 32 18.304 44.8 32.704 42.656 47.232 64 99.84 64 157.856 0 34.016-3.552 64.32-10.656 90.944s-16.096 48.928-26.944 66.912c-10.848 18.016-26.048 33.216-45.6 45.632s-38.496 22.016-56.8 28.8c-18.304 6.784-41.952 12.096-70.944 15.904s-54.944 6.112-77.856 6.912c-22.944 0.8-51.808 1.216-86.656 1.216s-63.648-0.416-86.4-1.216c-22.752-0.8-48.608-3.104-77.6-6.912s-52.608-9.12-70.944-15.904c-18.304-6.816-37.248-16.416-56.8-28.8s-34.752-27.616-45.6-45.632c-10.848-18.016-19.84-40.32-26.944-66.912s-10.656-56.928-10.656-90.944zM256.032 608c0-53.024 28.64-96 64-96s64 42.976 64 96-28.64 96-64 96-64-42.976-64-96z m384 0c0-53.024 28.64-96 64-96s64 42.976 64 96-28.64 96-64 96-64-42.976-64-96z" p-id="19188"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1 @@
|
||||
<svg t="1629270772080" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1817" width="128" height="128"><path d="M658.059636 187.826424a31.030303 31.030303 0 0 1 0 43.876849l-288.364606 288.364606 288.364606 288.364606a31.030303 31.030303 0 0 1-43.876848 43.876848l-310.30303-310.30303a31.030303 31.030303 0 0 1 0-43.876848l310.30303-310.303031a31.030303 31.030303 0 0 1 43.876848 0z" p-id="1818"></path></svg>
|
||||
|
After Width: | Height: | Size: 452 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1629182866847" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="22424" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><defs><style type="text/css"></style></defs><path d="M938.666667 682.666667v170.666666a85.333333 85.333333 0 0 1-85.333334 85.333334H170.666667a85.333333 85.333333 0 0 1-85.333334-85.333334v-170.666666h85.333334v170.666666h682.666666v-170.666666h85.333334z m-384-145.664l140.501333-140.501334 60.330667 60.330667L512 700.330667l-243.498667-243.498667 60.330667-60.330667L469.333333 537.002667V85.333333h85.333334v451.669334z" p-id="22425" fill="#ffffff"></path></svg>
|
||||
|
After Width: | Height: | Size: 792 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1629183863957" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="22616" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><defs><style type="text/css"></style></defs><path d="M938.666667 682.666667v170.666666a85.333333 85.333333 0 0 1-85.333334 85.333334H170.666667a85.333333 85.333333 0 0 1-85.333334-85.333334v-170.666666h85.333334v170.666666h682.666666v-170.666666h85.333334z m-384-145.664l140.501333-140.501334 60.330667 60.330667L512 700.330667l-243.498667-243.498667 60.330667-60.330667L469.333333 537.002667V85.333333h85.333334v451.669334z" p-id="22617" fill="#e6e6e6"></path></svg>
|
||||
|
After Width: | Height: | Size: 792 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1629182847499" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="22040" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><defs><style type="text/css"></style></defs><path d="M512 981.333333C252.8 981.333333 42.666667 771.2 42.666667 512S252.8 42.666667 512 42.666667s469.333333 210.133333 469.333333 469.333333-210.133333 469.333333-469.333333 469.333333z m0-85.333333a384 384 0 1 0 0-768 384 384 0 0 0 0 768z m42.837333-298.752h42.624v85.333333h-170.666666v-85.333333h42.666666v-85.333333h-42.666666v-85.333334h128v170.666667z m-42.837333-213.333333a42.666667 42.666667 0 1 1 0-85.333334 42.666667 42.666667 0 0 1 0 85.333334z" p-id="22041" fill="#ffffff"></path></svg>
|
||||
|
After Width: | Height: | Size: 874 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1629181942259" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="21410" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><defs><style type="text/css"></style></defs><path d="M696.32 635.989333l229.845333 229.845334-60.330666 60.330666-229.845334-229.845333a341.333333 341.333333 0 1 1 60.330667-60.330667zM426.666667 682.666667a256 256 0 1 0 0-512 256 256 0 0 0 0 512z" p-id="21411" fill="#ffffff"></path></svg>
|
||||
|
After Width: | Height: | Size: 615 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1629182857650" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="22232" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><defs><style type="text/css"></style></defs><path d="M890.581333 797.013333l-94.592 94.592-121.088-33.706666-34.602666 14.250666L578.133333 981.333333h-133.76l-61.824-109.525333-34.56-14.506667-121.088 33.322667-94.549333-94.549333 33.706667-121.088-14.250667-34.602667L42.666667 578.133333v-133.76l109.568-61.824 14.506666-34.56-33.322666-121.088 94.506666-94.506666 121.088 33.749333 34.56-14.250667L445.696 42.666667h133.802667l61.824 109.568 34.56 14.506666 121.045333-33.322666 94.72 94.506666-33.792 121.130667 14.293333 34.56L981.333333 445.738667v133.802666l-109.525333 61.781334-14.506667 34.688 33.28 121.045333z m-123.392-126.805333l37.205334-88.832L896 529.664v-34.304l-91.605333-52.053333-36.650667-88.874667 28.245333-101.333333-24.277333-24.234667-101.674667 27.946667-88.746666-37.205334L529.621333 128h-34.304l-52.053333 91.605333-88.874667 36.650667-101.376-28.288-24.149333 24.149333 27.946667 101.674667-37.205334 88.746667L128 494.208v34.346667l91.52 52.138666 36.650667 88.874667-28.245334 101.376 24.192 24.192 101.674667-27.946667 88.746667 37.205334 51.626666 91.562666h34.346667l52.138667-91.52 88.874666-36.650666 101.376 28.245333 24.234667-24.234667-27.946667-101.589333zM512 682.666667a170.666667 170.666667 0 1 1 0-341.333334 170.666667 170.666667 0 0 1 0 341.333334z m0-85.333334a85.333333 85.333333 0 1 0 0-170.666666 85.333333 85.333333 0 0 0 0 170.666666z" p-id="22233" fill="#ffffff"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1 @@
|
||||
<svg t="1629268420515" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16731" width="128" height="128"><path d="M350.8 591.8c-7 38.4-34.8 217.4-43 268-0.6 3.6-2 5-6 5H152.6c-15.2 0-26.2-13.2-24.2-27.8L245.6 93.2c3-19.2 20.2-33.8 40-33.8 304.6 0 330.2-7.4 408 22.8 120.2 46.6 131.2 159 88 280.6-43 125.2-145 179-280.2 180.6-86.8 1.4-139-14-150.6 48.4zM842.2 304c-3.6-2.6-5-3.6-6 2.6-4 22.8-10.2 45-17.6 67.2-79.8 227.6-301 207.8-409 207.8-12.2 0-20.2 6.6-21.8 18.8-45.2 280.8-54.2 339.4-54.2 339.4-2 14.2 7 25.8 21.2 25.8h127c17.2 0 31.4-12.6 34.8-29.8 1.4-10.8-2.2 12.2 28.8-182.6 9.2-44 28.6-39.4 58.6-39.4 142 0 252.8-57.6 285.8-224.6 13-69.6 9.2-142.8-47.6-185.2z" p-id="16732" fill="#ffffff"></path></svg>
|
||||
|
After Width: | Height: | Size: 753 B |
@@ -0,0 +1 @@
|
||||
<svg t="1629270798317" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1968" width="128" height="128"><path d="M365.940364 187.826424a31.030303 31.030303 0 0 1 43.876848 0l310.30303 310.303031a31.030303 31.030303 0 0 1 0 43.876848l-310.30303 310.30303a31.030303 31.030303 0 0 1-43.876848-43.876848l288.364606-288.364606-288.364606-288.364606a31.030303 31.030303 0 0 1 0-43.876849z" p-id="1969"></path></svg>
|
||||
|
After Width: | Height: | Size: 451 B |
@@ -0,0 +1 @@
|
||||
<svg t="1629269306451" class="icon" viewBox="0 0 1057 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15526" width="128" height="128"><path d="M409.012356 811.965935c-108.676129 0.660645-213.058065-41.851871-289.924129-117.991225A401.242839 401.242839 0 0 1 0.006937 406.594065a401.242839 401.242839 0 0 1 119.015226-287.512775A408.344774 408.344774 0 0 1 409.012356 0.990968a408.344774 408.344774 0 0 1 289.924129 118.05729 401.275871 401.275871 0 0 1 119.08129 287.44671 401.275871 401.275871 0 0 1-119.048258 287.380645 408.344774 408.344774 0 0 1-289.891096 118.090322h-0.066065z m0-695.130838A290.617806 290.617806 0 0 0 201.63584 200.836129a285.563871 285.563871 0 0 0-84.69471 205.625806c0 162.221419 128.495484 289.626839 292.071226 289.626839 163.641806 0 292.13729-127.405419 292.13729-289.560774 0-162.221419-128.495484-289.725935-292.071225-289.725935h-0.066065zM957.942421 1005.799226l-173.980904-171.866839a55.130839 55.130839 0 0 1 0-80.235355c23.221677-22.990452 58.004645-22.990452 81.193291 0l173.947871 171.965936a55.130839 55.130839 0 0 1 0 80.235355 56.419097 56.419097 0 0 1-81.160258 0v-0.099097z" p-id="15527" fill="#ffffff"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg t="1629269306451" class="icon" viewBox="0 0 1057 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15526" width="128" height="128"><path d="M409.012356 811.965935c-108.676129 0.660645-213.058065-41.851871-289.924129-117.991225A401.242839 401.242839 0 0 1 0.006937 406.594065a401.242839 401.242839 0 0 1 119.015226-287.512775A408.344774 408.344774 0 0 1 409.012356 0.990968a408.344774 408.344774 0 0 1 289.924129 118.05729 401.275871 401.275871 0 0 1 119.08129 287.44671 401.275871 401.275871 0 0 1-119.048258 287.380645 408.344774 408.344774 0 0 1-289.891096 118.090322h-0.066065z m0-695.130838A290.617806 290.617806 0 0 0 201.63584 200.836129a285.563871 285.563871 0 0 0-84.69471 205.625806c0 162.221419 128.495484 289.626839 292.071226 289.626839 163.641806 0 292.13729-127.405419 292.13729-289.560774 0-162.221419-128.495484-289.725935-292.071225-289.725935h-0.066065zM957.942421 1005.799226l-173.980904-171.866839a55.130839 55.130839 0 0 1 0-80.235355c23.221677-22.990452 58.004645-22.990452 81.193291 0l173.947871 171.965936a55.130839 55.130839 0 0 1 0 80.235355 56.419097 56.419097 0 0 1-81.160258 0v-0.099097z" p-id="15527" fill="#0f0f0f"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48"><path fill-opacity=".01" fill="#fff" d="M0 0h48v48H0z" data-follow-fill="#fff"/><path stroke-linejoin="round" stroke-linecap="round" stroke-width="4" stroke="#333" d="m14 14 20 20M14 34l20-20" data-follow-stroke="#333"/></svg>
|
||||
|
After Width: | Height: | Size: 298 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48"><path fill-opacity=".01" fill="#fff" d="M0 0h48v48H0z" data-follow-fill="#fff"/><path stroke-linejoin="round" stroke-width="4" stroke="#333" d="M8 15h32l-3 29H11L8 15z" clip-rule="evenodd" data-follow-stroke="#333"/><path stroke-linecap="round" stroke-width="4" stroke="#333" d="M20.002 25.002v10M28.002 25v9.997" data-follow-stroke="#333"/><path stroke-linejoin="round" stroke-linecap="round" stroke-width="4" stroke="#333" d="M12 15 28.324 3 36 15" data-follow-stroke="#333"/></svg>
|
||||
|
After Width: | Height: | Size: 556 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48"><path stroke-linejoin="round" stroke-linecap="round" stroke-width="4" stroke="#333" d="M22 42H6V26M26 6h16v16" data-follow-stroke="#333"/></svg>
|
||||
|
After Width: | Height: | Size: 216 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48"><path stroke-linejoin="round" stroke-width="4" stroke="#333" d="M5 8a2 2 0 0 1 2-2h12l5 6h17a2 2 0 0 1 2 2v26a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V8z" data-follow-stroke="#333"/><path stroke-linejoin="round" stroke-linecap="round" stroke-width="4" stroke="#333" d="m30 28-6.007 6L18 28.013M24 20v14" data-follow-stroke="#333"/></svg>
|
||||
|
After Width: | Height: | Size: 398 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48"><path fill-opacity=".01" fill="#fff" d="M0 0h48v48H0z" data-follow-fill="#fff"/><path stroke-linejoin="round" stroke-linecap="round" stroke-width="4" stroke="#333" d="M36.728 36.728A17.943 17.943 0 0 1 24 42c-9.941 0-18-8.059-18-18S14.059 6 24 6c4.97 0 9.47 2.015 12.728 5.272C38.386 12.93 42 17 42 17" data-follow-stroke="#333"/><path stroke-linejoin="round" stroke-linecap="round" stroke-width="4" stroke="#333" d="M42 8v9h-9" data-follow-stroke="#333"/></svg>
|
||||
|
After Width: | Height: | Size: 534 B |
@@ -0,0 +1 @@
|
||||
<svg t="1629255942168" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8597" width="128" height="128"><path d="M512 0C229.248 0 0 229.248 0 512s229.248 512 512 512 512-229.248 512-512S794.752 0 512 0zM432.64 748.256 197.056 512.64l90.496-90.496 145.056 145.12 307.744-307.744 90.496 90.496L432.64 748.256z" p-id="8598" fill="#2DB84D"></path></svg>
|
||||
|
After Width: | Height: | Size: 391 B |
@@ -0,0 +1 @@
|
||||
<svg t="1629255998157" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8792" width="128" height="128"><path d="M512 0C229.248 0 0 229.248 0 512s229.248 512 512 512 512-229.248 512-512S794.752 0 512 0zM320 768 320 256l512.256 256L320 768z" p-id="8793" fill="#00BCD4"></path></svg>
|
||||
|
After Width: | Height: | Size: 323 B |
@@ -0,0 +1 @@
|
||||
<svg t="1629256110134" class="icon" viewBox="0 0 1185 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3025" width="128" height="128"><path d="M700.365715 60.074208l468.319709 783.01037a118.307323 118.307323 0 0 1-45.345458 164.539234 127.444603 127.444603 0 0 1-62.324434 16.297089H124.376114A122.739586 122.739586 0 0 1 0 903.567918a117.557248 117.557248 0 0 1 16.706221-60.346963L485.02593 60.210586A126.694528 126.694528 0 0 1 655.020257 16.365278a122.739586 122.739586 0 0 1 45.345458 43.845308zM592.764011 916.046443a80.803561 80.803561 0 1 0-80.80356-80.803561 80.803561 80.803561 0 0 0 80.735372 80.803561z m0-673.567572a95.464122 95.464122 0 0 0-94.373104 106.988006l34.09433 270.640786a61.369793 61.369793 0 0 0 121.103058 0l34.094329-270.640786a95.464122 95.464122 0 0 0-94.373103-106.919818z" p-id="3026" fill="#DB3340"></path></svg>
|
||||
|
After Width: | Height: | Size: 857 B |
@@ -0,0 +1 @@
|
||||
<svg t="1629251564375" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1864" width="128" height="128"><path d="M490.061576 311.947636a31.030303 31.030303 0 0 1 43.876848 0l310.303031 310.303031a31.030303 31.030303 0 0 1-43.876849 43.876848L512 377.762909l-288.364606 288.364606a31.030303 31.030303 0 0 1-43.876849-43.876848l310.303031-310.303031z" p-id="1865" fill="#326cf3"></path></svg>
|
||||
|
After Width: | Height: | Size: 432 B |
@@ -0,0 +1,5 @@
|
||||
<svg fill="#cbcbcb" t="1605778065489" class="icon" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="6682" width="16" height="16">
|
||||
<path d="M622.276923 508.061538l257.969231-257.96923c11.815385-11.815385 11.815385-29.538462 0-41.353846l-41.353846-41.353847c-11.815385-11.815385-29.538462-11.815385-41.353846 0L539.569231 425.353846c-7.876923 7.876923-19.692308 7.876923-27.569231 0L254.030769 165.415385c-11.815385-11.815385-29.538462-11.815385-41.353846 0l-41.353846 41.353846c-11.815385 11.815385-11.815385 29.538462 0 41.353846l257.969231 257.969231c7.876923 7.876923 7.876923 19.692308 0 27.56923L169.353846 793.6c-11.815385 11.815385-11.815385 29.538462 0 41.353846l41.353846 41.353846c11.815385 11.815385 29.538462 11.815385 41.353846 0L512 618.338462c7.876923-7.876923 19.692308-7.876923 27.569231 0l257.969231 257.96923c11.815385 11.815385 29.538462 11.815385 41.353846 0l41.353846-41.353846c11.815385-11.815385 11.815385-29.538462 0-41.353846L622.276923 535.630769c-5.907692-7.876923-5.907692-19.692308 0-27.569231z"
|
||||
p-id="6683"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg fill="#ffffff" t="1605778065489" class="icon" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="6682" width="16" height="16">
|
||||
<path d="M622.276923 508.061538l257.969231-257.96923c11.815385-11.815385 11.815385-29.538462 0-41.353846l-41.353846-41.353847c-11.815385-11.815385-29.538462-11.815385-41.353846 0L539.569231 425.353846c-7.876923 7.876923-19.692308 7.876923-27.569231 0L254.030769 165.415385c-11.815385-11.815385-29.538462-11.815385-41.353846 0l-41.353846 41.353846c-11.815385 11.815385-11.815385 29.538462 0 41.353846l257.969231 257.969231c7.876923 7.876923 7.876923 19.692308 0 27.56923L169.353846 793.6c-11.815385 11.815385-11.815385 29.538462 0 41.353846l41.353846 41.353846c11.815385 11.815385 29.538462 11.815385 41.353846 0L512 618.338462c7.876923-7.876923 19.692308-7.876923 27.569231 0l257.969231 257.96923c11.815385 11.815385 29.538462 11.815385 41.353846 0l41.353846-41.353846c11.815385-11.815385 11.815385-29.538462 0-41.353846L622.276923 535.630769c-5.907692-7.876923-5.907692-19.692308 0-27.569231z"
|
||||
p-id="6683"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg fill="#cbcbcb" t="1605778299751" class="icon" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="12048" width="16" height="16">
|
||||
<path d="M170.666667 85.333333h682.666666a85.333333 85.333333 0 0 1 85.333334 85.333334v682.666666a85.333333 85.333333 0 0 1-85.333334 85.333334H170.666667a85.333333 85.333333 0 0 1-85.333334-85.333334V170.666667a85.333333 85.333333 0 0 1 85.333334-85.333334z m0 85.333334v682.666666h682.666666V170.666667H170.666667z"
|
||||
p-id="12049"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 526 B |
@@ -0,0 +1,4 @@
|
||||
<svg fill="#cbcbcb" t="1605777927585" class="icon" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="5690" width="16" height="16">
|
||||
<path d="M938.666667 469.333333v85.333334H85.333333v-85.333334z" p-id="5691"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 260 B |
@@ -0,0 +1,774 @@
|
||||
:root {
|
||||
--Font_Family: Microsoft YaHei UI;
|
||||
--Font_Size: 12px;
|
||||
--Font_Size_TabHeader: 12px;
|
||||
--Font_Size_TableHeader: 12px;
|
||||
--Font_Size_PageTitle: 15px;
|
||||
--Font_Size_HugeTitle: 30px;
|
||||
--Font_Size_MsgLabel: 15px;
|
||||
|
||||
--Color_Default: #ffffff;
|
||||
--Color_DefaultHover: #f0f0f0;
|
||||
--Color_DefaultPressed: #ececec;
|
||||
--Color_DefaultText: #212121;
|
||||
|
||||
--Color_Primary: #326cf3;
|
||||
--Color_PrimaryHover: #477bf4;
|
||||
--Color_PrimaryPressed: #84a7f8;
|
||||
--Color_PrimaryText: #ffffff;
|
||||
|
||||
--Color_Success: #2db84d;
|
||||
--Color_SuccessHover: #42bf5f;
|
||||
--Color_SuccessPressed: #81d494;
|
||||
|
||||
--Color_Danger: #db3340;
|
||||
--Color_DangerHover: #df4853;
|
||||
--Color_DangerPressed: #e9858c;
|
||||
|
||||
--Color_Warning: #e9af20;
|
||||
--Color_WarningHover: #ebb737;
|
||||
--Color_WarningPressed: #f2cf79;
|
||||
|
||||
--Color_Info: #00bcd4;
|
||||
--Color_InfoHover: #1ac3d8;
|
||||
--Color_InfoPressed: #66d7e5;
|
||||
|
||||
--Color_Border: #cbcbcb;
|
||||
--Color_WindowsIcon: #cbcbcb;
|
||||
--Color_Background: #eeeeee;
|
||||
--Color_Shadow: #AA000000;
|
||||
|
||||
--Color_Transparent: transparent;
|
||||
|
||||
--Size_InputPaddingLR: 8px;
|
||||
--Size_InputPaddingTB: 6px;
|
||||
|
||||
--Size_HeaderHeight: 45px;
|
||||
--Size_ItemHeight: 18px;
|
||||
--Size_ProgressHeight: 26px;
|
||||
--Size_ControlHeight: 18px;
|
||||
|
||||
--Size_ControlPaddingLR: 8px;
|
||||
--Size_ControlPaddingTB: 6px;
|
||||
|
||||
--Size_IconSize: 24px;
|
||||
--Size_CornerRadius: 4px;
|
||||
|
||||
--Size_CalendarWidth: 287px;
|
||||
--Size_CalendarHeight: 300px;
|
||||
}
|
||||
|
||||
/* #QSS_START */
|
||||
|
||||
* {
|
||||
font-family: var(--Font_Family);
|
||||
font-size: var(--Font_Size);
|
||||
|
||||
background: var(--Color_Default);
|
||||
color: var(--Color_DefaultText);
|
||||
}
|
||||
|
||||
/* Widget */
|
||||
|
||||
QWidget#MainViewLeftWidget {
|
||||
background: #000000;
|
||||
}
|
||||
|
||||
QWidget#TestWidget {
|
||||
background: #000000;
|
||||
}
|
||||
|
||||
QWidget#BaseWidget {
|
||||
background: var(--Color_Default);
|
||||
}
|
||||
|
||||
QWidget#TaskItemView {
|
||||
background: var(--Color_Default);
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: var(--Color_Border);
|
||||
border-radius: var(--Size_CornerRadius);
|
||||
}
|
||||
|
||||
QWidget#AboutWidget {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* PushButton Style */
|
||||
|
||||
QPushButton {
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: var(--Color_DefaultPressed);
|
||||
padding-left: var(--Size_InputPaddingLR);
|
||||
padding-right: var(--Size_InputPaddingLR);
|
||||
padding-top: var(--Size_InputPaddingTB);
|
||||
padding-bottom: var(--Size_InputPaddingTB);
|
||||
border-radius: var(--Size_CornerRadius);
|
||||
color: var(--Color_DefaultText);
|
||||
background: var(--Color_Default);
|
||||
}
|
||||
|
||||
QPushButton::hover {
|
||||
color: var(--Color_DefaultText);
|
||||
background: var(--Color_DefaultHover);
|
||||
}
|
||||
|
||||
QPushButton::pressed {
|
||||
color: var(--Color_DefaultText);
|
||||
background: var(--Color_DefaultPressed);
|
||||
}
|
||||
|
||||
|
||||
QPushButton#PrimaryPushButton {
|
||||
border-color: var(--Color_PrimaryPressed);
|
||||
color: var(--Color_PrimaryText);
|
||||
background: var(--Color_Primary);
|
||||
}
|
||||
|
||||
QPushButton#PrimaryPushButton::hover {
|
||||
background: var(--Color_PrimaryHover);
|
||||
}
|
||||
|
||||
QPushButton#PrimaryPushButton::pressed {
|
||||
background: var(--Color_PrimaryPressed);
|
||||
}
|
||||
|
||||
QPushButton#SuccessPushButton {
|
||||
border-color: var(--Color_SuccessPressed);
|
||||
color: var(--Color_PrimaryText);
|
||||
background: var(--Color_Success);
|
||||
}
|
||||
|
||||
QPushButton#SuccessPushButton::hover {
|
||||
background: var(--Color_SuccessHover);
|
||||
}
|
||||
|
||||
QPushButton#SuccessPushButton::pressed {
|
||||
background: var(--Color_SuccessPressed);
|
||||
}
|
||||
|
||||
QPushButton#DangerPushButton {
|
||||
border-color: var(--Color_DangerPressed);
|
||||
color: var(--Color_PrimaryText);
|
||||
background: var(--Color_Danger);
|
||||
}
|
||||
|
||||
QPushButton#DangerPushButton::hover {
|
||||
background: var(--Color_DangerHover);
|
||||
}
|
||||
|
||||
QPushButton#DangerPushButton::pressed {
|
||||
background: var(--Color_DangerPressed);
|
||||
}
|
||||
|
||||
QPushButton#WarningPushButton {
|
||||
border-color: var(--Color_WarningPressed);
|
||||
color: var(--Color_PrimaryText);
|
||||
background: var(--Color_Warning);
|
||||
}
|
||||
|
||||
QPushButton#WarningPushButton::hover {
|
||||
background: var(--Color_WarningHover);
|
||||
}
|
||||
|
||||
QPushButton#WarningPushButton::pressed {
|
||||
background: var(--Color_WarningPressed);
|
||||
}
|
||||
|
||||
QPushButton#InfoPushButton {
|
||||
border-color: var(--Color_InfoPressed);
|
||||
color: var(--Color_PrimaryText);
|
||||
background: var(--Color_Info);
|
||||
}
|
||||
|
||||
QPushButton#InfoPushButton::hover {
|
||||
background: var(--Color_InfoHover);
|
||||
}
|
||||
|
||||
QPushButton#InfoPushButton::pressed {
|
||||
background: var(--Color_InfoPressed);
|
||||
}
|
||||
|
||||
/* Software Buton */
|
||||
|
||||
QPushButton#SoftwareIconPushButton {
|
||||
border-style: none;
|
||||
border-radius: 0px;
|
||||
height: 26;
|
||||
width: 26;
|
||||
background: var(--Color_Transparent);
|
||||
image: url($RESOURCE_PATH$/svg/V.svg)
|
||||
}
|
||||
QPushButton#SoftwareIconPushButton::hover {
|
||||
image: url($RESOURCE_PATH$/svg/V.svg)
|
||||
}
|
||||
QPushButton#SoftwareIconPushButton::pressed {
|
||||
image: url($RESOURCE_PATH$/svg/V.svg)
|
||||
}
|
||||
|
||||
|
||||
/* Windows Buton */
|
||||
QPushButton#CloseWindowPushButton {
|
||||
border-style: none;
|
||||
border-radius: 0px;
|
||||
background: var(--Color_Transparent);
|
||||
image: url($RESOURCE_PATH$/svg/windows/close.svg)
|
||||
}
|
||||
|
||||
QPushButton#CloseWindowPushButton::hover {
|
||||
background: #ff1a1a;
|
||||
image: url($RESOURCE_PATH$/svg/windows/closeHover.svg)
|
||||
}
|
||||
|
||||
QPushButton#CloseWindowPushButton::pressed {
|
||||
background: #ff6666;
|
||||
image: url($RESOURCE_PATH$/svg/windows/closeHover.svg)
|
||||
}
|
||||
|
||||
QPushButton#MaxWindowPushButton {
|
||||
border-style: none;
|
||||
border-radius: 0px;
|
||||
background: var(--Color_Transparent);
|
||||
image: url($RESOURCE_PATH$/svg/windows/max.svg)
|
||||
}
|
||||
|
||||
QPushButton#MaxWindowPushButton::hover {
|
||||
background: var(--Color_DefaultHover);
|
||||
}
|
||||
|
||||
QPushButton#MaxWindowPushButton::pressed {
|
||||
background: var(--Color_DefaultPressed);
|
||||
}
|
||||
|
||||
QPushButton#MinWindowPushButton {
|
||||
border-style: none;
|
||||
border-radius: 0px;
|
||||
background: var(--Color_Transparent);
|
||||
image: url($RESOURCE_PATH$/svg/windows/min.svg)
|
||||
}
|
||||
|
||||
QPushButton#MinWindowPushButton::hover {
|
||||
background: var(--Color_DefaultHover);
|
||||
}
|
||||
|
||||
QPushButton#MinWindowPushButton::pressed {
|
||||
background: var(--Color_DefaultPressed);
|
||||
}
|
||||
|
||||
/* Task item tool button */
|
||||
QPushButton#TaskRetryPushButton {
|
||||
border-style: none;
|
||||
background: var(--Color_Default);
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
image: url($RESOURCE_PATH$/svg/taskItem/retry.svg)
|
||||
}
|
||||
QPushButton#TaskRetryPushButton::hover {
|
||||
background: var(--Color_DefaultHover);
|
||||
}
|
||||
QPushButton#TaskRetryPushButton::pressed {
|
||||
background: var(--Color_DefaultPressed);
|
||||
}
|
||||
|
||||
QPushButton#TaskCancelPushButton {
|
||||
border-style: none;
|
||||
background: var(--Color_Default);
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
image: url($RESOURCE_PATH$/svg/taskItem/cancel.svg)
|
||||
}
|
||||
QPushButton#TaskCancelPushButton::hover {
|
||||
background: var(--Color_DefaultHover);
|
||||
}
|
||||
QPushButton#TaskCancelPushButton::pressed {
|
||||
background: var(--Color_DefaultPressed);
|
||||
}
|
||||
|
||||
QPushButton#TaskDeletePushButton {
|
||||
border-style: none;
|
||||
background: var(--Color_Default);
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
image: url($RESOURCE_PATH$/svg/taskItem/delete.svg)
|
||||
}
|
||||
QPushButton#TaskDeletePushButton::hover {
|
||||
background: var(--Color_DefaultHover);
|
||||
}
|
||||
QPushButton#TaskDeletePushButton::pressed {
|
||||
background: var(--Color_DefaultPressed);
|
||||
}
|
||||
|
||||
QPushButton#TaskOpenPushButton {
|
||||
border-style: none;
|
||||
background: var(--Color_Default);
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
image: url($RESOURCE_PATH$/svg/taskItem/open.svg)
|
||||
}
|
||||
QPushButton#TaskOpenPushButton::hover {
|
||||
background: var(--Color_DefaultHover);
|
||||
}
|
||||
QPushButton#TaskOpenPushButton::pressed {
|
||||
background: var(--Color_DefaultPressed);
|
||||
}
|
||||
|
||||
QPushButton#TaskExpandPushButton {
|
||||
border-style: none;
|
||||
background: var(--Color_Default);
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
image: url($RESOURCE_PATH$/svg/taskItem/expand.svg)
|
||||
}
|
||||
QPushButton#TaskExpandPushButton::hover {
|
||||
background: var(--Color_DefaultHover);
|
||||
}
|
||||
QPushButton#TaskExpandPushButton::pressed {
|
||||
background: var(--Color_DefaultPressed);
|
||||
}
|
||||
|
||||
/* MainTab Icon Button */
|
||||
QPushButton#SearchIconPushButton {
|
||||
border-style: none;
|
||||
border-radius: 0px;
|
||||
height: 26;
|
||||
width: 26;
|
||||
background: var(--Color_Transparent);
|
||||
image: url($RESOURCE_PATH$/svg/leftTab/search.svg)
|
||||
}
|
||||
QPushButton#SearchIconPushButton::hover {
|
||||
image: url($RESOURCE_PATH$/svg/leftTab/search.svg)
|
||||
}
|
||||
QPushButton#SearchIconPushButton::pressed {
|
||||
image: url($RESOURCE_PATH$/svg/leftTab/search.svg)
|
||||
}
|
||||
|
||||
QPushButton#TaskIconPushButton {
|
||||
border-style: none;
|
||||
border-radius: 0px;
|
||||
height: 26;
|
||||
width: 26;
|
||||
background: var(--Color_Transparent);
|
||||
image: url($RESOURCE_PATH$/svg/leftTab/download.svg)
|
||||
}
|
||||
QPushButton#TaskIconPushButton::hover {
|
||||
image: url($RESOURCE_PATH$/svg/leftTab/downloadHover.svg)
|
||||
}
|
||||
QPushButton#TaskIconPushButton::pressed {
|
||||
image: url($RESOURCE_PATH$/svg/leftTab/downloadHover.svg)
|
||||
}
|
||||
|
||||
QPushButton#SettingsIconPushButton {
|
||||
border-style: none;
|
||||
border-radius: 0px;
|
||||
height: 26;
|
||||
width: 26;
|
||||
background: var(--Color_Transparent);
|
||||
image: url($RESOURCE_PATH$/svg/leftTab/settings.svg)
|
||||
}
|
||||
QPushButton#SettingsIconPushButton::hover {
|
||||
image: url($RESOURCE_PATH$/svg/leftTab/settings.svg)
|
||||
}
|
||||
QPushButton#SettingsIconPushButton::pressed {
|
||||
image: url($RESOURCE_PATH$/svg/leftTab/settings.svg)
|
||||
}
|
||||
|
||||
QPushButton#AboutIconPushButton {
|
||||
border-style: none;
|
||||
border-radius: 0px;
|
||||
height: 26;
|
||||
width: 26;
|
||||
background: var(--Color_Transparent);
|
||||
image: url($RESOURCE_PATH$/svg/leftTab/info.svg)
|
||||
}
|
||||
QPushButton#AboutIconPushButton::hover {
|
||||
image: url($RESOURCE_PATH$/svg/leftTab/info.svg)
|
||||
}
|
||||
QPushButton#AboutIconPushButton::pressed {
|
||||
image: url($RESOURCE_PATH$/svg/leftTab/info.svg)
|
||||
}
|
||||
|
||||
|
||||
QPushButton#PrePagePushButton {
|
||||
border-style: none;
|
||||
border-radius: 0px;
|
||||
border-width: 1px;
|
||||
border-color: var(--Color_Border);
|
||||
height: 18;
|
||||
width: 18;
|
||||
background: var(--Color_DefaultHover);
|
||||
image: url($RESOURCE_PATH$/svg/left.svg)
|
||||
}
|
||||
QPushButton#PrePagePushButton::hover {
|
||||
border-style: solid;
|
||||
image: url($RESOURCE_PATH$/svg/left.svg)
|
||||
}
|
||||
QPushButton#PrePagePushButton::pressed {
|
||||
image: url($RESOURCE_PATH$/svg/left.svg)
|
||||
}
|
||||
|
||||
QPushButton#NextPagePushButton {
|
||||
border-style: none;
|
||||
border-radius: 0px;
|
||||
border-width: 1px;
|
||||
border-color: var(--Color_Border);
|
||||
height: 18;
|
||||
width: 18;
|
||||
background: var(--Color_DefaultHover);
|
||||
image: url($RESOURCE_PATH$/svg/right.svg)
|
||||
}
|
||||
QPushButton#NextPagePushButton::hover {
|
||||
border-style: solid;
|
||||
image: url($RESOURCE_PATH$/svg/right.svg)
|
||||
}
|
||||
QPushButton#NextPagePushButton::pressed {
|
||||
image: url($RESOURCE_PATH$/svg/right.svg)
|
||||
}
|
||||
/* LineEdit Style */
|
||||
|
||||
QLineEdit {
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: var(--Color_Border);
|
||||
padding-left: var(--Size_InputPaddingLR);
|
||||
padding-right: var(--Size_InputPaddingLR);
|
||||
padding-top: var(--Size_InputPaddingTB);
|
||||
padding-bottom: var(--Size_InputPaddingTB);
|
||||
border-radius: var(--Size_CornerRadius);
|
||||
min-height: var(--Size_ControlHeight);
|
||||
color: var(--Color_DefaultText);
|
||||
background: var(--Color_Default);
|
||||
}
|
||||
|
||||
QLineEdit::hover {
|
||||
border-color: var(--Color_Primary);
|
||||
}
|
||||
|
||||
QLineEdit::focus {
|
||||
border-color: var(--Color_Primary);
|
||||
}
|
||||
|
||||
QLineEdit::disabled {
|
||||
background: var(--Color_DefaultHover);
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/* CheckBox Style */
|
||||
|
||||
QCheckBox::indicator {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
padding: 2px;
|
||||
|
||||
border: 1px solid var(--Color_Border);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
QCheckBox::indicator::unchecked:hover {
|
||||
border: 1px solid var(--Color_PrimaryHover);
|
||||
}
|
||||
|
||||
QCheckBox::indicator::checked {
|
||||
background: var(--Color_Primary);
|
||||
image: url($RESOURCE_PATH$/svg/check.svg);
|
||||
border: 1px solid var(--Color_Primary);
|
||||
}
|
||||
|
||||
/* TabWidget */
|
||||
|
||||
QTabWidget::pane {
|
||||
border:none;
|
||||
border-top: 1px solid var(--Color_Border);
|
||||
}
|
||||
QTabBar::tab {
|
||||
border:none;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
padding-bottom: 3px;
|
||||
font-size: var(--Font_Size_TabHeader);
|
||||
font-weight: bold;
|
||||
min-width: 60;
|
||||
}
|
||||
QTabBar::tab:selected {
|
||||
color: var(--Color_Primary);
|
||||
border-color: var(--Color_Primary);
|
||||
border-style: solid;
|
||||
border-bottom-width: 3px;
|
||||
}
|
||||
|
||||
/* Label */
|
||||
QLabel#PageTitleLabel {
|
||||
font-size: var(--Font_Size_PageTitle);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
QLabel#PageSubTitleLabel {
|
||||
font-size: var(--Font_Size_PageTitle);
|
||||
}
|
||||
|
||||
QLabel#HugeTitleLabel {
|
||||
font-size: var(--Font_Size_HugeTitle);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
QLabel#LogoBottomLabel {
|
||||
font-size: var(--Font_Size_MsgLabel);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
QLabel#SearchErrLabel {
|
||||
font-size: var(--Font_Size);
|
||||
color: var(--Color_Danger);
|
||||
max-height: var(--Font_Size_MsgLabel);
|
||||
}
|
||||
|
||||
QLabel#IconLabel {
|
||||
background: var(--Color_Transparent);
|
||||
}
|
||||
|
||||
QLabel#BoldLabel {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* QFrame */
|
||||
QFrame#VLineQFrame
|
||||
{
|
||||
border-top-style: none;
|
||||
border-bottom-style: none;
|
||||
border-right-style: none;
|
||||
border-left: 1px solid var(--Color_Border);
|
||||
}
|
||||
|
||||
QFrame#HLineQFrame
|
||||
{
|
||||
border-top: 1px solid var(--Color_Border);
|
||||
border-bottom-style: none;
|
||||
border-right-style: none;
|
||||
border-left-style: none;
|
||||
}
|
||||
|
||||
/* QComboBox */
|
||||
QComboBox {
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: var(--Color_Border);
|
||||
padding-left: var(--Size_InputPaddingLR);
|
||||
padding-right: var(--Size_InputPaddingLR);
|
||||
padding-top: var(--Size_InputPaddingTB);
|
||||
padding-bottom: var(--Size_InputPaddingTB);
|
||||
border-radius: var(--Size_CornerRadius);
|
||||
min-height: var(--Size_ControlHeight);
|
||||
color: var(--Color_DefaultText);
|
||||
background: var(--Color_Default);
|
||||
}
|
||||
|
||||
QComboBox::hover {
|
||||
border-color: var(--Color_Primary);
|
||||
}
|
||||
|
||||
QComboBox::focus {
|
||||
border-color: var(--Color_Primary);
|
||||
}
|
||||
|
||||
QComboBox::drop-down {
|
||||
subcontrol-position:center right;
|
||||
margin-right: 15px;
|
||||
border-left-style: none;
|
||||
}
|
||||
QComboBox::down-arrow {
|
||||
image: url($RESOURCE_PATH$/svg/down.svg);
|
||||
width : 24px;
|
||||
height : 24px;
|
||||
}
|
||||
QComboBox::down-arrow:hover {
|
||||
image: url($RESOURCE_PATH$/svg/downHover.svg);
|
||||
}
|
||||
|
||||
QComboBox::down-arrow:on {
|
||||
image: url($RESOURCE_PATH$/svg/upHover.svg);
|
||||
}
|
||||
|
||||
QComboBox QAbstractItemView {
|
||||
margin-top:5px;
|
||||
border:1px solid var(--Color_Primary);
|
||||
outline:none;
|
||||
}
|
||||
|
||||
QComboBox QAbstractItemView::item {
|
||||
height: var(--Size_ItemHeight);
|
||||
|
||||
padding-top: var(--Size_InputPaddingTB);
|
||||
padding-bottom: var(--Size_InputPaddingTB);
|
||||
}
|
||||
|
||||
QComboBox QAbstractItemView::item:selected{
|
||||
color: var(--Color_PrimaryText);
|
||||
background: var(--Color_Primary);
|
||||
}
|
||||
|
||||
|
||||
/* QListWidget */
|
||||
QListWidget#TaskTabListWidget {
|
||||
border-style: none;
|
||||
background: var(--Color_Default);
|
||||
max-width: 150px;
|
||||
outline:none;
|
||||
}
|
||||
|
||||
QListWidget#TaskTabListWidget::item
|
||||
{
|
||||
height:32px;
|
||||
padding-left: var(--Size_ControlPaddingLR);
|
||||
border: none;
|
||||
border-radius: var(--Size_CornerRadius);
|
||||
margin-top:5px;
|
||||
}
|
||||
|
||||
QListWidget#TaskTabListWidget::item:hover
|
||||
{
|
||||
background: var(--Color_DefaultHover);
|
||||
color: var(--Color_DefaultText);
|
||||
}
|
||||
|
||||
QListWidget#TaskTabListWidget::item:selected
|
||||
{
|
||||
background: var(--Color_Primary);
|
||||
color: var(--Color_PrimaryText);
|
||||
}
|
||||
|
||||
|
||||
QListWidget#TaskContentListWidget {
|
||||
border-style: none;
|
||||
background: var(--Color_Default);
|
||||
outline:none;
|
||||
}
|
||||
|
||||
QListWidget#TaskContentListWidget::item
|
||||
{
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: var(--Size_CornerRadius);
|
||||
border-color: var(--Color_Border);
|
||||
|
||||
min-height:80px;
|
||||
margin-top:5px;
|
||||
background: var(--Color_Default);
|
||||
}
|
||||
|
||||
QListWidget#TaskContentListWidget::item:hover
|
||||
{
|
||||
background: var(--Color_Default);
|
||||
}
|
||||
|
||||
QListWidget#TaskContentListWidget::item:selected
|
||||
{
|
||||
background: var(--Color_Default);
|
||||
}
|
||||
|
||||
|
||||
|
||||
QListWidget#DownloadItemsListWidget {
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-top: 1px solid var(--Color_Border);
|
||||
border-bottom: none;
|
||||
background: var(--Color_Default);
|
||||
outline:none;
|
||||
}
|
||||
|
||||
QListWidget#DownloadItemsListWidget::item
|
||||
{
|
||||
border-style: none;
|
||||
min-height:45px;
|
||||
margin-top:2px;
|
||||
background: var(--Color_Default);
|
||||
}
|
||||
|
||||
QListWidget#DownloadItemsListWidget::item:hover
|
||||
{
|
||||
background: var(--Color_Default);
|
||||
}
|
||||
|
||||
QListWidget#DownloadItemsListWidget::item:selected
|
||||
{
|
||||
background: var(--Color_Default);
|
||||
}
|
||||
|
||||
QListWidget QScrollBar::vertical{
|
||||
background:transparent;
|
||||
width: 4px;
|
||||
border-radius:6px;
|
||||
}
|
||||
QListWidget QScrollBar::handle{
|
||||
background: lightgray;
|
||||
border-radius:6px;
|
||||
}
|
||||
QListWidget QScrollBar::handle:hover{background:gray;}
|
||||
QListWidget QScrollBar::sub-line{background:transparent;}
|
||||
QListWidget QScrollBar::add-line{background:transparent;}
|
||||
|
||||
/* QTableWidget */
|
||||
QTableWidget {
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-top: none;
|
||||
border-bottom: 1px solid var(--Color_Border);
|
||||
|
||||
text-align: center;
|
||||
color: var(--Color_DefaultText);
|
||||
background: var(--Color_DefaultHover);
|
||||
padding:5px;
|
||||
|
||||
}
|
||||
|
||||
QTableWidget::item{
|
||||
margin-top: 5px;
|
||||
background: var(--Color_Default);
|
||||
}
|
||||
QTableWidget::item:selected{
|
||||
color: var(--Color_PrimaryText);
|
||||
background: var(--Color_Primary);
|
||||
}
|
||||
|
||||
|
||||
QTableWidget QHeaderView::section {
|
||||
color: var(--Color_DefaultText);
|
||||
background: var(--Color_DefaultHover);
|
||||
text-align: center;
|
||||
height: var(--Size_HeaderHeight);
|
||||
border: none;
|
||||
font-size: var(--Font_Size_TableHeader);
|
||||
}
|
||||
|
||||
QTableWidget QScrollBar::vertical{
|
||||
background:transparent;
|
||||
width: 4px;
|
||||
border-radius:6px;
|
||||
}
|
||||
QTableWidget QScrollBar::handle{
|
||||
background: lightgray;
|
||||
border-radius:6px;
|
||||
}
|
||||
QTableWidget QScrollBar::handle:hover{background:gray;}
|
||||
QTableWidget QScrollBar::sub-line{background:transparent;}
|
||||
QTableWidget QScrollBar::add-line{background:transparent;}
|
||||
|
||||
/* QProgressBar */
|
||||
QProgressBar {
|
||||
border:none;
|
||||
background:rgb(230, 230, 230);
|
||||
border-radius:0px;
|
||||
text-align:center;
|
||||
color:gray;
|
||||
}
|
||||
QProgressBar::chunk {
|
||||
background:rgb(71, 137, 250);
|
||||
border-radius:0px;
|
||||
}
|
||||
|
||||
/* QScrollArea */
|
||||
QScrollArea {
|
||||
border: none;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
'''
|
||||
@File : enum.py
|
||||
@Date : 2021/05/08
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
'''
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ButtonStyle(Enum):
|
||||
Default = 0,
|
||||
Primary = 1,
|
||||
Success = 2,
|
||||
Danger = 3,
|
||||
Warning = 4,
|
||||
Info = 5,
|
||||
|
||||
CloseWindow = 6,
|
||||
MaxWindow = 7,
|
||||
MinWindow = 8,
|
||||
|
||||
SearchIcon = 9,
|
||||
TaskIcon = 10,
|
||||
SettingsIcon = 11,
|
||||
AboutIcon = 12,
|
||||
|
||||
SoftwareIcon = 13
|
||||
|
||||
PrePage = 14
|
||||
NextPage = 15
|
||||
|
||||
TaskRetry = 16,
|
||||
TaskCancel = 17,
|
||||
TaskDelete = 18,
|
||||
TaskOpen = 19,
|
||||
TaskExpand = 20,
|
||||
|
||||
|
||||
class LabelStyle(Enum):
|
||||
Default = 0,
|
||||
PageTitle = 1,
|
||||
PageSubTitle = 2,
|
||||
HugeTitle = 3,
|
||||
LogoBottom = 4,
|
||||
SearchErr = 5,
|
||||
Icon = 6,
|
||||
Bold = 7,
|
||||
|
||||
|
||||
class ThemeStyle(Enum):
|
||||
Default = 0,
|
||||
Dark = 1,
|
||||
|
||||
|
||||
class ListWidgetStyle(Enum):
|
||||
Default = 0,
|
||||
TaskTab = 1,
|
||||
DownloadItems = 2,
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : qssParse.py
|
||||
@Date : 2021/05/08
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
|
||||
import os
|
||||
import aigpy
|
||||
from tidal_gui.style import ThemeStyle
|
||||
|
||||
_RESOURCE_PATH = './resource'
|
||||
if os.path.isdir(_RESOURCE_PATH):
|
||||
_RESOURCE_PATH = os.path.abspath(_RESOURCE_PATH).replace('\\', '/')
|
||||
else:
|
||||
_RESOURCE_PATH = aigpy.path.getDirName(__file__).replace('\\', '/') + "resource"
|
||||
|
||||
|
||||
|
||||
def __getParam__(line: str):
|
||||
key = aigpy.string.getSub(line, "--", ":")
|
||||
value = aigpy.string.getSub(line, ":", ";")
|
||||
return key, value
|
||||
|
||||
|
||||
def __parseParamsList__(content: str) -> dict:
|
||||
globalStr = aigpy.string.getSub(content, ":root", "}")
|
||||
lines = globalStr.split("\n")
|
||||
|
||||
array = {}
|
||||
for line in lines:
|
||||
key, value = __getParam__(line)
|
||||
if key == "" or value == "":
|
||||
continue
|
||||
array[key] = value
|
||||
return array
|
||||
|
||||
|
||||
def __parseQss__(content: str, params: dict) -> str:
|
||||
content = aigpy.string.getSub(content, "/* #QSS_START */")
|
||||
for key in params:
|
||||
content = content.replace("var(--" + key + ")", params[key])
|
||||
|
||||
content = content.replace("$RESOURCE_PATH$", _RESOURCE_PATH)
|
||||
return content
|
||||
|
||||
|
||||
def __getQss__(filePath: str) -> str:
|
||||
content = aigpy.file.getContent(filePath)
|
||||
params = __parseParamsList__(content)
|
||||
qss = __parseQss__(content, params)
|
||||
return qss
|
||||
|
||||
def getResourcePath():
|
||||
return _RESOURCE_PATH
|
||||
|
||||
def getThemeQssContent(style: ThemeStyle = ThemeStyle.Default):
|
||||
name = "theme" + style.name + ".qss"
|
||||
return __getQss__(_RESOURCE_PATH + '/' + name)
|
||||
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : tidalImp.py
|
||||
@Date : 2021/9/2
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
import time
|
||||
|
||||
import requests
|
||||
import tidal_dl.model
|
||||
import tidal_dl.enums
|
||||
from aigpy.stringHelper import isNull
|
||||
from tidal_dl import TokenSettings, TOKEN, TidalAPI, CONF
|
||||
from tidal_dl.util import getAlbumPath, getPlaylistPath, getTrackPath
|
||||
|
||||
|
||||
class TidalImp(TidalAPI):
|
||||
def __init__(self):
|
||||
super(TidalImp, self).__init__()
|
||||
|
||||
def loginByConfig(self):
|
||||
if isNull(TOKEN.accessToken):
|
||||
return False
|
||||
|
||||
msg, check = self.verifyAccessToken(TOKEN.accessToken)
|
||||
if check:
|
||||
self.key.countryCode = TOKEN.countryCode
|
||||
self.key.userId = TOKEN.userid
|
||||
self.key.accessToken = TOKEN.accessToken
|
||||
return True
|
||||
|
||||
msg, check = self.refreshAccessToken(TOKEN.refreshToken)
|
||||
if check:
|
||||
TOKEN.userid = self.key.userId
|
||||
TOKEN.countryCode = self.key.countryCode
|
||||
TOKEN.accessToken = self.key.accessToken
|
||||
TOKEN.expiresAfter = time.time() + int(self.key.expiresIn)
|
||||
TokenSettings.save(TOKEN)
|
||||
return True
|
||||
else:
|
||||
tmp = TokenSettings() # clears saved tokens
|
||||
TokenSettings.save(tmp)
|
||||
return False
|
||||
|
||||
def loginByWeb(self):
|
||||
start = time.time()
|
||||
elapsed = 0
|
||||
while elapsed < self.key.authCheckTimeout:
|
||||
elapsed = time.time() - start
|
||||
msg, check = self.checkAuthStatus()
|
||||
if not check:
|
||||
if msg == "pending":
|
||||
time.sleep(self.key.authCheckInterval + 1)
|
||||
continue
|
||||
return False
|
||||
if check:
|
||||
TOKEN.userid = self.key.userId
|
||||
TOKEN.countryCode = self.key.countryCode
|
||||
TOKEN.accessToken = self.key.accessToken
|
||||
TOKEN.refreshToken = self.key.refreshToken
|
||||
TOKEN.expiresAfter = time.time() + int(self.key.expiresIn)
|
||||
TokenSettings.save(TOKEN)
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def getArtistsNames(artists): # : list[tidal_dl.model.Artist]
|
||||
ret = []
|
||||
for item in artists:
|
||||
ret.append(item.name)
|
||||
return ','.join(ret)
|
||||
|
||||
@staticmethod
|
||||
def getDurationString(seconds: int):
|
||||
m, s = divmod(seconds, 60)
|
||||
h, m = divmod(m, 60)
|
||||
return "%02d:%02d:%02d" % (h, m, s)
|
||||
|
||||
def getCoverData(self, sid, width="320", height="320"):
|
||||
url = self.getCoverUrl(sid, width, height)
|
||||
try:
|
||||
respond = requests.get(url)
|
||||
return respond.content
|
||||
except:
|
||||
return ''
|
||||
|
||||
@staticmethod
|
||||
def getAudioQualityList():
|
||||
return map(lambda quality: quality.name, tidal_dl.enums.AudioQuality)
|
||||
|
||||
@staticmethod
|
||||
def getVideoQualityList():
|
||||
return map(lambda quality: quality.name, tidal_dl.enums.VideoQuality)
|
||||
|
||||
def getBasePath(self, model):
|
||||
if isinstance(model, tidal_dl.model.Album):
|
||||
return getAlbumPath(CONF, model)
|
||||
if isinstance(model, tidal_dl.model.Playlist):
|
||||
return getPlaylistPath(CONF, model)
|
||||
if isinstance(model, tidal_dl.model.Track):
|
||||
return getAlbumPath(CONF, model.album)
|
||||
if isinstance(model, tidal_dl.model.Video):
|
||||
return CONF.downloadPath + '/Video/'
|
||||
return './'
|
||||
|
||||
def getConfig(self):
|
||||
return CONF
|
||||
|
||||
# def getTackPath(self, basePath, track, stream, album=None, playlist=None):
|
||||
# # number
|
||||
# number = __getIndexStr__(track.trackNumber)
|
||||
# if playlist is not None and CONF.usePlaylistFolder:
|
||||
# number = __getIndexStr__(track.trackNumberOnPlaylist)
|
||||
# # artist
|
||||
# artist = aigpy.path.replaceLimitChar(__getArtistsString__(track.artists), '-')
|
||||
# # title
|
||||
# title = track.title
|
||||
# if not aigpy.string.isNull(track.version):
|
||||
# title += ' (' + track.version + ')'
|
||||
# title = aigpy.path.replaceLimitChar(title, '-')
|
||||
# # get explicit
|
||||
# explicit = "(Explicit)" if CONF.addExplicitTag and track.explicit else ''
|
||||
# # album and addyear
|
||||
# albumname = aigpy.path.replaceLimitChar(album.title, '-')
|
||||
# year = ""
|
||||
# if album.releaseDate is not None:
|
||||
# year = aigpy.string.getSubOnlyEnd(album.releaseDate, '-')
|
||||
# # extension
|
||||
# extension = __getExtension__(stream.url)
|
||||
# retpath = CONF.trackFileFormat
|
||||
# if retpath is None or len(retpath) <= 0:
|
||||
# retpath = Settings.getDefaultTrackFileFormat()
|
||||
# retpath = retpath.replace(R"{TrackNumber}", number)
|
||||
# retpath = retpath.replace(R"{ArtistName}", artist.strip())
|
||||
# retpath = retpath.replace(R"{TrackTitle}", title)
|
||||
# retpath = retpath.replace(R"{ExplicitFlag}", explicit)
|
||||
# retpath = retpath.replace(R"{AlbumYear}", year)
|
||||
# retpath = retpath.replace(R"{AlbumTitle}", albumname.strip())
|
||||
# retpath = retpath.strip()
|
||||
# return basePath + retpath + extension
|
||||
|
||||
# def getVideoPath(self, basePath, video):
|
||||
# # hyphen
|
||||
# hyphen = ' - ' if CONF.addHyphen else ' '
|
||||
# # get number
|
||||
# number = ''
|
||||
# if CONF.useTrackNumber:
|
||||
# number = __getIndexStr__(video.trackNumber) + hyphen
|
||||
# # get artist
|
||||
# artist = ''
|
||||
# if CONF.artistBeforeTitle:
|
||||
# artist = aigpy.path.replaceLimitChar(__getArtistsString__(video.artists), '-') + hyphen
|
||||
# # get explicit
|
||||
# explicit = "(Explicit)" if CONF.addExplicitTag and video.explicit else ''
|
||||
# # title
|
||||
# title = aigpy.path.replaceLimitChar(video.title, '-')
|
||||
# # extension
|
||||
# extension = ".mp4"
|
||||
# return basePath + number + artist.strip() + title + explicit + extension
|
||||
|
||||
tidalImp = TidalImp()
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : __init__.py
|
||||
@Date : 2021/05/11
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : aboutView.py
|
||||
@Date : 2021/8/17
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QPixmap
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QGridLayout, QHBoxLayout, QLabel
|
||||
|
||||
from tidal_gui.control.label import Label
|
||||
from tidal_gui.control.pushButton import PushButton
|
||||
from tidal_gui.style import LabelStyle, ButtonStyle
|
||||
from tidal_gui.theme import getResourcePath
|
||||
|
||||
|
||||
class AboutView(QWidget):
|
||||
def __init__(self):
|
||||
super(AboutView, self).__init__()
|
||||
self.__initView__()
|
||||
|
||||
def __initView__(self):
|
||||
grid = QGridLayout(self)
|
||||
grid.addWidget(self.__initLogo__(), 0, 0, Qt.AlignLeft)
|
||||
grid.addLayout(self.__initContent__(), 0, 1)
|
||||
|
||||
def __initLogo__(self):
|
||||
path = getResourcePath() + "/svg/V.svg"
|
||||
self._logo = QLabel()
|
||||
self._logo.setPixmap(QPixmap(path))
|
||||
return self._logo
|
||||
|
||||
def __initButton__(self):
|
||||
path = getResourcePath() + "/svg/"
|
||||
|
||||
self._feedbackBtn = PushButton('Feedback', ButtonStyle.Default, iconUrl=path + 'github.svg')
|
||||
self._buymeacoffeeBtn = PushButton('Buymeacoffee', ButtonStyle.Info, iconUrl=path + 'buymeacoffee.svg')
|
||||
self._paypalBtn = PushButton('Paypal', ButtonStyle.Primary, iconUrl=path + 'paypal.svg')
|
||||
|
||||
layout = QHBoxLayout()
|
||||
layout.addWidget(self._feedbackBtn)
|
||||
layout.addWidget(self._buymeacoffeeBtn)
|
||||
layout.addWidget(self._paypalBtn)
|
||||
return layout
|
||||
|
||||
def __initContent__(self):
|
||||
self._titleLabel = Label('', LabelStyle.HugeTitle)
|
||||
self._authorLabel = Label('')
|
||||
self._versionLabel = Label('')
|
||||
self._lastVersionLabel = Label('')
|
||||
|
||||
layout = QVBoxLayout()
|
||||
layout.addWidget(self._titleLabel)
|
||||
layout.addWidget(self._authorLabel)
|
||||
layout.addWidget(self._versionLabel)
|
||||
layout.addWidget(self._lastVersionLabel)
|
||||
layout.addLayout(self.__initButton__())
|
||||
return layout
|
||||
|
||||
def setTitle(self, text: str):
|
||||
self._titleLabel.setText(text)
|
||||
|
||||
def setAuthor(self, text: str):
|
||||
self._authorLabel.setText('MADE WITH ♥ BY ' + text)
|
||||
|
||||
def setVersion(self, text: str):
|
||||
self._versionLabel.setText('VERSION ' + text)
|
||||
|
||||
def setLastVersion(self, text: str):
|
||||
self._lastVersionLabel.setText('LAST-VERSION ' + text)
|
||||
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : downloadItemView.py
|
||||
@Date : 2021/10/08
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
from PyQt5.QtGui import QPixmap
|
||||
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QTableWidget, QVBoxLayout, QGridLayout, QProgressBar
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
from tidal_gui.control.label import Label
|
||||
from tidal_gui.control.layout import createHBoxLayout, createVBoxLayout
|
||||
from tidal_gui.control.pushButton import PushButton
|
||||
from tidal_gui.style import LabelStyle, ButtonStyle
|
||||
|
||||
|
||||
class DownloadItemView(QWidget):
|
||||
def __init__(self):
|
||||
super(DownloadItemView, self).__init__()
|
||||
self.__initView__()
|
||||
|
||||
def __initView__(self):
|
||||
self._indexLabel = Label('1')
|
||||
self._titleLabel = Label('title', LabelStyle.Bold)
|
||||
self._ownLabel = Label('own')
|
||||
self._ownLabel.setMaximumWidth(200)
|
||||
self._actionLabel = Label('')
|
||||
self._actionLabel.setFixedWidth(80)
|
||||
self._errLabel = Label('')
|
||||
self._sizeLabel = Label('/')
|
||||
self._speedLabel = Label('')
|
||||
|
||||
self._progress = QProgressBar()
|
||||
self._progress.setTextVisible(False)
|
||||
self._progress.setFixedHeight(3)
|
||||
self._progress.setFixedWidth(300)
|
||||
|
||||
grid = QGridLayout(self)
|
||||
grid.addWidget(self._indexLabel, 0, 0, Qt.AlignLeft | Qt.AlignVCenter)
|
||||
grid.addWidget(self._titleLabel, 0, 1, Qt.AlignLeft | Qt.AlignVCenter)
|
||||
grid.addWidget(self._ownLabel, 0, 2, Qt.AlignRight | Qt.AlignVCenter)
|
||||
grid.addWidget(self._progress, 0, 3, Qt.AlignRight | Qt.AlignVCenter)
|
||||
grid.addWidget(self._actionLabel, 0, 4, Qt.AlignRight | Qt.AlignVCenter)
|
||||
grid.addWidget(self._errLabel, 1, 1, Qt.AlignLeft | Qt.AlignVCenter)
|
||||
|
||||
grid.setColumnStretch(1, 1)
|
||||
|
||||
layout = QHBoxLayout()
|
||||
layout.setSpacing(30)
|
||||
layout.addWidget(self._sizeLabel)
|
||||
layout.addWidget(self._speedLabel)
|
||||
|
||||
grid.addLayout(layout, 1, 3, Qt.AlignLeft | Qt.AlignVCenter)
|
||||
|
||||
def setLabel(self, index, title, own):
|
||||
self._indexLabel.setText(str(index))
|
||||
self._titleLabel.setText(title)
|
||||
self._ownLabel.setText(own)
|
||||
|
||||
def setErrmsg(self, msg):
|
||||
self._errLabel.setText(msg)
|
||||
|
||||
def setAction(self, msg):
|
||||
self._actionLabel.setText(msg)
|
||||
|
||||
def setProgress(self, value):
|
||||
self._progress.setValue(value)
|
||||
pass
|
||||
|
||||
def setSize(self, curSize: str, totalSize: str):
|
||||
self._sizeLabel.setText(f'{curSize}/{totalSize}')
|
||||
|
||||
def setSpeed(self, speed: str):
|
||||
self._speedLabel.setText(speed)
|
||||
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : loginView.py
|
||||
@Date : 2021/04/30
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QPixmap
|
||||
from PyQt5.QtWidgets import *
|
||||
|
||||
from tidal_gui.control.checkBox import CheckBox
|
||||
from tidal_gui.control.framelessWidget import FramelessWidget
|
||||
from tidal_gui.control.label import Label
|
||||
from tidal_gui.control.layout import createHBoxLayout
|
||||
from tidal_gui.control.lineEdit import LineEdit
|
||||
from tidal_gui.control.pushButton import PushButton
|
||||
from tidal_gui.style import ButtonStyle, LabelStyle
|
||||
from tidal_gui.theme import getResourcePath
|
||||
|
||||
|
||||
class LoginView(FramelessWidget):
|
||||
viewWidth = 650
|
||||
viewHeight = 400
|
||||
logoWidth = 300
|
||||
|
||||
def __init__(self):
|
||||
super(LoginView, self).__init__()
|
||||
self.setFixedSize(self.viewWidth, self.viewHeight)
|
||||
self.__initView__()
|
||||
self.setWindowButton(True, False, False)
|
||||
|
||||
def __initAccountTab__(self):
|
||||
self._deviceCodeEdit = LineEdit()
|
||||
self._confirmBtn = PushButton("LOGIN", ButtonStyle.Primary)
|
||||
|
||||
grid = QGridLayout()
|
||||
grid.setSpacing(15)
|
||||
grid.setRowStretch(0, 1)
|
||||
|
||||
grid.addLayout(createHBoxLayout([Label("DeviceCode"), self._deviceCodeEdit]), 1, 0, 1, 2)
|
||||
grid.setRowStretch(3, 1)
|
||||
grid.addWidget(self._confirmBtn, 5, 0, 1, 2)
|
||||
grid.setRowStretch(6, 1)
|
||||
|
||||
widget = QWidget()
|
||||
widget.setLayout(grid)
|
||||
return widget
|
||||
|
||||
def __initProxyTab__(self):
|
||||
self._enableProxyCheck = CheckBox('')
|
||||
self._proxyHostEdit = LineEdit()
|
||||
self._proxyPortEdit = LineEdit()
|
||||
self._proxyUserEdit = LineEdit()
|
||||
self._proxyPwdEdit = LineEdit()
|
||||
|
||||
grid = QGridLayout()
|
||||
grid.setSpacing(8)
|
||||
grid.setRowStretch(0, 1)
|
||||
grid.addWidget(Label("HttpProxy"), 1, 0)
|
||||
grid.addWidget(self._enableProxyCheck, 1, 1)
|
||||
grid.addWidget(Label("Host"), 2, 0)
|
||||
grid.addWidget(self._proxyHostEdit, 2, 1)
|
||||
grid.addWidget(Label("Port"), 3, 0)
|
||||
grid.addWidget(self._proxyPortEdit, 3, 1)
|
||||
grid.addWidget(Label("UserName"), 4, 0)
|
||||
grid.addWidget(self._proxyUserEdit, 4, 1)
|
||||
grid.addWidget(Label("Password"), 5, 0)
|
||||
grid.addWidget(self._proxyPwdEdit, 5, 1)
|
||||
grid.setRowStretch(6, 1)
|
||||
|
||||
widget = QWidget()
|
||||
widget.setLayout(grid)
|
||||
return widget
|
||||
|
||||
def __initIconWidget__(self):
|
||||
self._icon = Label('')
|
||||
self._icon.setStyleSheet("QLabel{background-color:rgb(0,0,0);}")
|
||||
|
||||
self._icon.setPixmap(QPixmap(getResourcePath() + "/svg/V.svg"))
|
||||
self._icon.setAlignment(Qt.AlignCenter)
|
||||
|
||||
self._iconLabel = Label('', LabelStyle.LogoBottom)
|
||||
self._iconLabel.setAlignment(Qt.AlignCenter)
|
||||
self._iconLabel.hide()
|
||||
|
||||
layout = QVBoxLayout()
|
||||
layout.setSpacing(15)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addStretch(1)
|
||||
layout.addWidget(self._icon)
|
||||
layout.addWidget(self._iconLabel)
|
||||
layout.addStretch(1)
|
||||
|
||||
widget = QWidget()
|
||||
widget.setStyleSheet("QWidget{background-color:rgb(0,0,0);}")
|
||||
widget.setLayout(layout)
|
||||
return widget
|
||||
|
||||
def __initView__(self):
|
||||
iconWidget = self.__initIconWidget__()
|
||||
|
||||
self._tab = QTabWidget(self)
|
||||
self._tab.addTab(self.__initAccountTab__(), "LOGIN")
|
||||
self._tab.addTab(self.__initProxyTab__(), "PROXY")
|
||||
self._tab.setFixedWidth(self.viewWidth - self.logoWidth - 60)
|
||||
self._tab.hide()
|
||||
|
||||
grid = self.getGrid()
|
||||
grid.setSpacing(15)
|
||||
grid.setContentsMargins(0, 0, 0, 0)
|
||||
grid.addWidget(iconWidget)
|
||||
grid.addWidget(self._tab, 0, 1, Qt.AlignCenter)
|
||||
|
||||
self.setValidMoveWidget(iconWidget)
|
||||
|
||||
def showEnterView(self):
|
||||
self._tab.show()
|
||||
|
||||
def hideEnterView(self):
|
||||
self._tab.hide()
|
||||
|
||||
def setDeviceCode(self, text):
|
||||
self._deviceCodeEdit.setText(text)
|
||||
|
||||
def enableHttpProxy(self) -> bool:
|
||||
return self._enableProxyCheck.isChecked()
|
||||
|
||||
def getProxyInfo(self) -> dict:
|
||||
infos = {'host': self._proxyHostEdit.text(), 'port': self._proxyPortEdit.text(),
|
||||
'username': self._proxyUserEdit.text(), 'password': self._proxyPwdEdit.text()}
|
||||
return infos
|
||||
|
||||
def connectConfirmButton(self, func):
|
||||
self._confirmBtn.clicked.connect(func)
|
||||
|
||||
def enableConfirmButton(self, enable):
|
||||
self._confirmBtn.setEnabled(enable)
|
||||
|
||||
def setMsg(self, text):
|
||||
if len(text) <= 0:
|
||||
self._iconLabel.hide()
|
||||
else:
|
||||
self._iconLabel.setText(text)
|
||||
self._iconLabel.show()
|
||||
|
||||
def showErrMessage(self, text: str):
|
||||
qmb = QMessageBox(self)
|
||||
qmb.setWindowTitle('Error')
|
||||
qmb.setIcon(QMessageBox.Warning)
|
||||
qmb.setText(text)
|
||||
qmb.addButton(QPushButton('OK', qmb), QMessageBox.YesRole)
|
||||
qmb.open()
|
||||
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : main.py
|
||||
@Date : 2021/05/11
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
from enum import IntEnum
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QVBoxLayout, QWidget, QGridLayout
|
||||
|
||||
from tidal_gui.control.framelessWidget import FramelessWidget
|
||||
from tidal_gui.control.pushButton import PushButton
|
||||
from tidal_gui.style import ButtonStyle
|
||||
|
||||
|
||||
class PageType(IntEnum):
|
||||
Search = 0,
|
||||
Task = 1,
|
||||
Settings = 2,
|
||||
About = 3,
|
||||
Max = 4,
|
||||
|
||||
|
||||
class MainView(FramelessWidget):
|
||||
viewWidth = 1200
|
||||
viewHeight = 700
|
||||
|
||||
def __init__(self):
|
||||
super(MainView, self).__init__()
|
||||
self.setMinimumHeight(self.viewHeight)
|
||||
self.setMinimumWidth(self.viewWidth)
|
||||
self.__initPages__()
|
||||
self.__initView__()
|
||||
self.setWindowButton(True, True, True)
|
||||
|
||||
def __initPages__(self):
|
||||
self._pages = []
|
||||
for index in range(0, PageType.Max):
|
||||
self._pages.append(None)
|
||||
|
||||
def __initView__(self):
|
||||
leftTab = self.__initLeftTab__()
|
||||
content = self.__initContent__()
|
||||
moveWgt = self.__initMoveHead__()
|
||||
|
||||
grid = self.getGrid()
|
||||
grid.addWidget(leftTab, 0, 0, 2, 1, Qt.AlignLeft)
|
||||
grid.addWidget(moveWgt, 0, 1, Qt.AlignTop)
|
||||
grid.addLayout(content, 1, 1)
|
||||
|
||||
self.setValidMoveWidget(moveWgt)
|
||||
|
||||
def __initLeftTab__(self):
|
||||
self._icon = PushButton("", ButtonStyle.SoftwareIcon)
|
||||
self._searchBtn = PushButton("", ButtonStyle.SearchIcon)
|
||||
self._taskBtn = PushButton("", ButtonStyle.TaskIcon)
|
||||
self._settingsBtn = PushButton("", ButtonStyle.SettingsIcon)
|
||||
self._aboutBtn = PushButton("", ButtonStyle.AboutIcon)
|
||||
|
||||
self._searchBtn.clicked.connect(lambda: self.showPage(PageType.Search))
|
||||
self._taskBtn.clicked.connect(lambda: self.showPage(PageType.Task))
|
||||
self._settingsBtn.clicked.connect(lambda: self.showPage(PageType.Settings))
|
||||
self._aboutBtn.clicked.connect(lambda: self.showPage(PageType.About))
|
||||
|
||||
layout = QVBoxLayout()
|
||||
layout.setSpacing(15)
|
||||
layout.setContentsMargins(15, 45, 15, 20)
|
||||
layout.addWidget(self._icon)
|
||||
layout.addWidget(self._searchBtn)
|
||||
layout.addWidget(self._taskBtn)
|
||||
layout.addStretch(1)
|
||||
layout.addWidget(self._settingsBtn)
|
||||
layout.addWidget(self._aboutBtn)
|
||||
|
||||
widget = QWidget()
|
||||
widget.setLayout(layout)
|
||||
widget.setObjectName("MainViewLeftWidget")
|
||||
return widget
|
||||
|
||||
def __initMoveHead__(self) -> QWidget:
|
||||
self._moveWidget = QWidget()
|
||||
self._moveWidget.setFixedHeight(30)
|
||||
self._moveWidget.setObjectName("BaseWidget")
|
||||
return self._moveWidget
|
||||
|
||||
def __initContent__(self) -> QGridLayout:
|
||||
self._searchView = None
|
||||
self._settingsView = None
|
||||
self._aboutView = None
|
||||
self._taskView = None
|
||||
self._contentLayout = QGridLayout()
|
||||
self._contentLayout.setContentsMargins(10, 0, 10, 10)
|
||||
return self._contentLayout
|
||||
|
||||
def showPage(self, pageType: PageType = PageType.Search):
|
||||
for index in range(0, PageType.Max):
|
||||
if index == pageType:
|
||||
self._pages[index].show()
|
||||
else:
|
||||
self._pages[index].hide()
|
||||
|
||||
def __setContentPage__(self, view, pageType: PageType):
|
||||
self._pages[pageType] = view
|
||||
self._pages[pageType].hide()
|
||||
self._contentLayout.addWidget(view, 0, 0)
|
||||
|
||||
def setSearchView(self, view):
|
||||
self.__setContentPage__(view, PageType.Search)
|
||||
|
||||
def setTaskView(self, view):
|
||||
self.__setContentPage__(view, PageType.Task)
|
||||
|
||||
def setSettingsView(self, view):
|
||||
self.__setContentPage__(view, PageType.Settings)
|
||||
|
||||
def setAboutView(self, view: QWidget):
|
||||
# self.__setContentPage__(view, PageType.About)
|
||||
self._pages[PageType.About] = view
|
||||
self._pages[PageType.About].hide()
|
||||
@@ -0,0 +1,225 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : searchView.py
|
||||
@Date : 2021/8/17
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
import threading
|
||||
import tidal_dl.model
|
||||
from PyQt5.QtCore import Qt, QUrl
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QTabWidget
|
||||
from tidal_dl import Type
|
||||
|
||||
from tidal_gui.control.comboBox import ComboBox
|
||||
from tidal_gui.control.label import Label
|
||||
from tidal_gui.control.layout import createHBoxLayout, createVBoxLayout
|
||||
from tidal_gui.control.lineEdit import LineEdit
|
||||
from tidal_gui.control.pushButton import PushButton
|
||||
from tidal_gui.control.tableView import TableView
|
||||
from tidal_gui.control.tableWidget import TableWidget
|
||||
from tidal_gui.style import ButtonStyle, LabelStyle
|
||||
from tidal_gui.theme import getResourcePath
|
||||
from tidal_gui.tidalImp import tidalImp
|
||||
|
||||
|
||||
class SearchView(QWidget):
|
||||
def __init__(self):
|
||||
super(SearchView, self).__init__()
|
||||
self._rowCount = 20
|
||||
self._table = {}
|
||||
self._lock = threading.Lock()
|
||||
self.__initView__()
|
||||
self._searchEdit.setFocus()
|
||||
|
||||
def __initView__(self):
|
||||
grid = QVBoxLayout(self)
|
||||
grid.addLayout(self.__initHead__(), Qt.AlignTop)
|
||||
grid.addLayout(self.__initContent__())
|
||||
grid.addLayout(self.__initTail__(), Qt.AlignBottom)
|
||||
|
||||
def __initHead__(self):
|
||||
self._searchEdit = LineEdit(iconUrl=getResourcePath() + "/svg/search2.svg")
|
||||
self._searchErrLabel = Label('', LabelStyle.SearchErr)
|
||||
self._searchErrLabel.hide()
|
||||
layout = QVBoxLayout()
|
||||
layout.addWidget(self._searchEdit)
|
||||
layout.addWidget(self._searchErrLabel)
|
||||
layout.setContentsMargins(0, 0, 0, 5)
|
||||
return layout
|
||||
|
||||
def __initTail__(self):
|
||||
self._trackQualityComboBox = ComboBox([], 150)
|
||||
self._videoQualityComboBox = ComboBox([], 150)
|
||||
|
||||
self._prePageBtn = PushButton('', ButtonStyle.PrePage)
|
||||
self._nextPageBtn = PushButton('', ButtonStyle.NextPage)
|
||||
|
||||
self._pageIndexEdit = LineEdit('')
|
||||
self._pageIndexEdit.setAlignment(Qt.AlignCenter)
|
||||
self._pageIndexEdit.setEnabled(False)
|
||||
|
||||
self._downloadBtn = PushButton('Download', ButtonStyle.Primary)
|
||||
|
||||
layout = QHBoxLayout()
|
||||
layout.addWidget(Label('Track:'))
|
||||
layout.addWidget(self._trackQualityComboBox)
|
||||
layout.addWidget(Label('Video:'))
|
||||
layout.addWidget(self._videoQualityComboBox)
|
||||
layout.addStretch(1)
|
||||
layout.addWidget(self._prePageBtn)
|
||||
layout.addWidget(self._pageIndexEdit)
|
||||
layout.addWidget(self._nextPageBtn)
|
||||
layout.addWidget(self._downloadBtn)
|
||||
return layout
|
||||
|
||||
def __initContent__(self):
|
||||
self._tabWidget = QTabWidget(self)
|
||||
self._tabWidget.addTab(self.__initTable__(Type.Album), "ALBUM")
|
||||
self._tabWidget.addTab(self.__initTable__(Type.Track), "TRACK")
|
||||
self._tabWidget.addTab(self.__initTable__(Type.Video), "VIDEO")
|
||||
self._tabWidget.addTab(self.__initTable__(Type.Playlist), "PLAYLIST")
|
||||
|
||||
layout = QVBoxLayout()
|
||||
layout.addWidget(self._tabWidget)
|
||||
return layout
|
||||
|
||||
def __initTable__(self, stype: Type):
|
||||
columnHeads = []
|
||||
columnWidths = []
|
||||
if stype == Type.Album:
|
||||
columnHeads = ['#', ' ', ' ', 'Title', 'Artists', 'Release', 'Duration']
|
||||
columnWidths = [50, 60, 60, 400, 200, 200]
|
||||
elif stype == Type.Track:
|
||||
columnHeads = ['#', ' ', 'Title', 'Album', 'Artists', 'Duration']
|
||||
columnWidths = [50, 60, 400, 200, 200]
|
||||
elif stype == Type.Video:
|
||||
columnHeads = ['#', ' ', ' ', 'Title', 'Artists', 'Duration']
|
||||
columnWidths = [50, 60, 60, 400, 200, 200]
|
||||
elif stype == Type.Playlist:
|
||||
columnHeads = ['#', ' ', 'Title', 'Artist', 'Duration']
|
||||
columnWidths = [50, 60, 400, 200, 200]
|
||||
|
||||
self._table[stype] = TableWidget(columnHeads, self._rowCount)
|
||||
for index, width in enumerate(columnWidths):
|
||||
self._table[stype].setColumnWidth(index, width)
|
||||
|
||||
return self._table[stype]
|
||||
|
||||
def __clearTableRowItems__(self, stype: Type, fromIndex: int):
|
||||
endIndex = self._rowCount - 1
|
||||
columnNum = self._table[stype].columnCount()
|
||||
while fromIndex <= endIndex:
|
||||
for colIdx in range(0, columnNum):
|
||||
self._table[stype].addItem(fromIndex, colIdx, '')
|
||||
fromIndex += 1
|
||||
|
||||
def setTableItems(self, stype: Type, indexOffset: int, result: tidal_dl.model.SearchResult):
|
||||
if stype == Type.Album:
|
||||
items = result.albums.items
|
||||
datas = []
|
||||
for index, item in enumerate(items):
|
||||
rowData = [str(index + 1 + indexOffset),
|
||||
QUrl(tidalImp.getCoverUrl(item.cover)),
|
||||
tidalImp.getFlag(item, Type.Album, True),
|
||||
item.title,
|
||||
item.artists[0].name,
|
||||
str(item.releaseDate),
|
||||
tidalImp.getDurationString(item.duration)]
|
||||
datas.append(rowData)
|
||||
|
||||
elif stype == Type.Track:
|
||||
items = result.tracks.items
|
||||
datas = []
|
||||
for index, item in enumerate(items):
|
||||
rowData = [str(index + 1 + indexOffset),
|
||||
tidalImp.getFlag(item, Type.Track, True),
|
||||
item.title,
|
||||
item.album.title,
|
||||
item.artists[0].name,
|
||||
tidalImp.getDurationString(item.duration)]
|
||||
datas.append(rowData)
|
||||
|
||||
elif stype == Type.Video:
|
||||
items = result.videos.items
|
||||
datas = []
|
||||
for index, item in enumerate(items):
|
||||
rowData = [str(index + 1 + indexOffset),
|
||||
QUrl(tidalImp.getCoverUrl(item.imageID)),
|
||||
tidalImp.getFlag(item, Type.Video, True),
|
||||
item.title,
|
||||
item.artists[0].name,
|
||||
tidalImp.getDurationString(item.duration)]
|
||||
datas.append(rowData)
|
||||
|
||||
elif stype == Type.Playlist:
|
||||
items = result.playlists.items
|
||||
datas = []
|
||||
for index, item in enumerate(items):
|
||||
rowData = [str(index + 1 + indexOffset),
|
||||
QUrl(tidalImp.getCoverUrl(item.squareImage)),
|
||||
item.title,
|
||||
'',
|
||||
tidalImp.getDurationString(item.duration)]
|
||||
datas.append(rowData)
|
||||
|
||||
for index, rowData in enumerate(datas):
|
||||
for colIdx, obj in enumerate(rowData):
|
||||
self._table[stype].addItem(index, colIdx, obj)
|
||||
|
||||
self.__clearTableRowItems__(stype, len(items))
|
||||
self._table[stype].viewport().update()
|
||||
|
||||
def getSearchText(self):
|
||||
return self._searchEdit.text()
|
||||
|
||||
def setSearchErrmsg(self, text: str):
|
||||
self._searchErrLabel.setText(text)
|
||||
if text != '':
|
||||
self._searchErrLabel.show()
|
||||
else:
|
||||
self._searchErrLabel.hide()
|
||||
|
||||
def setPageIndex(self, index, sum):
|
||||
self._pageIndexEdit.setText(str(index) + '/' + str(sum))
|
||||
|
||||
def getPageIndex(self) -> (int, int):
|
||||
nums = self._pageIndexEdit.text().split('/')
|
||||
return int(nums[0]), int(nums[1])
|
||||
|
||||
def getSelectedTabIndex(self):
|
||||
return self._tabWidget.currentIndex()
|
||||
|
||||
def getSelectedTableIndex(self, stype: Type):
|
||||
array = self._table[stype].selectedIndexes()
|
||||
if len(array) <= 0:
|
||||
return -1
|
||||
return array[0].row()
|
||||
|
||||
def setTrackQualityItems(self, items: list):
|
||||
self._trackQualityComboBox.setItems(items)
|
||||
|
||||
def setVideoQualityItems(self, items: list):
|
||||
self._videoQualityComboBox.setItems(items)
|
||||
|
||||
def getTrackQualityText(self):
|
||||
return self._trackQualityComboBox.currentText()
|
||||
|
||||
def getVideoQualityText(self):
|
||||
return self._videoQualityComboBox.currentText()
|
||||
|
||||
def connectButton(self, name: str, func):
|
||||
if name == 'search':
|
||||
self._searchEdit.returnPressed.connect(func)
|
||||
elif name == 'prePage':
|
||||
self._prePageBtn.clicked.connect(func)
|
||||
elif name == 'nextPage':
|
||||
self._nextPageBtn.clicked.connect(func)
|
||||
elif name == 'download':
|
||||
self._downloadBtn.clicked.connect(func)
|
||||
|
||||
def connectTab(self, func):
|
||||
self._tabWidget.currentChanged.connect(func)
|
||||
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : settingsView.py
|
||||
@Date : 2021/05/11
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QGridLayout
|
||||
|
||||
from tidal_gui.control.checkBox import CheckBox
|
||||
from tidal_gui.control.comboBox import ComboBox
|
||||
from tidal_gui.control.label import Label
|
||||
from tidal_gui.control.line import Line
|
||||
from tidal_gui.control.lineEdit import LineEdit
|
||||
from tidal_gui.control.pushButton import PushButton
|
||||
from tidal_gui.style import LabelStyle, ButtonStyle, ThemeStyle
|
||||
|
||||
|
||||
class SettingsView(QWidget):
|
||||
def __init__(self):
|
||||
super(SettingsView, self).__init__()
|
||||
self.__initView__()
|
||||
|
||||
def __initView__(self):
|
||||
grid = QVBoxLayout(self)
|
||||
grid.addLayout(self.__initHeader__())
|
||||
grid.addLayout(self.__initContent__())
|
||||
grid.addLayout(self.__initToolButton__())
|
||||
|
||||
def __initHeader__(self):
|
||||
self._header = Label("SETTINGS", LabelStyle.PageTitle)
|
||||
layout = QVBoxLayout()
|
||||
layout.addWidget(self._header)
|
||||
layout.addWidget(Line('H'))
|
||||
layout.setSpacing(10)
|
||||
layout.setContentsMargins(0, 0, 0, 10)
|
||||
return layout
|
||||
|
||||
def __initToolButton__(self) -> QHBoxLayout:
|
||||
self._logoutBtn = PushButton('Logout', ButtonStyle.Danger, 100)
|
||||
self._cancelBtn = PushButton('Cancel', ButtonStyle.Default, 100)
|
||||
self._confirmBtn = PushButton('OK', ButtonStyle.Primary, 100)
|
||||
|
||||
layout = QHBoxLayout()
|
||||
layout.addWidget(self._logoutBtn)
|
||||
layout.addStretch(1)
|
||||
layout.addWidget(self._cancelBtn)
|
||||
layout.addWidget(self._confirmBtn)
|
||||
return layout
|
||||
|
||||
def __addContent__(self, control, desc=''):
|
||||
rowIdx = self._contentLayout.rowCount()
|
||||
if desc != '':
|
||||
self._contentLayout.addWidget(Label(desc), rowIdx, 0, Qt.AlignRight)
|
||||
self._contentLayout.addWidget(control, rowIdx, 1)
|
||||
|
||||
def __initContent__(self):
|
||||
self._contentLayout = QGridLayout()
|
||||
self._contentLayout.setSpacing(15)
|
||||
|
||||
self._pathEdit = LineEdit()
|
||||
self._threadNumComboBox = ComboBox([1, 5, 10, 20])
|
||||
self._searchNumComboBox = ComboBox([10, 20, 30, 40, 50])
|
||||
self.__addContent__(self._pathEdit, 'Path:')
|
||||
self.__addContent__(self._threadNumComboBox, 'ThreadNum:')
|
||||
self.__addContent__(self._searchNumComboBox, 'SearchNum:')
|
||||
|
||||
self._contentLayout.setRowStretch(self._contentLayout.rowCount(), 1)
|
||||
|
||||
self._albumFolderFormatEdit = LineEdit()
|
||||
self._trackFileFormatEdit = LineEdit()
|
||||
self._videoFileFormatEdit = LineEdit()
|
||||
self.__addContent__(self._albumFolderFormatEdit, 'AlbumFolderFormat:')
|
||||
self.__addContent__(self._trackFileFormatEdit, 'TrackFileFormat:')
|
||||
self.__addContent__(self._videoFileFormatEdit, 'VideoFileFormat:')
|
||||
|
||||
self._saveCoverCheck = CheckBox('Download album cover')
|
||||
self._skipExistCheck = CheckBox('Skip exist file when downloading')
|
||||
self._convertMp4ToM4a = CheckBox('Convert mp4 to m4a')
|
||||
self.__addContent__(self._saveCoverCheck)
|
||||
self.__addContent__(self._skipExistCheck)
|
||||
self.__addContent__(self._convertMp4ToM4a)
|
||||
|
||||
self._contentLayout.setRowStretch(self._contentLayout.rowCount(), 1)
|
||||
|
||||
self._themeComboBox = ComboBox(list(map(lambda x: x.name, ThemeStyle)))
|
||||
self._languageComboBox = ComboBox(['Default'])
|
||||
self.__addContent__(self._themeComboBox, 'Theme:')
|
||||
self.__addContent__(self._languageComboBox, 'Language:')
|
||||
|
||||
self._contentLayout.setRowStretch(self._contentLayout.rowCount(), 10)
|
||||
return self._contentLayout
|
||||
|
||||
def connectButton(self, name: str, func):
|
||||
if name == "Logout":
|
||||
self._logoutBtn.clicked.connect(func)
|
||||
elif name == "Cancel":
|
||||
self._cancelBtn.clicked.connect(func)
|
||||
elif name == "OK":
|
||||
self._confirmBtn.clicked.connect(func)
|
||||
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : taskItemView.py
|
||||
@Date : 2021/9/14
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
from PyQt5.QtGui import QPixmap
|
||||
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QTableWidget, QVBoxLayout, QListView, QGridLayout
|
||||
from PyQt5.QtCore import Qt, QPoint
|
||||
|
||||
from tidal_gui.control.label import Label
|
||||
from tidal_gui.control.layout import createHBoxLayout, createVBoxLayout
|
||||
from tidal_gui.control.pushButton import PushButton
|
||||
from tidal_gui.control.listWidget import ListWidget
|
||||
from tidal_gui.style import LabelStyle, ButtonStyle, ListWidgetStyle
|
||||
|
||||
|
||||
class TaskItemView(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.__initView__()
|
||||
self.setObjectName('TaskItemView')
|
||||
self.setAttribute(Qt.WA_StyledBackground)
|
||||
|
||||
def __initView__(self):
|
||||
layout = QVBoxLayout()
|
||||
layout.addLayout(self.__initHead__(), Qt.AlignTop)
|
||||
layout.addWidget(self.__initList__(), Qt.AlignTop)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
def __initHead__(self):
|
||||
self._btnRetry = PushButton('', ButtonStyle.TaskRetry)
|
||||
self._btnCancel = PushButton('', ButtonStyle.TaskCancel)
|
||||
self._btnDelete = PushButton('', ButtonStyle.TaskDelete)
|
||||
self._btnOpen = PushButton('', ButtonStyle.TaskOpen)
|
||||
self._btnExpand = PushButton('', ButtonStyle.TaskExpand)
|
||||
self._btnExpand.clicked.connect(self.__expandClick__)
|
||||
btnLayout = createHBoxLayout([self._btnRetry, self._btnCancel,
|
||||
self._btnDelete, self._btnOpen,
|
||||
self._btnExpand])
|
||||
|
||||
self._titleLabel = Label('', LabelStyle.PageTitle)
|
||||
self._descLabel = Label()
|
||||
self._errLabel = Label()
|
||||
self._errLabel.hide()
|
||||
labelLayout = createVBoxLayout([self._titleLabel, self._descLabel, self._errLabel])
|
||||
|
||||
self._picLabel = Label('', LabelStyle.Icon)
|
||||
self._picLabel.setMinimumHeight(64)
|
||||
headLayout = QHBoxLayout()
|
||||
headLayout.addWidget(self._picLabel)
|
||||
headLayout.addLayout(labelLayout)
|
||||
headLayout.addStretch(1)
|
||||
headLayout.addLayout(btnLayout)
|
||||
|
||||
return headLayout
|
||||
|
||||
def __initList__(self):
|
||||
self._list = ListWidget(ListWidgetStyle.DownloadItems)
|
||||
self._list.setResizeMode(QListView.Adjust)
|
||||
return self._list
|
||||
|
||||
def setLabel(self, title, desc):
|
||||
self._titleLabel.setText(title)
|
||||
self._descLabel.setText(desc)
|
||||
|
||||
def setErrmsg(self, msg):
|
||||
self._errLabel.setText(msg)
|
||||
|
||||
def setPic(self, data):
|
||||
pic = QPixmap()
|
||||
pic.loadFromData(data)
|
||||
self._picLabel.setPixmap(pic.scaled(64, 64))
|
||||
|
||||
def addListItem(self, view):
|
||||
self._list.addWidgetItem(view)
|
||||
|
||||
def __expandClick__(self):
|
||||
if self._list.isHidden():
|
||||
self._list.show()
|
||||
else:
|
||||
self._list.hide()
|
||||
|
||||
def connectButton(self, name: str, func):
|
||||
if name == 'retry':
|
||||
self._btnRetry.clicked.connect(func)
|
||||
elif name == 'cancel':
|
||||
self._btnCancel.clicked.connect(func)
|
||||
elif name == 'delete':
|
||||
self._btnDelete.clicked.connect(func)
|
||||
elif name == 'open':
|
||||
self._btnOpen.clicked.connect(func)
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : taskView.py
|
||||
@Date : 2021/8/17
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
from enum import Enum
|
||||
from PyQt5.QtCore import Qt, QSize
|
||||
from PyQt5.QtWidgets import QWidget, QGridLayout, QListWidgetItem, QListView
|
||||
|
||||
from tidal_gui.control.label import Label
|
||||
from tidal_gui.control.line import Line
|
||||
from tidal_gui.control.listWidget import ListWidget
|
||||
from tidal_gui.control.scrollWidget import ScrollWidget
|
||||
from tidal_gui.style import LabelStyle, ListWidgetStyle
|
||||
from tidal_gui.theme import getResourcePath
|
||||
from tidal_gui.view.taskItemView import TaskItemView
|
||||
|
||||
|
||||
class TaskListType(Enum):
|
||||
Download = 0,
|
||||
Complete = 1,
|
||||
Error = 2,
|
||||
|
||||
|
||||
class TaskView(QWidget):
|
||||
def __init__(self):
|
||||
super(TaskView, self).__init__()
|
||||
self._listMap = {}
|
||||
self._pageMap = {}
|
||||
|
||||
for item in map(lambda typeItem: typeItem.name, TaskListType):
|
||||
self._listMap[item] = ScrollWidget()
|
||||
self._pageMap[item] = QWidget()
|
||||
|
||||
self.__initView__()
|
||||
self._listTab.setCurrentRow(0)
|
||||
self._pageMap[TaskListType.Download.name].show()
|
||||
|
||||
def __initView__(self):
|
||||
grid = QGridLayout(self)
|
||||
grid.addLayout(self.__initLefTab__(), 0, 0, Qt.AlignLeft)
|
||||
for item in map(lambda typeItem: typeItem.name, TaskListType):
|
||||
grid.addWidget(self.__createContent__(item), 0, 1)
|
||||
|
||||
def __initLefTab__(self):
|
||||
self._listTab = ListWidget(ListWidgetStyle.TaskTab)
|
||||
self._listTab.setIconSize(QSize(20, 20))
|
||||
|
||||
iconPath = getResourcePath() + "/svg/taskTab/"
|
||||
self._listTab.addIConTextItem(iconPath + 'download.svg', TaskListType.Download.name)
|
||||
self._listTab.addIConTextItem(iconPath + 'complete.svg', TaskListType.Complete.name)
|
||||
self._listTab.addIConTextItem(iconPath + 'error.svg', TaskListType.Error.name)
|
||||
|
||||
self._listTab.itemClicked.connect(self.__tabItemChanged__)
|
||||
|
||||
layout = QGridLayout()
|
||||
layout.addWidget(Label("TASK LIST", LabelStyle.PageTitle), 0, 0, Qt.AlignLeft)
|
||||
layout.addWidget(self._listTab, 1, 0, Qt.AlignLeft)
|
||||
layout.addWidget(Line('V'), 0, 1, 2, 1, Qt.AlignLeft)
|
||||
return layout
|
||||
|
||||
def __createContent__(self, typeStr: str):
|
||||
layout = QGridLayout()
|
||||
layout.setContentsMargins(0, 0, 10, 10)
|
||||
layout.setSpacing(10)
|
||||
layout.addWidget(Label(typeStr, LabelStyle.PageSubTitle), 0, 0, Qt.AlignTop)
|
||||
layout.addWidget(Line('H'), 1, 0, Qt.AlignTop)
|
||||
layout.addWidget(self._listMap[typeStr], 2, 0)
|
||||
|
||||
self._pageMap[typeStr].setLayout(layout)
|
||||
self._pageMap[typeStr].hide()
|
||||
return self._pageMap[typeStr]
|
||||
|
||||
def __tabItemChanged__(self, item: QListWidgetItem):
|
||||
for name in self._listMap:
|
||||
if name == item.text():
|
||||
self._pageMap[name].show()
|
||||
else:
|
||||
self._pageMap[name].hide()
|
||||
|
||||
def addItemView(self, stype: TaskListType, view):
|
||||
self._listMap[stype.name].addWidgetItem(view)
|
||||
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : __init__.py
|
||||
@Date : 2021/8/17
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : aboutModel.py
|
||||
@Date : 2021/8/17
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
from tidal_gui.view.aboutView import AboutView
|
||||
from tidal_gui.viewModel.viewModel import ViewModel
|
||||
|
||||
|
||||
class AboutModel(ViewModel):
|
||||
def __init__(self):
|
||||
super(AboutModel, self).__init__()
|
||||
self.view = AboutView()
|
||||
self.view.setTitle('TIDAL-GUI')
|
||||
self.view.setAuthor('Yaronzz')
|
||||
self.view.setVersion('1.0.0.1')
|
||||
self.view.setLastVersion('1.0.0.1')
|
||||
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : downloadItemModel.py
|
||||
@Date : 2021/10/08
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
|
||||
import _thread
|
||||
import time
|
||||
from enum import Enum
|
||||
import aigpy.stringHelper
|
||||
from tidal_dl import Type
|
||||
from tidal_dl.model import Album, Track, Video, Playlist
|
||||
|
||||
from tidal_gui.tidalImp import tidalImp
|
||||
from tidal_gui.view.downloadItemView import DownloadItemView
|
||||
from tidal_gui.viewModel.viewModel import ViewModel
|
||||
|
||||
class DownloadStatus(Enum):
|
||||
Wait = 0,
|
||||
Running = 1,
|
||||
Finish = 2,
|
||||
Error = 3,
|
||||
Cancel = 4,
|
||||
|
||||
_endStatus_ = [DownloadStatus.Finish, DownloadStatus.Error, DownloadStatus.Cancel]
|
||||
|
||||
class DownloadItemModel(ViewModel):
|
||||
def __init__(self, index, data, basePath):
|
||||
super(DownloadItemModel, self).__init__()
|
||||
self.view = DownloadItemView()
|
||||
self.data = data
|
||||
self.basePath = basePath
|
||||
self.isTrack = isinstance(data, Track)
|
||||
self.__setStatus__(DownloadStatus.Wait)
|
||||
|
||||
if self.isTrack:
|
||||
self.__initTrack__(index)
|
||||
else:
|
||||
self.__initVideo__(index)
|
||||
|
||||
def __setStatus__(self, status: DownloadStatus, desc: str = ''):
|
||||
self.status = status
|
||||
if desc == '':
|
||||
self.view.setAction(status.name)
|
||||
else:
|
||||
self.view.setAction(status.name + '-' + desc)
|
||||
|
||||
def __initTrack__(self, index):
|
||||
title = self.data.title
|
||||
own = self.data.album.title
|
||||
self.view.setLabel(index, title, own)
|
||||
|
||||
def __initVideo__(self, index):
|
||||
title = self.data.title
|
||||
own = tidalImp.getArtistsNames(self.data.artists)
|
||||
self.view.setLabel(index, title, own)
|
||||
|
||||
def isInWait(self):
|
||||
return self.status == DownloadStatus.Wait
|
||||
|
||||
def stopDownload(self):
|
||||
if self.status not in _endStatus_:
|
||||
self.__setStatus__(DownloadStatus.Cancel)
|
||||
|
||||
def retry(self):
|
||||
self.__setStatus__(DownloadStatus.Wait)
|
||||
|
||||
def download(self):
|
||||
self.__setStatus__(DownloadStatus.Running)
|
||||
|
||||
# if self.isTrack:
|
||||
# if not self.__dlTrack__(index):
|
||||
# return
|
||||
# else:
|
||||
# if not self.__dlVideo__(index):
|
||||
# return
|
||||
|
||||
self.view.setProgress(100)
|
||||
self.__setStatus__(DownloadStatus.Finish)
|
||||
|
||||
def __dlTrack__(self):
|
||||
try:
|
||||
track = self.data
|
||||
conf = tidalImp.getConfig()
|
||||
|
||||
msg, stream = tidalImp.getStreamUrl(track.id, conf.audioQuality)
|
||||
if not aigpy.string.isNull(msg) or stream is None:
|
||||
self.__setStatus__(DownloadStatus.Error)
|
||||
return False
|
||||
|
||||
tidalImp.getBasePath(model)
|
||||
path = tidalImp.getTackPath(self.basePath, track, stream)
|
||||
|
||||
# check exist
|
||||
if conf.checkExist and __isNeedDownload__(path, stream.url) == False:
|
||||
return True
|
||||
|
||||
tool = aigpy.download.DownloadTool(path + '.part', [stream.url])
|
||||
check, err = tool.start(conf.showProgress)
|
||||
|
||||
if not check:
|
||||
Printf.err("Download failed! " + aigpy.path.getFileName(path) + ' (' + str(err) + ')')
|
||||
return
|
||||
# encrypted -> decrypt and remove encrypted file
|
||||
if aigpy.string.isNull(stream.encryptionKey):
|
||||
os.replace(path + '.part', path)
|
||||
else:
|
||||
key, nonce = decrypt_security_token(stream.encryptionKey)
|
||||
decrypt_file(path + '.part', path, key, nonce)
|
||||
os.remove(path + '.part')
|
||||
|
||||
path = __convertToM4a__(path, stream.codec)
|
||||
|
||||
# contributors
|
||||
msg, contributors = API.getTrackContributors(track.id)
|
||||
msg, tidalLyrics = API.getLyrics(track.id)
|
||||
|
||||
lyrics = '' if tidalLyrics is None else tidalLyrics.subtitles
|
||||
if conf.addLyrics and lyrics == '':
|
||||
lyrics = __getLyrics__(track.title, __getArtistsString__(track.artists), conf.lyricsServerProxy)
|
||||
|
||||
if conf.lyricFile:
|
||||
if tidalLyrics is None:
|
||||
Printf.info(f'Failed to get lyrics from tidal!"{track.title}"')
|
||||
else:
|
||||
lrcPath = path.rsplit(".", 1)[0] + '.lrc'
|
||||
aigpy.fileHelper.write(lrcPath, tidalLyrics.subtitles, 'w')
|
||||
|
||||
__setMetaData__(track, album, path, contributors, lyrics)
|
||||
Printf.success(aigpy.path.getFileName(path))
|
||||
except Exception as e:
|
||||
Printf.err("Download failed! " + track.title + ' (' + str(e) + ')')
|
||||
|
||||
def __dlVideo__(self):
|
||||
pass
|
||||
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : loginModel.py
|
||||
@Date : 2021/8/17
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
import _thread
|
||||
import time
|
||||
import webbrowser
|
||||
|
||||
from PyQt5.QtCore import QTimer, pyqtSignal, QObject
|
||||
|
||||
from tidal_gui.tidalImp import tidalImp
|
||||
from tidal_gui.view.loginView import LoginView
|
||||
from tidal_gui.viewModel.viewModel import ViewModel
|
||||
|
||||
class LoginModel(ViewModel):
|
||||
SIGNAL_LOGIN_SUCCESS = pyqtSignal()
|
||||
|
||||
def __init__(self):
|
||||
super(LoginModel, self).__init__()
|
||||
self.view = LoginView()
|
||||
self.view.connectConfirmButton(self.__openWeb__)
|
||||
self.SIGNAL_REFRESH_VIEW.connect(self.__refresh__)
|
||||
|
||||
def __refresh__(self, stype: str, text: str):
|
||||
if stype == "userCode":
|
||||
self.view.setDeviceCode(text)
|
||||
self.view.enableConfirmButton(True)
|
||||
self.view.setMsg(' ')
|
||||
self.view.showEnterView()
|
||||
elif stype == "showMsg":
|
||||
self.view.hideEnterView()
|
||||
self.view.setMsg(text)
|
||||
|
||||
def login(self, useConfig = True):
|
||||
self.SIGNAL_REFRESH_VIEW.emit('showMsg', "LOGIN...")
|
||||
|
||||
def __thread_login__(model: LoginModel, useConfig: bool):
|
||||
if useConfig and tidalImp.loginByConfig():
|
||||
model.SIGNAL_LOGIN_SUCCESS.emit()
|
||||
return
|
||||
model.getDeviceCode()
|
||||
|
||||
_thread.start_new_thread(__thread_login__, (self, useConfig))
|
||||
|
||||
def getDeviceCode(self):
|
||||
self.SIGNAL_REFRESH_VIEW.emit('showMsg', "GET DEVICE-CODE...")
|
||||
|
||||
def __thread_getCode__(model: LoginModel):
|
||||
msg, check = tidalImp.getDeviceCode()
|
||||
if check:
|
||||
model.SIGNAL_REFRESH_VIEW.emit('userCode', tidalImp.key.userCode)
|
||||
else:
|
||||
model.SIGNAL_REFRESH_VIEW.emit('showMsg', msg)
|
||||
|
||||
_thread.start_new_thread(__thread_getCode__, (self,))
|
||||
|
||||
def __openWeb__(self):
|
||||
self.view.enableConfirmButton(False)
|
||||
webbrowser.open('http://link.tidal.com/' + tidalImp.key.userCode, new=0, autoraise=True)
|
||||
|
||||
def __thread_waitLogin__(model: LoginModel):
|
||||
if tidalImp.loginByWeb():
|
||||
model.SIGNAL_LOGIN_SUCCESS.emit()
|
||||
else:
|
||||
model.getDeviceCode()
|
||||
|
||||
_thread.start_new_thread(__thread_waitLogin__, (self,))
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : mainModel.py
|
||||
@Date : 2021/8/17
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
from tidal_gui.downloader import downloadImp
|
||||
from tidal_gui.view.mainView import MainView
|
||||
from tidal_gui.viewModel.aboutModel import AboutModel
|
||||
from tidal_gui.viewModel.loginModel import LoginModel
|
||||
from tidal_gui.viewModel.searchModel import SearchModel
|
||||
from tidal_gui.viewModel.settingsModel import SettingsModel
|
||||
from tidal_gui.viewModel.taskModel import TaskModel
|
||||
from tidal_gui.viewModel.viewModel import ViewModel
|
||||
|
||||
|
||||
class MainModel(ViewModel):
|
||||
def __init__(self):
|
||||
super(MainModel, self).__init__()
|
||||
self.loginModel = LoginModel()
|
||||
self.searchModel = SearchModel()
|
||||
self.taskModel = TaskModel()
|
||||
self.settingsModel = SettingsModel(self)
|
||||
self.aboutModel = AboutModel()
|
||||
|
||||
self.loginModel.SIGNAL_LOGIN_SUCCESS.connect(self.__loginSuccess__)
|
||||
self.searchModel.SIGNAL_ADD_TASKITEM.connect(self.taskModel.addTaskItem)
|
||||
|
||||
self.view = MainView()
|
||||
self.view.setSearchView(self.searchModel.view)
|
||||
self.view.setTaskView(self.taskModel.view)
|
||||
self.view.setSettingsView(self.settingsModel.view)
|
||||
self.view.setAboutView(self.aboutModel.view)
|
||||
|
||||
self.view.showPage()
|
||||
|
||||
downloadImp.setTaskModel(self.taskModel)
|
||||
downloadImp.start()
|
||||
|
||||
def uninit(self):
|
||||
self.taskModel.stopDownloadItem()
|
||||
downloadImp.stop()
|
||||
|
||||
def show(self, relogin:bool = False):
|
||||
self.view.hide()
|
||||
self.loginModel.login(bool(1-relogin))
|
||||
self.loginModel.show()
|
||||
|
||||
def __loginSuccess__(self):
|
||||
self.loginModel.hide()
|
||||
self.view.show()
|
||||
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : searchModel.py
|
||||
@Date : 2021/8/17
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
import _thread
|
||||
import threading
|
||||
|
||||
import aigpy.stringHelper
|
||||
import tidal_dl
|
||||
from PyQt5.QtCore import pyqtSignal
|
||||
from aigpy.modelHelper import ModelBase
|
||||
from tidal_dl import Type
|
||||
from tidal_dl.model import Album, SearchResult
|
||||
|
||||
from tidal_gui.tidalImp import tidalImp
|
||||
from tidal_gui.view.searchView import SearchView
|
||||
from tidal_gui.viewModel.viewModel import ViewModel
|
||||
|
||||
|
||||
class SearchModel(ViewModel):
|
||||
SIGNAL_ADD_TASKITEM = pyqtSignal(ModelBase)
|
||||
|
||||
def __init__(self):
|
||||
super(SearchModel, self).__init__()
|
||||
self._lock = threading.Lock()
|
||||
self._resultData = None
|
||||
|
||||
self.view = SearchView()
|
||||
self.view.setPageIndex(1, 1)
|
||||
self.view.setTrackQualityItems(tidalImp.getAudioQualityList())
|
||||
self.view.setVideoQualityItems(tidalImp.getVideoQualityList())
|
||||
self.view.connectButton('search', self.__search__)
|
||||
self.view.connectButton('prePage', self.__searchPre__)
|
||||
self.view.connectButton('nextPage', self.__searchNext__)
|
||||
self.view.connectButton('download', self.__download__)
|
||||
self.view.connectTab(lambda: self.__search__(0))
|
||||
self.SIGNAL_REFRESH_VIEW.connect(self.__refresh__)
|
||||
|
||||
def __getSumByResult__(self, stype: Type):
|
||||
if stype == Type.Album:
|
||||
return self._resultData.albums.totalNumberOfItems
|
||||
elif stype == Type.Artist:
|
||||
return self._resultData.artists.totalNumberOfItems
|
||||
elif stype == Type.Track:
|
||||
return self._resultData.tracks.totalNumberOfItems
|
||||
elif stype == Type.Video:
|
||||
return self._resultData.videos.totalNumberOfItems
|
||||
elif stype == Type.Playlist:
|
||||
return self._resultData.playlists.totalNumberOfItems
|
||||
return 0
|
||||
|
||||
def __refresh__(self, stype: str, data):
|
||||
if stype == 'setPageIndex':
|
||||
self.view.setPageIndex(data[0], data[1])
|
||||
elif stype == 'setTableItems':
|
||||
self.view.setTableItems(data[0], data[1], data[2])
|
||||
elif stype == 'setSearchErrmsg':
|
||||
self.view.setSearchErrmsg(data)
|
||||
|
||||
def __startThread__(self, index: int):
|
||||
def __thread_search__(model: SearchModel, index: int):
|
||||
typeIndex = model.view.getSelectedTabIndex()
|
||||
searchText = model.view.getSearchText()
|
||||
|
||||
if aigpy.stringHelper.isNull(searchText):
|
||||
model._lock.release()
|
||||
return
|
||||
|
||||
# search
|
||||
limit = 20
|
||||
offset = (index - 1) * limit
|
||||
stype = tidal_dl.Type(typeIndex)
|
||||
msg, model._resultData = tidalImp.search(searchText, stype, offset, limit)
|
||||
|
||||
if not aigpy.stringHelper.isNull(msg):
|
||||
model.SIGNAL_REFRESH_VIEW.emit('setSearchErrmsg', msg)
|
||||
model._lock.release()
|
||||
return
|
||||
|
||||
# get page index
|
||||
total = model.__getSumByResult__(stype)
|
||||
if total <= 0:
|
||||
model.SIGNAL_REFRESH_VIEW.emit('setSearchErrmsg', 'Search results are empty...')
|
||||
model._lock.release()
|
||||
return
|
||||
|
||||
maxIdx = total // limit + (1 if total % limit > 0 else 0)
|
||||
if index > maxIdx:
|
||||
model._lock.release()
|
||||
return
|
||||
|
||||
# set view
|
||||
model.SIGNAL_REFRESH_VIEW.emit('setPageIndex', (index, maxIdx))
|
||||
model.SIGNAL_REFRESH_VIEW.emit('setTableItems', (stype, offset, model._resultData))
|
||||
model._lock.release()
|
||||
|
||||
_thread.start_new_thread(__thread_search__, (self, index))
|
||||
|
||||
def __search__(self, num: int = 0):
|
||||
if not self._lock.acquire(False):
|
||||
return
|
||||
|
||||
self.view.setSearchErrmsg('')
|
||||
|
||||
if aigpy.string.isNull(self.view.getSearchText()):
|
||||
self._lock.release()
|
||||
return
|
||||
|
||||
index = 1
|
||||
if num != 0:
|
||||
curIdx, curSum = self.view.getPageIndex()
|
||||
if curIdx + num > 1:
|
||||
index = curIdx + num
|
||||
|
||||
self.__startThread__(index)
|
||||
|
||||
def __searchNext__(self):
|
||||
self.__search__(1)
|
||||
|
||||
def __searchPre__(self):
|
||||
self.__search__(-1)
|
||||
|
||||
def __download__(self):
|
||||
if self._resultData is None:
|
||||
self.view.setSearchErrmsg('Please search first...')
|
||||
return
|
||||
|
||||
typeIndex = self.view.getSelectedTabIndex()
|
||||
stype = tidal_dl.Type(typeIndex)
|
||||
|
||||
items = []
|
||||
if stype == Type.Album:
|
||||
items = self._resultData.albums.items
|
||||
elif stype == Type.Track:
|
||||
items = self._resultData.tracks.items
|
||||
elif stype == Type.Video:
|
||||
items = self._resultData.videos.items
|
||||
elif stype == Type.Playlist:
|
||||
items = self._resultData.playlists.items
|
||||
|
||||
index = self.view.getSelectedTableIndex(stype)
|
||||
if index < 0 or index >= len(items):
|
||||
self.view.setSearchErrmsg('Please select one item...')
|
||||
return
|
||||
|
||||
self.SIGNAL_ADD_TASKITEM.emit(items[index])
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : settingsModel.py
|
||||
@Date : 2021/8/17
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
from tidal_gui.view.settingsView import SettingsView
|
||||
from tidal_gui.viewModel.viewModel import ViewModel
|
||||
|
||||
|
||||
class SettingsModel(ViewModel):
|
||||
def __init__(self, parent):
|
||||
super(SettingsModel, self).__init__()
|
||||
self._parent = parent
|
||||
self.view = SettingsView()
|
||||
self.view.connectButton('Logout', self.__logout__)
|
||||
self.view.connectButton('Cancel', self.__cancel__)
|
||||
self.view.connectButton('OK', self.__ok__)
|
||||
|
||||
def __logout__(self):
|
||||
self._parent.show(True)
|
||||
|
||||
def __cancel__(self):
|
||||
pass
|
||||
|
||||
def __ok__(self):
|
||||
pass
|
||||
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : taskItemModel.py
|
||||
@Date : 2021/9/14
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
import _thread
|
||||
import os
|
||||
|
||||
import aigpy.stringHelper
|
||||
from tidal_dl import Type
|
||||
from tidal_dl.model import Album, Track, Video, Playlist
|
||||
|
||||
from tidal_gui.tidalImp import tidalImp
|
||||
from tidal_gui.view.taskItemView import TaskItemView
|
||||
from tidal_gui.viewModel.viewModel import ViewModel
|
||||
from tidal_gui.viewModel.downloadItemModel import DownloadItemModel
|
||||
from PyQt5.QtWidgets import QListWidget, QListWidgetItem, QWidget, QScrollArea, QVBoxLayout, QPushButton
|
||||
import time
|
||||
|
||||
|
||||
class TaskItemModel(ViewModel):
|
||||
def __init__(self, data):
|
||||
super(TaskItemModel, self).__init__()
|
||||
self.view = TaskItemView()
|
||||
self.data = data
|
||||
self.downloadModelList = []
|
||||
self.path = tidalImp.getBasePath(data)
|
||||
|
||||
aigpy.path.mkdirs(self.path)
|
||||
|
||||
if isinstance(data, Album):
|
||||
self.__initAlbum__(data)
|
||||
elif isinstance(data, Track):
|
||||
self.__initTrack__(data)
|
||||
elif isinstance(data, Video):
|
||||
self.__initVideo__(data)
|
||||
elif isinstance(data, Playlist):
|
||||
self.__initPlaylist__(data)
|
||||
|
||||
self.view.connectButton('retry', self.__btnFuncRetry__)
|
||||
self.view.connectButton('cancel', self.__btnFuncCancel__)
|
||||
self.view.connectButton('delete', self.__btnFuncDelete__)
|
||||
self.view.connectButton('open', self.__btnFuncOpen__)
|
||||
|
||||
self.SIGNAL_REFRESH_VIEW.connect(self.__refresh__)
|
||||
|
||||
def __refresh__(self, stype: str, text: str):
|
||||
if stype == "setPic":
|
||||
self.view.setPic(text)
|
||||
elif stype == "addListItem":
|
||||
for index, item in enumerate(text):
|
||||
downItem = DownloadItemModel(index + 1, item, self.path)
|
||||
self.view.addListItem(downItem.view)
|
||||
self.downloadModelList.append(downItem)
|
||||
|
||||
def __btnFuncRetry__(self):
|
||||
for item in self.downloadModelList:
|
||||
item.retry()
|
||||
|
||||
def __btnFuncCancel__(self):
|
||||
for item in self.downloadModelList:
|
||||
item.stopDownload()
|
||||
|
||||
def __btnFuncDelete__(self):
|
||||
for item in self.downloadModelList:
|
||||
item.stopDownload()
|
||||
|
||||
def __btnFuncOpen__(self):
|
||||
if os.path.exists(self.path):
|
||||
os.startfile(self.path)
|
||||
|
||||
def __initAlbum__(self, data: Album):
|
||||
title = data.title
|
||||
desc = f"by {tidalImp.getArtistsNames(data.artists)} " \
|
||||
f"{tidalImp.getDurationString(data.duration)} " \
|
||||
f"Track-{data.numberOfTracks} " \
|
||||
f"Video-{data.numberOfVideos}"
|
||||
self.view.setLabel(title, desc)
|
||||
|
||||
def __thread_func__(model: TaskItemModel, album: Album):
|
||||
cover = tidalImp.getCoverData(album.cover, '1280', '1280')
|
||||
model.SIGNAL_REFRESH_VIEW.emit('setPic', cover)
|
||||
|
||||
msg, tracks, videos = tidalImp.getItems(album.id, Type.Album)
|
||||
if not aigpy.stringHelper.isNull(msg):
|
||||
model.view.setErrmsg(msg)
|
||||
return
|
||||
|
||||
model.SIGNAL_REFRESH_VIEW.emit('addListItem', tracks + videos)
|
||||
print('__initAlbum__')
|
||||
time.sleep(1)
|
||||
|
||||
_thread.start_new_thread(__thread_func__, (self, data))
|
||||
|
||||
def __initTrack__(self, data: Track):
|
||||
pass
|
||||
|
||||
def __initVideo__(self, data: Video):
|
||||
pass
|
||||
|
||||
def __initPlaylist__(self, data: Playlist):
|
||||
pass
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : taskModel.py
|
||||
@Date : 2021/8/17
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
from PyQt5.QtWidgets import QPushButton, QCheckBox, QWidget
|
||||
from tidal_dl import Type
|
||||
from tidal_dl.model import Album, Artist
|
||||
|
||||
from tidal_gui.control.layout import createHBoxLayout
|
||||
from tidal_gui.view.taskView import TaskView, TaskListType
|
||||
from tidal_gui.viewModel.taskItemModel import TaskItemModel
|
||||
from tidal_gui.viewModel.viewModel import ViewModel
|
||||
from tidal_gui.viewModel.downloadItemModel import DownloadItemModel
|
||||
|
||||
|
||||
class TaskModel(ViewModel):
|
||||
def __init__(self):
|
||||
super(TaskModel, self).__init__()
|
||||
self.view = TaskView()
|
||||
|
||||
self._listMap = {}
|
||||
for item in map(lambda typeItem: typeItem.name, TaskListType):
|
||||
self._listMap[item] = []
|
||||
|
||||
self.test()
|
||||
|
||||
def addTaskItem(self, data):
|
||||
item = TaskItemModel(data)
|
||||
self._listMap[TaskListType.Download.name].append(item)
|
||||
self.view.addItemView(TaskListType.Download, item.view)
|
||||
|
||||
def getWaitDownloadItem(self) -> DownloadItemModel:
|
||||
for item in self._listMap[TaskListType.Download.name]:
|
||||
for downItem in item.downloadModelList:
|
||||
if downItem.isInWait():
|
||||
return downItem
|
||||
return None
|
||||
|
||||
def stopDownloadItem(self):
|
||||
for item in self._listMap[TaskListType.Download.name]:
|
||||
for downItem in item.downloadModelList:
|
||||
downItem.stopDownload()
|
||||
|
||||
def test(self):
|
||||
ar = Artist()
|
||||
ar.name = 'yaron'
|
||||
ar.id = 110
|
||||
album = Album()
|
||||
album.artist = None
|
||||
album.artists = [ar]
|
||||
album.audioModes = ['STEREO']
|
||||
album.audioQuality = 'LOSSLESS'
|
||||
album.cover = '8203ff9a-47e3-49a0-9b44-a6dbbe9c9469'
|
||||
album.duration = 1140
|
||||
album.explicit = False
|
||||
album.id = 177748204
|
||||
album.numberOfTracks = 10
|
||||
album.numberOfVideos = 0
|
||||
album.numberOfVolumes = 1
|
||||
album.releaseDate = '2021-03-16'
|
||||
album.title = 'Love'
|
||||
album.type = 'ALBUM'
|
||||
album.version = None
|
||||
|
||||
self.addTaskItem(album)
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@File : viewModel.py
|
||||
@Date : 2021/8/17
|
||||
@Author : Yaronzz
|
||||
@Version : 1.0
|
||||
@Contact : yaronhuang@foxmail.com
|
||||
@Desc :
|
||||
"""
|
||||
from PyQt5.QtCore import QObject
|
||||
from PyQt5.QtCore import QTimer, pyqtSignal
|
||||
|
||||
|
||||
class ViewModel(QObject):
|
||||
SIGNAL_REFRESH_VIEW = pyqtSignal(str, object)
|
||||
|
||||
def __init__(self):
|
||||
super(ViewModel, self).__init__()
|
||||
self.view = None
|
||||
|
||||
def show(self):
|
||||
self.view.show()
|
||||
|
||||
def hide(self):
|
||||
self.view.hide()
|
||||