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.
Files changed (79) hide show
  1. chartflow/__init__.py +28 -0
  2. chartflow/_verify_importexport.py +169 -0
  3. chartflow/edit/__init__.py +119 -0
  4. chartflow/edit/code_editor.py +1007 -0
  5. chartflow/edit/code_folder.py +523 -0
  6. chartflow/edit/completer.py +2652 -0
  7. chartflow/edit/context_analyzer.py +521 -0
  8. chartflow/edit/demo.py +469 -0
  9. chartflow/edit/line_number_area.py +548 -0
  10. chartflow/edit/minimap.py +581 -0
  11. chartflow/edit/python_editor.py +509 -0
  12. chartflow/edit/syntax_highlighter.py +291 -0
  13. chartflow/edit/test_editor.py +190 -0
  14. chartflow/flow/__init__.py +31 -0
  15. chartflow/flow/_demo.py +98 -0
  16. chartflow/flow/chart.py +1101 -0
  17. chartflow/flow/editor.py +375 -0
  18. chartflow/flow/flow.py +6 -0
  19. chartflow/flow/qchart.py +1250 -0
  20. chartflow/flow/test/__init__.py +0 -0
  21. chartflow/flow/test/test_chart.py +537 -0
  22. chartflow/flow/test_decorator_sync.py +102 -0
  23. chartflow/fsm/__init__.py +84 -0
  24. chartflow/fsm/decorators.py +395 -0
  25. chartflow/fsm/demo.py +381 -0
  26. chartflow/fsm/demo_pyqt6.py +881 -0
  27. chartflow/fsm/demo_tree.py +46 -0
  28. chartflow/fsm/logic.py +447 -0
  29. chartflow/fsm/meta.py +569 -0
  30. chartflow/fsm/scheduler.py +427 -0
  31. chartflow/fsm/stage.py +2079 -0
  32. chartflow/fsm/stdio.py +66 -0
  33. chartflow/fsm/test/__init__.py +7 -0
  34. chartflow/fsm/test/test_decorators.py +210 -0
  35. chartflow/fsm/test/test_events.py +202 -0
  36. chartflow/fsm/test/test_integration.py +596 -0
  37. chartflow/fsm/test/test_logic.py +371 -0
  38. chartflow/fsm/test/test_meta.py +192 -0
  39. chartflow/fsm/test/test_runtime_param.py +336 -0
  40. chartflow/fsm/test/test_scheduler.py +280 -0
  41. chartflow/fsm/test/test_stage.py +314 -0
  42. chartflow/fsm/test_behavior_events.py +263 -0
  43. chartflow/fsm/test_nested_parallel.py +80 -0
  44. chartflow/fsm/test_stage_spec.py +448 -0
  45. chartflow/fsm/utils.py +34 -0
  46. chartflow/node/__init__.py +66 -0
  47. chartflow/node/_node_base.py +727 -0
  48. chartflow/node/_node_interaction.py +488 -0
  49. chartflow/node/_node_interface.py +426 -0
  50. chartflow/node/_node_markers.py +648 -0
  51. chartflow/node/_node_ports.py +536 -0
  52. chartflow/node/_port_interface.py +111 -0
  53. chartflow/node/arrow_item.py +287 -0
  54. chartflow/node/color_utils.py +113 -0
  55. chartflow/node/connection_item.py +939 -0
  56. chartflow/node/demo.py +258 -0
  57. chartflow/node/demo_arrow_drag.py +46 -0
  58. chartflow/node/demo_decorator.py +63 -0
  59. chartflow/node/demo_ports.py +69 -0
  60. chartflow/node/enums.py +35 -0
  61. chartflow/node/example.py +84 -0
  62. chartflow/node/layout_utils.py +307 -0
  63. chartflow/node/main_window.py +196 -0
  64. chartflow/node/menu_bar.py +240 -0
  65. chartflow/node/node_canvas.py +1730 -0
  66. chartflow/node/node_item.py +754 -0
  67. chartflow/node/popmenu.py +472 -0
  68. chartflow/node/port_item.py +591 -0
  69. chartflow/node/qnode_editor.py +73 -0
  70. chartflow/node/selection_rect.py +53 -0
  71. chartflow/node/style_panel.py +615 -0
  72. chartflow/node/styles.py +63 -0
  73. chartflow/node/test_qnode.py +2336 -0
  74. chartflow/showcase.py +528 -0
  75. chartflow/theme.py +508 -0
  76. chartflow-0.1.dist-info/METADATA +23 -0
  77. chartflow-0.1.dist-info/RECORD +79 -0
  78. chartflow-0.1.dist-info/WHEEL +5 -0
  79. chartflow-0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,581 @@
