chartflow 0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chartflow/__init__.py +28 -0
- chartflow/_verify_importexport.py +169 -0
- chartflow/edit/__init__.py +119 -0
- chartflow/edit/code_editor.py +1007 -0
- chartflow/edit/code_folder.py +523 -0
- chartflow/edit/completer.py +2652 -0
- chartflow/edit/context_analyzer.py +521 -0
- chartflow/edit/demo.py +469 -0
- chartflow/edit/line_number_area.py +548 -0
- chartflow/edit/minimap.py +581 -0
- chartflow/edit/python_editor.py +509 -0
- chartflow/edit/syntax_highlighter.py +291 -0
- chartflow/edit/test_editor.py +190 -0
- chartflow/flow/__init__.py +31 -0
- chartflow/flow/_demo.py +98 -0
- chartflow/flow/chart.py +1101 -0
- chartflow/flow/editor.py +375 -0
- chartflow/flow/flow.py +6 -0
- chartflow/flow/qchart.py +1250 -0
- chartflow/flow/test/__init__.py +0 -0
- chartflow/flow/test/test_chart.py +537 -0
- chartflow/flow/test_decorator_sync.py +102 -0
- chartflow/fsm/__init__.py +84 -0
- chartflow/fsm/decorators.py +395 -0
- chartflow/fsm/demo.py +381 -0
- chartflow/fsm/demo_pyqt6.py +881 -0
- chartflow/fsm/demo_tree.py +46 -0
- chartflow/fsm/logic.py +447 -0
- chartflow/fsm/meta.py +569 -0
- chartflow/fsm/scheduler.py +427 -0
- chartflow/fsm/stage.py +2079 -0
- chartflow/fsm/stdio.py +66 -0
- chartflow/fsm/test/__init__.py +7 -0
- chartflow/fsm/test/test_decorators.py +210 -0
- chartflow/fsm/test/test_events.py +202 -0
- chartflow/fsm/test/test_integration.py +596 -0
- chartflow/fsm/test/test_logic.py +371 -0
- chartflow/fsm/test/test_meta.py +192 -0
- chartflow/fsm/test/test_runtime_param.py +336 -0
- chartflow/fsm/test/test_scheduler.py +280 -0
- chartflow/fsm/test/test_stage.py +314 -0
- chartflow/fsm/test_behavior_events.py +263 -0
- chartflow/fsm/test_nested_parallel.py +80 -0
- chartflow/fsm/test_stage_spec.py +448 -0
- chartflow/fsm/utils.py +34 -0
- chartflow/node/__init__.py +66 -0
- chartflow/node/_node_base.py +727 -0
- chartflow/node/_node_interaction.py +488 -0
- chartflow/node/_node_interface.py +426 -0
- chartflow/node/_node_markers.py +648 -0
- chartflow/node/_node_ports.py +536 -0
- chartflow/node/_port_interface.py +111 -0
- chartflow/node/arrow_item.py +287 -0
- chartflow/node/color_utils.py +113 -0
- chartflow/node/connection_item.py +939 -0
- chartflow/node/demo.py +258 -0
- chartflow/node/demo_arrow_drag.py +46 -0
- chartflow/node/demo_decorator.py +63 -0
- chartflow/node/demo_ports.py +69 -0
- chartflow/node/enums.py +35 -0
- chartflow/node/example.py +84 -0
- chartflow/node/layout_utils.py +307 -0
- chartflow/node/main_window.py +196 -0
- chartflow/node/menu_bar.py +240 -0
- chartflow/node/node_canvas.py +1730 -0
- chartflow/node/node_item.py +754 -0
- chartflow/node/popmenu.py +472 -0
- chartflow/node/port_item.py +591 -0
- chartflow/node/qnode_editor.py +73 -0
- chartflow/node/selection_rect.py +53 -0
- chartflow/node/style_panel.py +615 -0
- chartflow/node/styles.py +63 -0
- chartflow/node/test_qnode.py +2336 -0
- chartflow/showcase.py +528 -0
- chartflow/theme.py +508 -0
- chartflow-0.1.dist-info/METADATA +23 -0
- chartflow-0.1.dist-info/RECORD +79 -0
- chartflow-0.1.dist-info/WHEEL +5 -0
- chartflow-0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
框选矩形模块
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from PyQt6.QtWidgets import QGraphicsItem, QGraphicsRectItem
|
|
8
|
+
from PyQt6.QtCore import Qt
|
|
9
|
+
from PyQt6.QtGui import QBrush, QPen, QColor
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SelectionRect(QGraphicsRectItem):
|
|
13
|
+
"""框选矩形"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, parent: Optional[QGraphicsItem] = None) -> None:
|
|
16
|
+
super().__init__(parent)
|
|
17
|
+
|
|
18
|
+
# 设置样式(半透明蓝色)
|
|
19
|
+
self._fill_color = QColor(0, 120, 215, 50)
|
|
20
|
+
self._border_color = QColor(0, 120, 215, 200)
|
|
21
|
+
self._border_width = 1
|
|
22
|
+
|
|
23
|
+
self.setBrush(QBrush(self._fill_color))
|
|
24
|
+
self.setPen(QPen(self._border_color, self._border_width, Qt.PenStyle.DashLine))
|
|
25
|
+
|
|
26
|
+
# 设置Z值,确保在最上层
|
|
27
|
+
self.setZValue(1000)
|
|
28
|
+
|
|
29
|
+
def setColor(self, color: QColor) -> None:
|
|
30
|
+
"""
|
|
31
|
+
设置框选矩形的填充颜色
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
color: 填充颜色
|
|
35
|
+
"""
|
|
36
|
+
self._fill_color = color
|
|
37
|
+
self.setBrush(QBrush(self._fill_color))
|
|
38
|
+
|
|
39
|
+
def setBorder(self, width: int = 1, color: QColor = None) -> None:
|
|
40
|
+
"""
|
|
41
|
+
设置框选矩形的边框宽度和颜色
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
width: 边框宽度,<=0 表示无边框
|
|
45
|
+
color: 边框颜色,None 表示使用默认颜色
|
|
46
|
+
"""
|
|
47
|
+
if width <= 0:
|
|
48
|
+
self.setPen(QPen(Qt.PenStyle.NoPen))
|
|
49
|
+
else:
|
|
50
|
+
self._border_width = width
|
|
51
|
+
if color is not None:
|
|
52
|
+
self._border_color = color
|
|
53
|
+
self.setPen(QPen(self._border_color, self._border_width, Qt.PenStyle.DashLine))
|
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
"""
|
|
2
|
+
样式面板模块 - 用于编辑选中节点的样式
|
|
3
|
+
贴合在canvas下方,支持多节点样式编辑
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import List, Optional
|
|
7
|
+
from PyQt6.QtWidgets import (
|
|
8
|
+
QWidget, QHBoxLayout, QVBoxLayout, QLabel, QPushButton,
|
|
9
|
+
QComboBox, QSpinBox, QSlider, QColorDialog,
|
|
10
|
+
QFrame, QSizePolicy
|
|
11
|
+
)
|
|
12
|
+
from PyQt6.QtCore import Qt, pyqtSignal
|
|
13
|
+
from PyQt6.QtGui import QColor, QFontDatabase
|
|
14
|
+
|
|
15
|
+
from chartflow.node.node_item import QNodeItem
|
|
16
|
+
from chartflow.theme import PALETTE
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ColorButton(QPushButton):
|
|
20
|
+
"""颜色选择按钮 - 圆形带颜色显示"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, parent=None, color: QColor = QColor(128, 128, 128)):
|
|
23
|
+
super().__init__(parent)
|
|
24
|
+
self._color = color
|
|
25
|
+
self.setFixedSize(24, 24)
|
|
26
|
+
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
|
27
|
+
self._updateStyle()
|
|
28
|
+
|
|
29
|
+
def _updateStyle(self):
|
|
30
|
+
"""更新按钮样式"""
|
|
31
|
+
self.setStyleSheet(f"""
|
|
32
|
+
ColorButton {{
|
|
33
|
+
background-color: {self._color.name()};
|
|
34
|
+
border: 2px solid #c0c0c0;
|
|
35
|
+
border-radius: 12px;
|
|
36
|
+
}}
|
|
37
|
+
ColorButton:hover {{
|
|
38
|
+
border: 2px solid #808080;
|
|
39
|
+
}}
|
|
40
|
+
ColorButton:pressed {{
|
|
41
|
+
border: 2px solid #404040;
|
|
42
|
+
}}
|
|
43
|
+
""")
|
|
44
|
+
|
|
45
|
+
def setColor(self, color: QColor):
|
|
46
|
+
"""设置按钮显示的颜色"""
|
|
47
|
+
self._color = color
|
|
48
|
+
self._updateStyle()
|
|
49
|
+
|
|
50
|
+
def getColor(self) -> QColor:
|
|
51
|
+
"""获取当前颜色"""
|
|
52
|
+
return self._color
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class StylePanel(QWidget):
|
|
56
|
+
"""
|
|
57
|
+
样式面板 - 用于编辑选中节点的样式
|
|
58
|
+
当画布上有节点被选中时显示,贴合在canvas下方
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
# 信号定义
|
|
62
|
+
styleChanged = pyqtSignal() # 样式改变信号
|
|
63
|
+
|
|
64
|
+
# 面板高度(两行布局)—— 供 NodeCanvas 定位时引用,避免魔法数字
|
|
65
|
+
PANEL_HEIGHT: int = 88
|
|
66
|
+
|
|
67
|
+
def __init__(self, parent: Optional[QWidget] = None) -> None:
|
|
68
|
+
"""
|
|
69
|
+
初始化样式面板
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
parent: 父窗口(通常是NodeCanvas)
|
|
73
|
+
"""
|
|
74
|
+
super().__init__(parent)
|
|
75
|
+
|
|
76
|
+
self._selected_nodes: List[QNodeItem] = []
|
|
77
|
+
self._is_updating_ui: bool = False
|
|
78
|
+
|
|
79
|
+
self._setupUI()
|
|
80
|
+
self._connectSignals()
|
|
81
|
+
self.hide()
|
|
82
|
+
|
|
83
|
+
def _setupUI(self) -> None:
|
|
84
|
+
"""设置UI组件 —— 两行布局:第一行(字体+填充色),第二行(边框+大小)"""
|
|
85
|
+
# 主布局:纵向,容纳两行
|
|
86
|
+
main_layout = QVBoxLayout(self)
|
|
87
|
+
main_layout.setContentsMargins(15, 8, 15, 8)
|
|
88
|
+
main_layout.setSpacing(6)
|
|
89
|
+
|
|
90
|
+
# 第一行:字体 + 填充色
|
|
91
|
+
row1 = QHBoxLayout()
|
|
92
|
+
row1.setSpacing(12)
|
|
93
|
+
row1.addWidget(self._createFontGroup())
|
|
94
|
+
row1.addWidget(self._createSeparator())
|
|
95
|
+
row1.addWidget(self._createFillColorGroup())
|
|
96
|
+
row1.addStretch()
|
|
97
|
+
main_layout.addLayout(row1)
|
|
98
|
+
|
|
99
|
+
# 第二行:边框 + 大小
|
|
100
|
+
row2 = QHBoxLayout()
|
|
101
|
+
row2.setSpacing(12)
|
|
102
|
+
row2.addWidget(self._createBorderGroup())
|
|
103
|
+
row2.addWidget(self._createSeparator())
|
|
104
|
+
row2.addWidget(self._createSizeGroup())
|
|
105
|
+
row2.addStretch()
|
|
106
|
+
main_layout.addLayout(row2)
|
|
107
|
+
|
|
108
|
+
# 设置面板样式 —— 与全局暗色主题保持一致
|
|
109
|
+
self.setStyleSheet(f"""
|
|
110
|
+
/* 面板整体样式 */
|
|
111
|
+
StylePanel {{
|
|
112
|
+
background-color: {PALETTE.panel};
|
|
113
|
+
border-top: 1px solid {PALETTE.border};
|
|
114
|
+
border-bottom: 1px solid {PALETTE.border};
|
|
115
|
+
}}
|
|
116
|
+
|
|
117
|
+
/* 标签样式 */
|
|
118
|
+
QLabel {{
|
|
119
|
+
color: {PALETTE.text};
|
|
120
|
+
font-size: 12px;
|
|
121
|
+
font-family: "Segoe UI", "Microsoft YaHei", sans-serif;
|
|
122
|
+
background: transparent;
|
|
123
|
+
}}
|
|
124
|
+
|
|
125
|
+
/* 下拉框样式 */
|
|
126
|
+
QComboBox {{
|
|
127
|
+
padding: 4px 8px;
|
|
128
|
+
border: 1px solid {PALETTE.border};
|
|
129
|
+
background-color: {PALETTE.input};
|
|
130
|
+
color: {PALETTE.text};
|
|
131
|
+
border-radius: 4px;
|
|
132
|
+
min-height: 22px;
|
|
133
|
+
font-size: 12px;
|
|
134
|
+
}}
|
|
135
|
+
QComboBox:hover {{
|
|
136
|
+
border: 1px solid {PALETTE.accent};
|
|
137
|
+
}}
|
|
138
|
+
QComboBox:focus {{
|
|
139
|
+
border: 1px solid {PALETTE.accent};
|
|
140
|
+
}}
|
|
141
|
+
QComboBox::drop-down {{
|
|
142
|
+
border: none;
|
|
143
|
+
width: 20px;
|
|
144
|
+
}}
|
|
145
|
+
QComboBox::down-arrow {{
|
|
146
|
+
image: none;
|
|
147
|
+
border-left: 4px solid transparent;
|
|
148
|
+
border-right: 4px solid transparent;
|
|
149
|
+
border-top: 5px solid {PALETTE.text_dim};
|
|
150
|
+
width: 0;
|
|
151
|
+
height: 0;
|
|
152
|
+
}}
|
|
153
|
+
QComboBox QAbstractItemView {{
|
|
154
|
+
background-color: {PALETTE.panel};
|
|
155
|
+
color: {PALETTE.text};
|
|
156
|
+
border: 1px solid {PALETTE.border};
|
|
157
|
+
border-radius: 4px;
|
|
158
|
+
selection-background-color: {PALETTE.accent_soft};
|
|
159
|
+
selection-color: {PALETTE.accent_hover};
|
|
160
|
+
outline: none;
|
|
161
|
+
}}
|
|
162
|
+
|
|
163
|
+
/* 数字输入框样式 */
|
|
164
|
+
QSpinBox {{
|
|
165
|
+
padding: 4px 8px;
|
|
166
|
+
border: 1px solid {PALETTE.border};
|
|
167
|
+
background-color: {PALETTE.input};
|
|
168
|
+
color: {PALETTE.text};
|
|
169
|
+
border-radius: 4px;
|
|
170
|
+
min-height: 22px;
|
|
171
|
+
font-size: 12px;
|
|
172
|
+
}}
|
|
173
|
+
QSpinBox:hover {{
|
|
174
|
+
border: 1px solid {PALETTE.accent};
|
|
175
|
+
}}
|
|
176
|
+
QSpinBox:focus {{
|
|
177
|
+
border: 1px solid {PALETTE.accent};
|
|
178
|
+
}}
|
|
179
|
+
QSpinBox::up-button, QSpinBox::down-button {{
|
|
180
|
+
width: 16px;
|
|
181
|
+
border: none;
|
|
182
|
+
background: {PALETTE.surface};
|
|
183
|
+
}}
|
|
184
|
+
QSpinBox::up-button:hover, QSpinBox::down-button:hover {{
|
|
185
|
+
background: {PALETTE.accent_soft};
|
|
186
|
+
}}
|
|
187
|
+
QSpinBox::up-button {{
|
|
188
|
+
border-bottom: 1px solid {PALETTE.border};
|
|
189
|
+
}}
|
|
190
|
+
|
|
191
|
+
/* 滑块样式 */
|
|
192
|
+
QSlider::groove:horizontal {{
|
|
193
|
+
height: 4px;
|
|
194
|
+
background: {PALETTE.border};
|
|
195
|
+
border-radius: 2px;
|
|
196
|
+
}}
|
|
197
|
+
QSlider::sub-page:horizontal {{
|
|
198
|
+
background: {PALETTE.accent};
|
|
199
|
+
border-radius: 2px;
|
|
200
|
+
}}
|
|
201
|
+
QSlider::handle:horizontal {{
|
|
202
|
+
width: 14px;
|
|
203
|
+
height: 14px;
|
|
204
|
+
margin: -5px 0;
|
|
205
|
+
background: {PALETTE.surface};
|
|
206
|
+
border: 2px solid {PALETTE.accent};
|
|
207
|
+
border-radius: 7px;
|
|
208
|
+
}}
|
|
209
|
+
QSlider::handle:horizontal:hover {{
|
|
210
|
+
background: {PALETTE.accent_hover};
|
|
211
|
+
}}
|
|
212
|
+
|
|
213
|
+
/* 增量按钮样式 (A+, A-, B+, B-, R+, R-) */
|
|
214
|
+
QPushButton#incrementBtn {{
|
|
215
|
+
padding: 2px 6px;
|
|
216
|
+
border: 1px solid {PALETTE.border};
|
|
217
|
+
background-color: {PALETTE.surface};
|
|
218
|
+
color: {PALETTE.text};
|
|
219
|
+
border-radius: 4px;
|
|
220
|
+
font-size: 11px;
|
|
221
|
+
font-weight: bold;
|
|
222
|
+
min-width: 24px;
|
|
223
|
+
min-height: 22px;
|
|
224
|
+
}}
|
|
225
|
+
QPushButton#incrementBtn:hover {{
|
|
226
|
+
background-color: {PALETTE.accent_soft};
|
|
227
|
+
border: 1px solid {PALETTE.accent};
|
|
228
|
+
color: {PALETTE.accent_hover};
|
|
229
|
+
}}
|
|
230
|
+
QPushButton#incrementBtn:pressed {{
|
|
231
|
+
background-color: {PALETTE.accent_pressed};
|
|
232
|
+
color: #ffffff;
|
|
233
|
+
}}
|
|
234
|
+
|
|
235
|
+
/* 颜色按钮基础样式 */
|
|
236
|
+
ColorButton {{
|
|
237
|
+
border-radius: 12px;
|
|
238
|
+
}}
|
|
239
|
+
""")
|
|
240
|
+
|
|
241
|
+
def _createFontGroup(self) -> QFrame:
|
|
242
|
+
"""创建字体选择区域"""
|
|
243
|
+
frame = QFrame()
|
|
244
|
+
layout = QHBoxLayout(frame)
|
|
245
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
246
|
+
layout.setSpacing(8)
|
|
247
|
+
|
|
248
|
+
# 字体选择
|
|
249
|
+
font_label = QLabel("字体:")
|
|
250
|
+
font_label.setStyleSheet("font-weight: bold;")
|
|
251
|
+
layout.addWidget(font_label)
|
|
252
|
+
|
|
253
|
+
self._font_combo = QComboBox()
|
|
254
|
+
self._font_combo.setFixedWidth(110)
|
|
255
|
+
fonts = QFontDatabase.families()
|
|
256
|
+
self._font_combo.addItem("-")
|
|
257
|
+
self._font_combo.addItems(fonts[:20]) # 只添加前20个常用字体
|
|
258
|
+
self._font_combo.setCurrentIndex(0)
|
|
259
|
+
layout.addWidget(self._font_combo)
|
|
260
|
+
|
|
261
|
+
# 字号
|
|
262
|
+
size_label = QLabel("字号:")
|
|
263
|
+
layout.addWidget(size_label)
|
|
264
|
+
|
|
265
|
+
self._font_size_spin = QSpinBox()
|
|
266
|
+
self._font_size_spin.setRange(6, 72)
|
|
267
|
+
self._font_size_spin.setValue(12)
|
|
268
|
+
self._font_size_spin.setFixedWidth(50)
|
|
269
|
+
self._font_size_spin.setSpecialValueText("-")
|
|
270
|
+
layout.addWidget(self._font_size_spin)
|
|
271
|
+
|
|
272
|
+
# A+ A- 按钮
|
|
273
|
+
self._font_size_up_btn = QPushButton("A+")
|
|
274
|
+
self._font_size_up_btn.setObjectName("incrementBtn")
|
|
275
|
+
self._font_size_up_btn.setToolTip("增大字号")
|
|
276
|
+
layout.addWidget(self._font_size_up_btn)
|
|
277
|
+
|
|
278
|
+
self._font_size_down_btn = QPushButton("A-")
|
|
279
|
+
self._font_size_down_btn.setObjectName("incrementBtn")
|
|
280
|
+
self._font_size_down_btn.setToolTip("减小字号")
|
|
281
|
+
layout.addWidget(self._font_size_down_btn)
|
|
282
|
+
|
|
283
|
+
return frame
|
|
284
|
+
|
|
285
|
+
def _createFillColorGroup(self) -> QFrame:
|
|
286
|
+
"""创建填充色选择区域"""
|
|
287
|
+
frame = QFrame()
|
|
288
|
+
layout = QHBoxLayout(frame)
|
|
289
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
290
|
+
layout.setSpacing(8)
|
|
291
|
+
|
|
292
|
+
label = QLabel("填充色:")
|
|
293
|
+
label.setStyleSheet("font-weight: bold;")
|
|
294
|
+
layout.addWidget(label)
|
|
295
|
+
|
|
296
|
+
self._fill_color_btn = ColorButton(color=QColor(128, 128, 128))
|
|
297
|
+
self._fill_color_btn.setToolTip("点击选择填充颜色")
|
|
298
|
+
layout.addWidget(self._fill_color_btn)
|
|
299
|
+
|
|
300
|
+
return frame
|
|
301
|
+
|
|
302
|
+
def _createBorderGroup(self) -> QFrame:
|
|
303
|
+
"""创建边框选择区域"""
|
|
304
|
+
frame = QFrame()
|
|
305
|
+
layout = QHBoxLayout(frame)
|
|
306
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
307
|
+
layout.setSpacing(8)
|
|
308
|
+
|
|
309
|
+
# 厚度标签和滑块
|
|
310
|
+
label = QLabel("边框:")
|
|
311
|
+
label.setStyleSheet("font-weight: bold;")
|
|
312
|
+
layout.addWidget(label)
|
|
313
|
+
|
|
314
|
+
self._border_slider = QSlider(Qt.Orientation.Horizontal)
|
|
315
|
+
self._border_slider.setRange(0, 10)
|
|
316
|
+
self._border_slider.setValue(1)
|
|
317
|
+
self._border_slider.setFixedWidth(70)
|
|
318
|
+
layout.addWidget(self._border_slider)
|
|
319
|
+
|
|
320
|
+
# 厚度值显示
|
|
321
|
+
self._border_value_label = QLabel("1")
|
|
322
|
+
self._border_value_label.setFixedWidth(15)
|
|
323
|
+
self._border_value_label.setStyleSheet(f"color: {PALETTE.text_dim};")
|
|
324
|
+
layout.addWidget(self._border_value_label)
|
|
325
|
+
|
|
326
|
+
# B+ B- 按钮
|
|
327
|
+
self._border_up_btn = QPushButton("B+")
|
|
328
|
+
self._border_up_btn.setObjectName("incrementBtn")
|
|
329
|
+
self._border_up_btn.setToolTip("增加边框厚度")
|
|
330
|
+
layout.addWidget(self._border_up_btn)
|
|
331
|
+
|
|
332
|
+
self._border_down_btn = QPushButton("B-")
|
|
333
|
+
self._border_down_btn.setObjectName("incrementBtn")
|
|
334
|
+
self._border_down_btn.setToolTip("减小边框厚度")
|
|
335
|
+
layout.addWidget(self._border_down_btn)
|
|
336
|
+
|
|
337
|
+
# 边框颜色
|
|
338
|
+
self._border_color_btn = ColorButton(color=QColor(51, 51, 51))
|
|
339
|
+
self._border_color_btn.setToolTip("点击选择边框颜色")
|
|
340
|
+
layout.addWidget(self._border_color_btn)
|
|
341
|
+
|
|
342
|
+
return frame
|
|
343
|
+
|
|
344
|
+
def _createSizeGroup(self) -> QFrame:
|
|
345
|
+
"""创建大小选择区域"""
|
|
346
|
+
frame = QFrame()
|
|
347
|
+
layout = QHBoxLayout(frame)
|
|
348
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
349
|
+
layout.setSpacing(8)
|
|
350
|
+
|
|
351
|
+
label = QLabel("节点大小:")
|
|
352
|
+
label.setStyleSheet("font-weight: bold;")
|
|
353
|
+
layout.addWidget(label)
|
|
354
|
+
|
|
355
|
+
self._size_slider = QSlider(Qt.Orientation.Horizontal)
|
|
356
|
+
self._size_slider.setRange(20, 150)
|
|
357
|
+
self._size_slider.setValue(40)
|
|
358
|
+
self._size_slider.setFixedWidth(90)
|
|
359
|
+
layout.addWidget(self._size_slider)
|
|
360
|
+
|
|
361
|
+
self._size_value_label = QLabel("40")
|
|
362
|
+
self._size_value_label.setFixedWidth(25)
|
|
363
|
+
self._size_value_label.setStyleSheet(f"color: {PALETTE.text_dim};")
|
|
364
|
+
layout.addWidget(self._size_value_label)
|
|
365
|
+
|
|
366
|
+
# R+ R- 按钮
|
|
367
|
+
self._size_up_btn = QPushButton("R+")
|
|
368
|
+
self._size_up_btn.setObjectName("incrementBtn")
|
|
369
|
+
self._size_up_btn.setToolTip("增大节点")
|
|
370
|
+
layout.addWidget(self._size_up_btn)
|
|
371
|
+
|
|
372
|
+
self._size_down_btn = QPushButton("R-")
|
|
373
|
+
self._size_down_btn.setObjectName("incrementBtn")
|
|
374
|
+
self._size_down_btn.setToolTip("减小节点")
|
|
375
|
+
layout.addWidget(self._size_down_btn)
|
|
376
|
+
|
|
377
|
+
return frame
|
|
378
|
+
|
|
379
|
+
def _createSeparator(self) -> QFrame:
|
|
380
|
+
"""创建垂直分隔线"""
|
|
381
|
+
line = QFrame()
|
|
382
|
+
line.setFrameShape(QFrame.Shape.VLine)
|
|
383
|
+
line.setStyleSheet(f"color: {PALETTE.border}; margin: 0 4px;")
|
|
384
|
+
line.setFixedWidth(2)
|
|
385
|
+
return line
|
|
386
|
+
|
|
387
|
+
def _connectSignals(self) -> None:
|
|
388
|
+
"""连接信号槽"""
|
|
389
|
+
# 字体变化
|
|
390
|
+
self._font_combo.currentTextChanged.connect(self._onFontChanged)
|
|
391
|
+
self._font_size_spin.valueChanged.connect(self._onFontSizeChanged)
|
|
392
|
+
self._font_size_up_btn.clicked.connect(self._onFontSizeUp)
|
|
393
|
+
self._font_size_down_btn.clicked.connect(self._onFontSizeDown)
|
|
394
|
+
|
|
395
|
+
# 填充色
|
|
396
|
+
self._fill_color_btn.clicked.connect(self._onFillColorClicked)
|
|
397
|
+
|
|
398
|
+
# 边框
|
|
399
|
+
self._border_slider.valueChanged.connect(self._onBorderWidthChanged)
|
|
400
|
+
self._border_up_btn.clicked.connect(self._onBorderUp)
|
|
401
|
+
self._border_down_btn.clicked.connect(self._onBorderDown)
|
|
402
|
+
self._border_color_btn.clicked.connect(self._onBorderColorClicked)
|
|
403
|
+
|
|
404
|
+
# 大小
|
|
405
|
+
self._size_slider.valueChanged.connect(self._onSizeChanged)
|
|
406
|
+
self._size_up_btn.clicked.connect(self._onSizeUp)
|
|
407
|
+
self._size_down_btn.clicked.connect(self._onSizeDown)
|
|
408
|
+
|
|
409
|
+
# ========== 事件处理方法(与之前相同)==========
|
|
410
|
+
|
|
411
|
+
def _onFontChanged(self, font_family: str) -> None:
|
|
412
|
+
"""字体改变"""
|
|
413
|
+
if self._is_updating_ui or font_family == "-":
|
|
414
|
+
return
|
|
415
|
+
for node in self._selected_nodes:
|
|
416
|
+
node.setFont(family=font_family)
|
|
417
|
+
self.styleChanged.emit()
|
|
418
|
+
|
|
419
|
+
def _onFontSizeChanged(self, size: int) -> None:
|
|
420
|
+
"""字号改变(绝对设置)"""
|
|
421
|
+
if self._is_updating_ui:
|
|
422
|
+
return
|
|
423
|
+
for node in self._selected_nodes:
|
|
424
|
+
node.setFont(size=size)
|
|
425
|
+
self.styleChanged.emit()
|
|
426
|
+
|
|
427
|
+
def _onFontSizeUp(self) -> None:
|
|
428
|
+
"""字号增加(相对调整)"""
|
|
429
|
+
for node in self._selected_nodes:
|
|
430
|
+
current_size = node.fontSize
|
|
431
|
+
node.setFont(size=current_size + 1)
|
|
432
|
+
self._updateUIFromNodes()
|
|
433
|
+
self.styleChanged.emit()
|
|
434
|
+
|
|
435
|
+
def _onFontSizeDown(self) -> None:
|
|
436
|
+
"""字号减小(相对调整)"""
|
|
437
|
+
for node in self._selected_nodes:
|
|
438
|
+
current_size = node.fontSize
|
|
439
|
+
if current_size > 6:
|
|
440
|
+
node.setFont(size=current_size - 1)
|
|
441
|
+
self._updateUIFromNodes()
|
|
442
|
+
self.styleChanged.emit()
|
|
443
|
+
|
|
444
|
+
def _onFillColorClicked(self) -> None:
|
|
445
|
+
"""点击填充色按钮"""
|
|
446
|
+
color = QColorDialog.getColor(
|
|
447
|
+
self._fill_color_btn.getColor(),
|
|
448
|
+
self,
|
|
449
|
+
"选择填充颜色"
|
|
450
|
+
)
|
|
451
|
+
if color.isValid():
|
|
452
|
+
self._fill_color_btn.setColor(color)
|
|
453
|
+
for node in self._selected_nodes:
|
|
454
|
+
node.setColor(color)
|
|
455
|
+
self.styleChanged.emit()
|
|
456
|
+
|
|
457
|
+
def _onBorderWidthChanged(self, width: int) -> None:
|
|
458
|
+
"""边框厚度改变(绝对设置)"""
|
|
459
|
+
if self._is_updating_ui:
|
|
460
|
+
return
|
|
461
|
+
self._border_value_label.setText(str(width))
|
|
462
|
+
for node in self._selected_nodes:
|
|
463
|
+
if width == 0:
|
|
464
|
+
node.setBorder(0)
|
|
465
|
+
else:
|
|
466
|
+
current_color = node.strokeColor
|
|
467
|
+
node.setBorder(width, current_color)
|
|
468
|
+
self.styleChanged.emit()
|
|
469
|
+
|
|
470
|
+
def _onBorderUp(self) -> None:
|
|
471
|
+
"""边框增加(相对调整)"""
|
|
472
|
+
for node in self._selected_nodes:
|
|
473
|
+
current_width = int(node.strokeWidth)
|
|
474
|
+
if current_width < 10:
|
|
475
|
+
current_color = node.strokeColor
|
|
476
|
+
node.setBorder(current_width + 1, current_color)
|
|
477
|
+
self._updateUIFromNodes()
|
|
478
|
+
self.styleChanged.emit()
|
|
479
|
+
|
|
480
|
+
def _onBorderDown(self) -> None:
|
|
481
|
+
"""边框减小(相对调整)"""
|
|
482
|
+
for node in self._selected_nodes:
|
|
483
|
+
current_width = int(node.strokeWidth)
|
|
484
|
+
if current_width > 0:
|
|
485
|
+
current_color = node.strokeColor
|
|
486
|
+
node.setBorder(max(0, current_width - 1), current_color)
|
|
487
|
+
self._updateUIFromNodes()
|
|
488
|
+
self.styleChanged.emit()
|
|
489
|
+
|
|
490
|
+
def _onBorderColorClicked(self) -> None:
|
|
491
|
+
"""点击边框色按钮"""
|
|
492
|
+
color = QColorDialog.getColor(
|
|
493
|
+
self._border_color_btn.getColor(),
|
|
494
|
+
self,
|
|
495
|
+
"选择边框颜色"
|
|
496
|
+
)
|
|
497
|
+
if color.isValid():
|
|
498
|
+
self._border_color_btn.setColor(color)
|
|
499
|
+
for node in self._selected_nodes:
|
|
500
|
+
current_width = int(node.strokeWidth)
|
|
501
|
+
if current_width > 0:
|
|
502
|
+
node.setBorder(current_width, color)
|
|
503
|
+
self.styleChanged.emit()
|
|
504
|
+
|
|
505
|
+
def _onSizeChanged(self, size: int) -> None:
|
|
506
|
+
"""大小改变(绝对设置)"""
|
|
507
|
+
if self._is_updating_ui:
|
|
508
|
+
return
|
|
509
|
+
self._size_value_label.setText(str(size))
|
|
510
|
+
for node in self._selected_nodes:
|
|
511
|
+
node.setRadius(size)
|
|
512
|
+
self.styleChanged.emit()
|
|
513
|
+
|
|
514
|
+
def _onSizeUp(self) -> None:
|
|
515
|
+
"""大小增加(相对调整)"""
|
|
516
|
+
for node in self._selected_nodes:
|
|
517
|
+
current_radius = node.radius
|
|
518
|
+
node.setRadius(current_radius + 5)
|
|
519
|
+
self._updateUIFromNodes()
|
|
520
|
+
self.styleChanged.emit()
|
|
521
|
+
|
|
522
|
+
def _onSizeDown(self) -> None:
|
|
523
|
+
"""大小减小(相对调整)"""
|
|
524
|
+
for node in self._selected_nodes:
|
|
525
|
+
current_radius = node.radius
|
|
526
|
+
if current_radius > 20:
|
|
527
|
+
node.setRadius(current_radius - 5)
|
|
528
|
+
self._updateUIFromNodes()
|
|
529
|
+
self.styleChanged.emit()
|
|
530
|
+
|
|
531
|
+
# ========== 公共方法(与之前相同)==========
|
|
532
|
+
|
|
533
|
+
def setSelectedNodes(self, nodes: List[QNodeItem]) -> None:
|
|
534
|
+
"""
|
|
535
|
+
设置当前选中的节点列表
|
|
536
|
+
|
|
537
|
+
Args:
|
|
538
|
+
nodes: 选中的节点列表
|
|
539
|
+
"""
|
|
540
|
+
self._selected_nodes = nodes
|
|
541
|
+
|
|
542
|
+
if not nodes:
|
|
543
|
+
self.hide()
|
|
544
|
+
return
|
|
545
|
+
|
|
546
|
+
self.show()
|
|
547
|
+
self._updateUIFromNodes()
|
|
548
|
+
|
|
549
|
+
def _updateUIFromNodes(self) -> None:
|
|
550
|
+
"""从节点数据更新UI显示(处理混合状态)"""
|
|
551
|
+
if not self._selected_nodes:
|
|
552
|
+
return
|
|
553
|
+
|
|
554
|
+
self._is_updating_ui = True
|
|
555
|
+
|
|
556
|
+
# 1. 检查字体家族是否一致
|
|
557
|
+
font_families = set(node.fontFamily for node in self._selected_nodes)
|
|
558
|
+
if len(font_families) == 1:
|
|
559
|
+
family = font_families.pop()
|
|
560
|
+
index = self._font_combo.findText(family)
|
|
561
|
+
if index >= 0:
|
|
562
|
+
self._font_combo.setCurrentIndex(index)
|
|
563
|
+
else:
|
|
564
|
+
self._font_combo.setCurrentText(family)
|
|
565
|
+
else:
|
|
566
|
+
self._font_combo.setCurrentIndex(0)
|
|
567
|
+
|
|
568
|
+
# 2. 检查字号是否一致
|
|
569
|
+
font_sizes = set(node.fontSize for node in self._selected_nodes)
|
|
570
|
+
if len(font_sizes) == 1:
|
|
571
|
+
self._font_size_spin.setValue(font_sizes.pop())
|
|
572
|
+
else:
|
|
573
|
+
self._font_size_spin.setValue(6)
|
|
574
|
+
|
|
575
|
+
# 3. 检查填充色是否一致
|
|
576
|
+
fill_colors = set(node.fillColor.name() for node in self._selected_nodes)
|
|
577
|
+
if len(fill_colors) == 1:
|
|
578
|
+
color = QColor(fill_colors.pop())
|
|
579
|
+
self._fill_color_btn.setColor(color)
|
|
580
|
+
else:
|
|
581
|
+
self._fill_color_btn.setColor(QColor(128, 128, 128))
|
|
582
|
+
|
|
583
|
+
# 4. 检查边框厚度是否一致
|
|
584
|
+
border_widths = set(int(node.strokeWidth) for node in self._selected_nodes)
|
|
585
|
+
if len(border_widths) == 1:
|
|
586
|
+
width = border_widths.pop()
|
|
587
|
+
self._border_slider.setValue(width)
|
|
588
|
+
self._border_value_label.setText(str(width))
|
|
589
|
+
else:
|
|
590
|
+
self._border_slider.setValue(1)
|
|
591
|
+
self._border_value_label.setText("-")
|
|
592
|
+
|
|
593
|
+
# 5. 检查边框颜色是否一致
|
|
594
|
+
border_colors = set(node.strokeColor.name() for node in self._selected_nodes)
|
|
595
|
+
if len(border_colors) == 1:
|
|
596
|
+
color = QColor(border_colors.pop())
|
|
597
|
+
self._border_color_btn.setColor(color)
|
|
598
|
+
else:
|
|
599
|
+
self._border_color_btn.setColor(QColor(128, 128, 128))
|
|
600
|
+
|
|
601
|
+
# 6. 检查大小是否一致
|
|
602
|
+
sizes = set(int(node.radius) for node in self._selected_nodes)
|
|
603
|
+
if len(sizes) == 1:
|
|
604
|
+
size = sizes.pop()
|
|
605
|
+
self._size_slider.setValue(size)
|
|
606
|
+
self._size_value_label.setText(str(size))
|
|
607
|
+
else:
|
|
608
|
+
self._size_slider.setValue(40)
|
|
609
|
+
self._size_value_label.setText("-")
|
|
610
|
+
|
|
611
|
+
self._is_updating_ui = False
|
|
612
|
+
|
|
613
|
+
def getSelectedNodes(self) -> List[QNodeItem]:
|
|
614
|
+
"""获取当前选中的节点列表"""
|
|
615
|
+
return self._selected_nodes.copy()
|