功能可用
parent
e4e5455761
commit
3b644a3acb
|
|
@ -1,25 +0,0 @@
|
|||
[
|
||||
{
|
||||
"widget_type": "image",
|
||||
"x": -244.5,
|
||||
"y": -262.5,
|
||||
"w": 401.0,
|
||||
"h": 301.0,
|
||||
"z": 0.0,
|
||||
"config": {
|
||||
"imagePath": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"widget_type": "web",
|
||||
"x": -369.0,
|
||||
"y": -404.0,
|
||||
"w": 986.0,
|
||||
"h": 634.0,
|
||||
"z": 0.0,
|
||||
"config": {
|
||||
"url": "https://www.baidu.com",
|
||||
"locked": true
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -4,5 +4,5 @@
|
|||
"org": "MEASCON",
|
||||
"bucket": "PCM",
|
||||
"interval_ms": 1000,
|
||||
"query": "from(bucket: \"PCM\")\n |> range(start: -1h)\n |> filter(fn: (r) => r._measurement == \"PCM_Measurement\")\n |> filter(fn: (r) => r.data_type == \"LSDAQ\")\n |> filter(fn: (r) =>\n r._field == \"主轴承#3\" or\n r._field == \"主轴承#4\"\n )\n |> keep(columns: [\"_time\", \"_field\", \"_value\"])\n |> last()"
|
||||
"query": "from(bucket: \"PCM\")\n |> range(start: -3h)\n |> filter(fn: (r) => r._measurement == \"PCM_Measurement\")\n |> filter(fn: (r) => r.data_type == \"LSDAQ\")\n |> keep(columns: [\"_time\", \"_field\", \"_value\"])\n |> last()"
|
||||
}
|
||||
704
main.py
704
main.py
|
|
@ -15,8 +15,8 @@ import sys
|
|||
from dataclasses import dataclass, asdict, field
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
from PyQt6.QtCore import Qt, QRectF, pyqtSignal, QObject, QTimer
|
||||
from PyQt6.QtGui import QBrush, QColor, QPen, QPixmap, QFont
|
||||
from PyQt6.QtCore import Qt, QRectF, pyqtSignal, QObject, QTimer, QPointF
|
||||
from PyQt6.QtGui import QBrush, QColor, QPen, QPixmap, QFont, QCursor
|
||||
from PyQt6.QtWidgets import (
|
||||
QApplication,
|
||||
QMainWindow,
|
||||
|
|
@ -36,6 +36,7 @@ from PyQt6.QtWidgets import (
|
|||
QGraphicsPixmapItem,
|
||||
QGraphicsTextItem,
|
||||
QGraphicsProxyWidget,
|
||||
QGraphicsItem,
|
||||
QMessageBox,
|
||||
QPlainTextEdit,
|
||||
QListWidget,
|
||||
|
|
@ -45,6 +46,7 @@ from PyQt6.QtWidgets import (
|
|||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QCheckBox,
|
||||
QFrame,
|
||||
)
|
||||
|
||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||
|
|
@ -85,6 +87,9 @@ class InfluxSettings:
|
|||
class DashboardItem(QGraphicsRectItem):
|
||||
"""可拖拽、可简单缩放的组件基类"""
|
||||
|
||||
# 磁吸阈值:距离边缘多少像素时触发磁吸
|
||||
SNAP_THRESHOLD = 10.0
|
||||
|
||||
def __init__(self, x: float, y: float, w: float, h: float, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setRect(QRectF(0, 0, w, h))
|
||||
|
|
@ -94,13 +99,17 @@ class DashboardItem(QGraphicsRectItem):
|
|||
| QGraphicsRectItem.GraphicsItemFlag.ItemIsSelectable
|
||||
| QGraphicsRectItem.GraphicsItemFlag.ItemSendsGeometryChanges
|
||||
)
|
||||
self.setBrush(QBrush(QColor(40, 40, 40)))
|
||||
# 启用鼠标悬停事件,用于改变光标样式
|
||||
self.setAcceptHoverEvents(True)
|
||||
# 组件本体颜色稍微比画布亮一些,方便区分
|
||||
self.setBrush(QBrush(QColor(60, 60, 60)))
|
||||
self.setPen(QPen(QColor(120, 120, 120), 1))
|
||||
|
||||
self._resizing = False
|
||||
self._resize_margin = 8
|
||||
self._drag_start_rect: Optional[QRectF] = None
|
||||
self._drag_start_pos = None
|
||||
self._snapping = False # 是否正在磁吸,避免递归调用
|
||||
|
||||
# 简单的右下角缩放
|
||||
def mousePressEvent(self, event):
|
||||
|
|
@ -119,6 +128,45 @@ class DashboardItem(QGraphicsRectItem):
|
|||
return
|
||||
super().mousePressEvent(event)
|
||||
|
||||
def hoverMoveEvent(self, event):
|
||||
"""鼠标悬停移动时,根据位置改变光标样式"""
|
||||
if not self._resizing:
|
||||
r = self.rect()
|
||||
pt = event.pos()
|
||||
margin = self._resize_margin
|
||||
|
||||
# 右下角:调整大小光标
|
||||
if (r.width() - margin <= pt.x() <= r.width() and
|
||||
r.height() - margin <= pt.y() <= r.height()):
|
||||
self.setCursor(QCursor(Qt.CursorShape.SizeFDiagCursor))
|
||||
# 右上角
|
||||
elif (r.width() - margin <= pt.x() <= r.width() and
|
||||
0 <= pt.y() <= margin):
|
||||
self.setCursor(QCursor(Qt.CursorShape.SizeBDiagCursor))
|
||||
# 左下角
|
||||
elif (0 <= pt.x() <= margin and
|
||||
r.height() - margin <= pt.y() <= r.height()):
|
||||
self.setCursor(QCursor(Qt.CursorShape.SizeBDiagCursor))
|
||||
# 左上角
|
||||
elif (0 <= pt.x() <= margin and 0 <= pt.y() <= margin):
|
||||
self.setCursor(QCursor(Qt.CursorShape.SizeFDiagCursor))
|
||||
# 右边缘
|
||||
elif r.width() - margin <= pt.x() <= r.width():
|
||||
self.setCursor(QCursor(Qt.CursorShape.SizeHorCursor))
|
||||
# 左边缘
|
||||
elif 0 <= pt.x() <= margin:
|
||||
self.setCursor(QCursor(Qt.CursorShape.SizeHorCursor))
|
||||
# 下边缘
|
||||
elif r.height() - margin <= pt.y() <= r.height():
|
||||
self.setCursor(QCursor(Qt.CursorShape.SizeVerCursor))
|
||||
# 上边缘
|
||||
elif 0 <= pt.y() <= margin:
|
||||
self.setCursor(QCursor(Qt.CursorShape.SizeVerCursor))
|
||||
else:
|
||||
# 其他区域:恢复默认光标(如果正在拖拽,Qt会自动处理为移动光标)
|
||||
self.unsetCursor()
|
||||
super().hoverMoveEvent(event)
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
if self._resizing and self._drag_start_rect is not None and self._drag_start_pos is not None:
|
||||
delta = event.pos() - self._drag_start_pos
|
||||
|
|
@ -130,6 +178,11 @@ class DashboardItem(QGraphicsRectItem):
|
|||
return
|
||||
super().mouseMoveEvent(event)
|
||||
|
||||
def hoverLeaveEvent(self, event):
|
||||
"""鼠标离开组件时,恢复默认光标"""
|
||||
self.unsetCursor()
|
||||
super().hoverLeaveEvent(event)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if self._resizing:
|
||||
self._resizing = False
|
||||
|
|
@ -164,6 +217,147 @@ class DashboardItem(QGraphicsRectItem):
|
|||
"""子类可重写:在几何变化后调整内部元素(例如 Web 视图大小、图片缩放等)"""
|
||||
pass
|
||||
|
||||
def _get_canvas_bounds(self) -> Optional[QRectF]:
|
||||
"""获取画布的边界矩形(scene 坐标)"""
|
||||
scene = self.scene()
|
||||
if not scene or not isinstance(scene, DashboardScene):
|
||||
return None
|
||||
canvas_item = scene.canvas_item
|
||||
if not canvas_item:
|
||||
return None
|
||||
# 画布的实际边界 = canvas_item 的位置 + rect
|
||||
canvas_pos = canvas_item.pos()
|
||||
canvas_rect = canvas_item.rect()
|
||||
return QRectF(
|
||||
canvas_pos.x(),
|
||||
canvas_pos.y(),
|
||||
canvas_rect.width(),
|
||||
canvas_rect.height()
|
||||
)
|
||||
|
||||
def _get_other_items_bounds(self) -> list[QRectF]:
|
||||
"""获取场景中其他组件的边界矩形列表(scene 坐标)"""
|
||||
scene = self.scene()
|
||||
if not scene:
|
||||
return []
|
||||
bounds = []
|
||||
for item in scene.items():
|
||||
if (isinstance(item, DashboardItem) and
|
||||
item is not self and
|
||||
item is not scene.canvas_item):
|
||||
pos = item.pos()
|
||||
rect = item.rect()
|
||||
bounds.append(QRectF(
|
||||
pos.x(),
|
||||
pos.y(),
|
||||
rect.width(),
|
||||
rect.height()
|
||||
))
|
||||
return bounds
|
||||
|
||||
def itemChange(self, change: QGraphicsItem.GraphicsItemChange, value):
|
||||
"""重写 itemChange 以实现磁吸功能(画布边缘 + 组件间吸附)"""
|
||||
if change == QGraphicsItem.GraphicsItemChange.ItemPositionChange and not self._snapping:
|
||||
# 位置变化时,检测是否需要磁吸
|
||||
new_pos: QPointF = value
|
||||
r = self.rect()
|
||||
# 组件在 scene 坐标下的边界
|
||||
item_left = new_pos.x()
|
||||
item_top = new_pos.y()
|
||||
item_right = item_left + r.width()
|
||||
item_bottom = item_top + r.height()
|
||||
|
||||
final_x = new_pos.x()
|
||||
final_y = new_pos.y()
|
||||
|
||||
# 1. 检测画布边缘磁吸 + 边界限制
|
||||
canvas_bounds = self._get_canvas_bounds()
|
||||
canvas_left = None
|
||||
canvas_top = None
|
||||
canvas_right = None
|
||||
canvas_bottom = None
|
||||
|
||||
if canvas_bounds:
|
||||
canvas_left = canvas_bounds.x()
|
||||
canvas_top = canvas_bounds.y()
|
||||
canvas_right = canvas_left + canvas_bounds.width()
|
||||
canvas_bottom = canvas_top + canvas_bounds.height()
|
||||
|
||||
# 左边缘磁吸
|
||||
if abs(item_left - canvas_left) < self.SNAP_THRESHOLD:
|
||||
final_x = canvas_left
|
||||
# 右边缘磁吸
|
||||
elif abs(item_right - canvas_right) < self.SNAP_THRESHOLD:
|
||||
final_x = canvas_right - r.width()
|
||||
# 上边缘磁吸
|
||||
if abs(item_top - canvas_top) < self.SNAP_THRESHOLD:
|
||||
final_y = canvas_top
|
||||
# 下边缘磁吸
|
||||
elif abs(item_bottom - canvas_bottom) < self.SNAP_THRESHOLD:
|
||||
final_y = canvas_bottom - r.height()
|
||||
|
||||
# 2. 检测其他组件的磁吸(组件间对齐)
|
||||
other_bounds = self._get_other_items_bounds()
|
||||
for other_rect in other_bounds:
|
||||
other_left = other_rect.x()
|
||||
other_top = other_rect.y()
|
||||
other_right = other_left + other_rect.width()
|
||||
other_bottom = other_top + other_rect.height()
|
||||
|
||||
# 左边缘对齐(当前组件的左边缘对齐到其他组件的左边缘)
|
||||
if abs(item_left - other_left) < self.SNAP_THRESHOLD:
|
||||
final_x = other_left
|
||||
# 右边缘对齐(当前组件的右边缘对齐到其他组件的右边缘)
|
||||
elif abs(item_right - other_right) < self.SNAP_THRESHOLD:
|
||||
final_x = other_right - r.width()
|
||||
# 当前组件的左边缘对齐到其他组件的右边缘(相邻)
|
||||
elif abs(item_left - other_right) < self.SNAP_THRESHOLD:
|
||||
final_x = other_right
|
||||
# 当前组件的右边缘对齐到其他组件的左边缘(相邻)
|
||||
elif abs(item_right - other_left) < self.SNAP_THRESHOLD:
|
||||
final_x = other_left - r.width()
|
||||
|
||||
# 上边缘对齐
|
||||
if abs(item_top - other_top) < self.SNAP_THRESHOLD:
|
||||
final_y = other_top
|
||||
# 下边缘对齐
|
||||
elif abs(item_bottom - other_bottom) < self.SNAP_THRESHOLD:
|
||||
final_y = other_bottom - r.height()
|
||||
# 当前组件的上边缘对齐到其他组件的下边缘(相邻)
|
||||
elif abs(item_top - other_bottom) < self.SNAP_THRESHOLD:
|
||||
final_y = other_bottom
|
||||
# 当前组件的下边缘对齐到其他组件的上边缘(相邻)
|
||||
elif abs(item_bottom - other_top) < self.SNAP_THRESHOLD:
|
||||
final_y = other_top - r.height()
|
||||
|
||||
# 3. 限制组件不能超出画布边界(在磁吸之后再次检查,确保不会超出)
|
||||
if canvas_bounds and canvas_left is not None:
|
||||
# 重新计算组件边界(因为 final_x/final_y 可能已经被磁吸调整过)
|
||||
item_left = final_x
|
||||
item_top = final_y
|
||||
item_right = item_left + r.width()
|
||||
item_bottom = item_top + r.height()
|
||||
|
||||
# 限制在画布范围内
|
||||
if item_left < canvas_left:
|
||||
final_x = canvas_left
|
||||
if item_top < canvas_top:
|
||||
final_y = canvas_top
|
||||
if item_right > canvas_right:
|
||||
final_x = canvas_right - r.width()
|
||||
if item_bottom > canvas_bottom:
|
||||
final_y = canvas_bottom - r.height()
|
||||
|
||||
# 如果位置被调整了,返回调整后的位置
|
||||
if final_x != new_pos.x() or final_y != new_pos.y():
|
||||
self._snapping = True
|
||||
return QPointF(final_x, final_y)
|
||||
elif change == QGraphicsItem.GraphicsItemChange.ItemPositionHasChanged:
|
||||
# 位置变化完成后,重置磁吸标志
|
||||
self._snapping = False
|
||||
|
||||
return super().itemChange(change, value)
|
||||
|
||||
|
||||
class ImageItem(DashboardItem):
|
||||
def __init__(self, x: float, y: float, w: float, h: float, parent=None):
|
||||
|
|
@ -316,21 +510,19 @@ class LabelItem(DashboardItem):
|
|||
def paint(self, painter, option, widget=None):
|
||||
"""重写绘制:标签背景透明,编辑模式有边框,展示模式无边框"""
|
||||
if not self._edit_mode:
|
||||
# 展示模式:不画任何边框,只保持文本
|
||||
pen = QPen(Qt.PenStyle.NoPen)
|
||||
painter.setPen(pen)
|
||||
painter.setBrush(Qt.BrushStyle.NoBrush)
|
||||
QGraphicsRectItem.paint(self, painter, option, widget)
|
||||
# 展示模式:不画任何边框和背景,边界完全透明
|
||||
# 直接返回,不调用父类的 paint,避免绘制任何边框
|
||||
return
|
||||
|
||||
# 编辑模式:选中时高亮蓝色,否则浅灰色;背景依然透明
|
||||
# 先设置 pen,确保父类 paint 使用正确的边框颜色
|
||||
if self.isSelected():
|
||||
pen = QPen(QColor(80, 160, 255), 2)
|
||||
self.setPen(QPen(QColor(80, 160, 255), 2))
|
||||
else:
|
||||
pen = QPen(QColor(170, 170, 170), 1)
|
||||
painter.setPen(pen)
|
||||
painter.setBrush(Qt.BrushStyle.NoBrush)
|
||||
QGraphicsRectItem.paint(self, painter, option, widget)
|
||||
self.setPen(QPen(QColor(170, 170, 170), 1))
|
||||
# 确保背景透明
|
||||
self.setBrush(QBrush(Qt.GlobalColor.transparent))
|
||||
super().paint(painter, option, widget)
|
||||
|
||||
|
||||
class WebItem(DashboardItem):
|
||||
|
|
@ -405,38 +597,116 @@ class DashboardScene(QGraphicsScene):
|
|||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.selectionChanged.connect(self._on_selection_changed)
|
||||
self._canvas_item: Optional[QGraphicsRectItem] = None
|
||||
self.reset_scene()
|
||||
|
||||
def _on_selection_changed(self):
|
||||
items = self.selectedItems()
|
||||
self.selectionChangedEx.emit(items[0] if items else None)
|
||||
|
||||
@property
|
||||
def canvas_item(self) -> QGraphicsRectItem:
|
||||
return self._canvas_item
|
||||
|
||||
def canvas_rect(self) -> QRectF:
|
||||
return self._canvas_item.rect()
|
||||
|
||||
def set_canvas_rect(self, r: QRectF):
|
||||
# canvas_item 用 rect 表示范围;其位置用 setPos 表示“画布坐标原点”
|
||||
self._canvas_item.setRect(QRectF(0, 0, max(1.0, r.width()), max(1.0, r.height())))
|
||||
self._canvas_item.setPos(r.x(), r.y())
|
||||
self.setSceneRect(QRectF(r.x(), r.y(), r.width(), r.height()))
|
||||
|
||||
def canvas_state(self) -> Dict[str, float]:
|
||||
r = self._canvas_item.rect()
|
||||
p = self._canvas_item.pos()
|
||||
return {"x": float(p.x()), "y": float(p.y()), "w": float(r.width()), "h": float(r.height())}
|
||||
|
||||
def set_canvas_edit_mode(self, edit_mode: bool):
|
||||
# 编辑时显示边界;展示时隐藏边界
|
||||
if edit_mode:
|
||||
self._canvas_item.setPen(QPen(QColor(140, 140, 140), 1, Qt.PenStyle.DashLine))
|
||||
else:
|
||||
self._canvas_item.setPen(QPen(Qt.GlobalColor.transparent))
|
||||
|
||||
def reset_scene(self):
|
||||
"""清空所有 items,并重建 canvas item(保留信号连接)。"""
|
||||
self.clear()
|
||||
# 画布边界(用于定义“屏幕/展示区域”的坐标系与尺寸)
|
||||
self._canvas_item = QGraphicsRectItem()
|
||||
self._canvas_item.setZValue(-1e9)
|
||||
# 画布用更暗的颜色,与组件形成对比
|
||||
self._canvas_item.setPen(QPen(QColor(90, 90, 90), 1, Qt.PenStyle.DashLine))
|
||||
self._canvas_item.setBrush(QBrush(QColor(15, 15, 15)))
|
||||
self._canvas_item.setFlag(QGraphicsRectItem.GraphicsItemFlag.ItemIsSelectable, False)
|
||||
self._canvas_item.setFlag(QGraphicsRectItem.GraphicsItemFlag.ItemIsMovable, False)
|
||||
self.addItem(self._canvas_item)
|
||||
# 默认画布:0,0 1920x1080(可点击画布空白处修改)
|
||||
self.set_canvas_rect(QRectF(0, 0, 1920, 1080))
|
||||
|
||||
|
||||
class DashboardView(QGraphicsView):
|
||||
canvasClicked = pyqtSignal(object) # QRectF
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._scene = DashboardScene(self)
|
||||
self.setScene(self._scene)
|
||||
self.setRenderHints(self.renderHints())
|
||||
self.setBackgroundBrush(QColor(30, 30, 30))
|
||||
# 取消自身的边框,避免全屏时出现白边
|
||||
self.setFrameShape(QFrame.Shape.NoFrame)
|
||||
self.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
|
||||
self._edit_mode = True
|
||||
|
||||
@property
|
||||
def scene_obj(self) -> DashboardScene:
|
||||
return self._scene
|
||||
|
||||
def set_edit_mode(self, edit_mode: bool):
|
||||
self._edit_mode = bool(edit_mode)
|
||||
self._scene.set_canvas_edit_mode(self._edit_mode)
|
||||
|
||||
def reset_scroll(self):
|
||||
"""将滚动条复位到 (0,0) 位置(左上角)。"""
|
||||
h = self.horizontalScrollBar()
|
||||
v = self.verticalScrollBar()
|
||||
if h is not None:
|
||||
h.setValue(h.minimum())
|
||||
if v is not None:
|
||||
v.setValue(v.minimum())
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
# 点击画布空白处:选中“画布”,由右侧属性面板展示/编辑(仅编辑模式)
|
||||
if self._edit_mode and event.button() == Qt.MouseButton.LeftButton:
|
||||
it = self.itemAt(event.pos())
|
||||
if it is None or it is self._scene.canvas_item:
|
||||
r = self._scene.canvas_state()
|
||||
# 清空当前 item 选中,让属性面板进入“画布模式”
|
||||
self._scene.clearSelection()
|
||||
self.canvasClicked.emit(QRectF(r["x"], r["y"], r["w"], r["h"]))
|
||||
event.accept()
|
||||
return
|
||||
super().mousePressEvent(event)
|
||||
|
||||
# 工厂方法
|
||||
def add_image_item(self) -> ImageItem:
|
||||
# 创建新组件前,先清空旧选中,保证只选中新建的这一个
|
||||
self._scene.clearSelection()
|
||||
item = ImageItem(20, 20, 400, 300)
|
||||
self._scene.addItem(item)
|
||||
item.setSelected(True)
|
||||
return item
|
||||
|
||||
def add_label_item(self) -> LabelItem:
|
||||
self._scene.clearSelection()
|
||||
item = LabelItem(50, 50, 160, 60)
|
||||
self._scene.addItem(item)
|
||||
item.setSelected(True)
|
||||
return item
|
||||
|
||||
def add_web_item(self) -> WebItem:
|
||||
self._scene.clearSelection()
|
||||
item = WebItem(40, 40, 500, 350)
|
||||
self._scene.addItem(item)
|
||||
item.setSelected(True)
|
||||
|
|
@ -449,7 +719,11 @@ class DashboardView(QGraphicsView):
|
|||
if isinstance(it, DashboardItem):
|
||||
states.append(asdict(it.to_state()))
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(states, f, indent=2, ensure_ascii=False)
|
||||
payload = {
|
||||
"canvas": self._scene.canvas_state(),
|
||||
"widgets": states,
|
||||
}
|
||||
json.dump(payload, f, indent=2, ensure_ascii=False)
|
||||
|
||||
def load_layout(self, path: str):
|
||||
if not os.path.exists(path):
|
||||
|
|
@ -458,14 +732,25 @@ class DashboardView(QGraphicsView):
|
|||
data = json.load(f)
|
||||
|
||||
# 兼容旧格式:如果不是列表,直接忽略,避免崩溃
|
||||
canvas = None
|
||||
if isinstance(data, dict):
|
||||
# 可能是早期的 {"widgets": [...]} 格式
|
||||
canvas = data.get("canvas", None)
|
||||
# {"widgets": [...]} 格式
|
||||
data = data.get("widgets", [])
|
||||
if not isinstance(data, list):
|
||||
print("layout file format not recognized, ignore:", path)
|
||||
return
|
||||
|
||||
self._scene.clear()
|
||||
self._scene.reset_scene()
|
||||
if isinstance(canvas, dict):
|
||||
try:
|
||||
cx = float(canvas.get("x", 0.0))
|
||||
cy = float(canvas.get("y", 0.0))
|
||||
cw = float(canvas.get("w", 1920.0))
|
||||
ch = float(canvas.get("h", 1080.0))
|
||||
self._scene.set_canvas_rect(QRectF(cx, cy, cw, ch))
|
||||
except Exception:
|
||||
pass
|
||||
for st in data:
|
||||
if not isinstance(st, dict):
|
||||
continue
|
||||
|
|
@ -491,9 +776,12 @@ class DashboardView(QGraphicsView):
|
|||
# -----------------------------
|
||||
|
||||
class PropertyPanel(QWidget):
|
||||
canvasGeometryChanged = pyqtSignal(float, float, float, float)
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._current_item: Optional[DashboardItem] = None
|
||||
# mode: "none" | "item" | "canvas"
|
||||
self._mode: str = "none"
|
||||
|
||||
self.x_spin = QSpinBox()
|
||||
self.y_spin = QSpinBox()
|
||||
|
|
@ -599,12 +887,16 @@ class PropertyPanel(QWidget):
|
|||
def set_current_item(self, item: Optional[DashboardItem]):
|
||||
self._current_item = item
|
||||
if not item:
|
||||
self._update_enabled(False)
|
||||
self.img_group.setVisible(False)
|
||||
self.label_group.setVisible(False)
|
||||
self.web_group.setVisible(False)
|
||||
# 如果当前是 canvas 模式,则保持几何编辑可用;否则整体禁用
|
||||
if self._mode != "canvas":
|
||||
self._mode = "none"
|
||||
self._update_enabled(False)
|
||||
self.img_group.setVisible(False)
|
||||
self.label_group.setVisible(False)
|
||||
self.web_group.setVisible(False)
|
||||
return
|
||||
|
||||
self._mode = "item"
|
||||
self._update_enabled(True)
|
||||
r = item.sceneBoundingRect()
|
||||
self.x_spin.blockSignals(True)
|
||||
|
|
@ -662,17 +954,50 @@ class PropertyPanel(QWidget):
|
|||
self.web_group.setVisible(False)
|
||||
|
||||
def _on_geom_changed(self):
|
||||
if not self._current_item:
|
||||
return
|
||||
x = self.x_spin.value()
|
||||
y = self.y_spin.value()
|
||||
w = max(40, self.w_spin.value())
|
||||
h = max(40, self.h_spin.value())
|
||||
z = self.z_spin.value()
|
||||
|
||||
if self._mode == "canvas":
|
||||
# 画布几何变化:通知主窗口更新 scene 的 canvas_rect
|
||||
self.canvasGeometryChanged.emit(float(x), float(y), float(w), float(h))
|
||||
return
|
||||
|
||||
if not self._current_item:
|
||||
return
|
||||
self._current_item.setPos(x, y)
|
||||
self._current_item.setRect(QRectF(0, 0, w, h))
|
||||
self._current_item.setZValue(float(z))
|
||||
|
||||
def set_canvas(self, rect: QRectF):
|
||||
"""点击画布时调用:右侧面板切换到“画布模式”,X/Y/W/H 表示画布本身几何。"""
|
||||
self._mode = "canvas"
|
||||
self._current_item = None
|
||||
# 只用到几何,隐藏组件类别配置
|
||||
self.img_group.setVisible(False)
|
||||
self.label_group.setVisible(False)
|
||||
self.web_group.setVisible(False)
|
||||
self._update_enabled(True)
|
||||
|
||||
self.x_spin.blockSignals(True)
|
||||
self.y_spin.blockSignals(True)
|
||||
self.w_spin.blockSignals(True)
|
||||
self.h_spin.blockSignals(True)
|
||||
self.z_spin.blockSignals(True)
|
||||
self.x_spin.setValue(int(rect.x()))
|
||||
self.y_spin.setValue(int(rect.y()))
|
||||
self.w_spin.setValue(int(rect.width()))
|
||||
self.h_spin.setValue(int(rect.height()))
|
||||
# 画布不需要 z 值,固定为 0
|
||||
self.z_spin.setValue(0)
|
||||
self.x_spin.blockSignals(False)
|
||||
self.y_spin.blockSignals(False)
|
||||
self.w_spin.blockSignals(False)
|
||||
self.h_spin.blockSignals(False)
|
||||
self.z_spin.blockSignals(False)
|
||||
|
||||
def _on_browse_image(self):
|
||||
if not isinstance(self._current_item, ImageItem):
|
||||
return
|
||||
|
|
@ -782,60 +1107,92 @@ class MainWindow(QMainWindow):
|
|||
self.setWindowTitle("PCM Viewer - Widgets Dashboard")
|
||||
self.resize(1400, 840)
|
||||
self._edit_mode = True
|
||||
# 全屏编辑标志(展示模式本身也会全屏)
|
||||
self._fullscreen_edit = False
|
||||
self._first_show = True # 用于在第一次显示时最大化并校正滚动条
|
||||
|
||||
self.view = DashboardView()
|
||||
self.prop = PropertyPanel()
|
||||
self.item_list = QListWidget()
|
||||
self.item_list.setMinimumHeight(120)
|
||||
self.item_list.setSelectionMode(QListWidget.SelectionMode.SingleSelection)
|
||||
self.influx_settings = InfluxSettings()
|
||||
# 全局 Influx 客户端:查询结果分发给所有 LabelItem
|
||||
self.influx_client = InfluxDBClient(self)
|
||||
self.influx_client.dataReceived.connect(self._on_influx_data)
|
||||
self.influx_client.errorOccurred.connect(self._on_influx_error)
|
||||
|
||||
splitter = QSplitter()
|
||||
splitter.addWidget(self.view)
|
||||
splitter.addWidget(self.prop)
|
||||
self.splitter = QSplitter()
|
||||
# 左侧:画布
|
||||
self.splitter.addWidget(self.view)
|
||||
|
||||
# 右侧:属性面板 + 组件列表
|
||||
self.right_panel = QWidget()
|
||||
right_layout = QVBoxLayout(self.right_panel)
|
||||
right_layout.setContentsMargins(0, 0, 0, 0)
|
||||
right_layout.addWidget(self.prop)
|
||||
group_list = QGroupBox("当前组件列表")
|
||||
glay = QVBoxLayout(group_list)
|
||||
glay.addWidget(self.item_list)
|
||||
right_layout.addWidget(group_list)
|
||||
|
||||
self.splitter.addWidget(self.right_panel)
|
||||
# 左侧画布权重大一些,右侧保持适中宽度
|
||||
self.prop.setMaximumWidth(420)
|
||||
splitter.setStretchFactor(0, 4)
|
||||
splitter.setStretchFactor(1, 1)
|
||||
splitter.setSizes([1000, 400])
|
||||
# 使用 setMinimumWidth 和 setMaximumWidth 来限制右侧面板,但让 splitter 自动管理比例
|
||||
self.right_panel.setMinimumWidth(300)
|
||||
self.right_panel.setMaximumWidth(500)
|
||||
self.splitter.setStretchFactor(0, 4)
|
||||
self.splitter.setStretchFactor(1, 1)
|
||||
# 不设置固定 sizes,让 splitter 根据窗口大小自动分配(但受 min/max 限制)
|
||||
|
||||
central = QWidget()
|
||||
layout = QVBoxLayout(central)
|
||||
# 取消外边距与间距,避免全屏时出现多余空白
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
|
||||
toolbar_layout = QHBoxLayout()
|
||||
btn_mode = QPushButton("展示模式")
|
||||
btn_influx = QPushButton("Influx配置")
|
||||
btn_add_img = QPushButton("新增图片组件")
|
||||
btn_add_label = QPushButton("新增标签组件")
|
||||
btn_add_web = QPushButton("新增曲线组件")
|
||||
btn_delete = QPushButton("删除组件")
|
||||
btn_save = QPushButton("保存布局")
|
||||
btn_load = QPushButton("加载布局")
|
||||
toolbar_layout.addWidget(btn_mode)
|
||||
toolbar_layout.addWidget(btn_influx)
|
||||
toolbar_layout.addWidget(btn_add_img)
|
||||
toolbar_layout.addWidget(btn_add_label)
|
||||
toolbar_layout.addWidget(btn_add_web)
|
||||
self.toolbar_widget = QWidget()
|
||||
self.toolbar_widget.setFixedHeight(40) # 固定工具栏高度,避免与画布之间出现多余间距
|
||||
toolbar_layout = QHBoxLayout(self.toolbar_widget)
|
||||
# 保存按钮到成员,方便在其它方法(如键盘事件)中访问 / 控制可用性
|
||||
self.btn_mode = QPushButton("展示模式")
|
||||
self.btn_influx = QPushButton("Influx配置")
|
||||
self.btn_add_img = QPushButton("新增图片组件")
|
||||
self.btn_add_label = QPushButton("新增标签组件")
|
||||
self.btn_add_web = QPushButton("新增曲线组件")
|
||||
self.btn_full_edit = QPushButton("全屏编辑")
|
||||
self.btn_delete = QPushButton("删除组件")
|
||||
self.btn_save = QPushButton("保存布局")
|
||||
self.btn_load = QPushButton("加载布局")
|
||||
toolbar_layout.addWidget(self.btn_mode)
|
||||
toolbar_layout.addWidget(self.btn_influx)
|
||||
toolbar_layout.addWidget(self.btn_add_img)
|
||||
toolbar_layout.addWidget(self.btn_add_label)
|
||||
toolbar_layout.addWidget(self.btn_add_web)
|
||||
toolbar_layout.addWidget(self.btn_full_edit)
|
||||
toolbar_layout.addStretch(1)
|
||||
toolbar_layout.addWidget(btn_delete)
|
||||
toolbar_layout.addWidget(btn_save)
|
||||
toolbar_layout.addWidget(btn_load)
|
||||
toolbar_layout.addWidget(self.btn_delete)
|
||||
toolbar_layout.addWidget(self.btn_save)
|
||||
toolbar_layout.addWidget(self.btn_load)
|
||||
|
||||
layout.addLayout(toolbar_layout)
|
||||
layout.addWidget(splitter)
|
||||
layout.addWidget(self.toolbar_widget)
|
||||
layout.addWidget(self.splitter)
|
||||
self.setCentralWidget(central)
|
||||
|
||||
self.view.scene_obj.selectionChangedEx.connect(self.prop.set_current_item)
|
||||
self.view.canvasClicked.connect(self._on_canvas_clicked)
|
||||
self.prop.canvasGeometryChanged.connect(self._on_canvas_changed)
|
||||
self.item_list.itemDoubleClicked.connect(self._on_list_activated)
|
||||
|
||||
btn_influx.clicked.connect(self._on_influx_config)
|
||||
btn_add_img.clicked.connect(self._on_add_image)
|
||||
btn_add_label.clicked.connect(self._on_add_label)
|
||||
btn_add_web.clicked.connect(self._on_add_web)
|
||||
btn_save.clicked.connect(self._on_save)
|
||||
btn_load.clicked.connect(self._on_load)
|
||||
btn_mode.clicked.connect(lambda: self._toggle_mode(btn_mode))
|
||||
btn_delete.clicked.connect(self._on_delete)
|
||||
self.btn_influx.clicked.connect(self._on_influx_config)
|
||||
self.btn_add_img.clicked.connect(self._on_add_image)
|
||||
self.btn_add_label.clicked.connect(self._on_add_label)
|
||||
self.btn_add_web.clicked.connect(self._on_add_web)
|
||||
self.btn_save.clicked.connect(self._on_save)
|
||||
self.btn_load.clicked.connect(self._on_load)
|
||||
self.btn_mode.clicked.connect(self._toggle_mode)
|
||||
self.btn_full_edit.clicked.connect(self._on_fullscreen_edit)
|
||||
self.btn_delete.clicked.connect(self._on_delete)
|
||||
|
||||
# 布局 / Influx 配置文件路径
|
||||
base_dir = os.path.dirname(__file__)
|
||||
|
|
@ -845,10 +1202,36 @@ class MainWindow(QMainWindow):
|
|||
# 先加载全局 Influx 配置,再加载布局
|
||||
self._load_influx_settings()
|
||||
self.view.load_layout(self._layout_path)
|
||||
self.view.set_edit_mode(True)
|
||||
self._apply_ui_mode()
|
||||
self._refresh_item_list()
|
||||
# 默认在右侧显示画布尺寸/位置,避免“未选中时全是 0”的困惑
|
||||
cs = self.view.scene_obj.canvas_state()
|
||||
self.prop.set_canvas(QRectF(cs["x"], cs["y"], cs["w"], cs["h"]))
|
||||
|
||||
def _toggle_mode(self, btn: QPushButton):
|
||||
def _update_window_fullscreen(self):
|
||||
"""
|
||||
根据当前模式决定是否全屏:
|
||||
- 展示模式:始终全屏
|
||||
- 全屏编辑:在编辑模式下也全屏
|
||||
"""
|
||||
want_full = (not self._edit_mode) or self._fullscreen_edit
|
||||
if want_full:
|
||||
# 真全屏:使用 Qt 的 showFullScreen,退出时用 showMaximized 恢复普通 Windows 窗口
|
||||
self.showFullScreen()
|
||||
# 全屏时关闭滚动条,避免 1920x1080 屏幕上还出现滚动条
|
||||
self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
self.view.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
else:
|
||||
# 恢复为标准的最大化窗口(带边框/任务栏)
|
||||
self.showMaximized()
|
||||
self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
||||
self.view.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
||||
|
||||
def _toggle_mode(self):
|
||||
self._edit_mode = not self._edit_mode
|
||||
btn.setText("展示模式" if self._edit_mode else "编辑模式")
|
||||
# 按钮文字:当前是编辑 -> 显示“展示模式”;当前是展示 -> 显示“编辑模式”
|
||||
self.btn_mode.setText("展示模式" if self._edit_mode else "编辑模式")
|
||||
# 编辑模式:可拖动缩放;展示模式:锁定并启动 Influx 刷新
|
||||
for it in self.view.scene_obj.items():
|
||||
if isinstance(it, DashboardItem):
|
||||
|
|
@ -870,6 +1253,155 @@ class MainWindow(QMainWindow):
|
|||
# 回到编辑模式:停止查询
|
||||
self.influx_client.stopQuery()
|
||||
|
||||
# 展示模式需要全屏 / 编辑模式下根据是否“全屏编辑”决定
|
||||
self.view.set_edit_mode(self._edit_mode)
|
||||
self._update_window_fullscreen()
|
||||
self._apply_ui_mode()
|
||||
|
||||
def _on_fullscreen_edit(self):
|
||||
"""
|
||||
全屏编辑:不进入展示模式,只是把窗口拉到全屏,
|
||||
并且隐藏右侧属性面板,让用户只通过拖拽/缩放来调整布局。
|
||||
再次点击 / 按 ESC 退出全屏编辑。
|
||||
"""
|
||||
self._fullscreen_edit = not self._fullscreen_edit
|
||||
# 确保在“编辑模式”下
|
||||
if not self._edit_mode:
|
||||
# 切回编辑模式
|
||||
self._toggle_mode()
|
||||
|
||||
# 全屏编辑时隐藏属性面板,只保留画布
|
||||
self.prop.setVisible(not self._fullscreen_edit)
|
||||
|
||||
self._update_window_fullscreen()
|
||||
self._apply_ui_mode()
|
||||
|
||||
def _apply_ui_mode(self):
|
||||
"""根据当前模式控制按钮可用性,保证“全屏编辑只拖拽/缩放”更纯粹。"""
|
||||
if not self._edit_mode:
|
||||
# 展示模式:禁用编辑相关,并隐藏右侧/工具栏,只保留全屏画布
|
||||
for b in (
|
||||
self.btn_influx,
|
||||
self.btn_add_img,
|
||||
self.btn_add_label,
|
||||
self.btn_add_web,
|
||||
self.btn_full_edit,
|
||||
self.btn_delete,
|
||||
self.btn_save,
|
||||
self.btn_load,
|
||||
):
|
||||
b.setEnabled(False)
|
||||
self.btn_mode.setEnabled(True) # 允许退出展示
|
||||
# 右侧和工具栏隐藏,达到“画布全屏”效果(Esc 或按钮退出)
|
||||
self.right_panel.setVisible(False)
|
||||
self.toolbar_widget.setVisible(False)
|
||||
# 全屏展示:去掉 splitter 句柄、关闭滚动条,避免白边/滚动条露出
|
||||
self.splitter.setHandleWidth(0)
|
||||
self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
self.view.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
# 全屏模式:间距设为0(工具栏已隐藏,画布紧贴顶部)
|
||||
central_layout = self.centralWidget().layout()
|
||||
if central_layout:
|
||||
central_layout.setSpacing(0)
|
||||
return
|
||||
|
||||
# 编辑模式
|
||||
if self._fullscreen_edit:
|
||||
# 全屏编辑:只允许退出全屏编辑(以及关闭程序/ESC),同时隐藏右侧与工具栏
|
||||
for b in (
|
||||
self.btn_mode,
|
||||
self.btn_influx,
|
||||
self.btn_add_img,
|
||||
self.btn_add_label,
|
||||
self.btn_add_web,
|
||||
self.btn_delete,
|
||||
self.btn_save,
|
||||
self.btn_load,
|
||||
):
|
||||
b.setEnabled(False)
|
||||
self.btn_full_edit.setEnabled(True)
|
||||
self.right_panel.setVisible(False)
|
||||
self.toolbar_widget.setVisible(False)
|
||||
self.splitter.setHandleWidth(0)
|
||||
self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
self.view.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
# 全屏编辑模式:间距设为0(工具栏已隐藏,画布紧贴顶部)
|
||||
central_layout = self.centralWidget().layout()
|
||||
if central_layout:
|
||||
central_layout.setSpacing(0)
|
||||
else:
|
||||
# 普通编辑
|
||||
for b in (
|
||||
self.btn_mode,
|
||||
self.btn_influx,
|
||||
self.btn_add_img,
|
||||
self.btn_add_label,
|
||||
self.btn_add_web,
|
||||
self.btn_full_edit,
|
||||
self.btn_delete,
|
||||
self.btn_save,
|
||||
self.btn_load,
|
||||
):
|
||||
b.setEnabled(True)
|
||||
# 确保右侧面板和属性面板都可见
|
||||
self.right_panel.setVisible(True)
|
||||
self.prop.setVisible(True)
|
||||
self.toolbar_widget.setVisible(True)
|
||||
self.splitter.setHandleWidth(6)
|
||||
self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
||||
self.view.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
||||
# 普通编辑模式:工具栏和画布之间添加间距(像图2那样)
|
||||
central_layout = self.centralWidget().layout()
|
||||
if central_layout:
|
||||
central_layout.setSpacing(4)
|
||||
|
||||
def _on_canvas_clicked(self, rect: QRectF):
|
||||
# 只允许在编辑模式下选中画布
|
||||
if not self._edit_mode:
|
||||
return
|
||||
self.prop.set_canvas(rect)
|
||||
|
||||
def _on_canvas_changed(self, x: float, y: float, w: float, h: float):
|
||||
self.view.scene_obj.set_canvas_rect(QRectF(x, y, w, h))
|
||||
|
||||
# ---------- 组件列表 ----------
|
||||
|
||||
def _describe_item(self, it: DashboardItem) -> str:
|
||||
z = int(it.zValue())
|
||||
if isinstance(it, ImageItem):
|
||||
name = os.path.basename(getattr(it, "_image_path", "") or "图片")
|
||||
return f"[图片 z={z}] {name}"
|
||||
if isinstance(it, LabelItem):
|
||||
cfg = it.label_config()
|
||||
field = cfg.get("fieldName") or "未绑定"
|
||||
return f"[标签 z={z}] {field}"
|
||||
if isinstance(it, WebItem):
|
||||
url = getattr(it, "_url", "") or ""
|
||||
short = url if len(url) <= 32 else url[:29] + "..."
|
||||
return f"[曲线 z={z}] {short}"
|
||||
return f"[组件 z={z}]"
|
||||
|
||||
def _refresh_item_list(self):
|
||||
self.item_list.blockSignals(True)
|
||||
self.item_list.clear()
|
||||
for gitem in self.view.scene_obj.items():
|
||||
if isinstance(gitem, DashboardItem):
|
||||
text = self._describe_item(gitem)
|
||||
li = QListWidgetItem(text)
|
||||
li.setData(Qt.ItemDataRole.UserRole, gitem)
|
||||
self.item_list.addItem(li)
|
||||
self.item_list.blockSignals(False)
|
||||
|
||||
def _on_list_activated(self, item: QListWidgetItem):
|
||||
gitem = item.data(Qt.ItemDataRole.UserRole)
|
||||
if not isinstance(gitem, DashboardItem):
|
||||
return
|
||||
scene = self.view.scene_obj
|
||||
scene.clearSelection()
|
||||
gitem.setSelected(True)
|
||||
self.view.centerOn(gitem)
|
||||
self.prop.set_current_item(gitem)
|
||||
|
||||
def _on_influx_config(self):
|
||||
dlg = InfluxConfigDialog(self.influx_settings, self)
|
||||
if dlg.exec() == QDialog.DialogCode.Accepted:
|
||||
|
|
@ -879,14 +1411,17 @@ class MainWindow(QMainWindow):
|
|||
def _on_add_image(self):
|
||||
item = self.view.add_image_item()
|
||||
self.prop.set_current_item(item)
|
||||
self._refresh_item_list()
|
||||
|
||||
def _on_add_label(self):
|
||||
item = self.view.add_label_item()
|
||||
self.prop.set_current_item(item)
|
||||
self._refresh_item_list()
|
||||
|
||||
def _on_add_web(self):
|
||||
item = self.view.add_web_item()
|
||||
self.prop.set_current_item(item)
|
||||
self._refresh_item_list()
|
||||
|
||||
def _on_save(self):
|
||||
try:
|
||||
|
|
@ -898,6 +1433,10 @@ class MainWindow(QMainWindow):
|
|||
def _on_load(self):
|
||||
try:
|
||||
self.view.load_layout(self._layout_path)
|
||||
self._refresh_item_list()
|
||||
self.view.reset_scroll()
|
||||
cs = self.view.scene_obj.canvas_state()
|
||||
self.prop.set_canvas(QRectF(cs["x"], cs["y"], cs["w"], cs["h"]))
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "加载失败", str(e))
|
||||
|
||||
|
|
@ -910,6 +1449,55 @@ class MainWindow(QMainWindow):
|
|||
for it in items:
|
||||
scene.removeItem(it)
|
||||
self.prop.set_current_item(None)
|
||||
self._refresh_item_list()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
"""ESC 支持退出全屏展示 / 全屏编辑"""
|
||||
if event.key() == Qt.Key.Key_Escape:
|
||||
# 如果当前是展示模式:ESC 直接切回编辑模式(并退出全屏)
|
||||
if not self._edit_mode:
|
||||
self._toggle_mode()
|
||||
return
|
||||
# 如果是全屏编辑:只退出全屏编辑,保留编辑模式
|
||||
if self._fullscreen_edit:
|
||||
self._fullscreen_edit = False
|
||||
self._update_window_fullscreen()
|
||||
self._apply_ui_mode()
|
||||
return
|
||||
super().keyPressEvent(event)
|
||||
|
||||
def resizeEvent(self, event):
|
||||
"""窗口大小变化时,确保 splitter 正确分配空间"""
|
||||
super().resizeEvent(event)
|
||||
# 只在普通编辑模式下调整 splitter(全屏编辑/展示模式时右侧面板已隐藏)
|
||||
if self._edit_mode and not self._fullscreen_edit:
|
||||
# 延迟一下,确保窗口大小已经更新
|
||||
QTimer.singleShot(10, self._update_splitter_sizes)
|
||||
|
||||
def _update_splitter_sizes(self):
|
||||
"""更新 splitter 的 sizes,确保右侧面板在合理范围内"""
|
||||
if not self._edit_mode or self._fullscreen_edit:
|
||||
return
|
||||
total_width = self.width()
|
||||
if total_width < 100:
|
||||
return # 窗口太小,不处理
|
||||
# 右侧面板目标宽度:约 400px,但限制在 300-500 之间
|
||||
right_width = min(500, max(300, int(total_width * 0.2))) # 约 20% 宽度
|
||||
left_width = total_width - right_width
|
||||
if left_width > 0 and right_width > 0:
|
||||
self.splitter.setSizes([left_width, right_width])
|
||||
|
||||
def showEvent(self, event):
|
||||
"""第一次显示窗口时,默认最大化并把画布滚动条校正到 (0,0)。"""
|
||||
super().showEvent(event)
|
||||
if self._first_show:
|
||||
self._first_show = False
|
||||
self.showMaximized()
|
||||
# 需要在布局/最大化完成后再复位滚动条和初始化 splitter,避免被后续布局调整覆盖
|
||||
def _init_after_maximize():
|
||||
self.view.reset_scroll()
|
||||
self._update_splitter_sizes()
|
||||
QTimer.singleShot(100, _init_after_maximize)
|
||||
|
||||
# ---------- Influx 配置持久化 ----------
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue