802 lines
34 KiB
Python
802 lines
34 KiB
Python
import json
|
||
import sys
|
||
from datetime import datetime
|
||
from random import shuffle
|
||
|
||
from PyQt5 import uic
|
||
from PyQt5.QtCore import QSize, Qt, QTimer, QUrl, QStringListModel, pyqtSignal
|
||
from PyQt5.QtGui import QIcon, QPixmap, QDesktopServices
|
||
from PyQt5.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout, QGridLayout, QSpacerItem, QSizePolicy, QWidget, \
|
||
QScroller, QCompleter
|
||
from loguru import logger
|
||
from qfluentwidgets import MSFluentWindow, FluentIcon as fIcon, NavigationItemPosition, TitleLabel, \
|
||
ImageLabel, StrongBodyLabel, HyperlinkLabel, CaptionLabel, PrimaryPushButton, HorizontalFlipView, \
|
||
InfoBar, InfoBarPosition, SplashScreen, MessageBoxBase, TransparentToolButton, BodyLabel, \
|
||
PrimarySplitPushButton, RoundMenu, Action, PipsPager, TextBrowser, CardWidget, \
|
||
IndeterminateProgressRing, ComboBox, ProgressBar, SmoothScrollArea, SearchLineEdit, HyperlinkButton, \
|
||
MessageBox, SwitchButton, SubtitleLabel
|
||
|
||
import conf
|
||
import list_ as l
|
||
import network_thread as nt
|
||
from conf import base_directory
|
||
from file import config_center
|
||
from plugin import p_loader
|
||
from utils import restart, calculate_size
|
||
import platform
|
||
from loguru import logger
|
||
|
||
# 适配高DPI缩放
|
||
if platform.system() == 'Windows' and platform.release() not in ['7', 'XP', 'Vista']:
|
||
QApplication.setHighDpiScaleFactorRoundingPolicy(
|
||
Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
|
||
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
|
||
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
|
||
else:
|
||
logger.warning('不兼容的系统,跳过高DPI标识')
|
||
|
||
CONF_PATH = f"{base_directory}/plugins/plugins_from_pp.json"
|
||
PLAZA_REPO_URL = "https://raw.githubusercontent.com/Class-Widgets/plugin-plaza/"
|
||
PLAZA_REPO_DIR = "https://api.github.com/repos/Class-Widgets/plugin-plaza/contents/Plugins"
|
||
TEST_DOWNLOAD_LINK = "https://dldir1.qq.com/qqfile/qq/PCQQ9.7.17/QQ9.7.17.29225.exe"
|
||
|
||
restart_tips_flag = False # 重启提示
|
||
plugins_data = {} # 仓库插件信息
|
||
local_plugins_version = {} # 本地插件版本
|
||
download_progress = [] # 下载线程
|
||
|
||
installed_plugins = [] # 已安装插件(通过PluginPlaza获取)
|
||
tags = ['示例', '信息展示', '学习', '测试', '工具', '自动化'] # 测试用TAG
|
||
recommend_plugins = ['cw-example-plugin'] # 推荐插件(通过PluginPlaza获取)
|
||
search_items = []
|
||
SELF_PLUGIN_VERSION = config_center.read_conf('Plugin', 'version') # 自身版本号
|
||
SEARCH_FIELDS = ["name", "description", "tag", "author"] # 搜索字段
|
||
|
||
|
||
class TagLink(HyperlinkButton): # 标签链接
|
||
def __init__(self, text, parent=None):
|
||
super().__init__(parent)
|
||
self.parent = parent
|
||
self.tag = text
|
||
self.setText(text)
|
||
self.setIcon(fIcon.SEARCH)
|
||
|
||
self.setFixedHeight(30)
|
||
self.clicked.connect(self.search_tag)
|
||
|
||
def search_tag(self):
|
||
self.parent.search_plugin.setText(self.tag)
|
||
self.parent.search_plugin.searchSignal.emit(self.tag) # 发射搜索信号
|
||
|
||
|
||
class downloadProgressBar(InfoBar): # 下载进度条(创建下载进程)
|
||
def __init__(self, url=TEST_DOWNLOAD_LINK, branch='main', name="Test", parent=None):
|
||
global download_progress
|
||
self.p_name = url.split('/')[4] # repo
|
||
# user = url.split('/')[3]
|
||
self.name = name
|
||
self.url = f'{url}/archive/refs/heads/{branch}.zip'
|
||
|
||
super().__init__(icon=fIcon.DOWNLOAD,
|
||
title='',
|
||
content=f"正在下载 {name} (~ ̄▽ ̄)~)",
|
||
orient=Qt.Horizontal,
|
||
isClosable=False,
|
||
position=InfoBarPosition.TOP,
|
||
duration=-1,
|
||
parent=parent
|
||
)
|
||
self.setCustomBackgroundColor('white', '#202020')
|
||
self.bar = ProgressBar()
|
||
self.bar.setFixedWidth(300)
|
||
self.cancelBtn = HyperlinkLabel()
|
||
self.cancelBtn.setText("取消")
|
||
self.cancelBtn.clicked.connect(self.cancelDownload)
|
||
self.addWidget(self.bar)
|
||
self.addWidget(self.cancelBtn)
|
||
|
||
# 开始下载
|
||
|
||
download_progress.append(self.p_name)
|
||
self.download(self.url)
|
||
|
||
def download(self, url): # 接受下载连接并开始任务
|
||
self.download_thread = nt.DownloadAndExtract(url, self.p_name)
|
||
# self.download_thread = nt.DownloadAndExtract(TEST_DOWNLOAD_LINK, self.p_name)
|
||
self.download_thread.progress_signal.connect(lambda progress: self.bar.setValue(int(progress))) # 下载进度
|
||
self.download_thread.status_signal.connect(self.detect_status) # 判断状态
|
||
self.download_thread.start()
|
||
|
||
def cancelDownload(self):
|
||
global download_progress
|
||
download_progress.remove(self.p_name)
|
||
self.download_thread.stop()
|
||
self.download_thread.deleteLater()
|
||
self.close()
|
||
|
||
def detect_status(self, status):
|
||
if status == "DOWNLOADING":
|
||
self.content = f"正在下载 {self.name} (~ ̄▽ ̄)~)"
|
||
elif status == "EXTRACTING":
|
||
self.content = f"正在解压 {self.name} ( •̀ ω •́ )✧)"
|
||
elif status == "DONE":
|
||
self.download_finished()
|
||
elif status.startswith("ERROR"):
|
||
self.download_error(status[6:])
|
||
else:
|
||
pass
|
||
|
||
def download_finished(self):
|
||
global download_progress
|
||
download_progress.remove(self.p_name)
|
||
add2save_plugin(self.p_name) # 保存到配置
|
||
self.download_thread.finished.emit()
|
||
self.download_thread.deleteLater()
|
||
|
||
InfoBar.success(
|
||
title='下载成功!',
|
||
content=f"下载 {self.name} 成功!",
|
||
orient=Qt.Horizontal,
|
||
isClosable=True,
|
||
position=InfoBarPosition.TOP,
|
||
duration=5000,
|
||
parent=self.parent()
|
||
)
|
||
if not restart_tips_flag: # 重启提示
|
||
self.parent().restart_tips()
|
||
self.close()
|
||
|
||
def download_error(self, error_info):
|
||
global download_progress
|
||
download_progress.remove(self.p_name)
|
||
InfoBar.error(
|
||
title='下载失败(っ °Д °;)っ',
|
||
content=f"{error_info}",
|
||
orient=Qt.Horizontal,
|
||
isClosable=True,
|
||
position=InfoBarPosition.TOP,
|
||
duration=5000,
|
||
parent=self.parent()
|
||
)
|
||
self.close()
|
||
|
||
|
||
def install_plugin(parent, p_name, data):
|
||
plugin_ver = str(data.get('plugin_ver'))
|
||
if plugin_ver != SELF_PLUGIN_VERSION: # 插件版本不匹配
|
||
if plugin_ver > SELF_PLUGIN_VERSION:
|
||
content = (f'此插件版本({plugin_ver})高于当前设备中 Class Widgets 兼容的插件版本({SELF_PLUGIN_VERSION});\n'
|
||
f'请更新 Class Widgets 后再尝试安装此插件。')
|
||
else:
|
||
content = (f'此插件版本({plugin_ver})低于当前设备中 Class Widgets 兼容的插件版本({SELF_PLUGIN_VERSION});\n'
|
||
f'可能是插件缺乏维护,请联系插件作者更新插件,或在社区(GitHub、QQ群)中提出问题。')
|
||
|
||
cc = MessageBox(
|
||
"本插件不兼容当前版本的 Class Widgets",
|
||
f"{content}\n\n不建议安装此插件,否则将出现不可预料(包括崩溃、闪退等故障)的问题。",
|
||
parent
|
||
) # 兼容性检查窗口
|
||
cc.yesButton.setText("取消安装")
|
||
cc.cancelButton.setText("强制安装(不建议)")
|
||
if cc.exec(): # 取消安装
|
||
return False
|
||
|
||
if p_name not in download_progress: # 如果正在下载
|
||
url = data.get("url")
|
||
branch = data.get("branch")
|
||
title = data.get("name")
|
||
|
||
di = downloadProgressBar(
|
||
url=f"{url}",
|
||
branch=branch,
|
||
name=title,
|
||
parent=parent
|
||
)
|
||
di.show()
|
||
return True
|
||
return False
|
||
|
||
|
||
class PluginDetailPage(MessageBoxBase): # 插件详情页面
|
||
def __init__(self, icon, title, content, tag, version, author, url, data=None, parent=None):
|
||
super().__init__(parent)
|
||
self.data = data
|
||
self.branch = data.get("branch")
|
||
self.title = title
|
||
self.parent = parent
|
||
self.url = url
|
||
self.p_name = url.split('/')[-1] # repo
|
||
author_url = '/'.join(url.rsplit('/', 2)[:-1])
|
||
self.init_ui()
|
||
self.download_readme()
|
||
scroll_area_widget = self.findChild(QVBoxLayout, 'verticalLayout_9')
|
||
|
||
self.iconWidget = self.findChild(ImageLabel, 'pluginIcon')
|
||
self.iconWidget.setImage(icon)
|
||
self.iconWidget.setFixedSize(100, 100)
|
||
self.iconWidget.setBorderRadius(8, 8, 8, 8)
|
||
|
||
self.titleLabel = self.findChild(TitleLabel, 'titleLabel') # 标题
|
||
self.titleLabel.setText(title)
|
||
|
||
self.contentLabel = self.findChild(CaptionLabel, 'descLabel') # 描述
|
||
self.contentLabel.setText(content)
|
||
|
||
self.tagLabel = self.findChild(HyperlinkLabel, 'tagButton') # tag
|
||
self.tagLabel.setText(tag)
|
||
|
||
self.versionLabel = self.findChild(BodyLabel, 'versionLabel') # 版本
|
||
self.versionLabel.setText(version)
|
||
|
||
self.authorLabel = self.findChild(HyperlinkLabel, 'authorButton') # 作者
|
||
self.authorLabel.setText(author)
|
||
self.authorLabel.setUrl(author_url)
|
||
|
||
self.openGitHub = self.findChild(TransparentToolButton, 'openGitHub') # 打开连接
|
||
self.openGitHub.setIcon(fIcon.LINK)
|
||
self.openGitHub.setIconSize(QSize(18, 18))
|
||
self.openGitHub.clicked.connect(lambda: QDesktopServices.openUrl(QUrl(url)))
|
||
|
||
self.installButton = self.findChild(PrimarySplitPushButton, 'installButton')
|
||
self.installButton.setText(" 安装 ")
|
||
self.installButton.setIcon(fIcon.DOWNLOAD)
|
||
self.installButton.clicked.connect(self.install)
|
||
|
||
if self.p_name in download_progress: # 如果正在下载
|
||
self.installButton.setText(" 安装中 ")
|
||
self.installButton.setEnabled(False)
|
||
if self.p_name in installed_plugins: # 如果已安装
|
||
self.installButton.setText(" 已安装 ")
|
||
self.installButton.setEnabled(False)
|
||
|
||
if self.p_name in local_plugins_version: # 如果本地版本低于仓库版本
|
||
print(local_plugins_version[self.p_name], version)
|
||
if local_plugins_version[self.p_name] < version:
|
||
self.installButton.setText("更新")
|
||
self.installButton.setIcon(fIcon.SYNC)
|
||
self.installButton.setEnabled(True)
|
||
|
||
menu = RoundMenu(parent=self.installButton)
|
||
menu.addActions([
|
||
Action(fIcon.DOWNLOAD, "为 Class Widgets 安装", triggered=self.install),
|
||
Action(fIcon.LINK, "下载到本地",
|
||
triggered=lambda: QDesktopServices.openUrl(QUrl(f"{url}/releases/latest")))
|
||
])
|
||
self.installButton.setFlyout(menu)
|
||
|
||
self.readmePage = TextBrowser(self)
|
||
self.readmePage.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||
self.readmePage.setReadOnly(True)
|
||
scroll_area_widget.addWidget(self.readmePage)
|
||
|
||
def install(self):
|
||
if install_plugin(self.parent, self.p_name, self.data):
|
||
self.installButton.setText(" 安装中 ")
|
||
self.installButton.setEnabled(False)
|
||
|
||
def download_readme(self):
|
||
def display_readme(markdown_text):
|
||
self.readmePage.setMarkdown(markdown_text)
|
||
|
||
if self.data is None:
|
||
self.download_thread = nt.getReadme(f"{replace_to_file_server(self.url)}/README.md")
|
||
else:
|
||
self.download_thread = nt.getReadme(f"{replace_to_file_server(self.url, self.data['branch'])}/README.md")
|
||
self.download_thread.html_signal.connect(display_readme)
|
||
self.download_thread.start()
|
||
|
||
def init_ui(self):
|
||
# 加载ui文件
|
||
self.temp_widget = QWidget()
|
||
uic.loadUi(f'{base_directory}/view/pp/plugin_detail.ui', self.temp_widget)
|
||
self.viewLayout.addWidget(self.temp_widget)
|
||
self.viewLayout.setContentsMargins(0, 0, 0, 0)
|
||
# 隐藏原有按钮
|
||
self.yesButton.hide()
|
||
self.cancelButton.hide()
|
||
self.buttonGroup.hide()
|
||
|
||
# 自定关闭按钮
|
||
self.closeButton = self.findChild(TransparentToolButton, 'closeButton')
|
||
self.closeButton.setIcon(fIcon.CLOSE)
|
||
self.closeButton.clicked.connect(self.close)
|
||
|
||
self.widget.setMinimumWidth(875)
|
||
self.widget.setMinimumHeight(625)
|
||
|
||
|
||
class PluginCard_Horizontal(CardWidget): # 插件卡片(横向)
|
||
def __init__(
|
||
self, icon='img/plaza/plugin_pre.png', title='Plugin Name', content='Description...', tag='Unknown',
|
||
version='1.0.0', author="CW Support",
|
||
url="https://github.com/RinLit-233-shiroko/cw-example-plugin", data=None, parent=None):
|
||
super().__init__(parent)
|
||
self.icon = icon
|
||
self.title = title
|
||
self.plugin_ver = data.get('plugin_ver')
|
||
self.parent = parent
|
||
self.tag = tag
|
||
self.branch = data.get("branch")
|
||
self.url = url
|
||
self.p_name = url.split('/')[-1] # repo
|
||
self.data = data
|
||
author_url = '/'.join(self.url.rsplit('/', 2)[:-1])
|
||
|
||
self.iconWidget = ImageLabel(icon) # 插件图标
|
||
self.titleLabel = StrongBodyLabel(title, self) # 插件名
|
||
self.versionLabel = CaptionLabel(version, self) # 插件版本
|
||
self.authorLabel = HyperlinkLabel() # 插件作者
|
||
self.contentLabel = CaptionLabel(content, self) # 插件描述
|
||
self.installButton = PrimaryPushButton()
|
||
|
||
# layout
|
||
self.hBoxLayout = QHBoxLayout()
|
||
self.hBoxLayout_Title = QHBoxLayout()
|
||
self.hBoxLayout_Author = QHBoxLayout()
|
||
self.vBoxLayout = QVBoxLayout()
|
||
|
||
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||
self.setFixedHeight(110)
|
||
self.setMinimumWidth(250)
|
||
self.authorLabel.setText(author)
|
||
self.authorLabel.setUrl(author_url)
|
||
self.authorLabel.setSizePolicy(QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Fixed)
|
||
self.iconWidget.setFixedSize(84, 84)
|
||
self.iconWidget.setBorderRadius(5, 5, 5, 5) # 圆角
|
||
self.contentLabel.setTextColor("#606060", "#d2d2d2")
|
||
self.contentLabel.setWordWrap(True)
|
||
self.versionLabel.setTextColor("#999999", "#999999")
|
||
self.titleLabel.setSizePolicy(QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Fixed)
|
||
|
||
self.installButton.setText("安装")
|
||
self.installButton.setMaximumSize(100, 36)
|
||
self.installButton.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||
self.installButton.setIcon(fIcon.DOWNLOAD)
|
||
self.installButton.clicked.connect(self.install)
|
||
|
||
if self.p_name in installed_plugins: # 如果已安装
|
||
self.installButton.setText("已安装")
|
||
self.installButton.setEnabled(False)
|
||
|
||
if self.p_name in local_plugins_version: # 如果本地版本低于仓库版本
|
||
print(local_plugins_version[self.p_name], version)
|
||
if local_plugins_version[self.p_name] < version:
|
||
self.installButton.setText("更新")
|
||
self.installButton.setIcon(fIcon.SYNC)
|
||
self.installButton.setEnabled(True)
|
||
|
||
self.hBoxLayout.setContentsMargins(20, 11, 11, 11)
|
||
self.hBoxLayout.setSpacing(15)
|
||
self.hBoxLayout.addWidget(self.iconWidget)
|
||
|
||
self.blank = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
|
||
|
||
self.vBoxLayout.setContentsMargins(0, 5, 0, 5)
|
||
self.vBoxLayout.setSpacing(0)
|
||
self.vBoxLayout.addLayout(self.hBoxLayout_Title)
|
||
self.vBoxLayout.addLayout(self.hBoxLayout_Author)
|
||
self.vBoxLayout.addItem(self.blank)
|
||
self.vBoxLayout.addWidget(self.contentLabel, 0, Qt.AlignmentFlag.AlignTop)
|
||
self.vBoxLayout.setAlignment(Qt.AlignmentFlag.AlignVCenter)
|
||
|
||
self.hBoxLayout.addLayout(self.vBoxLayout)
|
||
self.hBoxLayout.addWidget(self.installButton)
|
||
self.setLayout(self.hBoxLayout)
|
||
|
||
self.hBoxLayout_Title.setSpacing(12)
|
||
self.hBoxLayout_Title.addWidget(self.titleLabel, 0, Qt.AlignmentFlag.AlignVCenter)
|
||
self.hBoxLayout_Title.addWidget(self.versionLabel, 0, Qt.AlignmentFlag.AlignVCenter)
|
||
|
||
self.hBoxLayout_Author.addWidget(self.authorLabel, 0, Qt.AlignmentFlag.AlignLeft)
|
||
|
||
def install(self):
|
||
install_plugin(self.parent, self.p_name, self.data)
|
||
|
||
def set_img(self, img):
|
||
try:
|
||
self.icon = img
|
||
self.iconWidget.setImage(img)
|
||
self.iconWidget.setFixedSize(84, 84)
|
||
except Exception as e:
|
||
logger.error(f"设置插件图片失败: {e}")
|
||
|
||
def show_detail(self):
|
||
w = PluginDetailPage(
|
||
icon=self.icon, title=self.title, content=self.contentLabel.text(),
|
||
tag=self.tag, version=self.versionLabel.text(), author=self.authorLabel.text(),
|
||
url=self.url, data=self.data, parent=self.parent
|
||
)
|
||
w.exec()
|
||
|
||
|
||
class PluginPlaza(MSFluentWindow):
|
||
closed = pyqtSignal()
|
||
|
||
def __init__(self):
|
||
super().__init__()
|
||
self.splashScreen = None
|
||
global installed_plugins
|
||
try:
|
||
with open(CONF_PATH, 'r', encoding='utf-8') as file:
|
||
installed_plugins = json.load(file).get('plugins')
|
||
# 校验
|
||
for plugin in installed_plugins:
|
||
if plugin not in p_loader.plugins_name:
|
||
logger.warning(f"已在插件广场安装的插件 {plugin} 未找到,可能已遭删除")
|
||
installed_plugins.remove(plugin)
|
||
except Exception as e:
|
||
logger.error(f"读取已安装的插件失败: {e}")
|
||
try:
|
||
self.homeInterface = uic.loadUi(f'{base_directory}/view/pp/home.ui') # 首页
|
||
self.homeInterface.setObjectName("homeInterface")
|
||
self.latestsInterface = uic.loadUi(f'{base_directory}/view/pp/latests.ui') # 最新更新
|
||
self.latestsInterface.setObjectName("latestInterface")
|
||
self.settingsInterface = uic.loadUi(f'{base_directory}/view/pp/settings.ui') # 设置
|
||
self.settingsInterface.setObjectName("settingsInterface")
|
||
self.searchInterface = uic.loadUi(f'{base_directory}/view/pp/search.ui') # 搜索
|
||
self.searchInterface.setObjectName("searchInterface")
|
||
|
||
load_local_plugins_version() # 加载本地插件版本
|
||
self.init_nav()
|
||
self.init_window()
|
||
self.get_pp_data()
|
||
self.get_banner_img()
|
||
except Exception as e:
|
||
logger.error(f'初始化插件广场时发生错误:{e}')
|
||
|
||
def load_all_interface(self):
|
||
self.setup_homeInterface()
|
||
self.setup_latestInterface()
|
||
self.setup_settingsInterface()
|
||
self.setup_searchInterface()
|
||
|
||
def setup_latestInterface(self): # 初始化最新更新
|
||
latest_scroll = self.latestsInterface.findChild(SmoothScrollArea, 'latest_scroll')
|
||
QScroller.grabGesture(latest_scroll.viewport(), QScroller.LeftMouseButtonGesture)
|
||
|
||
def setup_searchInterface(self): # 初始化搜索
|
||
search_scroll = self.searchInterface.findChild(SmoothScrollArea, 'search_scroll')
|
||
|
||
def search(keyword): # 搜索
|
||
if keyword == '/all':
|
||
return plugins_data
|
||
|
||
result = {}
|
||
for key, value in plugins_data.items():
|
||
if any(keyword.lower() in str(value.get(field, "")).lower() for field in SEARCH_FIELDS):
|
||
result[key] = value
|
||
return result
|
||
|
||
def clear_results():
|
||
for i in reversed(range(self.search_plugin_grid.count())):
|
||
widget = self.search_plugin_grid.itemAt(i).widget()
|
||
if widget:
|
||
widget.setParent(None) # 移除控件
|
||
widget.destroy() # 销毁控件
|
||
|
||
def search_plugins(): # 搜索插件
|
||
if not plugins_data:
|
||
return
|
||
clear_results()
|
||
|
||
if self.search_plugin.text():
|
||
def set_plugin_image(plugin_card, data):
|
||
pixmap = QPixmap()
|
||
pixmap.loadFromData(data)
|
||
plugin_card.set_img(pixmap)
|
||
|
||
keyword = self.search_plugin.text()
|
||
print(f'结果:{search(keyword)}') # 结果
|
||
plugin_num = 0 # 计数
|
||
for key, data in search(keyword).items():
|
||
plugin_card = PluginCard_Horizontal(title=data['name'], content=data['description'],
|
||
tag=data['tag'], version=data['version'], url=data['url'],
|
||
author=data['author'], data=data, parent=self)
|
||
plugin_card.clicked.connect(plugin_card.show_detail) # 点击事件
|
||
|
||
# 启动线程加载图片
|
||
image_thread = nt.getImg(f"{replace_to_file_server(data['url'], data['branch'])}/icon.png")
|
||
image_thread.repo_signal.connect(
|
||
lambda img_data, card=plugin_card: set_plugin_image(card, img_data))
|
||
image_thread.start()
|
||
|
||
self.search_plugin_grid.addWidget(plugin_card, plugin_num // 2, plugin_num % 2) # 排列
|
||
plugin_num += 1
|
||
|
||
self.search_plugin_grid = self.searchInterface.findChild(QGridLayout, 'search_plugin_grid') # 插件表格
|
||
self.tags_layout = self.searchInterface.findChild(QGridLayout, 'tags_layout') # tag 布局
|
||
self.search_plugin = self.searchInterface.findChild(SearchLineEdit, 'search_plugin')
|
||
self.search_plugin.searchSignal.connect(search_plugins)
|
||
self.search_plugin.returnPressed.connect(search_plugins)
|
||
self.search_plugin.clearSignal.connect(clear_results)
|
||
self.search_completer = QCompleter(search_items, self.search_plugin)
|
||
# 设置显示的选项数
|
||
self.search_completer.setMaxVisibleItems(10)
|
||
self.search_completer.setFilterMode(Qt.MatchContains) # 内容匹配
|
||
self.search_completer.setCaseSensitivity(Qt.CaseInsensitive) # 不区分大小写
|
||
self.search_completer.activated.connect(search_plugins)
|
||
self.search_plugin.setCompleter(self.search_completer)
|
||
|
||
QScroller.grabGesture(search_scroll.viewport(), QScroller.LeftMouseButtonGesture)
|
||
|
||
def setup_settingsInterface(self): # 初始化设置
|
||
# 选择代理
|
||
select_mirror = self.settingsInterface.findChild(ComboBox, 'select_proxy')
|
||
select_mirror.addItems(nt.mirror_list)
|
||
select_mirror.setCurrentIndex(nt.mirror_list.index(config_center.read_conf('Plugin', 'mirror')))
|
||
select_mirror.currentIndexChanged.connect(
|
||
lambda: config_center.write_conf('Plugin', 'mirror', select_mirror.currentText()))
|
||
|
||
# 开关自动启用插件
|
||
auto_enable_plugin = self.settingsInterface.findChild(SwitchButton, 'auto_enable_plugin')
|
||
auto_enable_plugin.setChecked(int(config_center.read_conf('Plugin', 'auto_enable_plugin')))
|
||
auto_enable_plugin.checkedChanged.connect(
|
||
lambda: config_center.write_conf('Plugin', 'auto_enable_plugin', int(auto_enable_plugin.isChecked()))
|
||
)
|
||
|
||
def setup_homeInterface(self): # 初始化首页
|
||
# 标题和副标题
|
||
home_scroll = self.homeInterface.findChild(SmoothScrollArea, 'home_scroll')
|
||
time_today_label = self.homeInterface.findChild(TitleLabel, 'time_today_label')
|
||
time_today_label.setText(f"{datetime.now().month}月{datetime.now().day}日 {l.week[datetime.now().weekday()]}")
|
||
|
||
# Banner
|
||
self.banner_view = self.homeInterface.findChild(HorizontalFlipView, 'banner_view')
|
||
self.banner_view.setAspectRatioMode(Qt.AspectRatioMode.KeepAspectRatio)
|
||
self.banner_view.setItemSize(QSize(900, 450)) # 设置图片大小(banner图片尺寸比)
|
||
self.banner_view.setBorderRadius(8)
|
||
self.banner_view.setSpacing(5)
|
||
self.banner_view.clicked.connect(self.open_banner_link)
|
||
|
||
self.auto_play_timer = QTimer(self) # 自动轮播
|
||
self.auto_play_timer.timeout.connect(lambda: self.switch_banners())
|
||
self.auto_play_timer.setInterval(2500)
|
||
|
||
# 翻页
|
||
self.banner_pager = self.homeInterface.findChild(PipsPager, 'banner_pager')
|
||
self.banner_pager.setVisibleNumber(5)
|
||
self.banner_pager.currentIndexChanged.connect(
|
||
lambda: (self.banner_view.scrollToIndex(self.banner_pager.currentIndex()),
|
||
self.auto_play_timer.stop(),
|
||
self.auto_play_timer.start(2500))
|
||
)
|
||
QScroller.grabGesture(home_scroll.viewport(), QScroller.LeftMouseButtonGesture)
|
||
|
||
def open_banner_link(self):
|
||
if not hasattr(self, 'img_list'):
|
||
QDesktopServices.openUrl(QUrl(
|
||
'https://www.yuque.com/rinlit/class-widgets_help/ez4vv7tv8wikxc0s#Se2Bb'
|
||
))
|
||
return False # 没有图片
|
||
|
||
if self.img_list[self.banner_view.currentIndex()] in self.banners_data:
|
||
if not self.banners_data[self.img_list[self.banner_view.currentIndex()]]['link']:
|
||
return False # 无链接
|
||
QDesktopServices.openUrl(QUrl(
|
||
self.banners_data[self.img_list[self.banner_view.currentIndex()]]['link']
|
||
))
|
||
|
||
def set_tags_data(self, data):
|
||
global tags, search_items, recommend_plugins
|
||
rec_data = {}
|
||
if data:
|
||
tags = data.get('tags')
|
||
recommend_plugins = data.get('recommend_plugin')
|
||
shuffle(tags) # 随机
|
||
for tag in tags:
|
||
search_items.append(tag)
|
||
self.search_completer.setModel(QStringListModel(search_items)) # 设置搜索提示
|
||
tag_num = 0 # 计数
|
||
for tag in tags[:6]:
|
||
tag_link = TagLink(tag, self)
|
||
self.tags_layout.addWidget(tag_link, tag_num // 3, tag_num % 3) # 排列
|
||
tag_num += 1
|
||
|
||
for key, data in plugins_data.items():
|
||
if key in recommend_plugins:
|
||
rec_data[key] = data
|
||
self.load_plugins(rec_data, 'home')
|
||
|
||
def load_plugins(self, p_data, page):
|
||
global search_items
|
||
|
||
for plugin in p_data.values(): # 遍历插件数据
|
||
search_items.append(plugin['name'])
|
||
if plugin['author'] not in search_items:
|
||
search_items.append(plugin['author'])
|
||
self.search_completer.setModel(QStringListModel(search_items)) # 设置搜索提示
|
||
|
||
def set_plugin_image(plugin_card, data):
|
||
pixmap = QPixmap()
|
||
pixmap.loadFromData(data)
|
||
plugin_card.set_img(pixmap)
|
||
|
||
if page == 'latest':
|
||
self.plugin_grid = self.latestsInterface.findChild(QGridLayout, 'all_plugin_grid') # 插件表格
|
||
elif page == 'home':
|
||
self.plugin_grid = self.homeInterface.findChild(QGridLayout, 'rec_plugin_grid') # 插件表格
|
||
else:
|
||
self.plugin_grid = self.latestsInterface.findChild(QGridLayout, 'all_plugin_grid') # 插件表格
|
||
plugin_num = 0 # 计数
|
||
|
||
for plugin, data in p_data.items(): # 遍历插件数据
|
||
plugin_card = PluginCard_Horizontal(title=data['name'], content=data['description'],
|
||
tag=data['tag'], version=data['version'], url=data['url'],
|
||
author=data['author'], data=data, parent=self)
|
||
plugin_card.clicked.connect(plugin_card.show_detail) # 点击事件
|
||
|
||
# 启动线程加载图片
|
||
image_thread = nt.getImg(f"{replace_to_file_server(data['url'], data['branch'])}/icon.png")
|
||
image_thread.repo_signal.connect(lambda img_data, card=plugin_card: set_plugin_image(card, img_data))
|
||
image_thread.start()
|
||
|
||
self.plugin_grid.addWidget(plugin_card, plugin_num // 2, plugin_num % 2) # 排列
|
||
plugin_num += 1
|
||
|
||
self.homeInterface.findChild(IndeterminateProgressRing, 'load_plugin_progress').hide()
|
||
|
||
def get_banner_img(self):
|
||
def display_banner(data, index=0):
|
||
if index == 0:
|
||
self.auto_play_timer.start()
|
||
if data:
|
||
pixmap = QPixmap()
|
||
pixmap.loadFromData(data)
|
||
self.banner_view.setItemImage(index, pixmap)
|
||
self.splashScreen.hide()
|
||
|
||
def get_banner(data=dict):
|
||
try:
|
||
if 'error' not in data:
|
||
self.banners_data = data
|
||
self.img_list = self.img_links = list(data.keys())
|
||
self.img_links = [f'https://raw.githubusercontent.com/Class-Widgets/plugin-plaza/main/Banner/'
|
||
f'{img}.png' for img in self.img_links]
|
||
self.banner_pager.setPageNumber(len(data))
|
||
banner_placeholders = ["img/plaza/banner_pre.png" for _ in range(len(data))]
|
||
self.banner_view.addImages(banner_placeholders)
|
||
else:
|
||
error_info = data.get("error", "未知错误")
|
||
logger.error(f'PluginPlaza 无法联网,错误:{error_info}')
|
||
self.findChild(BodyLabel, 'tips').setText(f'错误原因:{error_info}')
|
||
self.banner_view.addImage("img/plaza/banner_network-failed.png")
|
||
self.banner_view.addImage("img/plaza/banner_network-failed.png")
|
||
self.splashScreen.hide()
|
||
self.homeInterface.findChild(SubtitleLabel, 'SubtitleLabel_3').hide() # 隐藏副标题
|
||
return
|
||
|
||
# 定义一个内部函数来启动下一个线程
|
||
def start_next_banner(index):
|
||
if index < len(data):
|
||
self.banner_thread = nt.getImg(self.img_links[index])
|
||
self.banner_thread.repo_signal.connect(lambda data: display_banner(data, index))
|
||
self.banner_thread.repo_signal.connect(lambda: start_next_banner(index + 1)) # 连接完成信号
|
||
self.banner_thread.start()
|
||
|
||
start_next_banner(0) # 启动第一个线程
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取Banner失败:{e}")
|
||
|
||
self.banner_list_thread = nt.getRepoFileList()
|
||
self.banner_list_thread.repo_signal.connect(get_banner)
|
||
self.banner_list_thread.start()
|
||
|
||
def restart_tips(self):
|
||
global restart_tips_flag
|
||
restart_tips_flag = True
|
||
w = InfoBar.info(
|
||
title='需要重启',
|
||
content='若要应用插件配置,需重启 Class Widgets',
|
||
orient=Qt.Horizontal,
|
||
isClosable=True,
|
||
position=InfoBarPosition.TOP,
|
||
duration=-1,
|
||
parent=self
|
||
)
|
||
restart_btn = HyperlinkLabel('现在重启')
|
||
restart_btn.clicked.connect(restart)
|
||
w.addWidget(restart_btn)
|
||
w.show()
|
||
|
||
def get_pp_data(self):
|
||
global plugins_data
|
||
def callback(data):
|
||
global plugins_data
|
||
plugins_data = data # 保存插件数据
|
||
self.load_plugins(data, 'latest')
|
||
self.get_tags_data()
|
||
|
||
self.get_plugin_list_thread = nt.getPluginInfo()
|
||
self.get_plugin_list_thread.repo_signal.connect(callback)
|
||
self.get_plugin_list_thread.start()
|
||
|
||
def get_tags_data(self):
|
||
self.get_tags_list_thread = nt.getTags()
|
||
self.get_tags_list_thread.repo_signal.connect(self.set_tags_data)
|
||
self.get_tags_list_thread.start()
|
||
|
||
def switch_banners(self): # 切换Banner
|
||
if self.banner_view.currentIndex() == len(self.img_list) - 1:
|
||
self.banner_view.scrollToIndex(0)
|
||
self.banner_pager.setCurrentIndex(0)
|
||
else:
|
||
self.banner_view.scrollNext()
|
||
self.banner_pager.setCurrentIndex(self.banner_view.currentIndex())
|
||
|
||
def init_nav(self):
|
||
self.addSubInterface(self.homeInterface, fIcon.HOME, '首页', fIcon.HOME_FILL)
|
||
self.addSubInterface(self.latestsInterface, fIcon.LIBRARY, '分类', fIcon.LIBRARY_FILL)
|
||
self.addSubInterface(
|
||
self.searchInterface, fIcon.SEARCH, '搜索', position=NavigationItemPosition.BOTTOM
|
||
)
|
||
self.addSubInterface(
|
||
self.settingsInterface, fIcon.SETTING, '设置', fIcon.SETTING, position=NavigationItemPosition.BOTTOM
|
||
)
|
||
|
||
def init_window(self):
|
||
self.load_all_interface()
|
||
self.init_font()
|
||
|
||
self.setMinimumWidth(850)
|
||
self.setMinimumHeight(500)
|
||
self.setWindowTitle('插件广场')
|
||
self.setWindowIcon(QIcon(f'{base_directory}/img/pp_favicon.png'))
|
||
|
||
# 设置窗口大小
|
||
size, pos = calculate_size()
|
||
|
||
self.move(pos[0], pos[1])
|
||
self.resize(size[0], size[1])
|
||
|
||
# 启动屏幕
|
||
self.splashScreen = SplashScreen(self.windowIcon(), self)
|
||
self.splashScreen.setIconSize(QSize(102, 102))
|
||
self.show()
|
||
|
||
def init_font(self): # 设置字体
|
||
self.setStyleSheet("""QLabel {
|
||
font-family: 'Microsoft YaHei';
|
||
}""")
|
||
|
||
def closeEvent(self, event):
|
||
self.closed.emit()
|
||
event.accept()
|
||
|
||
|
||
def add2save_plugin(p_name): # 保存已安装插件
|
||
global installed_plugins
|
||
installed_plugins.append(p_name)
|
||
try:
|
||
with open(CONF_PATH, 'r+', encoding='utf-8') as f:
|
||
if p_name not in json.load(f)['plugins']:
|
||
f.seek(0) # 指针指向开头
|
||
json.dump({"plugins": installed_plugins}, f, ensure_ascii=False, indent=4)
|
||
f.truncate() # 截断文件
|
||
except Exception as e:
|
||
logger.error(f"保存已安装插件失败:{e}")
|
||
|
||
|
||
def replace_to_file_server(url, branch='main'):
|
||
return (f'{url.replace("https://github.com/", "https://raw.githubusercontent.com/")}'
|
||
f'/{branch}')
|
||
|
||
|
||
def load_local_plugins_version():
|
||
global local_plugins_version
|
||
for plugin in installed_plugins:
|
||
try:
|
||
with open(f"plugins/{plugin}/plugin.json", 'r', encoding='utf-8') as f:
|
||
data = json.load(f)
|
||
local_plugins_version[plugin] = data['version']
|
||
except Exception as e:
|
||
logger.error(f"加载本地插件版本失败:{e}")
|
||
print(local_plugins_version)
|
||
|
||
|
||
if __name__ == '__main__':
|
||
app = QApplication(sys.argv)
|
||
pp = PluginPlaza()
|
||
pp.show()
|
||
sys.exit(app.exec())
|