easycoder 251104.1__py2.py3-none-any.whl → 251215.2__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.
easycoder/__init__.py CHANGED
@@ -1,15 +1,17 @@
1
1
  '''EasyCoder for Python'''
2
2
 
3
3
  from .ec_classes import *
4
+ from .ec_gclasses import *
4
5
  from .ec_compiler import *
5
6
  from .ec_condition import *
6
7
  from .ec_core import *
8
+ from .ec_graphics import *
7
9
  from .ec_handler import *
8
10
  from .ec_keyboard import *
9
11
  from .ec_border import *
10
12
  from .ec_program import *
11
- from .ec_pyside import *
13
+ from .ec_psutil import *
12
14
  from .ec_timestamp import *
13
15
  from .ec_value import *
14
16
 
15
- __version__ = "251104.1"
17
+ __version__ = "251215.2"
@@ -0,0 +1,5 @@
1
+ """EasyCoder debugger module"""
2
+
3
+ from .ec_debug import Debugger
4
+
5
+ __all__ = ['Debugger']
@@ -0,0 +1,195 @@
1
+ """ValueDisplay widget for displaying variable values in the EasyCoder debugger"""
2
+
3
+ from PySide6.QtWidgets import (
4
+ QWidget,
5
+ QFrame,
6
+ QVBoxLayout,
7
+ QLabel,
8
+ QScrollArea,
9
+ )
10
+ from PySide6.QtCore import Qt
11
+
12
+
13
+ class ValueDisplay(QWidget):
14
+ """Widget to display a variable value with type-appropriate formatting"""
15
+
16
+ def __init__(self, parent=None):
17
+ super().__init__(parent)
18
+ vlayout = QVBoxLayout(self)
19
+ vlayout.setContentsMargins(0, 0, 0, 0)
20
+ vlayout.setSpacing(2)
21
+
22
+ # Main value label (always visible)
23
+ self.value_label = QLabel()
24
+ self.value_label.setStyleSheet("font-family: mono; padding: 1px 2px;")
25
+ self.value_label.setWordWrap(False)
26
+ vlayout.addWidget(self.value_label)
27
+
28
+ # Expanded details inside a scroll area (initially hidden)
29
+ self.details_scroll = QScrollArea()
30
+ self.details_scroll.setFrameShape(QFrame.Shape.NoFrame)
31
+ self.details_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
32
+ self.details_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
33
+ self.details_scroll.setWidgetResizable(True)
34
+ self.details_content = QWidget()
35
+ self.details_layout = QVBoxLayout(self.details_content)
36
+ self.details_layout.setContentsMargins(10, 0, 0, 0)
37
+ self.details_layout.setSpacing(1)
38
+ self.details_scroll.setWidget(self.details_content)
39
+ self.details_scroll.hide()
40
+ vlayout.addWidget(self.details_scroll)
41
+
42
+ self.is_expanded = False
43
+ self.current_value = None
44
+ self.max_detail_rows = 10
45
+
46
+ def setValue(self, symbol_record, program):
47
+ """Update display with current value from symbol record"""
48
+ if not symbol_record:
49
+ self.value_label.setText("<not found>")
50
+ return
51
+
52
+ # Check if variable has value capability
53
+ symbol_object = program.getObject(symbol_record)
54
+ if not symbol_object.hasRuntimeValue():
55
+ self.value_label.setText(f"<{symbol_record.get('type', 'no-value')}>")
56
+ return
57
+
58
+ # Get the value array
59
+ value_array = symbol_object.getValues()
60
+
61
+ if not value_array or len(value_array) == 0:
62
+ self.value_label.setText("<empty>")
63
+ return
64
+
65
+ # For arrays, show summary
66
+ if len(value_array) > 1:
67
+ index = symbol_record.get('index', 0)
68
+ self.value_label.setText(f"[{len(value_array)} elements] @{index}")
69
+
70
+ # If expanded, show individual elements
71
+ if self.is_expanded:
72
+ self._show_array_elements(value_array, index)
73
+ else:
74
+ self._hide_details()
75
+ else:
76
+ # Single value - show it directly
77
+ val = program.textify(symbol_object)
78
+ self._show_single_value(val)
79
+
80
+ def _show_single_value(self, content):
81
+ """Display a single value element"""
82
+ if content is None or content == {}:
83
+ self.value_label.setText("<none>")
84
+ return
85
+
86
+ if isinstance(content, bool):
87
+ self.value_label.setText(str(content))
88
+ elif isinstance(content, int):
89
+ self.value_label.setText(str(content))
90
+ elif isinstance(content, str):
91
+ # Check if it's JSON
92
+ if isinstance(content, str) and content.strip().startswith(('{', '[')):
93
+ # Likely JSON - show truncated with expand option
94
+ self._set_elided_text(str(content), multiplier=2.0)
95
+ if self.is_expanded and len(content) > 50:
96
+ self._show_text_details(content)
97
+ else:
98
+ # Regular string
99
+ text_s = str(content)
100
+ self._set_elided_text(text_s, multiplier=2.0)
101
+ if self.is_expanded:
102
+ self._show_text_details(text_s)
103
+ else:
104
+ self.value_label.setText(str(content))
105
+
106
+ def _show_array_elements(self, value_array, current_index):
107
+ """Show expanded array elements"""
108
+ self.details_scroll.show()
109
+ # Clear existing
110
+ while self.details_layout.count():
111
+ item = self.details_layout.takeAt(0)
112
+ if item.widget():
113
+ item.widget().deleteLater()
114
+
115
+ # Show all elements with internal vertical scrolling capped to N lines
116
+ for i in range(len(value_array)):
117
+ val = value_array[i]
118
+ marker = '→ ' if i == current_index else ' '
119
+
120
+ if val is None or val == {}:
121
+ text = f"{marker}[{i}]: <none>"
122
+ else:
123
+ val_type = val.get('type', '?')
124
+ content = val.get('content', '')
125
+ if val_type == 'str':
126
+ # keep each element concise
127
+ s = str(content)
128
+ if len(s) > 120:
129
+ content = s[:120] + '...'
130
+ text = f'{marker}[{i}]: {content}'
131
+
132
+ lbl = QLabel(text)
133
+ lbl.setStyleSheet("font-family: mono; font-size: 9pt;")
134
+ self.details_layout.addWidget(lbl)
135
+ # Cap scroll area height to max_detail_rows
136
+ fm = self.fontMetrics()
137
+ max_h = int(self.max_detail_rows * fm.height() * 1.2)
138
+ self.details_scroll.setMaximumHeight(max_h)
139
+
140
+ def _show_text_details(self, text):
141
+ """Show full text in details area"""
142
+ self.details_scroll.show()
143
+ # Clear existing
144
+ while self.details_layout.count():
145
+ item = self.details_layout.takeAt(0)
146
+ if item.widget():
147
+ item.widget().deleteLater()
148
+
149
+ lbl = QLabel(text)
150
+ lbl.setStyleSheet("font-family: mono; font-size: 9pt;")
151
+ lbl.setWordWrap(True)
152
+ self.details_layout.addWidget(lbl)
153
+ # Cap to max_detail_rows
154
+ fm = self.fontMetrics()
155
+ max_h = int(self.max_detail_rows * fm.height() * 1.2)
156
+ self.details_scroll.setMaximumHeight(max_h)
157
+
158
+ def _hide_details(self):
159
+ """Hide expanded details"""
160
+ self.details_scroll.hide()
161
+
162
+ def toggleExpand(self):
163
+ """Toggle between expanded and compact view"""
164
+ self.is_expanded = not self.is_expanded
165
+ # Caller should call setValue again to refresh display
166
+
167
+ def _approx_available_width(self) -> int:
168
+ try:
169
+ # Try to find nearest scroll area's viewport width
170
+ w = self
171
+ depth = 0
172
+ while w and depth < 6:
173
+ if isinstance(w, QScrollArea):
174
+ return max(200, w.viewport().width())
175
+ w = w.parentWidget()
176
+ depth += 1
177
+ # Fallback to our own width or a safe default
178
+ return max(240, self.width())
179
+ except Exception:
180
+ return 320
181
+
182
+ def _set_elided_text(self, text: str, multiplier: float = 2.0):
183
+ try:
184
+ fm = self.value_label.fontMetrics()
185
+ avail = int(self._approx_available_width() * multiplier)
186
+ # Apply quotes for display
187
+ quoted = f'"{text}"'
188
+ elided = fm.elidedText(quoted, Qt.TextElideMode.ElideRight, max(80, avail))
189
+ self.value_label.setText(elided)
190
+ except Exception:
191
+ # Fallback simple trim
192
+ s = text
193
+ if len(s) > 160:
194
+ s = s[:160] + '...'
195
+ self.value_label.setText(f'"{s}"')
@@ -0,0 +1,23 @@
1
+ """ValueDisplay widget for displaying variable values in the EasyCoder debugger"""
2
+
3
+ from PySide6.QtWidgets import (
4
+ QWidget,
5
+ QFrame,
6
+ QVBoxLayout,
7
+ QLabel,
8
+ QScrollArea,
9
+ )
10
+ from PySide6.QtCore import Qt
11
+
12
+
13
+ class ValueDisplay(QLabel):
14
+ """Widget to display a variable value with type-appropriate formatting"""
15
+
16
+ def __init__(self, parent=None):
17
+ super().__init__(parent)
18
+
19
+ def setValue(self, program, symbol_name):
20
+ record = program.getVariable(symbol_name)
21
+ value = program.textify(record)
22
+ self.setText(str(value))
23
+
@@ -0,0 +1,219 @@
1
+ """WatchListWidget for managing the variable watch list in the EasyCoder debugger"""
2
+
3
+ from PySide6.QtWidgets import (
4
+ QWidget,
5
+ QFrame,
6
+ QHBoxLayout,
7
+ QVBoxLayout,
8
+ QGridLayout,
9
+ QLabel,
10
+ """WatchListWidget for managing the variable watch list in the EasyCoder debugger
11
+
12
+ Simplified to show variables in single-line form: `Name = value`.
13
+ """
14
+
15
+ from PySide6.QtWidgets import (
16
+ QWidget,
17
+ QHBoxLayout,
18
+ QVBoxLayout,
19
+ QLabel,
20
+ QPushButton,
21
+ QSizePolicy,
22
+ )
23
+ from .ec_dbg_value_display import ValueDisplay
24
+
25
+
26
+ class WatchListWidget(QWidget):
27
+ """Simple watch list that renders each variable on a single line."""
28
+
29
+ def __init__(self, debugger):
30
+ super().__init__(debugger)
31
+ self.debugger = debugger
32
+ self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
33
+
34
+ self._rows: dict[str, dict] = {}
35
+ self._variable_set: set[str] = set()
36
+ self._placeholder: QLabel | None = None
37
+
38
+ self.layout = QVBoxLayout(self)
39
+ self.layout.setContentsMargins(0, 0, 0, 0)
40
+ self.layout.setSpacing(2)
41
+
42
+ self._show_placeholder()
43
+
44
+ # ------------------------------------------------------------------
45
+ def _show_placeholder(self):
46
+ if self._placeholder is not None:
47
+ return
48
+ self._placeholder = QLabel("No variables watched. Click + to add.")
49
+ self._placeholder.setStyleSheet("color: #666; font-style: italic; padding: 4px 2px;")
50
+ self.layout.addWidget(self._placeholder)
51
+
52
+ def _hide_placeholder(self):
53
+ if self._placeholder is None:
54
+ return
55
+ self.layout.removeWidget(self._placeholder)
56
+ self._placeholder.deleteLater()
57
+ self._placeholder = None
58
+
59
+ # ------------------------------------------------------------------
60
+ def addVariable(self, name: str):
61
+ if not name or name in self._variable_set:
62
+ return
63
+ if not hasattr(self.debugger, 'watched'):
64
+ self.debugger.watched = [] # type: ignore[attr-defined]
65
+ if name not in self.debugger.watched: # type: ignore[attr-defined]
66
+ self.debugger.watched.append(name) # type: ignore[attr-defined]
67
+
68
+ # Ensure placeholder hidden
69
+ self._hide_placeholder()
70
+
71
+ # Build a simple row: [ QLabel("Name = value") | remove_btn ]
72
+ row_widget = QWidget(self)
73
+ row_layout = QHBoxLayout(row_widget)
74
+ row_layout.setContentsMargins(4, 0, 4, 0)
75
+ row_layout.setSpacing(6)
76
+
77
+ label = QLabel("")
78
+ label.setWordWrap(False)
79
+ row_layout.addWidget(label, 1)
80
+
81
+ remove_btn = QPushButton("–")
82
+ remove_btn.setFixedSize(22, 22)
83
+
84
+ def on_remove():
85
+ try:
86
+ if hasattr(self.debugger, 'watched') and name in self.debugger.watched: # type: ignore[attr-defined]
87
+ self.debugger.watched.remove(name) # type: ignore[attr-defined]
88
+ if name in self._variable_set:
89
+ self._variable_set.remove(name)
90
+ self.layout.removeWidget(row_widget)
91
+ row_widget.deleteLater()
92
+ self._rows.pop(name, None)
93
+ if not self._rows:
94
+ """WatchListWidget for managing the variable watch list in the EasyCoder debugger
95
+
96
+ Simplified to show variables in single-line form: `Name = value`.
97
+ """
98
+
99
+ from PySide6.QtWidgets import (
100
+ QWidget,
101
+ QHBoxLayout,
102
+ QVBoxLayout,
103
+ QLabel,
104
+ QPushButton,
105
+ QSizePolicy,
106
+ )
107
+ from .ec_dbg_value_display import ValueDisplay
108
+
109
+
110
+ class WatchListWidget(QWidget):
111
+ """Simple watch list that renders each variable on a single line."""
112
+
113
+ def __init__(self, debugger):
114
+ super().__init__(debugger)
115
+ self.debugger = debugger
116
+ self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
117
+
118
+ self._rows: dict[str, dict] = {}
119
+ self._variable_set: set[str] = set()
120
+ self._placeholder: QLabel | None = None
121
+
122
+ self.layout = QVBoxLayout(self)
123
+ self.layout.setContentsMargins(0, 0, 0, 0)
124
+ self.layout.setSpacing(2)
125
+
126
+ self._show_placeholder()
127
+
128
+ # ------------------------------------------------------------------
129
+ def _show_placeholder(self):
130
+ if self._placeholder is not None:
131
+ return
132
+ self._placeholder = QLabel("No variables watched. Click + to add.")
133
+ self._placeholder.setStyleSheet("color: #666; font-style: italic; padding: 4px 2px;")
134
+ self.layout.addWidget(self._placeholder)
135
+
136
+ def _hide_placeholder(self):
137
+ if self._placeholder is None:
138
+ return
139
+ self.layout.removeWidget(self._placeholder)
140
+ self._placeholder.deleteLater()
141
+ self._placeholder = None
142
+
143
+ # ------------------------------------------------------------------
144
+ def addVariable(self, name: str):
145
+ if not name or name in self._variable_set:
146
+ return
147
+ if not hasattr(self.debugger, 'watched'):
148
+ self.debugger.watched = [] # type: ignore[attr-defined]
149
+ if name not in self.debugger.watched: # type: ignore[attr-defined]
150
+ self.debugger.watched.append(name) # type: ignore[attr-defined]
151
+
152
+ # Ensure placeholder hidden
153
+ self._hide_placeholder()
154
+
155
+ # Build a simple row: [ QLabel("Name = value") | remove_btn ]
156
+ row_widget = QWidget(self)
157
+ row_layout = QHBoxLayout(row_widget)
158
+ row_layout.setContentsMargins(4, 0, 4, 0)
159
+ row_layout.setSpacing(6)
160
+
161
+ label = QLabel("")
162
+ label.setWordWrap(False)
163
+ row_layout.addWidget(label, 1)
164
+
165
+ remove_btn = QPushButton("–")
166
+ remove_btn.setFixedSize(22, 22)
167
+
168
+ def on_remove():
169
+ try:
170
+ if hasattr(self.debugger, 'watched') and name in self.debugger.watched: # type: ignore[attr-defined]
171
+ self.debugger.watched.remove(name) # type: ignore[attr-defined]
172
+ if name in self._variable_set:
173
+ self._variable_set.remove(name)
174
+ self.layout.removeWidget(row_widget)
175
+ row_widget.deleteLater()
176
+ self._rows.pop(name, None)
177
+ if not self._rows:
178
+ self._show_placeholder()
179
+ except Exception:
180
+ pass
181
+
182
+ remove_btn.clicked.connect(on_remove)
183
+ row_layout.addWidget(remove_btn, 0)
184
+
185
+ # Track row
186
+ self.layout.addWidget(row_widget)
187
+ self._rows[name] = {
188
+ 'widget': row_widget,
189
+ 'label': label,
190
+ }
191
+ self._variable_set.add(name)
192
+
193
+ # Initial refresh for the new row
194
+ try:
195
+ self._refresh_one(name, self.debugger.program)
196
+ except Exception:
197
+ pass
198
+
199
+ # ------------------------------------------------------------------
200
+ def _refresh_one(self, name: str, program):
201
+ row = self._rows.get(name)
202
+ if not row:
203
+ return
204
+ try:
205
+ # ValueDisplay reduced to textify given symbol name
206
+ val_display = ValueDisplay()
207
+ val_display.setValue(program, name)
208
+ value_text = val_display.text()
209
+ except Exception as e:
210
+ value_text = f"<error: {e}>"
211
+ row['label'].setText(f"{name} = {value_text}")
212
+
213
+ # ------------------------------------------------------------------
214
+ def refreshVariables(self, program):
215
+ if not self._rows:
216
+ self._show_placeholder()
217
+ return
218
+ for name in list(self._rows.keys()):
219
+ self._refresh_one(name, program)
@@ -0,0 +1,159 @@
1
+ """WatchListWidget showing variables on a single line with scrollable values."""
2
+
3
+ import bisect
4
+
5
+ from PySide6.QtWidgets import (
6
+ QWidget,
7
+ QHBoxLayout,
8
+ QVBoxLayout,
9
+ QLabel,
10
+ QPushButton,
11
+ QSizePolicy,
12
+ QScrollArea,
13
+ )
14
+ from PySide6.QtCore import Qt
15
+ from .ec_dbg_value_display import ValueDisplay
16
+
17
+
18
+ class WatchListWidget(QWidget):
19
+ def __init__(self, debugger):
20
+ super().__init__(debugger)
21
+ self.debugger = debugger
22
+ self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
23
+
24
+ self._rows: dict[str, dict] = {}
25
+ self._variable_set: set[str] = set()
26
+ self._order: list[str] = []
27
+ self._placeholder: QLabel | None = None
28
+
29
+ # Outer layout: scrollable labels (left) and fixed button column (right)
30
+ outer = QHBoxLayout(self)
31
+ outer.setContentsMargins(0, 0, 0, 0)
32
+ outer.setSpacing(2)
33
+
34
+ # Left: scroll area for labels
35
+ self.scroll = QScrollArea(self)
36
+ self.scroll.setWidgetResizable(True)
37
+ self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
38
+ self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
39
+ self.scroll.setFrameShape(QScrollArea.Shape.NoFrame)
40
+
41
+ self.content = QWidget()
42
+ self.content_layout = QVBoxLayout(self.content)
43
+ self.content_layout.setContentsMargins(4, 2, 4, 2)
44
+ self.content_layout.setSpacing(4)
45
+ self.content_layout.addStretch(1) # keep items grouped at the top
46
+
47
+ self.scroll.setWidget(self.content)
48
+ outer.addWidget(self.scroll, 1)
49
+
50
+ # Right: button column, top-aligned
51
+ self.buttons_column = QVBoxLayout()
52
+ self.buttons_column.setContentsMargins(0, 2, 4, 2)
53
+ self.buttons_column.setSpacing(4)
54
+ self.buttons_column.addStretch(1)
55
+ outer.addLayout(self.buttons_column)
56
+
57
+ self._show_placeholder()
58
+
59
+ def _show_placeholder(self):
60
+ if self._placeholder is not None:
61
+ return
62
+ self._placeholder = QLabel("No variables watched. Click + to add.")
63
+ self._placeholder.setStyleSheet("color: #666; font-style: italic; padding: 4px 2px;")
64
+ self.content_layout.insertWidget(self.content_layout.count() - 1, self._placeholder)
65
+
66
+ def _hide_placeholder(self):
67
+ if self._placeholder is None:
68
+ return
69
+ self.content_layout.removeWidget(self._placeholder)
70
+ self._placeholder.deleteLater()
71
+ self._placeholder = None
72
+
73
+ def addVariable(self, name: str):
74
+ if not name or name in self._variable_set:
75
+ return
76
+ if not hasattr(self.debugger, 'watched'):
77
+ self.debugger.watched = [] # type: ignore[attr-defined]
78
+ if name not in self.debugger.watched: # type: ignore[attr-defined]
79
+ bisect.insort(self.debugger.watched, name) # type: ignore[attr-defined]
80
+
81
+ self._hide_placeholder()
82
+
83
+ # Row with label
84
+ row_widget = QWidget(self)
85
+ row_layout = QHBoxLayout(row_widget)
86
+ row_layout.setContentsMargins(4, 0, 4, 0)
87
+ row_layout.setSpacing(6)
88
+
89
+ label = QLabel("")
90
+ label.setWordWrap(False)
91
+ row_layout.addWidget(label, 1)
92
+
93
+ # Remove button in separate column
94
+ remove_btn = QPushButton("-")
95
+ remove_btn.setFixedWidth(22)
96
+
97
+ def on_remove():
98
+ try:
99
+ if hasattr(self.debugger, 'watched') and name in self.debugger.watched: # type: ignore[attr-defined]
100
+ self.debugger.watched.remove(name) # type: ignore[attr-defined]
101
+ if name in self._variable_set:
102
+ self._variable_set.remove(name)
103
+ if name in self._order:
104
+ self._order.remove(name)
105
+ self.content_layout.removeWidget(row_widget)
106
+ row_widget.deleteLater()
107
+ self.buttons_column.removeWidget(remove_btn)
108
+ remove_btn.deleteLater()
109
+ self._rows.pop(name, None)
110
+ if not self._rows:
111
+ self._show_placeholder()
112
+ except Exception:
113
+ pass
114
+
115
+ remove_btn.clicked.connect(on_remove)
116
+
117
+ insert_pos = bisect.bisect_left(self._order, name)
118
+ self._order.insert(insert_pos, name)
119
+ # Insert label row above stretch, keeping alphabetical order
120
+ self.content_layout.insertWidget(insert_pos, row_widget)
121
+ # Insert button above stretch on the right, same position
122
+ self.buttons_column.insertWidget(insert_pos, remove_btn)
123
+
124
+ # Align button height to row height
125
+ row_widget.adjustSize()
126
+ btn_h = row_widget.sizeHint().height()
127
+ if btn_h > 0:
128
+ remove_btn.setFixedHeight(btn_h)
129
+
130
+ self._rows[name] = {
131
+ 'widget': row_widget,
132
+ 'label': label,
133
+ 'button': remove_btn,
134
+ }
135
+ self._variable_set.add(name)
136
+
137
+ try:
138
+ self._refresh_one(name, self.debugger.program)
139
+ except Exception:
140
+ pass
141
+
142
+ def _refresh_one(self, name: str, program):
143
+ row = self._rows.get(name)
144
+ if not row:
145
+ return
146
+ try:
147
+ val_display = ValueDisplay()
148
+ val_display.setValue(program, name)
149
+ value_text = val_display.text()
150
+ except Exception as e:
151
+ value_text = f"<error: {e}>"
152
+ row['label'].setText(f"{name} = {value_text}")
153
+
154
+ def refreshVariables(self, program):
155
+ if not self._rows:
156
+ self._show_placeholder()
157
+ return
158
+ for name in list(self._rows.keys()):
159
+ self._refresh_one(name, program)