镜像自地址
https://github.com/a13zhy/a13rollcall.git
已同步 2026-10-20 11:27:47 +08:00
1052 行
43 KiB
Python
1052 行
43 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
A13课堂点名系统 - 可视化插件打包工具
|
||
将 Python 插件代码打包成 .arcx 格式,支持多页面配置、默认设置、元数据编辑
|
||
"""
|
||
|
||
import sys
|
||
import os
|
||
import json
|
||
import shutil
|
||
import base64
|
||
import zipfile
|
||
import tempfile
|
||
from datetime import datetime
|
||
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||
QLabel, QPushButton, QLineEdit, QTextEdit, QFileDialog,
|
||
QMessageBox, QTabWidget, QCheckBox, QSpinBox, QComboBox,
|
||
QListWidget, QListWidgetItem, QGroupBox, QFormLayout,
|
||
QScrollArea, QFrame, QSplitter, QProgressBar, QStackedWidget,
|
||
QSizePolicy, QGridLayout)
|
||
from PyQt5.QtCore import Qt, QSize, QTimer, pyqtSignal
|
||
from PyQt5.QtGui import QFont, QIcon, QColor, QPalette, QPainter, QBrush, QPen
|
||
|
||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||
|
||
|
||
class StepIndicator(QWidget):
|
||
"""步骤指示器"""
|
||
step_clicked = pyqtSignal(int)
|
||
|
||
def __init__(self, steps, parent=None):
|
||
super().__init__(parent)
|
||
self.steps = steps
|
||
self.current_step = 0
|
||
self.completed_steps = set()
|
||
self.setFixedHeight(80)
|
||
self.setMinimumWidth(600)
|
||
|
||
def set_current_step(self, step):
|
||
self.current_step = step
|
||
self.update()
|
||
|
||
def set_completed(self, step):
|
||
self.completed_steps.add(step)
|
||
self.update()
|
||
|
||
def paintEvent(self, event):
|
||
painter = QPainter(self)
|
||
painter.setRenderHint(QPainter.Antialiasing)
|
||
|
||
w = self.width()
|
||
h = self.height()
|
||
step_count = len(self.steps)
|
||
step_width = w / step_count
|
||
circle_radius = 18
|
||
line_y = h // 2 - 10
|
||
|
||
for i in range(step_count - 1):
|
||
x1 = int(i * step_width + step_width / 2 + circle_radius + 5)
|
||
x2 = int((i + 1) * step_width + step_width / 2 - circle_radius - 5)
|
||
if i in self.completed_steps:
|
||
painter.setPen(QPen(QColor("#107c10"), 3))
|
||
else:
|
||
painter.setPen(QPen(QColor("#e0e0e0"), 3))
|
||
painter.drawLine(x1, line_y, x2, line_y)
|
||
|
||
for i, step_name in enumerate(self.steps):
|
||
cx = int(i * step_width + step_width / 2)
|
||
cy = line_y
|
||
|
||
if i in self.completed_steps:
|
||
painter.setBrush(QBrush(QColor("#107c10")))
|
||
painter.setPen(Qt.NoPen)
|
||
painter.drawEllipse(cx - circle_radius, cy - circle_radius, circle_radius * 2, circle_radius * 2)
|
||
painter.setPen(QPen(QColor("white"), 3))
|
||
painter.drawLine(cx - 6, cy, cx - 2, cy + 5)
|
||
painter.drawLine(cx - 2, cy + 5, cx + 7, cy - 5)
|
||
elif i == self.current_step:
|
||
painter.setBrush(QBrush(QColor("#0067c0")))
|
||
painter.setPen(QPen(QColor("#0067c0"), 3))
|
||
painter.drawEllipse(cx - circle_radius, cy - circle_radius, circle_radius * 2, circle_radius * 2)
|
||
painter.setPen(QPen(QColor("white"), 2))
|
||
painter.setFont(QFont("Microsoft YaHei", 12, QFont.Bold))
|
||
painter.drawText(cx - circle_radius, cy - circle_radius, circle_radius * 2, circle_radius * 2, Qt.AlignCenter, str(i + 1))
|
||
else:
|
||
painter.setBrush(QBrush(QColor("white")))
|
||
painter.setPen(QPen(QColor("#cccccc"), 2))
|
||
painter.drawEllipse(cx - circle_radius, cy - circle_radius, circle_radius * 2, circle_radius * 2)
|
||
painter.setPen(QPen(QColor("#999999"), 2))
|
||
painter.setFont(QFont("Microsoft YaHei", 12))
|
||
painter.drawText(cx - circle_radius, cy - circle_radius, circle_radius * 2, circle_radius * 2, Qt.AlignCenter, str(i + 1))
|
||
|
||
if i == self.current_step:
|
||
painter.setPen(QColor("#0067c0"))
|
||
painter.setFont(QFont("Microsoft YaHei", 10, QFont.Bold))
|
||
elif i in self.completed_steps:
|
||
painter.setPen(QColor("#107c10"))
|
||
painter.setFont(QFont("Microsoft YaHei", 10))
|
||
else:
|
||
painter.setPen(QColor("#999999"))
|
||
painter.setFont(QFont("Microsoft YaHei", 10))
|
||
painter.drawText(int(cx - step_width / 2), cy + circle_radius + 8, int(step_width), 20, Qt.AlignCenter, step_name)
|
||
|
||
painter.end()
|
||
|
||
|
||
class ModernCard(QFrame):
|
||
"""现代化卡片"""
|
||
def __init__(self, title="", parent=None):
|
||
super().__init__(parent)
|
||
self.setStyleSheet("""
|
||
QFrame {
|
||
background: white;
|
||
border: 1px solid #e5e5e5;
|
||
border-radius: 10px;
|
||
}
|
||
""")
|
||
self._layout = QVBoxLayout(self)
|
||
self._layout.setContentsMargins(20, 16, 20, 16)
|
||
self._layout.setSpacing(12)
|
||
|
||
if title:
|
||
title_label = QLabel(title)
|
||
title_label.setStyleSheet("font-size: 15px; font-weight: 700; color: #0067c0;")
|
||
self._layout.addWidget(title_label)
|
||
|
||
def add_widget(self, widget):
|
||
self._layout.addWidget(widget)
|
||
|
||
def add_layout(self, layout):
|
||
self._layout.addLayout(layout)
|
||
|
||
def add_stretch(self):
|
||
self._layout.addStretch()
|
||
|
||
|
||
class ArcxPackager(QMainWindow):
|
||
def __init__(self):
|
||
super().__init__()
|
||
self.setWindowTitle("A13可视化插件打包工具")
|
||
self.setMinimumSize(1100, 780)
|
||
self.resize(1200, 820)
|
||
self._code_content = ""
|
||
self._pages = []
|
||
self._libs = []
|
||
self._icons = []
|
||
self._libs_dir = os.path.join(tempfile.gettempdir(), "a13_plugin_libs")
|
||
self._ensure_libs_dir()
|
||
self._steps = ["元数据", "插件代码", "页面配置", "默认设置", "依赖库", "图标资源", "预览打包"]
|
||
self._current_step = 0
|
||
self._setup_ui()
|
||
self._load_defaults()
|
||
|
||
def _ensure_libs_dir(self):
|
||
if not os.path.exists(self._libs_dir):
|
||
os.makedirs(self._libs_dir)
|
||
|
||
def _setup_ui(self):
|
||
central = QWidget()
|
||
self.setCentralWidget(central)
|
||
main_layout = QVBoxLayout(central)
|
||
main_layout.setContentsMargins(0, 0, 0, 0)
|
||
main_layout.setSpacing(0)
|
||
|
||
header = QFrame()
|
||
header.setStyleSheet("""
|
||
QFrame {
|
||
background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
|
||
stop:0 #0067c0, stop:1 #0078d4);
|
||
border: none;
|
||
}
|
||
""")
|
||
header.setFixedHeight(90)
|
||
header_layout = QVBoxLayout(header)
|
||
header_layout.setContentsMargins(30, 15, 30, 15)
|
||
header_layout.setSpacing(4)
|
||
|
||
title_row = QHBoxLayout()
|
||
title_label = QLabel("A13 可视化插件打包工具")
|
||
title_label.setStyleSheet("font-size: 22px; font-weight: 800; color: white;")
|
||
title_row.addWidget(title_label)
|
||
title_row.addStretch()
|
||
|
||
version_label = QLabel("v2.0 可视化版")
|
||
version_label.setStyleSheet("font-size: 12px; color: rgba(255,255,255,0.8); padding: 4px 12px; background: rgba(255,255,255,0.15); border-radius: 10px;")
|
||
title_row.addWidget(version_label)
|
||
header_layout.addLayout(title_row)
|
||
|
||
subtitle = QLabel("将 Python 插件代码打包成 .arcx 格式,支持多页面配置、默认设置、依赖库、图标资源")
|
||
subtitle.setStyleSheet("font-size: 12px; color: rgba(255,255,255,0.85);")
|
||
header_layout.addWidget(subtitle)
|
||
|
||
main_layout.addWidget(header)
|
||
|
||
step_container = QFrame()
|
||
step_container.setStyleSheet("background: #f8f9fa; border-bottom: 1px solid #e5e5e5;")
|
||
step_container.setFixedHeight(90)
|
||
step_layout = QHBoxLayout(step_container)
|
||
step_layout.setContentsMargins(30, 10, 30, 10)
|
||
|
||
self.step_indicator = StepIndicator(self._steps)
|
||
step_layout.addWidget(self.step_indicator)
|
||
main_layout.addWidget(step_container)
|
||
|
||
content_container = QWidget()
|
||
content_layout = QHBoxLayout(content_container)
|
||
content_layout.setContentsMargins(0, 0, 0, 0)
|
||
content_layout.setSpacing(0)
|
||
|
||
self.stacked_widget = QStackedWidget()
|
||
self.stacked_widget.setStyleSheet("QStackedWidget { background: #f0f2f5; }")
|
||
content_layout.addWidget(self.stacked_widget, 1)
|
||
|
||
main_layout.addWidget(content_container, 1)
|
||
|
||
footer = QFrame()
|
||
footer.setStyleSheet("background: white; border-top: 1px solid #e5e5e5;")
|
||
footer.setFixedHeight(70)
|
||
footer_layout = QHBoxLayout(footer)
|
||
footer_layout.setContentsMargins(30, 12, 30, 12)
|
||
footer_layout.setSpacing(12)
|
||
|
||
self.progress_bar = QProgressBar()
|
||
self.progress_bar.setRange(0, 100)
|
||
self.progress_bar.setValue(0)
|
||
self.progress_bar.setTextVisible(True)
|
||
self.progress_bar.setFormat("完成度: %p%")
|
||
self.progress_bar.setStyleSheet("""
|
||
QProgressBar {
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 6px;
|
||
background: #f5f5f5;
|
||
height: 22px;
|
||
text-align: center;
|
||
font-size: 11px;
|
||
}
|
||
QProgressBar::chunk {
|
||
background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
|
||
stop:0 #0067c0, stop:1 #0078d4);
|
||
border-radius: 5px;
|
||
}
|
||
""")
|
||
self.progress_bar.setFixedWidth(250)
|
||
footer_layout.addWidget(self.progress_bar)
|
||
|
||
footer_layout.addStretch()
|
||
|
||
self.prev_btn = QPushButton("上一步")
|
||
self.prev_btn.setStyleSheet("""
|
||
QPushButton {
|
||
background: #f0f0f0;
|
||
color: #333;
|
||
border: 1px solid #ddd;
|
||
border-radius: 6px;
|
||
padding: 10px 24px;
|
||
font-weight: 600;
|
||
font-size: 13px;
|
||
}
|
||
QPushButton:hover { background: #e5e5e5; }
|
||
QPushButton:disabled { background: #f5f5f5; color: #ccc; }
|
||
""")
|
||
self.prev_btn.clicked.connect(self._prev_step)
|
||
self.prev_btn.setEnabled(False)
|
||
footer_layout.addWidget(self.prev_btn)
|
||
|
||
self.next_btn = QPushButton("下一步")
|
||
self.next_btn.setStyleSheet("""
|
||
QPushButton {
|
||
background: #0067c0;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 6px;
|
||
padding: 10px 28px;
|
||
font-weight: 700;
|
||
font-size: 13px;
|
||
}
|
||
QPushButton:hover { background: #1a76c8; }
|
||
""")
|
||
self.next_btn.clicked.connect(self._next_step)
|
||
footer_layout.addWidget(self.next_btn)
|
||
|
||
self.package_btn = QPushButton("打包成 .arcx")
|
||
self.package_btn.setStyleSheet("""
|
||
QPushButton {
|
||
background: #107c10;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 6px;
|
||
padding: 10px 32px;
|
||
font-weight: 700;
|
||
font-size: 14px;
|
||
}
|
||
QPushButton:hover { background: #0e6c0e; }
|
||
""")
|
||
self.package_btn.clicked.connect(self._package)
|
||
self.package_btn.setVisible(False)
|
||
footer_layout.addWidget(self.package_btn)
|
||
|
||
main_layout.addWidget(footer)
|
||
|
||
self._build_all_pages()
|
||
self._update_step_ui()
|
||
|
||
def _build_all_pages(self):
|
||
self._build_meta_page()
|
||
self._build_code_page()
|
||
self._build_pages_page()
|
||
self._build_config_page()
|
||
self._build_libs_page()
|
||
self._build_icons_page()
|
||
self._build_preview_page()
|
||
|
||
def _build_meta_page(self):
|
||
page = QWidget()
|
||
page.setStyleSheet("background: #f0f2f5;")
|
||
layout = QVBoxLayout(page)
|
||
layout.setContentsMargins(30, 24, 30, 24)
|
||
layout.setSpacing(16)
|
||
|
||
scroll = QScrollArea()
|
||
scroll.setWidgetResizable(True)
|
||
scroll.setStyleSheet("QScrollArea { border: none; background: transparent; }")
|
||
scroll_content = QWidget()
|
||
scroll_layout = QVBoxLayout(scroll_content)
|
||
scroll_layout.setSpacing(16)
|
||
|
||
card = ModernCard("插件元数据")
|
||
form = QFormLayout()
|
||
form.setSpacing(12)
|
||
|
||
self.plugin_name = QLineEdit()
|
||
self.plugin_name.setPlaceholderText("例如:课堂回声洞")
|
||
self.plugin_name.setStyleSheet("QLineEdit { padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 13px; } QLineEdit:focus { border-color: #0067c0; }")
|
||
form.addRow("插件名称*:", self.plugin_name)
|
||
|
||
self.plugin_id = QLineEdit()
|
||
self.plugin_id.setPlaceholderText("例如:echo_hole(唯一标识)")
|
||
self.plugin_id.setStyleSheet("QLineEdit { padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 13px; } QLineEdit:focus { border-color: #0067c0; }")
|
||
form.addRow("插件ID*:", self.plugin_id)
|
||
|
||
self.plugin_version = QLineEdit("1.0.0")
|
||
self.plugin_version.setStyleSheet("QLineEdit { padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 13px; } QLineEdit:focus { border-color: #0067c0; }")
|
||
form.addRow("版本号:", self.plugin_version)
|
||
|
||
self.plugin_author = QLineEdit()
|
||
self.plugin_author.setPlaceholderText("作者名称")
|
||
self.plugin_author.setStyleSheet("QLineEdit { padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 13px; } QLineEdit:focus { border-color: #0067c0; }")
|
||
form.addRow("作者:", self.plugin_author)
|
||
|
||
self.plugin_description = QTextEdit()
|
||
self.plugin_description.setPlaceholderText("插件功能描述...")
|
||
self.plugin_description.setMaximumHeight(80)
|
||
self.plugin_description.setStyleSheet("QTextEdit { padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 13px; } QTextEdit:focus { border-color: #0067c0; }")
|
||
form.addRow("描述:", self.plugin_description)
|
||
|
||
self.plugin_category = QComboBox()
|
||
self.plugin_category.addItems(["工具", "娱乐", "教学", "数据", "其他"])
|
||
self.plugin_category.setStyleSheet("QComboBox { padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 13px; }")
|
||
form.addRow("分类:", self.plugin_category)
|
||
|
||
card.add_layout(form)
|
||
scroll_layout.addWidget(card)
|
||
|
||
info_card = ModernCard("提示")
|
||
info_text = QLabel("插件ID是插件的唯一标识,安装后会作为文件夹名称。请使用英文、数字和下划线,不要使用中文和特殊字符。")
|
||
info_text.setWordWrap(True)
|
||
info_text.setStyleSheet("font-size: 12px; color: #666; line-height: 1.6;")
|
||
info_card.add_widget(info_text)
|
||
scroll_layout.addWidget(info_card)
|
||
|
||
scroll_layout.addStretch()
|
||
scroll.setWidget(scroll_content)
|
||
layout.addWidget(scroll)
|
||
|
||
self.stacked_widget.addWidget(page)
|
||
|
||
def _build_code_page(self):
|
||
page = QWidget()
|
||
page.setStyleSheet("background: #f0f2f5;")
|
||
layout = QVBoxLayout(page)
|
||
layout.setContentsMargins(30, 24, 30, 24)
|
||
layout.setSpacing(16)
|
||
|
||
btn_row = QHBoxLayout()
|
||
load_btn = QPushButton("从文件加载代码")
|
||
load_btn.setStyleSheet("QPushButton { background: #0067c0; color: white; border: none; border-radius: 6px; padding: 8px 20px; font-weight: 600; } QPushButton:hover { background: #1a76c8; }")
|
||
load_btn.clicked.connect(self._load_code_file)
|
||
btn_row.addWidget(load_btn)
|
||
|
||
paste_btn = QPushButton("粘贴代码")
|
||
paste_btn.setStyleSheet("QPushButton { background: #666; color: white; border: none; border-radius: 6px; padding: 8px 20px; font-weight: 600; } QPushButton:hover { background: #555; }")
|
||
paste_btn.clicked.connect(self._paste_code)
|
||
btn_row.addWidget(paste_btn)
|
||
|
||
template_btn = QPushButton("加载示例模板")
|
||
template_btn.setStyleSheet("QPushButton { background: #8764b8; color: white; border: none; border-radius: 6px; padding: 8px 20px; font-weight: 600; } QPushButton:hover { background: #7a56a8; }")
|
||
template_btn.clicked.connect(self._load_template)
|
||
btn_row.addWidget(template_btn)
|
||
|
||
btn_row.addStretch()
|
||
|
||
self.code_status = QLabel("未加载代码")
|
||
self.code_status.setStyleSheet("font-size: 12px; color: #999;")
|
||
btn_row.addWidget(self.code_status)
|
||
|
||
layout.addLayout(btn_row)
|
||
|
||
self.code_editor = QTextEdit()
|
||
self.code_editor.setPlaceholderText("在此粘贴或输入插件 Python 代码...\n\n插件代码需要定义 main() 函数,接收 plugin_context 参数。")
|
||
self.code_editor.setStyleSheet("""
|
||
QTextEdit {
|
||
background: #1e1e1e;
|
||
color: #d4d4d4;
|
||
border: 1px solid #3c3c3c;
|
||
border-radius: 8px;
|
||
padding: 12px;
|
||
font-family: Consolas, Monaco, monospace;
|
||
font-size: 13px;
|
||
}
|
||
""")
|
||
layout.addWidget(self.code_editor, 1)
|
||
|
||
self.stacked_widget.addWidget(page)
|
||
|
||
def _build_pages_page(self):
|
||
from visual_page_editor import VisualPageEditor
|
||
page = QWidget()
|
||
page.setStyleSheet("background: #f0f2f5;")
|
||
layout = QVBoxLayout(page)
|
||
layout.setContentsMargins(20, 16, 20, 16)
|
||
layout.setSpacing(12)
|
||
|
||
pages_bar = QFrame()
|
||
pages_bar.setStyleSheet("""
|
||
QFrame {
|
||
background: white;
|
||
border: 1px solid #e5e5e5;
|
||
border-radius: 10px;
|
||
}
|
||
""")
|
||
pages_bar_layout = QHBoxLayout(pages_bar)
|
||
pages_bar_layout.setContentsMargins(12, 8, 12, 8)
|
||
pages_bar_layout.setSpacing(8)
|
||
|
||
pages_bar_layout.addWidget(QLabel("页面:"))
|
||
self.page_selector = QComboBox()
|
||
self.page_selector.setStyleSheet("QComboBox { padding: 6px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 13px; min-width: 200px; }")
|
||
self.page_selector.currentIndexChanged.connect(self._on_page_selected)
|
||
pages_bar_layout.addWidget(self.page_selector)
|
||
|
||
add_page_btn = QPushButton("+ 添加页面")
|
||
add_page_btn.setStyleSheet("QPushButton { background: #107c10; color: white; border: none; border-radius: 6px; padding: 8px 16px; font-weight: 600; } QPushButton:hover { background: #0e6c0e; }")
|
||
add_page_btn.clicked.connect(self._add_page)
|
||
pages_bar_layout.addWidget(add_page_btn)
|
||
|
||
rename_page_btn = QPushButton("重命名")
|
||
rename_page_btn.setStyleSheet("QPushButton { background: #0067c0; color: white; border: none; border-radius: 6px; padding: 8px 16px; font-weight: 600; } QPushButton:hover { background: #1a76c8; }")
|
||
rename_page_btn.clicked.connect(self._rename_page)
|
||
pages_bar_layout.addWidget(rename_page_btn)
|
||
|
||
remove_page_btn = QPushButton("删除页面")
|
||
remove_page_btn.setStyleSheet("QPushButton { background: #c42b1c; color: white; border: none; border-radius: 6px; padding: 8px 16px; font-weight: 600; } QPushButton:hover { background: #a02010; }")
|
||
remove_page_btn.clicked.connect(self._remove_page)
|
||
pages_bar_layout.addWidget(remove_page_btn)
|
||
|
||
pages_bar_layout.addStretch()
|
||
|
||
page_count_label = QLabel("共 0 个页面")
|
||
page_count_label.setStyleSheet("font-size: 12px; color: #999;")
|
||
self.page_count_label = page_count_label
|
||
pages_bar_layout.addWidget(page_count_label)
|
||
|
||
layout.addWidget(pages_bar)
|
||
|
||
self.page_editor = VisualPageEditor()
|
||
self.page_editor.page_changed.connect(self._on_page_edited)
|
||
layout.addWidget(self.page_editor, 1)
|
||
|
||
self.stacked_widget.addWidget(page)
|
||
self._refresh_page_selector()
|
||
|
||
def _build_config_page(self):
|
||
page = QWidget()
|
||
page.setStyleSheet("background: #f0f2f5;")
|
||
layout = QVBoxLayout(page)
|
||
layout.setContentsMargins(30, 24, 30, 24)
|
||
layout.setSpacing(16)
|
||
|
||
card = ModernCard("默认设置(JSON 格式)")
|
||
config_layout = QVBoxLayout()
|
||
config_layout.setSpacing(10)
|
||
|
||
hint = QLabel("定义插件的默认配置项,用户可以在设置中修改。使用 JSON 格式。")
|
||
hint.setStyleSheet("font-size: 12px; color: #666;")
|
||
config_layout.addWidget(hint)
|
||
|
||
self.config_editor = QTextEdit()
|
||
self.config_editor.setPlaceholderText('{\n "enabled": true,\n "interval": 5,\n "message": "你好"\n}')
|
||
self.config_editor.setStyleSheet("""
|
||
QTextEdit {
|
||
background: #1e1e1e;
|
||
color: #d4d4d4;
|
||
border: 1px solid #3c3c3c;
|
||
border-radius: 8px;
|
||
padding: 12px;
|
||
font-family: Consolas, Monaco, monospace;
|
||
font-size: 13px;
|
||
}
|
||
""")
|
||
config_layout.addWidget(self.config_editor, 1)
|
||
|
||
format_btn = QPushButton("格式化 JSON")
|
||
format_btn.setStyleSheet("QPushButton { background: #0067c0; color: white; border: none; border-radius: 6px; padding: 8px 20px; font-weight: 600; }")
|
||
format_btn.clicked.connect(self._format_json)
|
||
config_layout.addWidget(format_btn, alignment=Qt.AlignRight)
|
||
|
||
card.add_layout(config_layout)
|
||
layout.addWidget(card, 1)
|
||
|
||
self.stacked_widget.addWidget(page)
|
||
|
||
def _build_libs_page(self):
|
||
page = QWidget()
|
||
page.setStyleSheet("background: #f0f2f5;")
|
||
layout = QVBoxLayout(page)
|
||
layout.setContentsMargins(30, 24, 30, 24)
|
||
layout.setSpacing(16)
|
||
|
||
card = ModernCard("依赖库文件")
|
||
libs_layout = QVBoxLayout()
|
||
libs_layout.setSpacing(10)
|
||
|
||
hint = QLabel("如果插件依赖额外的 Python 库(.py 文件),可以在这里添加。打包时会一起包含在 .arcxpkg 中。")
|
||
hint.setWordWrap(True)
|
||
hint.setStyleSheet("font-size: 12px; color: #666; line-height: 1.6;")
|
||
libs_layout.addWidget(hint)
|
||
|
||
self.libs_list = QListWidget()
|
||
self.libs_list.setStyleSheet("QListWidget { background: white; border: 1px solid #e5e5e5; border-radius: 8px; padding: 4px; } QListWidget::item { padding: 10px; border-bottom: 1px solid #f0f0f0; }")
|
||
libs_layout.addWidget(self.libs_list, 1)
|
||
|
||
libs_btn_row = QHBoxLayout()
|
||
add_lib_btn = QPushButton("添加库文件")
|
||
add_lib_btn.setStyleSheet("QPushButton { background: #107c10; color: white; border: none; border-radius: 6px; padding: 8px 20px; font-weight: 600; }")
|
||
add_lib_btn.clicked.connect(self._add_lib_file)
|
||
libs_btn_row.addWidget(add_lib_btn)
|
||
|
||
remove_lib_btn = QPushButton("移除选中")
|
||
remove_lib_btn.setStyleSheet("QPushButton { background: #c42b1c; color: white; border: none; border-radius: 6px; padding: 8px 20px; font-weight: 600; }")
|
||
remove_lib_btn.clicked.connect(self._remove_lib_file)
|
||
libs_btn_row.addWidget(remove_lib_btn)
|
||
|
||
clear_lib_btn = QPushButton("清空")
|
||
clear_lib_btn.setStyleSheet("QPushButton { background: #666; color: white; border: none; border-radius: 6px; padding: 8px 20px; font-weight: 600; }")
|
||
clear_lib_btn.clicked.connect(self._clear_lib_files)
|
||
libs_btn_row.addWidget(clear_lib_btn)
|
||
|
||
libs_btn_row.addStretch()
|
||
self.libs_count_label = QLabel("共 0 个文件")
|
||
self.libs_count_label.setStyleSheet("font-size: 12px; color: #999;")
|
||
libs_btn_row.addWidget(self.libs_count_label)
|
||
|
||
libs_layout.addLayout(libs_btn_row)
|
||
card.add_layout(libs_layout)
|
||
layout.addWidget(card, 1)
|
||
|
||
self.stacked_widget.addWidget(page)
|
||
|
||
def _build_icons_page(self):
|
||
page = QWidget()
|
||
page.setStyleSheet("background: #f0f2f5;")
|
||
layout = QVBoxLayout(page)
|
||
layout.setContentsMargins(30, 24, 30, 24)
|
||
layout.setSpacing(16)
|
||
|
||
card = ModernCard("图标资源")
|
||
icons_layout = QVBoxLayout()
|
||
icons_layout.setSpacing(10)
|
||
|
||
hint = QLabel("添加插件需要的图标文件(.svg, .png, .ico)。插件可以通过 plugin_context.get_icon_path('文件名') 获取图标路径。")
|
||
hint.setWordWrap(True)
|
||
hint.setStyleSheet("font-size: 12px; color: #666; line-height: 1.6;")
|
||
icons_layout.addWidget(hint)
|
||
|
||
self.icons_list = QListWidget()
|
||
self.icons_list.setStyleSheet("QListWidget { background: white; border: 1px solid #e5e5e5; border-radius: 8px; padding: 4px; } QListWidget::item { padding: 10px; border-bottom: 1px solid #f0f0f0; }")
|
||
icons_layout.addWidget(self.icons_list, 1)
|
||
|
||
icons_btn_row = QHBoxLayout()
|
||
add_icon_btn = QPushButton("添加图标文件")
|
||
add_icon_btn.setStyleSheet("QPushButton { background: #107c10; color: white; border: none; border-radius: 6px; padding: 8px 20px; font-weight: 600; }")
|
||
add_icon_btn.clicked.connect(self._add_icon_file)
|
||
icons_btn_row.addWidget(add_icon_btn)
|
||
|
||
remove_icon_btn = QPushButton("移除选中")
|
||
remove_icon_btn.setStyleSheet("QPushButton { background: #c42b1c; color: white; border: none; border-radius: 6px; padding: 8px 20px; font-weight: 600; }")
|
||
remove_icon_btn.clicked.connect(self._remove_icon_file)
|
||
icons_btn_row.addWidget(remove_icon_btn)
|
||
|
||
clear_icon_btn = QPushButton("清空")
|
||
clear_icon_btn.setStyleSheet("QPushButton { background: #666; color: white; border: none; border-radius: 6px; padding: 8px 20px; font-weight: 600; }")
|
||
clear_icon_btn.clicked.connect(self._clear_icon_files)
|
||
icons_btn_row.addWidget(clear_icon_btn)
|
||
|
||
icons_btn_row.addStretch()
|
||
self.icons_count_label = QLabel("共 0 个文件")
|
||
self.icons_count_label.setStyleSheet("font-size: 12px; color: #999;")
|
||
icons_btn_row.addWidget(self.icons_count_label)
|
||
|
||
icons_layout.addLayout(icons_btn_row)
|
||
card.add_layout(icons_layout)
|
||
layout.addWidget(card, 1)
|
||
|
||
self.stacked_widget.addWidget(page)
|
||
|
||
def _build_preview_page(self):
|
||
page = QWidget()
|
||
page.setStyleSheet("background: #f0f2f5;")
|
||
layout = QVBoxLayout(page)
|
||
layout.setContentsMargins(30, 24, 30, 24)
|
||
layout.setSpacing(16)
|
||
|
||
splitter = QSplitter(Qt.Horizontal)
|
||
|
||
left_card = ModernCard("打包信息预览")
|
||
left_layout = QVBoxLayout()
|
||
left_layout.setSpacing(12)
|
||
|
||
self.preview_info = QLabel("点击「刷新预览」查看打包信息")
|
||
self.preview_info.setWordWrap(True)
|
||
self.preview_info.setStyleSheet("font-size: 13px; color: #555; line-height: 1.8;")
|
||
left_layout.addWidget(self.preview_info, 1)
|
||
|
||
refresh_btn = QPushButton("刷新预览")
|
||
refresh_btn.setStyleSheet("QPushButton { background: #0067c0; color: white; border: none; border-radius: 6px; padding: 10px 24px; font-weight: 600; }")
|
||
refresh_btn.clicked.connect(self._refresh_preview)
|
||
left_layout.addWidget(refresh_btn, alignment=Qt.AlignCenter)
|
||
|
||
left_card.add_layout(left_layout)
|
||
splitter.addWidget(left_card)
|
||
|
||
right_card = ModernCard("JSON 结构预览")
|
||
right_layout = QVBoxLayout()
|
||
right_layout.setSpacing(10)
|
||
|
||
self.preview_json = QTextEdit()
|
||
self.preview_json.setReadOnly(True)
|
||
self.preview_json.setStyleSheet("""
|
||
QTextEdit {
|
||
background: #1e1e1e;
|
||
color: #d4d4d4;
|
||
border: 1px solid #3c3c3c;
|
||
border-radius: 8px;
|
||
padding: 12px;
|
||
font-family: Consolas, Monaco, monospace;
|
||
font-size: 12px;
|
||
}
|
||
""")
|
||
right_layout.addWidget(self.preview_json, 1)
|
||
|
||
right_card.add_layout(right_layout)
|
||
splitter.addWidget(right_card)
|
||
|
||
splitter.setSizes([400, 600])
|
||
layout.addWidget(splitter, 1)
|
||
|
||
action_row = QHBoxLayout()
|
||
action_row.addStretch()
|
||
|
||
self.installer_btn = QPushButton("生成安装程序")
|
||
self.installer_btn.setStyleSheet("QPushButton { background: #0067c0; color: white; border: none; border-radius: 6px; padding: 12px 28px; font-weight: 700; font-size: 14px; } QPushButton:hover { background: #005a9e; }")
|
||
self.installer_btn.clicked.connect(self._generate_installer)
|
||
action_row.addWidget(self.installer_btn)
|
||
|
||
self.arcxpkg_btn = QPushButton("打包含依赖(.arcxpkg)")
|
||
self.arcxpkg_btn.setStyleSheet("QPushButton { background: #ca5010; color: white; border: none; border-radius: 6px; padding: 12px 28px; font-weight: 700; font-size: 14px; } QPushButton:hover { background: #b04810; }")
|
||
self.arcxpkg_btn.clicked.connect(self._generate_arcxpkg)
|
||
action_row.addWidget(self.arcxpkg_btn)
|
||
|
||
layout.addLayout(action_row)
|
||
|
||
self.stacked_widget.addWidget(page)
|
||
|
||
def _prev_step(self):
|
||
if self._current_step > 0:
|
||
self._current_step -= 1
|
||
self._update_step_ui()
|
||
|
||
def _next_step(self):
|
||
if self._current_step < len(self._steps) - 1:
|
||
self._current_step += 1
|
||
self._update_step_ui()
|
||
|
||
def _update_step_ui(self):
|
||
self.stacked_widget.setCurrentIndex(self._current_step)
|
||
self.step_indicator.set_current_step(self._current_step)
|
||
|
||
for i in range(self._current_step):
|
||
self.step_indicator.set_completed(i)
|
||
|
||
self.prev_btn.setEnabled(self._current_step > 0)
|
||
|
||
if self._current_step == len(self._steps) - 1:
|
||
self.next_btn.setVisible(False)
|
||
self.package_btn.setVisible(True)
|
||
self._refresh_preview()
|
||
else:
|
||
self.next_btn.setVisible(True)
|
||
self.package_btn.setVisible(False)
|
||
self.next_btn.setText("下一步")
|
||
|
||
progress = int((self._current_step + 1) / len(self._steps) * 100)
|
||
self.progress_bar.setValue(progress)
|
||
|
||
def _load_defaults(self):
|
||
self.config_editor.setPlainText('{\n "enabled": true\n}')
|
||
|
||
def _load_template(self):
|
||
template = '''# A13 插件示例模板
|
||
# 插件需要定义 main() 函数,接收 plugin_context 参数
|
||
|
||
def main(plugin_context):
|
||
"""
|
||
插件主函数
|
||
plugin_context: 插件上下文,提供各种API
|
||
"""
|
||
# 获取插件配置
|
||
config = plugin_context.get_config()
|
||
enabled = config.get("enabled", True)
|
||
|
||
if not enabled:
|
||
return
|
||
|
||
# 记录日志
|
||
plugin_context.log_info("插件已启动")
|
||
|
||
# 添加设置页面
|
||
plugin_context.add_settings_page(
|
||
title="示例插件设置",
|
||
description="这是一个示例插件的设置页面",
|
||
items=[
|
||
{"key": "enabled", "type": "switch", "label": "启用插件", "default": True},
|
||
{"key": "message", "type": "input", "label": "提示消息", "default": "你好"},
|
||
]
|
||
)
|
||
|
||
# 注册事件监听
|
||
def on_draw_result(name, text):
|
||
plugin_context.log_info(f"抽取结果: {name}")
|
||
|
||
plugin_context.on("draw_result", on_draw_result)
|
||
|
||
plugin_context.log_info("示例插件加载完成")
|
||
'''
|
||
self.code_editor.setPlainText(template)
|
||
self.code_status.setText("已加载示例模板")
|
||
self.plugin_name.setText("示例插件")
|
||
self.plugin_id.setText("example_plugin")
|
||
self.plugin_author.setText("A13")
|
||
self.plugin_description.setPlainText("这是一个示例插件,展示了插件的基本结构和API用法。")
|
||
|
||
def _load_code_file(self):
|
||
path, _ = QFileDialog.getOpenFileName(self, "选择插件代码文件", "", "Python Files (*.py)")
|
||
if path:
|
||
try:
|
||
with open(path, "r", encoding="utf-8") as f:
|
||
self.code_editor.setPlainText(f.read())
|
||
self.code_status.setText(f"已加载: {os.path.basename(path)}")
|
||
except Exception as e:
|
||
QMessageBox.warning(self, "加载失败", f"加载代码文件失败:{str(e)}")
|
||
|
||
def _paste_code(self):
|
||
clipboard = QApplication.clipboard()
|
||
text = clipboard.text()
|
||
if text:
|
||
self.code_editor.setPlainText(text)
|
||
self.code_status.setText("已从剪贴板粘贴")
|
||
else:
|
||
QMessageBox.information(self, "提示", "剪贴板中没有文本内容")
|
||
|
||
def _add_page(self):
|
||
from PyQt5.QtWidgets import QInputDialog
|
||
title, ok = QInputDialog.getText(self, "添加页面", "页面标题:", text=f"设置页面 {len(self._pages) + 1}")
|
||
if ok and title:
|
||
new_page = {"title": title, "description": "", "items": []}
|
||
self._pages.append(new_page)
|
||
self._refresh_page_selector()
|
||
self.page_selector.setCurrentIndex(len(self._pages) - 1)
|
||
|
||
def _remove_page(self):
|
||
index = self.page_selector.currentIndex()
|
||
if index >= 0 and index < len(self._pages):
|
||
reply = QMessageBox.question(self, "确认删除", f"确定要删除页面「{self._pages[index]['title']}」吗?",
|
||
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
||
if reply == QMessageBox.Yes:
|
||
del self._pages[index]
|
||
self._refresh_page_selector()
|
||
if self._pages:
|
||
self.page_selector.setCurrentIndex(min(index, len(self._pages) - 1))
|
||
|
||
def _rename_page(self):
|
||
index = self.page_selector.currentIndex()
|
||
if index >= 0 and index < len(self._pages):
|
||
from PyQt5.QtWidgets import QInputDialog
|
||
title, ok = QInputDialog.getText(self, "重命名页面", "新页面标题:", text=self._pages[index]["title"])
|
||
if ok and title:
|
||
self._pages[index]["title"] = title
|
||
self._refresh_page_selector()
|
||
self.page_selector.setCurrentIndex(index)
|
||
|
||
def _refresh_page_selector(self):
|
||
self.page_selector.blockSignals(True)
|
||
self.page_selector.clear()
|
||
for i, page in enumerate(self._pages):
|
||
self.page_selector.addItem(f"{i+1}. {page['title']} ({len(page.get('items', []))}项)")
|
||
self.page_selector.blockSignals(False)
|
||
self.page_count_label.setText(f"共 {len(self._pages)} 个页面")
|
||
|
||
if self._pages:
|
||
current = min(self.page_selector.currentIndex(), len(self._pages) - 1)
|
||
if current < 0:
|
||
current = 0
|
||
self.page_editor.load_page(self._pages[current])
|
||
else:
|
||
self.page_editor.load_page({"title": "", "description": "", "items": []})
|
||
|
||
def _on_page_selected(self, index):
|
||
if 0 <= index < len(self._pages):
|
||
self.page_editor.load_page(self._pages[index])
|
||
|
||
def _on_page_edited(self, page_data):
|
||
index = self.page_selector.currentIndex()
|
||
if 0 <= index < len(self._pages):
|
||
self._pages[index] = page_data
|
||
self._refresh_page_selector()
|
||
self.page_selector.setCurrentIndex(index)
|
||
|
||
def _format_json(self):
|
||
try:
|
||
text = self.config_editor.toPlainText()
|
||
data = json.loads(text)
|
||
formatted = json.dumps(data, indent=2, ensure_ascii=False)
|
||
self.config_editor.setPlainText(formatted)
|
||
except json.JSONDecodeError as e:
|
||
QMessageBox.warning(self, "格式错误", f"JSON 格式错误:{str(e)}")
|
||
|
||
def _add_lib_file(self):
|
||
paths, _ = QFileDialog.getOpenFileNames(self, "选择库文件", "", "Python Files (*.py)")
|
||
for path in paths:
|
||
if path not in self._libs:
|
||
self._libs.append(path)
|
||
self._update_libs_list()
|
||
|
||
def _remove_lib_file(self):
|
||
row = self.libs_list.currentRow()
|
||
if row >= 0 and row < len(self._libs):
|
||
del self._libs[row]
|
||
self._update_libs_list()
|
||
|
||
def _clear_lib_files(self):
|
||
self._libs.clear()
|
||
self._update_libs_list()
|
||
|
||
def _update_libs_list(self):
|
||
self.libs_list.clear()
|
||
for path in self._libs:
|
||
self.libs_list.addItem(os.path.basename(path))
|
||
self.libs_count_label.setText(f"共 {len(self._libs)} 个文件")
|
||
|
||
def _add_icon_file(self):
|
||
paths, _ = QFileDialog.getOpenFileNames(self, "选择图标文件", "", "Image Files (*.svg *.png *.ico *.jpg)")
|
||
for path in paths:
|
||
if path not in self._icons:
|
||
self._icons.append(path)
|
||
self._update_icons_list()
|
||
|
||
def _remove_icon_file(self):
|
||
row = self.icons_list.currentRow()
|
||
if row >= 0 and row < len(self._icons):
|
||
del self._icons[row]
|
||
self._update_icons_list()
|
||
|
||
def _clear_icon_files(self):
|
||
self._icons.clear()
|
||
self._update_icons_list()
|
||
|
||
def _update_icons_list(self):
|
||
self.icons_list.clear()
|
||
for path in self._icons:
|
||
self.icons_list.addItem(os.path.basename(path))
|
||
self.icons_count_label.setText(f"共 {len(self._icons)} 个文件")
|
||
|
||
def _build_arcx_data(self):
|
||
code = self.code_editor.toPlainText()
|
||
code_b64 = base64.b64encode(code.encode("utf-8")).decode("utf-8")
|
||
|
||
try:
|
||
config = json.loads(self.config_editor.toPlainText())
|
||
except:
|
||
config = {}
|
||
|
||
data = {
|
||
"format": "a13-plugin",
|
||
"version": "1.0",
|
||
"metadata": {
|
||
"name": self.plugin_name.text(),
|
||
"id": self.plugin_id.text(),
|
||
"version": self.plugin_version.text(),
|
||
"author": self.plugin_author.text(),
|
||
"description": self.plugin_description.toPlainText(),
|
||
"category": self.plugin_category.currentText(),
|
||
"created": datetime.now().isoformat(),
|
||
},
|
||
"code": code_b64,
|
||
"config": config,
|
||
"pages": self._pages,
|
||
"libs": [os.path.basename(p) for p in self._libs],
|
||
"icons": [os.path.basename(p) for p in self._icons],
|
||
}
|
||
return data
|
||
|
||
def _refresh_preview(self):
|
||
data = self._build_arcx_data()
|
||
meta = data["metadata"]
|
||
|
||
info_text = f"""
|
||
插件名称:{meta['name'] or '(未填写)'}
|
||
插件ID:{meta['id'] or '(未填写)'}
|
||
版本:{meta['version']}
|
||
作者:{meta['author'] or '(未填写)'}
|
||
分类:{meta['category']}
|
||
|
||
代码行数:{len(self.code_editor.toPlainText().splitlines())} 行
|
||
配置项:{len(data.get('config', {}))} 个
|
||
页面数:{len(self._pages)} 个
|
||
依赖库:{len(self._libs)} 个
|
||
图标资源:{len(self._icons)} 个
|
||
|
||
创建时间:{meta['created']}
|
||
"""
|
||
self.preview_info.setText(info_text.strip())
|
||
|
||
preview_data = {k: v for k, v in data.items() if k != "code"}
|
||
preview_data["code"] = f"<base64 encoded, {len(data['code'])} chars>"
|
||
self.preview_json.setPlainText(json.dumps(preview_data, indent=2, ensure_ascii=False))
|
||
|
||
def _package(self):
|
||
if not self.plugin_name.text():
|
||
QMessageBox.warning(self, "提示", "请填写插件名称")
|
||
self._current_step = 0
|
||
self._update_step_ui()
|
||
return
|
||
if not self.plugin_id.text():
|
||
QMessageBox.warning(self, "提示", "请填写插件ID")
|
||
self._current_step = 0
|
||
self._update_step_ui()
|
||
return
|
||
if not self.code_editor.toPlainText().strip():
|
||
QMessageBox.warning(self, "提示", "请添加插件代码")
|
||
self._current_step = 1
|
||
self._update_step_ui()
|
||
return
|
||
|
||
data = self._build_arcx_data()
|
||
default_name = f"{data['metadata']['id']}_v{data['metadata']['version']}.arcx"
|
||
path, _ = QFileDialog.getSaveFileName(self, "保存插件", default_name, "A13 Plugin (*.arcx)")
|
||
if not path:
|
||
return
|
||
|
||
try:
|
||
with open(path, "w", encoding="utf-8") as f:
|
||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||
QMessageBox.information(self, "打包成功", f"插件已打包到:\n{path}\n\n可以在主程序中安装使用。")
|
||
except Exception as e:
|
||
QMessageBox.warning(self, "打包失败", f"打包失败:{str(e)}")
|
||
|
||
def _generate_installer(self):
|
||
QMessageBox.information(self, "提示", "安装程序生成功能请使用 plugin_installer.py")
|
||
|
||
def _generate_arcxpkg(self):
|
||
if not self._libs and not self._icons:
|
||
QMessageBox.information(self, "提示", "当前没有依赖库或图标资源,无需打包含依赖版本。普通 .arcx 已足够。")
|
||
return
|
||
|
||
data = self._build_arcx_data()
|
||
default_name = f"{data['metadata']['id']}_v{data['metadata']['version']}.arcxpkg"
|
||
path, _ = QFileDialog.getSaveFileName(self, "保存含依赖插件", default_name, "A13 Plugin Package (*.arcxpkg)")
|
||
if not path:
|
||
return
|
||
|
||
try:
|
||
with zipfile.ZipFile(path, "w", zipfile.ZIP_DEFLATED) as zf:
|
||
arcx_data = json.dumps(data, indent=2, ensure_ascii=False)
|
||
zf.writestr("plugin.arcx", arcx_data)
|
||
|
||
for lib_path in self._libs:
|
||
zf.write(lib_path, f"libs/{os.path.basename(lib_path)}")
|
||
|
||
for icon_path in self._icons:
|
||
zf.write(icon_path, f"icons/{os.path.basename(icon_path)}")
|
||
|
||
QMessageBox.information(self, "打包成功", f"含依赖插件已打包到:\n{path}\n\n包含 {len(self._libs)} 个依赖库,{len(self._icons)} 个图标资源。")
|
||
except Exception as e:
|
||
QMessageBox.warning(self, "打包失败", f"打包失败:{str(e)}")
|
||
|
||
|
||
def main():
|
||
app = QApplication(sys.argv)
|
||
app.setStyle("Fusion")
|
||
app.setFont(QFont("Microsoft YaHei", 10))
|
||
|
||
palette = QPalette()
|
||
palette.setColor(QPalette.Window, QColor("#f0f2f5"))
|
||
palette.setColor(QPalette.WindowText, QColor("#333333"))
|
||
palette.setColor(QPalette.Base, QColor("white"))
|
||
palette.setColor(QPalette.AlternateBase, QColor("#f8f9fa"))
|
||
palette.setColor(QPalette.ToolTipBase, QColor("white"))
|
||
palette.setColor(QPalette.ToolTipText, QColor("#333333"))
|
||
palette.setColor(QPalette.Text, QColor("#333333"))
|
||
palette.setColor(QPalette.Button, QColor("#f0f0f0"))
|
||
palette.setColor(QPalette.ButtonText, QColor("#333333"))
|
||
palette.setColor(QPalette.BrightText, QColor("red"))
|
||
palette.setColor(QPalette.Link, QColor("#0067c0"))
|
||
palette.setColor(QPalette.Highlight, QColor("#0067c0"))
|
||
palette.setColor(QPalette.HighlightedText, QColor("white"))
|
||
app.setPalette(palette)
|
||
|
||
app.setStyleSheet("""
|
||
QWidget { font-family: "Microsoft YaHei"; }
|
||
QToolTip { background: #2b2b2b; color: white; border: none; padding: 5px 8px; border-radius: 4px; }
|
||
QScrollBar:vertical { background: transparent; width: 10px; margin: 0; }
|
||
QScrollBar::handle:vertical { background: #c1c1c1; border-radius: 5px; min-height: 30px; }
|
||
QScrollBar::handle:vertical:hover { background: #a8a8a8; }
|
||
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0; }
|
||
QScrollBar:horizontal { background: transparent; height: 10px; margin: 0; }
|
||
QScrollBar::handle:horizontal { background: #c1c1c1; border-radius: 5px; min-width: 30px; }
|
||
QScrollBar::handle:horizontal:hover { background: #a8a8a8; }
|
||
QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { width: 0; }
|
||
""")
|
||
|
||
icon_path = os.path.join(BASE_DIR, "app.ico")
|
||
if os.path.exists(icon_path):
|
||
app.setWindowIcon(QIcon(icon_path))
|
||
|
||
window = ArcxPackager()
|
||
window.show()
|
||
sys.exit(app.exec_())
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|