easycoder 251104.2__tar.gz → 251104.3__tar.gz
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-251104.2 → easycoder-251104.3}/PKG-INFO +1 -1
- {easycoder-251104.2 → easycoder-251104.3}/easycoder/__init__.py +1 -1
- {easycoder-251104.2 → easycoder-251104.3}/easycoder/ec_border.py +15 -11
- {easycoder-251104.2 → easycoder-251104.3}/easycoder/ec_debug.py +350 -33
- {easycoder-251104.2 → easycoder-251104.3}/easycoder/ec_handler.py +1 -0
- {easycoder-251104.2 → easycoder-251104.3}/easycoder/ec_keyboard.py +50 -50
- {easycoder-251104.2 → easycoder-251104.3}/easycoder/ec_program.py +3 -2
- {easycoder-251104.2 → easycoder-251104.3}/easycoder/ec_pyside.py +80 -95
- {easycoder-251104.2 → easycoder-251104.3}/easycoder/ec_timestamp.py +2 -1
- easycoder-251104.3/easycoder/icons/exit.png +0 -0
- easycoder-251104.3/easycoder/icons/run.png +0 -0
- easycoder-251104.3/easycoder/icons/step.png +0 -0
- easycoder-251104.3/easycoder/icons/stop.png +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/plugins/ec_keyboard.py +7 -7
- {easycoder-251104.2 → easycoder-251104.3}/scripts/ec_keyboard.py +10 -10
- {easycoder-251104.2 → easycoder-251104.3}/scripts/tests.ecs +29 -25
- {easycoder-251104.2 → easycoder-251104.3}/.github/copilot-instructions.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/.gitignore +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/.vscode/EXTENSION_GUIDE.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/.vscode/PYTHON_SETUP.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/.vscode/extensions/easycoder/README.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/.vscode/extensions/easycoder/language-configuration.json +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/.vscode/extensions/easycoder/package.json +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/.vscode/extensions/easycoder/snippets/easycoder.json +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/.vscode/extensions/easycoder/syntaxes/easycoder.tmLanguage.json +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/.vscode/settings.json +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/LICENSE +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/README.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/backdrop.jpg +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/README.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/README.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/boolean.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/empty.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/ends.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/even.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/exists.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/greater.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/hasProperty.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/includes.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/is.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/less.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/list.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/none.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/not.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/numeric.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/object.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/odd.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/starts.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/string.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/add.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/append.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/assert.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/begin.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/clear.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/close.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/create.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/debug.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/decrement.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/delete.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/divide.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/exit.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/file.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/fork.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/get.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/go.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/gosub.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/if.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/import.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/increment.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/index.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/init.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/input.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/load.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/lock.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/log.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/module.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/multiply.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/negate.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/on.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/open.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/pop.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/post.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/print.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/push.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/put.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/read.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/release.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/replace.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/return.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/run.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/save.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/script.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/send.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/set.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/shuffle.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/split.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/stack.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/stop.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/system.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/take.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/toggle.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/truncate.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/unlock.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/use.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/variable.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/wait.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/while.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/write.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/arg.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/args.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/cos.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/datime.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/decode.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/element.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/elements.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/empty.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/encode.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/error.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/files.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/float.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/from.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/hash.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/index.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/integer.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/json.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/keys.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/left.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/length.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/lowercase.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/memory.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/modification.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/modulo.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/newline.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/now.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/position.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/property.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/random.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/right.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/sin.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/stringify.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/tab.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/tan.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/timestamp.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/today.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/trim.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/type.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/uppercase.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/value.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/weekday.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/README.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/keywords/attach.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/keywords/close.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/keywords/create.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/keywords/ellipse.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/keywords/image.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/keywords/move.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/keywords/on.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/keywords/rectangle.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/keywords/render.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/keywords/run.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/keywords/set.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/keywords/text.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/values/attribute.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/values/window.md +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/easycoder/ec_classes.py +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/easycoder/ec_compiler.py +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/easycoder/ec_condition.py +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/easycoder/ec_core.py +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/easycoder/ec_value.py +0 -0
- {easycoder-251104.2/easycoder → easycoder-251104.3/easycoder/icons}/close.png +0 -0
- {easycoder-251104.2/easycoder → easycoder-251104.3/easycoder/icons}/tick.png +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/images/Semoigo Dawn.jpg +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/json/graphics-demo.json +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/plugins/ec_p100.py +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/plugins/ec_pyside6.py +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/plugins/points.py +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/plugins/sql.py +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/pyproject.toml +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/scripts/benchmark.ecs +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/scripts/fizzbuzz.ecs +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/scripts/hello.ecs +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/scripts/points.ecs +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/scripts/test.ecs +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/test.py +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/testrc.py +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/testsql.py +0 -0
- {easycoder-251104.2 → easycoder-251104.3}/testui.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: easycoder
|
|
3
|
-
Version: 251104.
|
|
3
|
+
Version: 251104.3
|
|
4
4
|
Summary: Rapid scripting in English
|
|
5
5
|
Keywords: compiler,scripting,prototyping,programming,coding,python,low code,hypertalk,computer language,learn to code
|
|
6
6
|
Author-email: Graham Trott <gtanyware@gmail.com>
|
|
@@ -9,34 +9,38 @@ class Border(QWidget):
|
|
|
9
9
|
|
|
10
10
|
def __init__(self):
|
|
11
11
|
super().__init__()
|
|
12
|
-
self.
|
|
13
|
-
self.setFixedHeight(self.
|
|
12
|
+
self._size = 40
|
|
13
|
+
self.setFixedHeight(self._size)
|
|
14
14
|
self._drag_active = False
|
|
15
15
|
self._drag_start_pos = None
|
|
16
|
+
self._tick: QPixmap = QPixmap()
|
|
17
|
+
self._close_icon: QPixmap = QPixmap()
|
|
16
18
|
|
|
17
19
|
def paintEvent(self, event):
|
|
18
20
|
painter = QPainter(self)
|
|
19
|
-
painter.setRenderHint(QPainter.Antialiasing)
|
|
21
|
+
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
|
20
22
|
# Draw the tick icon
|
|
21
|
-
self.
|
|
23
|
+
self._tick = QPixmap(f'{os.path.dirname(os.path.abspath(__file__))}/icons/tick.png').scaled(
|
|
24
|
+
self._size, self._size, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)
|
|
22
25
|
x = 0
|
|
23
26
|
y = 0
|
|
24
|
-
painter.drawPixmap(x, y, self.
|
|
27
|
+
painter.drawPixmap(x, y, self._tick)
|
|
25
28
|
# Draw the close icon
|
|
26
|
-
self.
|
|
27
|
-
|
|
29
|
+
self._close_icon = QPixmap(f'{os.path.dirname(os.path.abspath(__file__))}/icons/close.png').scaled(
|
|
30
|
+
self._size, self._size, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)
|
|
31
|
+
x = self.width() - self._close_icon.width()
|
|
28
32
|
y = 0
|
|
29
|
-
painter.drawPixmap(x, y, self.
|
|
33
|
+
painter.drawPixmap(x, y, self._close_icon)
|
|
30
34
|
|
|
31
35
|
def mousePressEvent(self, event):
|
|
32
36
|
# Tick icon
|
|
33
37
|
x = 0
|
|
34
38
|
y = 0
|
|
35
|
-
tickRect = self.
|
|
39
|
+
tickRect = self._tick.rect().translated(x, y)
|
|
36
40
|
# Close icon
|
|
37
|
-
x = self.width() - self.
|
|
41
|
+
x = self.width() - self._close_icon.width()
|
|
38
42
|
y = 0
|
|
39
|
-
closeRect = self.
|
|
43
|
+
closeRect = self._close_icon.rect().translated(x, y)
|
|
40
44
|
if tickRect.contains(event.pos()):
|
|
41
45
|
self.tickClicked.emit()
|
|
42
46
|
if closeRect.contains(event.pos()):
|
|
@@ -7,18 +7,19 @@ from PySide6.QtWidgets import (
|
|
|
7
7
|
QVBoxLayout,
|
|
8
8
|
QLabel,
|
|
9
9
|
QSplitter,
|
|
10
|
-
QFileDialog,
|
|
11
10
|
QMessageBox,
|
|
12
11
|
QScrollArea,
|
|
13
12
|
QSizePolicy,
|
|
14
|
-
QToolBar
|
|
13
|
+
QToolBar,
|
|
14
|
+
QPushButton,
|
|
15
|
+
QInputDialog
|
|
15
16
|
)
|
|
16
|
-
from PySide6.QtGui import
|
|
17
|
-
from PySide6.QtCore import Qt, QTimer
|
|
17
|
+
from PySide6.QtGui import QTextCursor, QIcon
|
|
18
|
+
from PySide6.QtCore import Qt, QTimer
|
|
18
19
|
from typing import Any
|
|
20
|
+
from typing import Any, Optional
|
|
19
21
|
|
|
20
22
|
class Object():
|
|
21
|
-
"""Dynamic object that allows arbitrary attribute assignment"""
|
|
22
23
|
def __setattr__(self, name: str, value: Any) -> None:
|
|
23
24
|
self.__dict__[name] = value
|
|
24
25
|
|
|
@@ -26,15 +27,198 @@ class Object():
|
|
|
26
27
|
return self.__dict__.get(name)
|
|
27
28
|
|
|
28
29
|
class Debugger(QMainWindow):
|
|
30
|
+
# Help type-checkers know these attributes exist
|
|
31
|
+
_flush_timer: Optional[QTimer]
|
|
32
|
+
|
|
33
|
+
class ConsoleWriter:
|
|
34
|
+
def __init__(self, debugger: 'Debugger'):
|
|
35
|
+
self.debugger = debugger
|
|
36
|
+
self._buf: list[str] = []
|
|
37
|
+
|
|
38
|
+
def write(self, text: str):
|
|
39
|
+
if not text:
|
|
40
|
+
return
|
|
41
|
+
# Buffer text and request a flush on the GUI timer
|
|
42
|
+
self._buf.append(text)
|
|
43
|
+
if self.debugger._flush_timer and not self.debugger._flush_timer.isActive():
|
|
44
|
+
self.debugger._flush_timer.start()
|
|
45
|
+
|
|
46
|
+
def flush(self):
|
|
47
|
+
# Explicit flush request
|
|
48
|
+
self.debugger._flush_console_buffer()
|
|
29
49
|
|
|
30
50
|
###########################################################################
|
|
31
51
|
# The left-hand column of the main window
|
|
32
52
|
class MainLeftColumn(QWidget):
|
|
33
53
|
def __init__(self, parent=None):
|
|
34
54
|
super().__init__(parent)
|
|
55
|
+
self.debugger = parent
|
|
35
56
|
layout = QVBoxLayout(self)
|
|
36
|
-
|
|
57
|
+
|
|
58
|
+
# Create toolbar with icon buttons
|
|
59
|
+
toolbar = QToolBar()
|
|
60
|
+
toolbar.setMovable(False)
|
|
61
|
+
|
|
62
|
+
# Get the icons directory path
|
|
63
|
+
icons_dir = os.path.join(os.path.dirname(__file__), 'icons')
|
|
64
|
+
|
|
65
|
+
# Run button
|
|
66
|
+
run_btn = QPushButton()
|
|
67
|
+
run_icon_path = os.path.join(icons_dir, 'run.png')
|
|
68
|
+
run_btn.setIcon(QIcon(run_icon_path))
|
|
69
|
+
run_btn.setToolTip("Run")
|
|
70
|
+
run_btn.clicked.connect(self.on_run_clicked)
|
|
71
|
+
toolbar.addWidget(run_btn)
|
|
72
|
+
|
|
73
|
+
# Step button
|
|
74
|
+
step_btn = QPushButton()
|
|
75
|
+
step_icon_path = os.path.join(icons_dir, 'step.png')
|
|
76
|
+
step_btn.setIcon(QIcon(step_icon_path))
|
|
77
|
+
step_btn.setToolTip("Step")
|
|
78
|
+
step_btn.clicked.connect(self.on_step_clicked)
|
|
79
|
+
toolbar.addWidget(step_btn)
|
|
80
|
+
|
|
81
|
+
# Stop button
|
|
82
|
+
stop_btn = QPushButton()
|
|
83
|
+
stop_icon_path = os.path.join(icons_dir, 'stop.png')
|
|
84
|
+
stop_btn.setIcon(QIcon(stop_icon_path))
|
|
85
|
+
stop_btn.setToolTip("Stop")
|
|
86
|
+
stop_btn.clicked.connect(self.on_stop_clicked)
|
|
87
|
+
toolbar.addWidget(stop_btn)
|
|
88
|
+
|
|
89
|
+
# Exit button
|
|
90
|
+
exit_btn = QPushButton()
|
|
91
|
+
exit_icon_path = os.path.join(icons_dir, 'exit.png')
|
|
92
|
+
exit_btn.setIcon(QIcon(exit_icon_path))
|
|
93
|
+
exit_btn.setToolTip("Exit")
|
|
94
|
+
exit_btn.clicked.connect(self.on_exit_clicked)
|
|
95
|
+
toolbar.addWidget(exit_btn)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
layout.addWidget(toolbar)
|
|
99
|
+
|
|
100
|
+
# --- Watch panel (like VS Code) ---
|
|
101
|
+
watch_panel = QFrame()
|
|
102
|
+
watch_panel.setFrameShape(QFrame.Shape.StyledPanel)
|
|
103
|
+
# Ensure the VARIABLES bar stretches to full available width
|
|
104
|
+
watch_panel.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
|
105
|
+
watch_layout = QHBoxLayout(watch_panel)
|
|
106
|
+
watch_layout.setContentsMargins(4, 4, 4, 4)
|
|
107
|
+
watch_layout.setSpacing(4)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# Title label
|
|
111
|
+
title_label = QLabel("VARIABLES")
|
|
112
|
+
title_label.setStyleSheet("font-weight: bold; letter-spacing: 1px;")
|
|
113
|
+
watch_layout.addWidget(title_label)
|
|
114
|
+
|
|
115
|
+
# Stretch to push buttons right
|
|
116
|
+
watch_layout.addStretch()
|
|
117
|
+
|
|
118
|
+
# Placeholder add/remove icons (replace with real icons later)
|
|
119
|
+
add_btn = QPushButton()
|
|
120
|
+
add_btn.setToolTip("Add variable to watch")
|
|
121
|
+
# TODO: set add_btn.setIcon(QIcon(path)) when icon is available
|
|
122
|
+
add_btn.setText("+")
|
|
123
|
+
add_btn.setFixedSize(24, 24)
|
|
124
|
+
add_btn.clicked.connect(self.on_add_clicked)
|
|
125
|
+
watch_layout.addWidget(add_btn)
|
|
126
|
+
|
|
127
|
+
layout.addWidget(watch_panel)
|
|
128
|
+
|
|
129
|
+
# Watch list area (renders selected variables beneath the toolbar)
|
|
130
|
+
self.watch_list_widget = QWidget()
|
|
131
|
+
self.watch_list_layout = QVBoxLayout(self.watch_list_widget)
|
|
132
|
+
self.watch_list_layout.setContentsMargins(6, 2, 6, 2)
|
|
133
|
+
self.watch_list_layout.setSpacing(2)
|
|
134
|
+
layout.addWidget(self.watch_list_widget)
|
|
135
|
+
|
|
136
|
+
# Keep a simple set to prevent duplicate labels
|
|
137
|
+
self._watch_set = set()
|
|
138
|
+
|
|
37
139
|
layout.addStretch()
|
|
140
|
+
|
|
141
|
+
def on_add_clicked(self):
|
|
142
|
+
# Build the variable list from the program. Prefer Program.symbols mapping.
|
|
143
|
+
try:
|
|
144
|
+
program = self.debugger.program # type: ignore[attr-defined]
|
|
145
|
+
# Fallback to scanning code if symbols is empty
|
|
146
|
+
items = []
|
|
147
|
+
if hasattr(program, 'symbols') and isinstance(program.symbols, dict) and program.symbols:
|
|
148
|
+
items = sorted([name for name in program.symbols.keys() if name and not name.endswith(':')])
|
|
149
|
+
else:
|
|
150
|
+
# Fallback heuristic: look for commands whose 'type' == 'symbol' (as per requirement)
|
|
151
|
+
for cmd in getattr(program, 'code', []):
|
|
152
|
+
try:
|
|
153
|
+
if cmd.get('type') == 'symbol' and 'name' in cmd:
|
|
154
|
+
items.append(cmd['name'])
|
|
155
|
+
except Exception:
|
|
156
|
+
pass
|
|
157
|
+
items = sorted(set(items))
|
|
158
|
+
if not items:
|
|
159
|
+
QMessageBox.information(self, "Add Watch", "No variables found in this program.")
|
|
160
|
+
return
|
|
161
|
+
choice, ok = QInputDialog.getItem(self, "Add Watch", "Select a variable:", items, 0, False)
|
|
162
|
+
if ok and choice:
|
|
163
|
+
# Record the choice for future use (UI for list will be added later)
|
|
164
|
+
if not hasattr(self.debugger, 'watched'):
|
|
165
|
+
self.debugger.watched = [] # type: ignore[attr-defined]
|
|
166
|
+
if choice not in self.debugger.watched: # type: ignore[attr-defined]
|
|
167
|
+
self.debugger.watched.append(choice) # type: ignore[attr-defined]
|
|
168
|
+
# Render as a plain label beneath the toolbar if not already present
|
|
169
|
+
if choice not in self._watch_set:
|
|
170
|
+
self._add_watch_row(choice)
|
|
171
|
+
self._watch_set.add(choice)
|
|
172
|
+
# Optionally echo to console for now
|
|
173
|
+
try:
|
|
174
|
+
self.debugger.console.append(f"Watching: {choice}") # type: ignore[attr-defined]
|
|
175
|
+
except Exception:
|
|
176
|
+
pass
|
|
177
|
+
except Exception as exc:
|
|
178
|
+
QMessageBox.warning(self, "Add Watch", f"Could not list variables: {exc}")
|
|
179
|
+
|
|
180
|
+
def _add_watch_row(self, name: str):
|
|
181
|
+
row = QWidget()
|
|
182
|
+
h = QHBoxLayout(row)
|
|
183
|
+
h.setContentsMargins(0, 0, 0, 0)
|
|
184
|
+
h.setSpacing(4)
|
|
185
|
+
lbl = QLabel(name)
|
|
186
|
+
lbl.setStyleSheet("font-family: mono; padding: 1px 2px;")
|
|
187
|
+
h.addWidget(lbl)
|
|
188
|
+
h.addStretch()
|
|
189
|
+
btn = QPushButton()
|
|
190
|
+
btn.setText("–") # placeholder until icon provided
|
|
191
|
+
btn.setToolTip(f"Remove '{name}' from watch")
|
|
192
|
+
btn.setFixedSize(20, 20)
|
|
193
|
+
|
|
194
|
+
def on_remove():
|
|
195
|
+
try:
|
|
196
|
+
# update internal structures
|
|
197
|
+
if hasattr(self.debugger, 'watched') and name in self.debugger.watched: # type: ignore[attr-defined]
|
|
198
|
+
self.debugger.watched.remove(name) # type: ignore[attr-defined]
|
|
199
|
+
if name in self._watch_set:
|
|
200
|
+
self._watch_set.remove(name)
|
|
201
|
+
# remove row from layout/UI
|
|
202
|
+
row.setParent(None)
|
|
203
|
+
row.deleteLater()
|
|
204
|
+
except Exception:
|
|
205
|
+
pass
|
|
206
|
+
|
|
207
|
+
btn.clicked.connect(on_remove)
|
|
208
|
+
h.addWidget(btn)
|
|
209
|
+
self.watch_list_layout.addWidget(row)
|
|
210
|
+
|
|
211
|
+
def on_run_clicked(self):
|
|
212
|
+
self.debugger.doRun() # type: ignore[attr-defined]
|
|
213
|
+
|
|
214
|
+
def on_step_clicked(self):
|
|
215
|
+
self.debugger.doStep() # type: ignore[attr-defined]
|
|
216
|
+
|
|
217
|
+
def on_stop_clicked(self):
|
|
218
|
+
self.debugger.doStop() # type: ignore[attr-defined]
|
|
219
|
+
|
|
220
|
+
def on_exit_clicked(self):
|
|
221
|
+
self.debugger.doClose() # type: ignore[attr-defined]
|
|
38
222
|
|
|
39
223
|
###########################################################################
|
|
40
224
|
# The right-hand column of the main window
|
|
@@ -75,24 +259,25 @@ class Debugger(QMainWindow):
|
|
|
75
259
|
#######################################################################
|
|
76
260
|
# Add a line to the right-hand column
|
|
77
261
|
def addLine(self, spec):
|
|
262
|
+
|
|
263
|
+
# Determine if this line is a command (not empty, not a comment), using the original script line
|
|
264
|
+
orig_line = getattr(spec, 'orig_line', spec.line) if hasattr(spec, 'orig_line') or 'orig_line' in spec.__dict__ else spec.line
|
|
265
|
+
line_lstripped = orig_line.lstrip()
|
|
266
|
+
is_command = bool(line_lstripped and not line_lstripped.startswith('!'))
|
|
267
|
+
|
|
78
268
|
class Label(QLabel):
|
|
79
|
-
def __init__(self, text, fixed_width=None, align=Qt.AlignmentFlag.AlignLeft, on_click=
|
|
269
|
+
def __init__(self, text, fixed_width=None, align=Qt.AlignmentFlag.AlignLeft, on_click=None):
|
|
80
270
|
super().__init__()
|
|
81
271
|
self.setText(text)
|
|
82
|
-
# remove QLabel's internal margins/padding to reduce top/bottom space
|
|
83
272
|
self.setMargin(0)
|
|
84
273
|
self.setContentsMargins(0, 0, 0, 0)
|
|
85
274
|
self.setStyleSheet("padding:0px; margin:0px; font-family: mono")
|
|
86
275
|
fm = self.fontMetrics()
|
|
87
|
-
# set a compact fixed height based on font metrics
|
|
88
276
|
self.setFixedHeight(fm.height())
|
|
89
|
-
# optional fixed width (used for the lino column)
|
|
90
277
|
if fixed_width is not None:
|
|
91
278
|
self.setFixedWidth(fixed_width)
|
|
92
|
-
# align horizontally (keep vertically centered)
|
|
93
279
|
self.setAlignment(align | Qt.AlignmentFlag.AlignVCenter)
|
|
94
|
-
|
|
95
|
-
self._on_click = on_click
|
|
280
|
+
self._on_click = on_click if is_command else None
|
|
96
281
|
|
|
97
282
|
def mousePressEvent(self, event):
|
|
98
283
|
if self._on_click:
|
|
@@ -120,11 +305,25 @@ class Debugger(QMainWindow):
|
|
|
120
305
|
fm_main = self.fontMetrics()
|
|
121
306
|
width_4 = fm_main.horizontalAdvance('0000') + 8
|
|
122
307
|
|
|
308
|
+
|
|
123
309
|
# create the red blob (always present). We'll toggle its opacity
|
|
124
310
|
# by changing the stylesheet (rgba alpha 255/0). Do NOT store it
|
|
125
311
|
# on the MainRightColumn instance — keep it per-line.
|
|
126
|
-
|
|
312
|
+
|
|
313
|
+
class ClickableBlob(QLabel):
|
|
314
|
+
def __init__(self, on_click=None):
|
|
315
|
+
super().__init__()
|
|
316
|
+
self._on_click = on_click if is_command else None
|
|
317
|
+
def mousePressEvent(self, event):
|
|
318
|
+
if self._on_click:
|
|
319
|
+
try:
|
|
320
|
+
self._on_click()
|
|
321
|
+
except Exception:
|
|
322
|
+
pass
|
|
323
|
+
super().mousePressEvent(event)
|
|
324
|
+
|
|
127
325
|
blob_size = 10
|
|
326
|
+
blob = ClickableBlob(on_click=(lambda: spec.onClick(spec.lino)) if is_command else None)
|
|
128
327
|
blob.setFixedSize(blob_size, blob_size)
|
|
129
328
|
|
|
130
329
|
def set_blob_visible(widget, visible):
|
|
@@ -149,7 +348,7 @@ class Debugger(QMainWindow):
|
|
|
149
348
|
|
|
150
349
|
# create the line-number label; clicking it reports back to the caller
|
|
151
350
|
lino_label = Label(str(spec.lino+1), fixed_width=width_4, align=Qt.AlignmentFlag.AlignRight,
|
|
152
|
-
on_click=lambda: spec.onClick(spec.lino))
|
|
351
|
+
on_click=(lambda: spec.onClick(spec.lino)) if is_command else None)
|
|
153
352
|
lino_label.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
|
154
353
|
# create the text label for the line itself
|
|
155
354
|
text_label = Label(spec.line, fixed_width=None, align=Qt.AlignmentFlag.AlignLeft)
|
|
@@ -178,6 +377,8 @@ class Debugger(QMainWindow):
|
|
|
178
377
|
self.program = program
|
|
179
378
|
self.setWindowTitle("EasyCoder Debugger")
|
|
180
379
|
self.setMinimumSize(width, height)
|
|
380
|
+
# Disable the window close button
|
|
381
|
+
self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint)
|
|
181
382
|
self.stopped = True
|
|
182
383
|
|
|
183
384
|
# try to load saved geometry from ~/.ecdebug.conf
|
|
@@ -210,7 +411,10 @@ class Debugger(QMainWindow):
|
|
|
210
411
|
self._writer = None
|
|
211
412
|
self._orig_stdout = None
|
|
212
413
|
self._orig_stderr = None
|
|
213
|
-
self._flush_timer =
|
|
414
|
+
self._flush_timer = QTimer(self)
|
|
415
|
+
self._flush_timer.setInterval(50)
|
|
416
|
+
self._flush_timer.timeout.connect(self._flush_console_buffer)
|
|
417
|
+
self._flush_timer.stop()
|
|
214
418
|
|
|
215
419
|
# Keep a ratio so proportions are preserved when window is resized
|
|
216
420
|
self.ratio = ratio
|
|
@@ -225,7 +429,7 @@ class Debugger(QMainWindow):
|
|
|
225
429
|
left.setFrameShape(QFrame.Shape.StyledPanel)
|
|
226
430
|
left_layout = QVBoxLayout(left)
|
|
227
431
|
left_layout.setContentsMargins(8, 8, 8, 8)
|
|
228
|
-
self.leftColumn = self.MainLeftColumn()
|
|
432
|
+
self.leftColumn = self.MainLeftColumn(self)
|
|
229
433
|
left_layout.addWidget(self.leftColumn)
|
|
230
434
|
left_layout.addStretch()
|
|
231
435
|
|
|
@@ -234,7 +438,7 @@ class Debugger(QMainWindow):
|
|
|
234
438
|
right.setFrameShape(QFrame.Shape.StyledPanel)
|
|
235
439
|
right_layout = QVBoxLayout(right)
|
|
236
440
|
right_layout.setContentsMargins(8, 8, 8, 8)
|
|
237
|
-
self.rightColumn = self.MainRightColumn()
|
|
441
|
+
self.rightColumn = self.MainRightColumn(self)
|
|
238
442
|
# Give the rightColumn a stretch factor so its scroll area fills the vertical space
|
|
239
443
|
right_layout.addWidget(self.rightColumn, 1)
|
|
240
444
|
|
|
@@ -264,6 +468,17 @@ class Debugger(QMainWindow):
|
|
|
264
468
|
console_layout.addWidget(self.console)
|
|
265
469
|
self.vsplitter.addWidget(console_frame)
|
|
266
470
|
|
|
471
|
+
# Redirect stdout/stderr so all program output is captured in the console
|
|
472
|
+
try:
|
|
473
|
+
self._orig_stdout = sys.stdout
|
|
474
|
+
self._orig_stderr = sys.stderr
|
|
475
|
+
self._writer = self.ConsoleWriter(self)
|
|
476
|
+
sys.stdout = self._writer # type: ignore[assignment]
|
|
477
|
+
sys.stderr = self._writer # type: ignore[assignment]
|
|
478
|
+
except Exception:
|
|
479
|
+
# Best effort; if redirection fails, continue without it
|
|
480
|
+
self._writer = None
|
|
481
|
+
|
|
267
482
|
# Set initial vertical sizes: prefer saved console_height if available
|
|
268
483
|
try:
|
|
269
484
|
total_h = int(h) if 'h' in locals() else max(300, self.height())
|
|
@@ -277,6 +492,21 @@ class Debugger(QMainWindow):
|
|
|
277
492
|
self.parse(program.script.lines)
|
|
278
493
|
self.show()
|
|
279
494
|
|
|
495
|
+
def _flush_console_buffer(self):
|
|
496
|
+
try:
|
|
497
|
+
writer = self._writer
|
|
498
|
+
if not writer:
|
|
499
|
+
return
|
|
500
|
+
if getattr(writer, '_buf', None):
|
|
501
|
+
text = ''.join(writer._buf)
|
|
502
|
+
writer._buf.clear()
|
|
503
|
+
# Append to the console and scroll to bottom
|
|
504
|
+
self.console.moveCursor(QTextCursor.MoveOperation.End)
|
|
505
|
+
self.console.insertPlainText(text)
|
|
506
|
+
self.console.moveCursor(QTextCursor.MoveOperation.End)
|
|
507
|
+
except Exception:
|
|
508
|
+
pass
|
|
509
|
+
|
|
280
510
|
def on_splitter_moved(self, pos, index):
|
|
281
511
|
# Update stored ratio when user drags the splitter
|
|
282
512
|
left_width = self.hsplitter.widget(0).width()
|
|
@@ -306,15 +536,17 @@ class Debugger(QMainWindow):
|
|
|
306
536
|
# Parse and add new lines
|
|
307
537
|
lino = 0
|
|
308
538
|
for line in script:
|
|
539
|
+
orig_line = line
|
|
309
540
|
if len(line) > 0:
|
|
310
541
|
line = line.replace("\t", " ")
|
|
311
|
-
|
|
542
|
+
color_line = self.coloriseLine(line, lino)
|
|
312
543
|
else:
|
|
313
544
|
# still need to call coloriseLine to keep token list in sync
|
|
314
|
-
self.coloriseLine(line, lino)
|
|
545
|
+
color_line = self.coloriseLine(line, lino)
|
|
315
546
|
lineSpec = Object()
|
|
316
547
|
lineSpec.lino = lino
|
|
317
|
-
lineSpec.line =
|
|
548
|
+
lineSpec.line = color_line
|
|
549
|
+
lineSpec.orig_line = orig_line
|
|
318
550
|
lineSpec.bp = False
|
|
319
551
|
lineSpec.onClick = self.onClickLino
|
|
320
552
|
lino += 1
|
|
@@ -402,14 +634,16 @@ class Debugger(QMainWindow):
|
|
|
402
634
|
###########################################################################
|
|
403
635
|
# Here when the user clicks a line number
|
|
404
636
|
def onClickLino(self, lino):
|
|
637
|
+
# Show or hide the red blob next to this line
|
|
405
638
|
lineSpec = self.scriptLines[lino]
|
|
406
639
|
lineSpec.bp = not lineSpec.bp
|
|
407
640
|
if lineSpec.bp: lineSpec.label.showBlob()
|
|
408
641
|
else: lineSpec.label.hideBlob()
|
|
409
|
-
# Set a breakpoint on this command
|
|
410
|
-
command
|
|
411
|
-
|
|
412
|
-
|
|
642
|
+
# Set or clear a breakpoint on this command
|
|
643
|
+
for command in self.program.code:
|
|
644
|
+
if 'lino' in command and command['lino'] == lino:
|
|
645
|
+
command['bp'] = lineSpec.bp
|
|
646
|
+
break
|
|
413
647
|
|
|
414
648
|
###########################################################################
|
|
415
649
|
# Scroll to a given line number
|
|
@@ -450,15 +684,98 @@ class Debugger(QMainWindow):
|
|
|
450
684
|
self.raise_()
|
|
451
685
|
self.activateWindow()
|
|
452
686
|
|
|
687
|
+
###########################################################################
|
|
688
|
+
# Set the background color of one line of the script
|
|
689
|
+
def setBackground(self, lino, color):
|
|
690
|
+
# Set the background color of the given line
|
|
691
|
+
if lino < 0 or lino >= len(self.scriptLines):
|
|
692
|
+
return
|
|
693
|
+
lineSpec = self.scriptLines[lino]
|
|
694
|
+
panel = lineSpec.panel
|
|
695
|
+
if not panel:
|
|
696
|
+
return
|
|
697
|
+
if color == 'none':
|
|
698
|
+
panel.setStyleSheet("")
|
|
699
|
+
else:
|
|
700
|
+
panel.setStyleSheet(f"background-color: {color};")
|
|
701
|
+
|
|
453
702
|
###########################################################################
|
|
454
703
|
# Here when each instruction is about to run
|
|
455
|
-
def
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
704
|
+
def continueExecution(self):
|
|
705
|
+
result = True
|
|
706
|
+
self.pc = self.program.pc
|
|
707
|
+
command = self.program.code[self.pc]
|
|
708
|
+
lino = command['lino'] + 1
|
|
709
|
+
if self.stopped: result = False
|
|
710
|
+
elif command['bp']:
|
|
711
|
+
print(f"Hit breakpoint at line {lino}")
|
|
712
|
+
self.stopped = True
|
|
713
|
+
result = False
|
|
714
|
+
if not result:
|
|
459
715
|
self.scrollTo(lino)
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
716
|
+
self.setBackground(command['lino'], 'LightYellow')
|
|
717
|
+
return result
|
|
718
|
+
|
|
719
|
+
def doRun(self):
|
|
720
|
+
self.stopped = False
|
|
721
|
+
print("Continuing execution at line", self.program.pc + 1)
|
|
722
|
+
self.program.run(self.pc)
|
|
723
|
+
|
|
724
|
+
def doStep(self):
|
|
725
|
+
command = self.program.code[self.pc]
|
|
726
|
+
# print("Stepping at line", command['lino'] + 1)
|
|
727
|
+
self.setBackground(command['lino'], 'none')
|
|
728
|
+
self.program.run(self.pc)
|
|
729
|
+
|
|
730
|
+
def doStop(self):
|
|
731
|
+
self.stopped = True
|
|
732
|
+
|
|
733
|
+
def doClose(self):
|
|
734
|
+
self.closeEvent(None)
|
|
735
|
+
|
|
736
|
+
###########################################################################
|
|
737
|
+
# Override closeEvent to save window geometry
|
|
738
|
+
def closeEvent(self, event):
|
|
739
|
+
"""Save window position and size to ~/.ecdebug.conf as JSON on exit."""
|
|
740
|
+
cfg = {
|
|
741
|
+
"x": self.x(),
|
|
742
|
+
"y": self.y(),
|
|
743
|
+
"width": self.width(),
|
|
744
|
+
"height": self.height(),
|
|
745
|
+
"ratio": self.ratio
|
|
746
|
+
}
|
|
747
|
+
# try to persist console height (bottom pane) if present
|
|
748
|
+
try:
|
|
749
|
+
ch = None
|
|
750
|
+
if hasattr(self, 'vsplitter'):
|
|
751
|
+
sizes = self.vsplitter.sizes()
|
|
752
|
+
if len(sizes) >= 2:
|
|
753
|
+
ch = int(sizes[1])
|
|
754
|
+
if ch is not None:
|
|
755
|
+
cfg['console_height'] = ch
|
|
756
|
+
except Exception:
|
|
757
|
+
pass
|
|
758
|
+
try:
|
|
759
|
+
cfg_path = os.path.join(os.path.expanduser("~"), ".ecdebug.conf")
|
|
760
|
+
with open(cfg_path, "w", encoding="utf-8") as f:
|
|
761
|
+
json.dump(cfg, f, indent=2)
|
|
762
|
+
except Exception as exc:
|
|
763
|
+
# best-effort only; avoid blocking shutdown
|
|
764
|
+
try:
|
|
765
|
+
self.statusBar().showMessage(f"Could not save config: {exc}", 3000)
|
|
766
|
+
except Exception:
|
|
463
767
|
pass
|
|
464
|
-
|
|
768
|
+
# Restore stdout/stderr and stop timers
|
|
769
|
+
try:
|
|
770
|
+
if self._orig_stdout is not None:
|
|
771
|
+
sys.stdout = self._orig_stdout
|
|
772
|
+
if self._orig_stderr is not None:
|
|
773
|
+
sys.stderr = self._orig_stderr
|
|
774
|
+
if self._flush_timer is not None:
|
|
775
|
+
try:
|
|
776
|
+
self._flush_timer.stop()
|
|
777
|
+
except Exception:
|
|
778
|
+
pass
|
|
779
|
+
except Exception:
|
|
780
|
+
pass
|
|
781
|
+
super().close()
|