easycoder 251104.2__py2.py3-none-any.whl → 260108.1__py2.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.
Potentially problematic release.
This version of easycoder might be problematic. Click here for more details.
- easycoder/__init__.py +6 -3
- easycoder/debugger/__init__.py +5 -0
- easycoder/debugger/ec_dbg_value_display copy.py +195 -0
- easycoder/debugger/ec_dbg_value_display.py +24 -0
- easycoder/debugger/ec_dbg_watch_list copy.py +219 -0
- easycoder/debugger/ec_dbg_watchlist.py +293 -0
- easycoder/debugger/ec_debug.py +1025 -0
- easycoder/ec_border.py +15 -11
- easycoder/ec_classes.py +493 -11
- easycoder/ec_compiler.py +81 -44
- easycoder/ec_condition.py +1 -1
- easycoder/ec_core.py +1043 -1089
- easycoder/ec_gclasses.py +236 -0
- easycoder/ec_graphics.py +1683 -0
- easycoder/ec_handler.py +18 -13
- easycoder/ec_keyboard.py +50 -50
- easycoder/ec_mqtt.py +249 -0
- easycoder/ec_program.py +308 -156
- easycoder/ec_psutil.py +48 -0
- easycoder/ec_timestamp.py +2 -1
- easycoder/ec_value.py +65 -47
- easycoder/icons/exit.png +0 -0
- easycoder/icons/run.png +0 -0
- easycoder/icons/step.png +0 -0
- easycoder/icons/stop.png +0 -0
- easycoder/pre/README.md +3 -0
- easycoder/pre/__init__.py +17 -0
- easycoder/pre/debugger/__init__.py +5 -0
- easycoder/pre/debugger/ec_dbg_value_display copy.py +195 -0
- easycoder/pre/debugger/ec_dbg_value_display.py +24 -0
- easycoder/pre/debugger/ec_dbg_watch_list copy.py +219 -0
- easycoder/pre/debugger/ec_dbg_watchlist.py +293 -0
- easycoder/pre/debugger/ec_debug.py +1014 -0
- easycoder/pre/ec_border.py +67 -0
- easycoder/pre/ec_classes.py +470 -0
- easycoder/pre/ec_compiler.py +291 -0
- easycoder/pre/ec_condition.py +27 -0
- easycoder/pre/ec_core.py +2772 -0
- easycoder/pre/ec_gclasses.py +230 -0
- easycoder/{ec_pyside.py → pre/ec_graphics.py} +631 -496
- easycoder/pre/ec_handler.py +79 -0
- easycoder/pre/ec_keyboard.py +439 -0
- easycoder/pre/ec_program.py +557 -0
- easycoder/pre/ec_psutil.py +48 -0
- easycoder/pre/ec_timestamp.py +11 -0
- easycoder/pre/ec_value.py +124 -0
- easycoder/pre/icons/close.png +0 -0
- easycoder/pre/icons/exit.png +0 -0
- easycoder/pre/icons/run.png +0 -0
- easycoder/pre/icons/step.png +0 -0
- easycoder/pre/icons/stop.png +0 -0
- easycoder/pre/icons/tick.png +0 -0
- {easycoder-251104.2.dist-info → easycoder-260108.1.dist-info}/METADATA +11 -1
- easycoder-260108.1.dist-info/RECORD +59 -0
- easycoder/ec_debug.py +0 -464
- easycoder-251104.2.dist-info/RECORD +0 -20
- /easycoder/{close.png → icons/close.png} +0 -0
- /easycoder/{tick.png → icons/tick.png} +0 -0
- {easycoder-251104.2.dist-info → easycoder-260108.1.dist-info}/WHEEL +0 -0
- {easycoder-251104.2.dist-info → easycoder-260108.1.dist-info}/entry_points.txt +0 -0
- {easycoder-251104.2.dist-info → easycoder-260108.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""WatchListWidget showing variables on a single line with scrollable values."""
|
|
2
|
+
|
|
3
|
+
import bisect
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
from PySide6.QtWidgets import (
|
|
7
|
+
QWidget,
|
|
8
|
+
QHBoxLayout,
|
|
9
|
+
QVBoxLayout,
|
|
10
|
+
QLabel,
|
|
11
|
+
QPushButton,
|
|
12
|
+
QSizePolicy,
|
|
13
|
+
QScrollArea,
|
|
14
|
+
QPlainTextEdit,
|
|
15
|
+
QFrame,
|
|
16
|
+
QSplitter,
|
|
17
|
+
)
|
|
18
|
+
from PySide6.QtCore import Qt
|
|
19
|
+
from easycoder.ec_classes import ECVariable, ECDictionary, ECList, ECValue
|
|
20
|
+
from .ec_dbg_value_display import ValueDisplay
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class WatchListWidget(QWidget):
|
|
24
|
+
def __init__(self, debugger):
|
|
25
|
+
super().__init__(debugger)
|
|
26
|
+
self.debugger = debugger
|
|
27
|
+
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
|
28
|
+
|
|
29
|
+
self._rows: dict[str, dict] = {}
|
|
30
|
+
self._variable_set: set[str] = set()
|
|
31
|
+
self._order: list[str] = []
|
|
32
|
+
self._placeholder: QLabel | None = None
|
|
33
|
+
self._expanded_name: str | None = None
|
|
34
|
+
|
|
35
|
+
main_layout = QVBoxLayout(self)
|
|
36
|
+
main_layout.setContentsMargins(0, 0, 0, 0)
|
|
37
|
+
main_layout.setSpacing(6)
|
|
38
|
+
|
|
39
|
+
self.splitter = QSplitter(Qt.Orientation.Vertical, self)
|
|
40
|
+
self.splitter.setHandleWidth(6)
|
|
41
|
+
self.splitter.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
|
42
|
+
|
|
43
|
+
list_container = QWidget(self)
|
|
44
|
+
list_container.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
|
45
|
+
# Outer layout: scrollable labels (left) and fixed button column (right)
|
|
46
|
+
outer = QHBoxLayout(list_container)
|
|
47
|
+
outer.setContentsMargins(0, 0, 0, 0)
|
|
48
|
+
outer.setSpacing(2)
|
|
49
|
+
|
|
50
|
+
# Left: scroll area for labels
|
|
51
|
+
self.scroll = QScrollArea(self)
|
|
52
|
+
self.scroll.setWidgetResizable(True)
|
|
53
|
+
self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
|
54
|
+
self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
|
55
|
+
self.scroll.setFrameShape(QScrollArea.Shape.NoFrame)
|
|
56
|
+
|
|
57
|
+
self.content = QWidget()
|
|
58
|
+
self.content_layout = QVBoxLayout(self.content)
|
|
59
|
+
self.content_layout.setContentsMargins(4, 2, 4, 2)
|
|
60
|
+
self.content_layout.setSpacing(4)
|
|
61
|
+
self.content_layout.addStretch(1) # keep items grouped at the top
|
|
62
|
+
|
|
63
|
+
self.scroll.setWidget(self.content)
|
|
64
|
+
outer.addWidget(self.scroll, 1)
|
|
65
|
+
|
|
66
|
+
# Right: button column, top-aligned
|
|
67
|
+
self.buttons_column = QVBoxLayout()
|
|
68
|
+
self.buttons_column.setContentsMargins(0, 2, 4, 2)
|
|
69
|
+
self.buttons_column.setSpacing(4)
|
|
70
|
+
self.buttons_column.addStretch(1)
|
|
71
|
+
outer.addLayout(self.buttons_column)
|
|
72
|
+
|
|
73
|
+
self.splitter.addWidget(list_container)
|
|
74
|
+
|
|
75
|
+
self._build_expanded_panel()
|
|
76
|
+
|
|
77
|
+
main_layout.addWidget(self.splitter, 1)
|
|
78
|
+
# Give the list more space by default
|
|
79
|
+
try:
|
|
80
|
+
self.splitter.setStretchFactor(0, 3)
|
|
81
|
+
self.splitter.setStretchFactor(1, 2)
|
|
82
|
+
except Exception:
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
self._show_placeholder()
|
|
86
|
+
|
|
87
|
+
def _build_expanded_panel(self):
|
|
88
|
+
self.expanded_frame = QFrame(self)
|
|
89
|
+
self.expanded_frame.setFrameShape(QFrame.Shape.StyledPanel)
|
|
90
|
+
self.expanded_frame.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
|
91
|
+
|
|
92
|
+
expanded_layout = QVBoxLayout(self.expanded_frame)
|
|
93
|
+
expanded_layout.setContentsMargins(6, 6, 6, 6)
|
|
94
|
+
expanded_layout.setSpacing(4)
|
|
95
|
+
|
|
96
|
+
self.expanded_title = QLabel("Expanded view")
|
|
97
|
+
self.expanded_title.setStyleSheet("font-weight: bold;")
|
|
98
|
+
expanded_layout.addWidget(self.expanded_title)
|
|
99
|
+
|
|
100
|
+
self.expanded_body = QPlainTextEdit(self.expanded_frame)
|
|
101
|
+
self.expanded_body.setReadOnly(True)
|
|
102
|
+
self.expanded_body.setLineWrapMode(QPlainTextEdit.LineWrapMode.WidgetWidth)
|
|
103
|
+
self.expanded_body.setStyleSheet("font-family: monospace; background-color: #fafafa;")
|
|
104
|
+
expanded_layout.addWidget(self.expanded_body)
|
|
105
|
+
|
|
106
|
+
self.splitter.addWidget(self.expanded_frame)
|
|
107
|
+
self._clear_expanded_panel()
|
|
108
|
+
|
|
109
|
+
def _show_placeholder(self):
|
|
110
|
+
if self._placeholder is not None:
|
|
111
|
+
return
|
|
112
|
+
self._placeholder = QLabel("No variables watched. Click + to add.")
|
|
113
|
+
self._placeholder.setStyleSheet("color: #666; font-style: italic; padding: 4px 2px;")
|
|
114
|
+
self.content_layout.insertWidget(self.content_layout.count() - 1, self._placeholder)
|
|
115
|
+
|
|
116
|
+
def _hide_placeholder(self):
|
|
117
|
+
if self._placeholder is None:
|
|
118
|
+
return
|
|
119
|
+
self.content_layout.removeWidget(self._placeholder)
|
|
120
|
+
self._placeholder.deleteLater()
|
|
121
|
+
self._placeholder = None
|
|
122
|
+
|
|
123
|
+
def addVariable(self, name: str):
|
|
124
|
+
if not name or name in self._variable_set:
|
|
125
|
+
return
|
|
126
|
+
if not hasattr(self.debugger, 'watched'):
|
|
127
|
+
self.debugger.watched = [] # type: ignore[attr-defined]
|
|
128
|
+
if name not in self.debugger.watched: # type: ignore[attr-defined]
|
|
129
|
+
bisect.insort(self.debugger.watched, name) # type: ignore[attr-defined]
|
|
130
|
+
|
|
131
|
+
self._hide_placeholder()
|
|
132
|
+
|
|
133
|
+
# Row with label
|
|
134
|
+
row_widget = QWidget(self)
|
|
135
|
+
row_layout = QHBoxLayout(row_widget)
|
|
136
|
+
row_layout.setContentsMargins(4, 0, 4, 0)
|
|
137
|
+
row_layout.setSpacing(6)
|
|
138
|
+
|
|
139
|
+
label = QLabel("")
|
|
140
|
+
label.setWordWrap(False)
|
|
141
|
+
row_layout.addWidget(label, 1)
|
|
142
|
+
|
|
143
|
+
# Buttons live in a side container: remove (x) and expand (+)
|
|
144
|
+
button_container = QWidget(self)
|
|
145
|
+
button_row = QHBoxLayout(button_container)
|
|
146
|
+
button_row.setContentsMargins(0, 0, 0, 0)
|
|
147
|
+
button_row.setSpacing(4)
|
|
148
|
+
|
|
149
|
+
remove_btn = QPushButton("x")
|
|
150
|
+
remove_btn.setFixedWidth(22)
|
|
151
|
+
expand_btn = QPushButton("+")
|
|
152
|
+
expand_btn.setFixedWidth(22)
|
|
153
|
+
button_row.addWidget(remove_btn)
|
|
154
|
+
button_row.addWidget(expand_btn)
|
|
155
|
+
|
|
156
|
+
def on_remove():
|
|
157
|
+
try:
|
|
158
|
+
if hasattr(self.debugger, 'watched') and name in self.debugger.watched: # type: ignore[attr-defined]
|
|
159
|
+
self.debugger.watched.remove(name) # type: ignore[attr-defined]
|
|
160
|
+
if name in self._variable_set:
|
|
161
|
+
self._variable_set.remove(name)
|
|
162
|
+
if name in self._order:
|
|
163
|
+
self._order.remove(name)
|
|
164
|
+
self.content_layout.removeWidget(row_widget)
|
|
165
|
+
row_widget.deleteLater()
|
|
166
|
+
self.buttons_column.removeWidget(button_container)
|
|
167
|
+
button_container.deleteLater()
|
|
168
|
+
self._rows.pop(name, None)
|
|
169
|
+
if self._expanded_name == name:
|
|
170
|
+
self._clear_expanded_panel()
|
|
171
|
+
if not self._rows:
|
|
172
|
+
self._clear_expanded_panel()
|
|
173
|
+
self._show_placeholder()
|
|
174
|
+
except Exception:
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
remove_btn.clicked.connect(on_remove)
|
|
178
|
+
expand_btn.clicked.connect(lambda: self._on_expand(name))
|
|
179
|
+
|
|
180
|
+
insert_pos = bisect.bisect_left(self._order, name)
|
|
181
|
+
self._order.insert(insert_pos, name)
|
|
182
|
+
# Insert label row above stretch, keeping alphabetical order
|
|
183
|
+
self.content_layout.insertWidget(insert_pos, row_widget)
|
|
184
|
+
# Insert button above stretch on the right, same position
|
|
185
|
+
self.buttons_column.insertWidget(insert_pos, button_container)
|
|
186
|
+
|
|
187
|
+
# Align button height to row height
|
|
188
|
+
row_widget.adjustSize()
|
|
189
|
+
btn_h = row_widget.sizeHint().height()
|
|
190
|
+
if btn_h > 0:
|
|
191
|
+
button_container.setFixedHeight(btn_h)
|
|
192
|
+
remove_btn.setFixedHeight(btn_h)
|
|
193
|
+
expand_btn.setFixedHeight(btn_h)
|
|
194
|
+
|
|
195
|
+
self._rows[name] = {
|
|
196
|
+
'widget': row_widget,
|
|
197
|
+
'label': label,
|
|
198
|
+
'buttons': button_container,
|
|
199
|
+
'remove_btn': remove_btn,
|
|
200
|
+
'expand_btn': expand_btn,
|
|
201
|
+
'summary': '',
|
|
202
|
+
'detail': '',
|
|
203
|
+
}
|
|
204
|
+
self._variable_set.add(name)
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
self._refresh_one(name, self.debugger.program)
|
|
208
|
+
except Exception:
|
|
209
|
+
pass
|
|
210
|
+
|
|
211
|
+
def _refresh_one(self, name: str, program):
|
|
212
|
+
row = self._rows.get(name)
|
|
213
|
+
if not row:
|
|
214
|
+
return
|
|
215
|
+
detail_text = ''
|
|
216
|
+
try:
|
|
217
|
+
summary_text, detail_text = self._get_value_texts(program, name)
|
|
218
|
+
row['summary'] = summary_text
|
|
219
|
+
row['detail'] = detail_text
|
|
220
|
+
except Exception as e:
|
|
221
|
+
summary_text = f"<error: {e}>"
|
|
222
|
+
row['summary'] = summary_text
|
|
223
|
+
row['detail'] = ''
|
|
224
|
+
row['label'].setText(f"{name} = {summary_text}")
|
|
225
|
+
if self._expanded_name == name:
|
|
226
|
+
self._apply_expanded_content(name, summary_text, detail_text)
|
|
227
|
+
|
|
228
|
+
def refreshVariables(self, program):
|
|
229
|
+
if not self._rows:
|
|
230
|
+
self._show_placeholder()
|
|
231
|
+
return
|
|
232
|
+
for name in list(self._rows.keys()):
|
|
233
|
+
self._refresh_one(name, program)
|
|
234
|
+
|
|
235
|
+
def _get_value_texts(self, program, name: str):
|
|
236
|
+
summary = ValueDisplay.render_text(program, name)
|
|
237
|
+
if summary is None:
|
|
238
|
+
summary = ''
|
|
239
|
+
|
|
240
|
+
try:
|
|
241
|
+
record = program.getVariable(name)
|
|
242
|
+
obj = program.getObject(record)
|
|
243
|
+
except Exception:
|
|
244
|
+
return summary, ''
|
|
245
|
+
|
|
246
|
+
detail = ''
|
|
247
|
+
try:
|
|
248
|
+
if isinstance(obj, ECVariable):
|
|
249
|
+
detail = summary
|
|
250
|
+
elif isinstance(obj, ECDictionary):
|
|
251
|
+
raw = obj.getValue()
|
|
252
|
+
if isinstance(raw, ECValue):
|
|
253
|
+
raw = raw.getContent()
|
|
254
|
+
if isinstance(raw, dict):
|
|
255
|
+
detail = json.dumps(raw, indent=2)
|
|
256
|
+
elif isinstance(obj, ECList):
|
|
257
|
+
raw = obj.getValue()
|
|
258
|
+
if isinstance(raw, ECValue):
|
|
259
|
+
raw = raw.getContent()
|
|
260
|
+
if isinstance(raw, list):
|
|
261
|
+
detail = json.dumps(raw, indent=2)
|
|
262
|
+
except Exception:
|
|
263
|
+
pass
|
|
264
|
+
return summary, detail
|
|
265
|
+
|
|
266
|
+
def _on_expand(self, name: str):
|
|
267
|
+
if name not in self._rows:
|
|
268
|
+
return
|
|
269
|
+
try:
|
|
270
|
+
if hasattr(self.debugger, 'program'):
|
|
271
|
+
self._refresh_one(name, self.debugger.program)
|
|
272
|
+
except Exception:
|
|
273
|
+
pass
|
|
274
|
+
|
|
275
|
+
row = self._rows.get(name)
|
|
276
|
+
if not row:
|
|
277
|
+
return
|
|
278
|
+
self._expanded_name = name
|
|
279
|
+
self._apply_expanded_content(name, row.get('summary', ''), row.get('detail', ''))
|
|
280
|
+
|
|
281
|
+
def _apply_expanded_content(self, name: str, summary: str, detail: str):
|
|
282
|
+
body = detail if detail else summary
|
|
283
|
+
if body is None:
|
|
284
|
+
body = ''
|
|
285
|
+
if not body:
|
|
286
|
+
body = 'No content available for this variable.'
|
|
287
|
+
self.expanded_title.setText(f"Expanded view: {name}")
|
|
288
|
+
self.expanded_body.setPlainText(body)
|
|
289
|
+
|
|
290
|
+
def _clear_expanded_panel(self):
|
|
291
|
+
self._expanded_name = None
|
|
292
|
+
self.expanded_title.setText("Expanded view")
|
|
293
|
+
self.expanded_body.setPlainText("Select a watched variable and click + to view its contents.")
|