a13rollcall/plugin_ui_injector.py
2026-09-26 20:17:31 +08:00

526 行
16 KiB
Python

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

"""
A13课堂点名系统 - 插件UI注入工具
提供在主程序任何位置添加按钮、修改UI的便捷方法
"""
from PyQt5.QtWidgets import (QPushButton, QWidget, QHBoxLayout, QVBoxLayout,
QLabel, QFrame, QSizePolicy, QMenu, QAction)
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui import QFont, QIcon
class UIInjector:
"""UI注入工具类 - 帮助插件在主程序任何位置添加UI元素"""
def __init__(self, main_window):
self.main_window = main_window
self.injected_elements = []
def find_widget_by_name(self, name):
"""按objectName查找控件"""
return self.main_window.findChild(QWidget, name)
def find_all_buttons(self):
"""查找所有按钮"""
return self.main_window.findChildren(QPushButton)
def find_button_by_text(self, text):
"""按文字查找按钮"""
for btn in self.find_all_buttons():
if btn.text() == text:
return btn
return None
def add_button_to_layout(self, layout, text, callback, position=None,
style="primary", icon=None, tooltip=None,
width=None, height=None):
"""
向指定布局添加按钮
Args:
layout: 目标布局 (QHBoxLayout/QVBoxLayout等)
text: 按钮文字
callback: 点击回调函数
position: 插入位置 (None=末尾, 0=开头, 或指定索引)
style: 按钮样式 (primary/success/warning/danger/ghost)
icon: 图标 (QIcon或None)
tooltip: 提示文字
width: 按钮宽度
height: 按钮高度
Returns:
QPushButton: 创建的按钮对象
"""
btn = QPushButton(text)
btn.clicked.connect(callback)
if icon:
btn.setIcon(icon)
if tooltip:
btn.setToolTip(tooltip)
if width:
btn.setFixedWidth(width)
if height:
btn.setFixedHeight(height)
btn.setStyleSheet(self._get_button_style(style))
btn.setCursor(Qt.PointingHandCursor)
if position is not None:
layout.insertWidget(position, btn)
else:
layout.addWidget(btn)
self.injected_elements.append(btn)
return btn
def add_button_to_top_bar(self, text, callback, position=None, **kwargs):
"""
向顶部栏添加按钮
Args:
text: 按钮文字
callback: 点击回调
position: 插入位置
**kwargs: 其他参数 (style, icon, tooltip, width, height)
Returns:
QPushButton: 按钮对象
"""
top_bar = self._get_top_bar()
if not top_bar:
return None
layout = top_bar.layout()
if not layout:
layout = QHBoxLayout(top_bar)
return self.add_button_to_layout(layout, text, callback, position, **kwargs)
def add_button_to_bottom_bar(self, text, callback, position=None, **kwargs):
"""
向底部状态栏添加按钮
Returns:
QPushButton: 按钮对象
"""
bottom_bar = self._get_bottom_bar()
if not bottom_bar:
return None
layout = bottom_bar.layout()
if not layout:
layout = QHBoxLayout(bottom_bar)
return self.add_button_to_layout(layout, text, callback, position, **kwargs)
def add_button_to_sidebar(self, text, callback, position=None, **kwargs):
"""
向左侧边栏添加按钮
Returns:
QPushButton: 按钮对象
"""
sidebar = self._get_sidebar()
if not sidebar:
return None
layout = sidebar.layout()
if not layout:
layout = QVBoxLayout(sidebar)
kwargs.setdefault("width", 120)
return self.add_button_to_layout(layout, text, callback, position, **kwargs)
def add_button_to_draw_area(self, text, callback, position=None, **kwargs):
"""
向抽取区域添加按钮
Returns:
QPushButton: 按钮对象
"""
draw_area = self._get_draw_area()
if not draw_area:
return None
layout = draw_area.layout()
if not layout:
layout = QVBoxLayout(draw_area)
return self.add_button_to_layout(layout, text, callback, position, **kwargs)
def add_button_next_to(self, target_button, text, callback, side="right", **kwargs):
"""
在指定按钮旁边添加按钮
Args:
target_button: 目标按钮 (QPushButton或按钮文字)
text: 新按钮文字
callback: 点击回调
side: 位置 (right/left)
**kwargs: 其他参数
Returns:
QPushButton: 新按钮对象
"""
if isinstance(target_button, str):
target_button = self.find_button_by_text(target_button)
if not target_button:
return None
parent_layout = target_button.parent().layout()
if not parent_layout:
return None
index = parent_layout.indexOf(target_button)
if index < 0:
return None
position = index + 1 if side == "right" else index
return self.add_button_to_layout(parent_layout, text, callback, position, **kwargs)
def add_separator(self, layout, position=None):
"""
向布局添加分隔线
Args:
layout: 目标布局
position: 插入位置
Returns:
QFrame: 分隔线对象
"""
line = QFrame()
line.setFrameShape(QFrame.VLine)
line.setStyleSheet("color: #e0e0e0;")
line.setFixedWidth(1)
if position is not None:
layout.insertWidget(position, line)
else:
layout.addWidget(line)
self.injected_elements.append(line)
return line
def add_label(self, layout, text, position=None, style="normal", **kwargs):
"""
向布局添加标签
Args:
layout: 目标布局
text: 标签文字
position: 插入位置
style: 样式 (normal/title/subtitle/hint)
Returns:
QLabel: 标签对象
"""
label = QLabel(text)
label.setStyleSheet(self._get_label_style(style))
if kwargs.get("alignment"):
label.setAlignment(kwargs["alignment"])
if kwargs.get("wordWrap"):
label.setWordWrap(True)
if position is not None:
layout.insertWidget(position, label)
else:
layout.addWidget(label)
self.injected_elements.append(label)
return label
def add_custom_widget(self, layout, widget, position=None):
"""
向布局添加自定义控件
Args:
layout: 目标布局
widget: 自定义控件
position: 插入位置
Returns:
QWidget: 控件对象
"""
if position is not None:
layout.insertWidget(position, widget)
else:
layout.addWidget(widget)
self.injected_elements.append(widget)
return widget
def modify_button(self, button, text=None, callback=None, style=None,
icon=None, tooltip=None):
"""
修改现有按钮
Args:
button: 按钮对象或按钮文字
text: 新文字 (None=不修改)
callback: 新回调 (None=不修改, 会先断开旧连接)
style: 新样式
icon: 新图标
tooltip: 新提示
"""
if isinstance(button, str):
button = self.find_button_by_text(button)
if not button:
return False
if text is not None:
button.setText(text)
if callback is not None:
try:
button.clicked.disconnect()
except Exception:
pass
button.clicked.connect(callback)
if style is not None:
button.setStyleSheet(self._get_button_style(style))
if icon is not None:
button.setIcon(icon)
if tooltip is not None:
button.setToolTip(tooltip)
return True
def hide_button(self, button):
"""隐藏按钮"""
if isinstance(button, str):
button = self.find_button_by_text(button)
if button:
button.hide()
return True
return False
def show_button(self, button):
"""显示按钮"""
if isinstance(button, str):
button = self.find_button_by_text(button)
if button:
button.show()
return True
return False
def add_menu_action(self, menu, text, callback, icon=None, shortcut=None):
"""
向菜单添加菜单项
Args:
menu: QMenu对象
text: 菜单项文字
callback: 点击回调
icon: 图标
shortcut: 快捷键
Returns:
QAction: 菜单项对象
"""
action = QAction(text, self.main_window)
action.triggered.connect(callback)
if icon:
action.setIcon(icon)
if shortcut:
action.setShortcut(shortcut)
menu.addAction(action)
self.injected_elements.append(action)
return action
def create_floating_button(self, text, callback, x=100, y=100,
size=(80, 40), style="primary"):
"""
创建悬浮按钮(可拖动)
Args:
text: 按钮文字
callback: 点击回调
x, y: 初始位置
size: 按钮大小 (width, height)
style: 按钮样式
Returns:
QPushButton: 悬浮按钮对象
"""
btn = DraggableButton(text, self.main_window)
btn.setFixedSize(*size)
btn.move(x, y)
btn.setStyleSheet(self._get_button_style(style))
btn.clicked.connect(callback)
btn.show()
btn.raise_()
self.injected_elements.append(btn)
return btn
def get_all_ui_elements(self):
"""获取所有UI元素的摘要(调试用)"""
elements = []
for btn in self.find_all_buttons():
elements.append({
"type": "QPushButton",
"text": btn.text(),
"objectName": btn.objectName(),
"visible": btn.isVisible()
})
for label in self.main_window.findChildren(QLabel):
elements.append({
"type": "QLabel",
"text": label.text()[:50],
"objectName": label.objectName()
})
return elements
def cleanup(self):
"""清理所有注入的元素"""
for element in self.injected_elements:
try:
if hasattr(element, 'deleteLater'):
element.deleteLater()
elif hasattr(element, 'setParent'):
element.setParent(None)
except Exception:
pass
self.injected_elements.clear()
def _get_top_bar(self):
"""获取顶部栏"""
for name in ["top_bar", "title_bar", "header", "topBar"]:
widget = self.find_widget_by_name(name)
if widget:
return widget
# 尝试查找第一个QFrame作为顶部栏
for frame in self.main_window.findChildren(QFrame):
if frame.height() < 80 and frame.y() < 50:
return frame
return None
def _get_bottom_bar(self):
"""获取底部状态栏"""
from PyQt5.QtWidgets import QStatusBar
for name in ["status_bar", "bottom_bar", "statusBar", "footer"]:
widget = self.find_widget_by_name(name)
if widget:
return widget
# 尝试查找QStatusBar
status_bar = self.main_window.findChild(QStatusBar)
if status_bar:
return status_bar
return None
def _get_sidebar(self):
"""获取左侧边栏"""
for name in ["sidebar", "side_bar", "left_panel", "leftBar"]:
widget = self.find_widget_by_name(name)
if widget:
return widget
return None
def _get_draw_area(self):
"""获取抽取区域"""
for name in ["draw_area", "drawArea", "center_widget", "centralWidget"]:
widget = self.find_widget_by_name(name)
if widget:
return widget
return self.main_window.centralWidget()
def _get_button_style(self, style):
"""获取按钮样式"""
styles = {
"primary": """
QPushButton {
background: #0067c0; color: white; border: none;
border-radius: 6px; padding: 8px 16px;
font-size: 13px; font-weight: 600;
}
QPushButton:hover { background: #005a9e; }
QPushButton:pressed { background: #004578; }
""",
"success": """
QPushButton {
background: #107c10; color: white; border: none;
border-radius: 6px; padding: 8px 16px;
font-size: 13px; font-weight: 600;
}
QPushButton:hover { background: #0e6c0e; }
""",
"warning": """
QPushButton {
background: #ff8c00; color: white; border: none;
border-radius: 6px; padding: 8px 16px;
font-size: 13px; font-weight: 600;
}
QPushButton:hover { background: #e67e00; }
""",
"danger": """
QPushButton {
background: #d13438; color: white; border: none;
border-radius: 6px; padding: 8px 16px;
font-size: 13px; font-weight: 600;
}
QPushButton:hover { background: #a8282c; }
""",
"ghost": """
QPushButton {
background: transparent; color: #333; border: 1px solid #ccc;
border-radius: 6px; padding: 8px 16px;
font-size: 13px; font-weight: 500;
}
QPushButton:hover { background: #f0f0f0; border-color: #0067c0; color: #0067c0; }
""",
"small": """
QPushButton {
background: #f0f0f0; color: #333; border: none;
border-radius: 4px; padding: 4px 8px;
font-size: 11px;
}
QPushButton:hover { background: #e0e0e0; }
"""
}
return styles.get(style, styles["primary"])
def _get_label_style(self, style):
"""获取标签样式"""
styles = {
"normal": "color: #333; font-size: 13px;",
"title": "color: #0067c0; font-size: 18px; font-weight: 700;",
"subtitle": "color: #666; font-size: 14px; font-weight: 600;",
"hint": "color: #999; font-size: 11px;",
"success": "color: #107c10; font-size: 13px; font-weight: 600;",
"warning": "color: #ff8c00; font-size: 13px; font-weight: 600;",
"danger": "color: #d13438; font-size: 13px; font-weight: 600;",
}
return styles.get(style, styles["normal"])
class DraggableButton(QPushButton):
"""可拖动的按钮"""
def __init__(self, text, parent=None):
super().__init__(text, parent)
self.dragging = False
self.drag_position = None
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.dragging = True
self.drag_position = event.globalPos() - self.frameGeometry().topLeft()
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if self.dragging and event.buttons() & Qt.LeftButton:
self.move(event.globalPos() - self.drag_position)
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self.dragging = False
super().mouseReleaseEvent(event)
__all__ = ['UIInjector', 'DraggableButton']