1
+ """
2
+ 缩略图滑块进度条
3
+ 提供代码缩略图和快速导航功能
4
+ """
5
+
6
+ from PyQt6.QtCore import QSize
7
+
8
+
9
+ from PyQt6.QtWidgets import (
10
+ QWidget, QVBoxLayout, QScrollArea, QFrame,
11
+ QGraphicsView, QGraphicsScene, QGraphicsTextItem,
12
+ QApplication
13
+ )
14
+ from PyQt6.QtCore import (
15
+ Qt, QRect, QRectF, QPoint, QPointF,
16
+ pyqtSignal, QTimer
17
+ )
18
+ from PyQt6.QtGui import (
19
+ QPainter, QColor, QFont, QFontMetrics,
20
+ QMouseEvent, QWheelEvent, QPaintEvent,
21
+ QTextDocument, QTextCursor, QTextCharFormat
22
+ )
23
+ from typing import Optional, Callable
24
+
25
+
26
+ class MinimapWidget(QWidget):
27
+ """代码缩略图控件"""
28
+
29
+ # 信号
30
+ clicked = pyqtSignal(int) # 点击位置对应的行号
31
+ viewport_changed = pyqtSignal(float, float) # 视口改变 (start_ratio, end_ratio)
32
+
33
+ # 颜色配置(浅色主题)
34
+ COLORS = {
35
+ 'background': '#f5f5f5',
36
+ 'text': '#666666',
37
+ 'keyword': '#0000ff',
38
+ 'string': '#008000',
39
+ 'comment': '#808080',
40
+ 'function': '#795e26',
41
+ 'class': '#267f99',
42
+ 'number': '#098658',
43
+ 'viewport': '#5a9bd5',
44
+ 'viewport_border': '#4a8bc5',
45
+ }
46
+
47
+ def __init__(self, parent=None):
48
+ super().__init__(parent)
49
+
50
+ self._code: str = ""
51
+ self._lines: list = []
52
+ self._line_count: int = 0
53
+
54
+ # 缩略图比例
55
+ self._scale: float = 0.15
56
+ self._line_height: int = 2 # 每行高度(像素)
57
+ self._char_width: float = 1.2 # 字符宽度
58
+
59
+ # 视口位置
60
+ self._viewport_start: float = 0.0 # 0.0 - 1.0
61
+ self._viewport_end: float = 1.0 # 0.0 - 1.0
62
+
63
+ # 拖拽状态
64
+ self._dragging: bool = False
65
+ self._drag_start_y: int = 0
66
+ self._drag_start_viewport: float = 0.0
67
+
68
+ # 设置固定宽度
69
+ self.setFixedWidth(120)
70
+ self.setMinimumHeight(100)
71
+
72
+ # 启用鼠标跟踪
73
+ self.setMouseTracking(True)
74
+
75
+ # 设置背景色
76
+ self.setStyleSheet(f"background-color: {self.COLORS['background']};")
77
+
78
+ def set_code(self, code: str):
79
+ """设置代码"""
80
+ self._code = code
81
+ self._lines = code.split('\n') if code else []
82
+ self._line_count = len(self._lines)
83
+ self.update()
84
+
85
+ def set_scale(self, scale: float):
86
+ """设置缩放比例"""
87
+ self._scale = max(0.05, min(0.5, scale))
88
+ self.update()
89
+
90
+ def set_viewport(self, start_ratio: float, end_ratio: float):
91
+ """设置视口位置"""
92
+ self._viewport_start = max(0.0, min(1.0, start_ratio))
93
+ self._viewport_end = max(0.0, min(1.0, end_ratio))
94
+ self.update()
95
+
96
+ def set_viewport_by_lines(self, first_visible: int, last_visible: int, total_lines: int):
97
+ """通过行号设置视口"""
98
+ if total_lines > 0:
99
+ self._viewport_start = first_visible / total_lines
100
+ self._viewport_end = (last_visible + 1) / total_lines
101
+ self.update()
102
+
103
+ def paintEvent(self, event: QPaintEvent):
104
+ """绘制事件"""
105
+ painter = QPainter(self)
106
+ painter.setRenderHint(QPainter.RenderHint.Antialiasing, False)
107
+
108
+ # 绘制背景
109
+ painter.fillRect(self.rect(), QColor(self.COLORS['background']))
110
+
111
+ if not self._lines:
112
+ return
113
+
114
+ # 计算绘制参数
115
+ widget_height = self.height()
116
+ content_height = self._line_count * self._line_height
117
+
118
+ # 如果内容太高,需要缩放
119
+ if content_height > widget_height:
120
+ scale_y = widget_height / content_height
121
+ else:
122
+ scale_y = 1.0
123
+
124
+ # 绘制代码行
125
+ painter.setPen(QColor(self.COLORS['text']))
126
+ painter.setFont(QFont('Consolas', 2))
127
+
128
+ for i, line in enumerate(self._lines):
129
+ y = int(i * self._line_height * scale_y)
130
+
131
+ # 绘制行(简化处理,只显示部分字符)
132
+ display_line = line[:80] # 限制行长度
133
+ if display_line.strip():
134
+ # 简单的语法高亮
135
+ self._draw_line(painter, display_line, 2, y, scale_y)
136
+
137
+ # 绘制视口指示器
138
+ self._draw_viewport(painter, scale_y)
139
+
140
+ painter.end()
141
+
142
+ def _draw_line(self, painter: QPainter, line: str, x: int, y: int, scale_y: float):
143
+ """绘制单行代码(简化语法高亮)"""
144
+ # 简单的关键字检测
145
+ stripped = line.strip()
146
+
147
+ # 根据行类型选择颜色
148
+ color = self.COLORS['text']
149
+ if stripped.startswith('#'):
150
+ color = self.COLORS['comment']
151
+ elif stripped.startswith('class '):
152
+ color = self.COLORS['class']
153
+ elif stripped.startswith('def '):
154
+ color = self.COLORS['function']
155
+ elif stripped.startswith(('import ', 'from ')):
156
+ color = self.COLORS['keyword']
157
+ elif any(kw in stripped for kw in ['return', 'if', 'for', 'while', 'with', 'try']):
158
+ color = self.COLORS['keyword']
159
+
160
+ painter.setPen(QColor(color))
161
+
162
+ # 绘制文本(限制长度)
163
+ display_text = line[:50].replace('\t', ' ')
164
+ painter.drawText(x, y + int(self._line_height * scale_y) - 1, display_text)
165
+
166
+ def _draw_viewport(self, painter: QPainter, scale_y: float):
167
+ """绘制视口指示器"""
168
+ widget_height = self.height()
169
+
170
+ # 计算视口矩形
171
+ viewport_y = int(self._viewport_start * self._line_count * self._line_height * scale_y)
172
+ viewport_height = int((self._viewport_end - self._viewport_start) *
173
+ self._line_count * self._line_height * scale_y)
174
+
175
+ # 限制在widget内
176
+ viewport_y = max(0, min(viewport_y, widget_height - 10))
177
+ viewport_height = max(10, min(viewport_height, widget_height - viewport_y))
178
+
179
+ # 绘制视口背景
180
+ viewport_rect = QRect(0, viewport_y, self.width(), viewport_height)
181
+ painter.fillRect(viewport_rect, QColor(self.COLORS['viewport']))
182
+
183
+ # 绘制视口边框
184
+ painter.setPen(QColor(self.COLORS['viewport_border']))
185
+ painter.drawRect(viewport_rect)
186
+
187
+ def mousePressEvent(self, event: QMouseEvent):
188
+ """鼠标按下事件"""
189
+ if event.button() == Qt.MouseButton.LeftButton:
190
+ self._dragging = True
191
+ self._drag_start_y = event.pos().y()
192
+ self._drag_start_viewport = self._viewport_start
193
+
194
+ # 计算点击位置对应的行号
195
+ line = self._get_line_at_y(event.pos().y())
196
+ self.clicked.emit(line)
197
+
198
+ def mouseMoveEvent(self, event: QMouseEvent):
199
+ """鼠标移动事件"""
200
+ if self._dragging:
201
+ # 计算拖拽偏移
202
+ delta_y = event.pos().y() - self._drag_start_y
203
+ widget_height = self.height()
204
+
205
+ # 转换为视口比例偏移
206
+ if self._line_count > 0:
207
+ delta_ratio = delta_y / (self._line_count * self._line_height)
208
+ new_start = self._drag_start_viewport + delta_ratio
209
+ new_start = max(0.0, min(1.0 - (self._viewport_end - self._viewport_start), new_start))
210
+
211
+ viewport_size = self._viewport_end - self._viewport_start
212
+ self._viewport_start = new_start
213
+ self._viewport_end = new_start + viewport_size
214
+
215
+ self.viewport_changed.emit(self._viewport_start, self._viewport_end)
216
+ self.update()
217
+
218
+ def mouseReleaseEvent(self, event: QMouseEvent):
219
+ """鼠标释放事件"""
220
+ if event.button() == Qt.MouseButton.LeftButton:
221
+ self._dragging = False
222
+
223
+ def wheelEvent(self, event: QWheelEvent):
224
+ """滚轮事件"""
225
+ # 根据滚轮滚动调整视口
226
+ delta = event.angleDelta().y()
227
+ scroll_step = 0.05
228
+
229
+ if delta > 0:
230
+ # 向上滚动
231
+ new_start = max(0.0, self._viewport_start - scroll_step)
232
+ viewport_size = self._viewport_end - self._viewport_start
233
+ self._viewport_start = new_start
234
+ self._viewport_end = new_start + viewport_size
235
+ else:
236
+ # 向下滚动
237
+ viewport_size = self._viewport_end - self._viewport_start
238
+ new_start = min(1.0 - viewport_size, self._viewport_start + scroll_step)
239
+ self._viewport_start = new_start
240
+ self._viewport_end = new_start + viewport_size
241
+
242
+ self.viewport_changed.emit(self._viewport_start, self._viewport_end)
243
+ self.update()
244
+
245
+ def _get_line_at_y(self, y: int) -> int:
246
+ """获取指定y坐标对应的行号"""
247
+ widget_height = self.height()
248
+ content_height = self._line_count * self._line_height
249
+
250
+ if content_height > widget_height:
251
+ scale_y = widget_height / content_height
252
+ else:
253
+ scale_y = 1.0
254
+
255
+ line = int(y / (self._line_height * scale_y))
256
+ return max(0, min(line, self._line_count - 1))
257
+
258
+ def get_preferred_height(self) -> int:
259
+ """获取首选高度"""
260
+ return max(100, int(self._line_count * self._line_height * self._scale))
261
+
262
+
263
+ class MinimapSlider(QWidget):
264
+ """缩略图滑块 - 类似PyCharm的右侧进度条"""
265
+
266
+ # 信号
267
+ position_changed = pyqtSignal(float) # 位置改变信号 (0.0 - 1.0)
268
+
269
+ # 颜色配置
270
+ COLORS = {
271
+ 'background': '#f0f0f0',
272
+ 'track': '#e0e0e0',
273
+ 'thumb': '#5a9bd5',
274
+ 'thumb_hover': '#4a8bc5',
275
+ 'thumb_border': '#3a7bb5',
276
+ 'highlight': '#ff8c00',
277
+ }
278
+
279
+ def __init__(self, parent=None):
280
+ super().__init__(parent)
281
+
282
+ self._value: float = 0.0 # 当前值 (0.0 - 1.0)
283
+ self._thumb_height: int = 50 # 滑块高度
284
+ self._thumb_ratio: float = 0.1 # 滑块高度比例
285
+ self._dragging: bool = False
286
+ self._hover: bool = False
287
+ self._drag_start_y: int = 0
288
+ self._drag_start_value: float = 0.0
289
+
290
+ # 设置固定宽度
291
+ self.setFixedWidth(15)
292
+ self.setMinimumHeight(100)
293
+
294
+ # 启用鼠标跟踪
295
+ self.setMouseTracking(True)
296
+
297
+ # 设置样式
298
+ self.setStyleSheet(f"background-color: {self.COLORS['background']};")
299
+
300
+ def set_value(self, value: float):
301
+ """设置当前值"""
302
+ self._value = max(0.0, min(1.0, value))
303
+ self.update()
304
+
305
+ def set_thumb_ratio(self, ratio: float):
306
+ """设置滑块高度比例(相对于整个控件)"""
307
+ self._thumb_ratio = max(0.05, min(1.0, ratio))
308
+ self.update()
309
+
310
+ def paintEvent(self, event: QPaintEvent):
311
+ """绘制事件"""
312
+ painter = QPainter(self)
313
+ painter.setRenderHint(QPainter.RenderHint.Antialiasing)
314
+
315
+ width = self.width()
316
+ height = self.height()
317
+
318
+ # 绘制背景轨道
319
+ painter.fillRect(self.rect(), QColor(self.COLORS['background']))
320
+
321
+ # 绘制轨道
322
+ track_rect = QRect(2, 0, width - 4, height)
323
+ painter.fillRect(track_rect, QColor(self.COLORS['track']))
324
+
325
+ # 计算滑块位置和大小
326
+ thumb_height = max(20, int(height * self._thumb_ratio))
327
+ max_thumb_y = height - thumb_height
328
+ thumb_y = int(self._value * max_thumb_y)
329
+
330
+ # 绘制滑块
331
+ thumb_rect = QRect(1, thumb_y, width - 2, thumb_height)
332
+
333
+ # 滑块颜色
334
+ thumb_color = self.COLORS['thumb_hover'] if self._hover else self.COLORS['thumb']
335
+ painter.fillRect(thumb_rect, QColor(thumb_color))
336
+
337
+ # 绘制滑块边框
338
+ painter.setPen(QColor(self.COLORS['thumb_border']))
339
+ painter.drawRect(thumb_rect)
340
+
341
+ painter.end()
342
+
343
+ def mousePressEvent(self, event: QMouseEvent):
344
+ """鼠标按下事件"""
345
+ if event.button() == Qt.MouseButton.LeftButton:
346
+ self._dragging = True
347
+ self._drag_start_y = event.pos().y()
348
+ self._drag_start_value = self._value
349
+
350
+ # 检查是否点击在滑块上
351
+ thumb_height = max(20, int(self.height() * self._thumb_ratio))
352
+ max_thumb_y = self.height() - thumb_height
353
+ thumb_y = int(self._value * max_thumb_y)
354
+
355
+ click_y = event.pos().y()
356
+ if not (thumb_y <= click_y <= thumb_y + thumb_height):
357
+ # 点击在轨道其他位置,直接跳转到该位置
358
+ new_value = click_y / self.height()
359
+ self.set_value(new_value)
360
+ self.position_changed.emit(self._value)
361
+
362
+ def mouseMoveEvent(self, event: QMouseEvent):
363
+ """鼠标移动事件"""
364
+ # 检查是否悬停在滑块上
365
+ thumb_height = max(20, int(self.height() * self._thumb_ratio))
366
+ max_thumb_y = self.height() - thumb_height
367
+ thumb_y = int(self._value * max_thumb_y)
368
+
369
+ click_y = event.pos().y()
370
+ was_hover = self._hover
371
+ self._hover = thumb_y <= click_y <= thumb_y + thumb_height
372
+
373
+ if was_hover != self._hover:
374
+ self.update()
375
+
376
+ if self._dragging:
377
+ # 计算拖拽偏移
378
+ delta_y = event.pos().y() - self._drag_start_y
379
+ max_thumb_y = self.height() - thumb_height
380
+
381
+ if max_thumb_y > 0:
382
+ delta_value = delta_y / max_thumb_y
383
+ new_value = self._drag_start_value + delta_value
384
+ self.set_value(new_value)
385
+ self.position_changed.emit(self._value)
386
+
387
+ def mouseReleaseEvent(self, event: QMouseEvent):
388
+ """鼠标释放事件"""
389
+ if event.button() == Qt.MouseButton.LeftButton:
390
+ self._dragging = False
391
+
392
+ def wheelEvent(self, event: QWheelEvent):
393
+ """滚轮事件"""
394
+ delta = event.angleDelta().y()
395
+ scroll_step = 0.05
396
+
397
+ if delta > 0:
398
+ self.set_value(self._value - scroll_step)
399
+ else:
400
+ self.set_value(self._value + scroll_step)
401
+
402
+ self.position_changed.emit(self._value)
403
+
404
+
405
+ class MinimapPanel(QWidget):
406
+ """缩略图面板 - 组合缩略图和滑块"""
407
+
408
+ # 信号
409
+ navigate_to_line = pyqtSignal(int) # 导航到指定行
410
+ viewport_scroll = pyqtSignal(float) # 视口滚动信号
411
+
412
+ def __init__(self, parent=None):
413
+ super().__init__(parent)
414
+
415
+ self._setup_ui()
416
+
417
+ def _setup_ui(self):
418
+ """设置UI"""
419
+ layout = QVBoxLayout(self)
420
+ layout.setContentsMargins(0, 0, 0, 0)
421
+ layout.setSpacing(0)
422
+
423
+ # 创建缩略图
424
+ self.minimap = MinimapWidget()
425
+ self.minimap.clicked.connect(self._on_minimap_clicked)
426
+ self.minimap.viewport_changed.connect(self._on_viewport_changed)
427
+ layout.addWidget(self.minimap, 1)
428
+
429
+ # 创建滑块
430
+ self.slider = MinimapSlider()
431
+ self.slider.position_changed.connect(self._on_slider_changed)
432
+
433
+ # 将滑块放在右侧
434
+ self._slider_container = QWidget(self)
435
+ slider_layout = QVBoxLayout(self._slider_container)
436
+ slider_layout.setContentsMargins(0, 0, 0, 0)
437
+ slider_layout.addWidget(self.slider)
438
+
439
+ # 设置固定宽度
440
+ self.setFixedWidth(135)
441
+
442
+ # 启用鼠标跟踪
443
+ self.setMouseTracking(True)
444
+
445
+ def resizeEvent(self, event):
446
+ """调整大小事件"""
447
+ super().resizeEvent(event)
448
+ # 调整滑块容器位置
449
+ self._slider_container.setGeometry(
450
+ self.width() - 15, 0, 15, self.height()
451
+ )
452
+
453
+ def set_code(self, code: str):
454
+ """设置代码"""
455
+ self.minimap.set_code(code)
456
+
457
+ def set_viewport(self, first_visible: int, last_visible: int, total_lines: int):
458
+ """设置视口"""
459
+ self.minimap.set_viewport_by_lines(first_visible, last_visible, total_lines)
460
+
461
+ # 更新滑块
462
+ if total_lines > 0:
463
+ thumb_ratio = (last_visible - first_visible + 1) / total_lines
464
+ self.slider.set_thumb_ratio(thumb_ratio)
465
+
466
+ value = first_visible / total_lines
467
+ self.slider.set_value(value)
468
+
469
+ def _on_minimap_clicked(self, line: int):
470
+ """缩略图点击事件"""
471
+ self.navigate_to_line.emit(line)
472
+
473
+ def _on_viewport_changed(self, start_ratio: float, end_ratio: float):
474
+ """视口改变事件"""
475
+ # 更新滑块位置
476
+ self.slider.set_value(start_ratio)
477
+
478
+ # 发送滚动信号
479
+ self.viewport_scroll.emit(start_ratio)
480
+
481
+ def _on_slider_changed(self, value: float):
482
+ """滑块改变事件"""
483
+ # 更新缩略图视口
484
+ thumb_ratio = self.slider._thumb_ratio
485
+ self.minimap.set_viewport(value, value + thumb_ratio)
486
+
487
+ # 发送导航信号
488
+ if self.minimap._line_count > 0:
489
+ line = int(value * self.minimap._line_count)
490
+ self.navigate_to_line.emit(line)
491
+
492
+
493
+ class CodeOverview(QWidget):
494
+ """代码概览控件 - 简化版的缩略图"""
495
+
496
+ clicked = pyqtSignal(int)
497
+
498
+ def __init__(self, parent=None):
499
+ super().__init__(parent)
500
+
501
+ self._code: str = ""
502
+ self._lines: list = []
503
+ self._line_height: int = 2
504
+
505
+ self.setFixedWidth(100)
506
+ self.setMinimumHeight(200)
507
+
508
+ # 设置背景
509
+ self.setStyleSheet("background-color: #f5f5f5; border-left: 1px solid #ddd;")
510
+
511
+ def set_code(self, code: str):
512
+ """设置代码"""
513
+ self._code = code
514
+ self._lines = code.split('\n') if code else []
515
+ self.update()
516
+
517
+ def paintEvent(self, event: QPaintEvent):
518
+ """绘制事件"""
519
+ painter = QPainter(self)
520
+
521
+ # 绘制背景
522
+ painter.fillRect(self.rect(), QColor('#f5f5f5'))
523
+
524
+ if not self._lines:
525
+ return
526
+
527
+ # 计算缩放
528
+ content_height = len(self._lines) * self._line_height
529
+ scale = 1.0
530
+ if content_height > self.height():
531
+ scale = self.height() / content_height
532
+
533
+ # 绘制代码行
534
+ painter.setPen(QColor('#999999'))
535
+
536
+ for i, line in enumerate(self._lines):
537
+ y = int(i * self._line_height * scale)
538
+
539
+ if line.strip():
540
+ # 根据行内容调整颜色
541
+ color = '#999999'
542
+ stripped = line.strip()
543
+
544
+ if stripped.startswith('class '):
545
+ color = '#267f99'
546
+ elif stripped.startswith('def '):
547
+ color = '#795e26'
548
+ elif stripped.startswith('#'):
549
+ color = '#808080'
550
+ elif any(kw in stripped for kw in ['if ', 'for ', 'while ', 'return']):
551
+ color = '#0000ff'
552
+
553
+ painter.setPen(QColor(color))
554
+
555
+ # 绘制简化表示
556
+ indent = len(line) - len(line.lstrip())
557
+ x = min(indent, 20)
558
+ width = min(len(line.strip()) * 0.6, self.width() - x)
559
+
560
+ painter.drawLine(x, y + 1, int(x + width), y + 1)
561
+
562
+ painter.end()
563
+
564
+ def mousePressEvent(self, event: QMouseEvent):
565
+ """鼠标按下事件"""
566
+ if event.button() == Qt.MouseButton.LeftButton:
567
+ # 计算点击的行号
568
+ content_height = len(self._lines) * self._line_height
569
+ scale = 1.0
570
+ if content_height > self.height():
571
+ scale = self.height() / content_height
572
+
573
+ y = event.pos().y()
574
+ line = int(y / (self._line_height * scale))
575
+ line = max(0, min(line, len(self._lines) - 1))
576
+
577
+ self.clicked.emit(line)
578
+
579
+ def sizeHint(self):
580
+ """大小建议"""
581
+ return QSize(100, 400)