easycoder 251104.1__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.

Files changed (60) hide show
  1. easycoder/__init__.py +6 -3
  2. easycoder/debugger/__init__.py +5 -0
  3. easycoder/debugger/ec_dbg_value_display copy.py +195 -0
  4. easycoder/debugger/ec_dbg_value_display.py +24 -0
  5. easycoder/debugger/ec_dbg_watch_list copy.py +219 -0
  6. easycoder/debugger/ec_dbg_watchlist.py +293 -0
  7. easycoder/debugger/ec_debug.py +1025 -0
  8. easycoder/ec_border.py +15 -11
  9. easycoder/ec_classes.py +494 -7
  10. easycoder/ec_compiler.py +82 -44
  11. easycoder/ec_condition.py +1 -1
  12. easycoder/ec_core.py +1043 -1089
  13. easycoder/ec_gclasses.py +236 -0
  14. easycoder/ec_graphics.py +1683 -0
  15. easycoder/ec_handler.py +18 -13
  16. easycoder/ec_keyboard.py +50 -50
  17. easycoder/ec_mqtt.py +249 -0
  18. easycoder/ec_program.py +313 -152
  19. easycoder/ec_psutil.py +48 -0
  20. easycoder/ec_timestamp.py +2 -1
  21. easycoder/ec_value.py +65 -47
  22. easycoder/icons/exit.png +0 -0
  23. easycoder/icons/run.png +0 -0
  24. easycoder/icons/step.png +0 -0
  25. easycoder/icons/stop.png +0 -0
  26. easycoder/pre/README.md +3 -0
  27. easycoder/pre/__init__.py +17 -0
  28. easycoder/pre/debugger/__init__.py +5 -0
  29. easycoder/pre/debugger/ec_dbg_value_display copy.py +195 -0
  30. easycoder/pre/debugger/ec_dbg_value_display.py +24 -0
  31. easycoder/pre/debugger/ec_dbg_watch_list copy.py +219 -0
  32. easycoder/pre/debugger/ec_dbg_watchlist.py +293 -0
  33. easycoder/pre/debugger/ec_debug.py +1014 -0
  34. easycoder/pre/ec_border.py +67 -0
  35. easycoder/pre/ec_classes.py +470 -0
  36. easycoder/pre/ec_compiler.py +291 -0
  37. easycoder/pre/ec_condition.py +27 -0
  38. easycoder/pre/ec_core.py +2772 -0
  39. easycoder/pre/ec_gclasses.py +230 -0
  40. easycoder/{ec_pyside.py → pre/ec_graphics.py} +631 -494
  41. easycoder/pre/ec_handler.py +79 -0
  42. easycoder/pre/ec_keyboard.py +439 -0
  43. easycoder/pre/ec_program.py +557 -0
  44. easycoder/pre/ec_psutil.py +48 -0
  45. easycoder/pre/ec_timestamp.py +11 -0
  46. easycoder/pre/ec_value.py +124 -0
  47. easycoder/pre/icons/close.png +0 -0
  48. easycoder/pre/icons/exit.png +0 -0
  49. easycoder/pre/icons/run.png +0 -0
  50. easycoder/pre/icons/step.png +0 -0
  51. easycoder/pre/icons/stop.png +0 -0
  52. easycoder/pre/icons/tick.png +0 -0
  53. {easycoder-251104.1.dist-info → easycoder-260108.1.dist-info}/METADATA +11 -1
  54. easycoder-260108.1.dist-info/RECORD +59 -0
  55. easycoder-251104.1.dist-info/RECORD +0 -19
  56. /easycoder/{close.png → icons/close.png} +0 -0
  57. /easycoder/{tick.png → icons/tick.png} +0 -0
  58. {easycoder-251104.1.dist-info → easycoder-260108.1.dist-info}/WHEEL +0 -0
  59. {easycoder-251104.1.dist-info → easycoder-260108.1.dist-info}/entry_points.txt +0 -0
  60. {easycoder-251104.1.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.")