pyflashgui 0.2.1__tar.gz → 0.2.2__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.
- {pyflashgui-0.2.1/pyflashgui.egg-info → pyflashgui-0.2.2}/PKG-INFO +2 -2
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/README.md +6 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/__init__.py +1 -1
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/backends/pyocd_packs.py +27 -5
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/core/events.py +12 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/core/pack_manager.py +9 -1
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/help/changelog.md +28 -1
- pyflashgui-0.2.2/pyflashgui/help/main-window.png +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/help/manual.md +10 -2
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/help/probe.md +2 -2
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/help_menu.py +6 -2
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/main_window.py +32 -2
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/theme.py +58 -4
- {pyflashgui-0.2.1 → pyflashgui-0.2.2/pyflashgui.egg-info}/PKG-INFO +2 -2
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui.egg-info/SOURCES.txt +3 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui.egg-info/requires.txt +1 -1
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyproject.toml +1 -1
- pyflashgui-0.2.2/tests/test_docking.py +713 -0
- pyflashgui-0.2.2/tests/test_event_log_times.py +72 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_flash_panes.py +17 -2
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_help.py +7 -0
- pyflashgui-0.2.2/tests/test_layout.py +166 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_packs.py +35 -4
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_theme.py +46 -2
- pyflashgui-0.2.1/pyflashgui/help/main-window.png +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/LICENSE +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/NOTICE +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/PYPI.md +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/__main__.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/backends/__init__.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/backends/base.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/backends/jlink_backend.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/backends/pyocd_backend.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/backends/simulated.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/core/__init__.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/core/checkout.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/core/compare.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/core/context.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/core/excepthook.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/core/flasher.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/core/images.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/core/installation.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/core/layout.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/core/library_log.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/core/memory_map.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/core/packages.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/core/paths.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/core/settings.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/core/shortcut.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/core/slow_start.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/core/timing.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/core/updates.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/core/worker.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/core/workspaces.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/help/__init__.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/help/about.md +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/help/console.md +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/help/files.md +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/help/memory-map.md +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/help/memory.md +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/resources/__init__.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/resources/pyflashgui.ico +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/resources/pyflashgui.png +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/resources/pyflashgui.svg +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/resources/pyflashgui_small.svg +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/resources/tick.svg +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/__init__.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/buttons.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/confirm.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/console_view.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/crop_dialog.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/detached.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/diagnostics.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/erase_dialog.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/event_colours.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/files_view.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/folders.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/fonts.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/map_view.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/memory_view.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/messages.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/packs_dialog.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/pane_bar.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/panes.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/persist.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/probe_bar.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/read_dialog.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/scripting.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/scripts_menu.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/session.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui/ui/workspace_menu.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui.egg-info/dependency_links.txt +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui.egg-info/entry_points.txt +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/pyflashgui.egg-info/top_level.txt +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/setup.cfg +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_backends.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_build_version.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_changelog.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_checkout.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_compare.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_console.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_diagnostics.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_erase.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_events.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_excepthook.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_flasher.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_folders.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_fonts.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_headers.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_icon.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_images.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_installation.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_jlink_backend.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_launcher.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_memory_map.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_messages.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_notice.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_packages.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_packaging.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_persist.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_read_back.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_readme.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_scripts_menu.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_shortcut.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_slow_start.py +0 -0
- {pyflashgui-0.2.1 → pyflashgui-0.2.2}/tests/test_workspaces.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyflashgui
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Graphical flash programming tool: Arm Cortex-M with any probe pyOCD supports, and more through J-Link Commander
|
|
5
5
|
Author: davhodg
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -34,7 +34,7 @@ Requires-Dist: bincopy>=20.0
|
|
|
34
34
|
Provides-Extra: dev
|
|
35
35
|
Requires-Dist: pytest; extra == "dev"
|
|
36
36
|
Requires-Dist: pytest-xdist; extra == "dev"
|
|
37
|
-
Requires-Dist: ruff==0.
|
|
37
|
+
Requires-Dist: ruff==0.17.*; extra == "dev"
|
|
38
38
|
Requires-Dist: trove-classifiers; extra == "dev"
|
|
39
39
|
Dynamic: license-file
|
|
40
40
|
|
|
@@ -126,6 +126,10 @@ pyflashgui can instead drive a J-Link through SEGGER's own J-Link Commander, whi
|
|
|
126
126
|
|
|
127
127
|
On Linux a probe is usable without root once a udev rule allows it; pyOCD publishes [a set of them](https://github.com/pyocd/pyOCD/tree/main/udev). Vendor software is never bundled with pyflashgui: install the vendor's own, and pyflashgui finds it.
|
|
128
128
|
|
|
129
|
+
### Isolating the probe
|
|
130
|
+
|
|
131
|
+
A USB isolator between the PC and the probe breaks a ground loop between them. [usb2-480mbps-isolator](https://github.com/davhodg/usb2-480mbps-isolator) is an open-source, hand-solderable one in the works, for USB 2.0 at every speed up to 480 Mbit/s, that limits the current it takes to what the PC's port can supply. **Available soon:** it is still being designed, and is for functional isolation only, not certified for safety isolation.
|
|
132
|
+
|
|
129
133
|
## Built with
|
|
130
134
|
|
|
131
135
|
| Package | For | Licence |
|
|
@@ -151,6 +155,8 @@ and no way of uninstalling touches them; delete that folder to remove them too.
|
|
|
151
155
|
|
|
152
156
|
See [CONTRIBUTING.md](CONTRIBUTING.md): the policy for changes, setting up to work on pyflashgui, running the tests, and building the Windows installer. Everyone taking part is asked to follow the [code of conduct](CODE_OF_CONDUCT.md). Security problems go through [SECURITY.md](SECURITY.md).
|
|
153
157
|
|
|
158
|
+
What is aimed for with a keyboard, a screen reader, large text and high contrast, what has been checked, and how to report a barrier, is in [ACCESSIBILITY.md](ACCESSIBILITY.md).
|
|
159
|
+
|
|
154
160
|
## Safety notice
|
|
155
161
|
|
|
156
162
|
pyflashgui talks to real equipment. It is intended only for people trained and experienced in working with microcontrollers, debug probes and the equipment they are part of. It is a tool for development and the bench, and is not meant for production programming.
|
|
@@ -93,7 +93,15 @@ class Device:
|
|
|
93
93
|
installed: bool
|
|
94
94
|
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
#: Called once, when fetching the list moves on from downloading to what
|
|
97
|
+
#: comes after: the library reads every description -- over fifteen hundred --
|
|
98
|
+
#: and writes the list of devices from them, twenty megabytes, which takes
|
|
99
|
+
#: half a minute or so with no count of its own to show.
|
|
100
|
+
Stage = Callable[[str], None]
|
|
101
|
+
PROCESSING = "Processing what was downloaded"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def open_cache(tick: Tick | None = None, stage: Stage | None = None) -> Any:
|
|
97
105
|
"""pyOCD's own store of packs. Replaced in the tests, which have no network."""
|
|
98
106
|
import cmsis_pack_manager
|
|
99
107
|
|
|
@@ -103,6 +111,14 @@ def open_cache(tick: Tick | None = None) -> Any:
|
|
|
103
111
|
if tick is not None:
|
|
104
112
|
tick(current, total or None)
|
|
105
113
|
|
|
114
|
+
# The first thing the library does after the last download. Should a
|
|
115
|
+
# later version of it name this otherwise, it is not called and the
|
|
116
|
+
# step is not said: nothing else changes.
|
|
117
|
+
def _call_rust_parse(self, pdsc_index):
|
|
118
|
+
if stage is not None:
|
|
119
|
+
stage(PROCESSING)
|
|
120
|
+
return super()._call_rust_parse(pdsc_index)
|
|
121
|
+
|
|
106
122
|
return Cache(tick is None, False)
|
|
107
123
|
|
|
108
124
|
|
|
@@ -158,14 +174,20 @@ def list_fetched() -> datetime | None:
|
|
|
158
174
|
return None
|
|
159
175
|
|
|
160
176
|
|
|
161
|
-
def fetch_list(tick: Tick | None = None) -> int:
|
|
162
|
-
"""Download the index of every pack. Returns how many devices it has.
|
|
163
|
-
|
|
177
|
+
def fetch_list(tick: Tick | None = None, stage: Stage | None = None) -> int:
|
|
178
|
+
"""Download the index of every pack. Returns how many devices it has.
|
|
179
|
+
|
|
180
|
+
``stage`` is told when the downloads have ended and what they brought is
|
|
181
|
+
processed, which is as long again.
|
|
182
|
+
"""
|
|
183
|
+
cache = open_cache(tick, stage)
|
|
164
184
|
try:
|
|
165
185
|
cache.cache_descriptors()
|
|
166
186
|
except Exception as exc:
|
|
167
187
|
raise _said("Fetching the list of devices", exc) from exc
|
|
168
|
-
|
|
188
|
+
# Through the copy kept of it, which the list of packs and the search
|
|
189
|
+
# that follow then use rather than reading twenty megabytes again.
|
|
190
|
+
count = len(_index(open_cache()))
|
|
169
191
|
if not count:
|
|
170
192
|
raise PackError(
|
|
171
193
|
"Fetching the list of devices brought nothing back. It comes from the internet: "
|
|
@@ -22,6 +22,8 @@ plain text, and nothing has to be imported to name one.
|
|
|
22
22
|
|
|
23
23
|
from __future__ import annotations
|
|
24
24
|
|
|
25
|
+
from datetime import datetime
|
|
26
|
+
|
|
25
27
|
from PySide6.QtCore import QObject, Signal
|
|
26
28
|
|
|
27
29
|
INFORMATION = "information"
|
|
@@ -43,6 +45,16 @@ LEVELS = (INFORMATION, GOOD, WARNING, ERROR)
|
|
|
43
45
|
PROBLEMS = (WARNING, ERROR)
|
|
44
46
|
|
|
45
47
|
|
|
48
|
+
def with_time(line: str, at: datetime) -> str:
|
|
49
|
+
"""A line with the time of day it was written in front, to the second.
|
|
50
|
+
|
|
51
|
+
To the second and no finer: a line is written a moment after it was said,
|
|
52
|
+
and the time is for reading a log back against a clock, not for timing
|
|
53
|
+
anything -- erasing, programming and reading say how long they took.
|
|
54
|
+
"""
|
|
55
|
+
return f"{at:%H:%M:%S} {line}"
|
|
56
|
+
|
|
57
|
+
|
|
46
58
|
class EventLog(QObject):
|
|
47
59
|
"""The one way in. What listens is somebody else's business."""
|
|
48
60
|
|
|
@@ -126,7 +126,15 @@ class PackManager(QObject):
|
|
|
126
126
|
self.events.good(message)
|
|
127
127
|
self.progress.emit(message)
|
|
128
128
|
|
|
129
|
-
|
|
129
|
+
def stage(now: str) -> None:
|
|
130
|
+
# A step with no count of its own, after the last download: the
|
|
131
|
+
# bar goes back and forth, and this says why it still does.
|
|
132
|
+
try:
|
|
133
|
+
self.progress.emit(f"{now}, which takes a while...")
|
|
134
|
+
except RuntimeError: # the window went while it was running
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
self._run(doing, lambda: pyocd_packs.fetch_list(self._tick(doing), stage), done)
|
|
130
138
|
|
|
131
139
|
def install(self, pack: Pack) -> None:
|
|
132
140
|
doing = f"Downloading {pack}"
|
|
@@ -9,6 +9,32 @@ since the last version, and comes out in the next one.
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [0.2.2] - 2026-11-08
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **The Qt style in use** is shown greyed out at the bottom of *Tools > Settings > Theme*
|
|
17
|
+
and in its tooltip, and *Help > Diagnostics* gives it with the theme -- and, for *Match
|
|
18
|
+
the system*, whether the system gave light or dark.
|
|
19
|
+
- **Time on Event Log lines**, under *Tools > Settings*: the time of day in front of each
|
|
20
|
+
line, to the second, kept when it is copied. Off to begin with, and kept with this
|
|
21
|
+
computer rather than the workspace, as the theme is.
|
|
22
|
+
- An accessibility statement, `ACCESSIBILITY.md`, and a form for reporting a barrier.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- The privacy policy in `CODE_SIGNING.md`, and `SECURITY.md`, name fetching the list of
|
|
27
|
+
device packs and installing a pack as the third thing pyflashgui goes online for, and a
|
|
28
|
+
CMSIS-Pack among the files that are data.
|
|
29
|
+
- *Tools > pyOCD device packs*: fetching the list says *Processing what was downloaded* once
|
|
30
|
+
every file has arrived, for the half minute or so that takes, in place of a count that
|
|
31
|
+
had reached its end.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- *View > Reset layout* left the Files pane or the Event Log floating, if it had been
|
|
36
|
+
undocked, rather than putting it back in the window.
|
|
37
|
+
|
|
12
38
|
## [0.2.1] - 2026-10-25
|
|
13
39
|
|
|
14
40
|
### Added
|
|
@@ -176,7 +202,8 @@ since the last version, and comes out in the next one.
|
|
|
176
202
|
for updates.
|
|
177
203
|
- Launchers for a source folder on Windows and Linux, and the build of the Windows installer.
|
|
178
204
|
|
|
179
|
-
[Unreleased]: https://github.com/davhodg/pyflashgui/compare/v0.2.
|
|
205
|
+
[Unreleased]: https://github.com/davhodg/pyflashgui/compare/v0.2.2...HEAD
|
|
206
|
+
[0.2.2]: https://github.com/davhodg/pyflashgui/compare/v0.2.1...v0.2.2
|
|
180
207
|
[0.2.1]: https://github.com/davhodg/pyflashgui/compare/v0.2.0...v0.2.1
|
|
181
208
|
[0.2.0]: https://github.com/davhodg/pyflashgui/compare/v0.1.0...v0.2.0
|
|
182
209
|
[0.1.0]: https://github.com/davhodg/pyflashgui/releases/tag/v0.1.0
|
|
Binary file
|
|
@@ -27,11 +27,19 @@ With no hardware, choose *Simulated* under **Using** and the same steps work on
|
|
|
27
27
|
|
|
28
28
|
## The window
|
|
29
29
|
|
|
30
|
-
Everything pyflashgui has to say arrives in the **Event Log**, with warnings and errors in their own colours. A warning or an error opens the pane if it has been closed; an ordinary note does not. Right-click in it to clear it. An error pyOCD or pylink reports for itself is put here too, named after the library; their other lines are kept for *Help > Diagnostics*.
|
|
30
|
+
Everything pyflashgui has to say arrives in the **Event Log**, with warnings and errors in their own colours. A warning or an error opens the pane if it has been closed; an ordinary note does not. Right-click in it to clear it. An error pyOCD or pylink reports for itself is put here too, named after the library; their other lines are kept for *Help > Diagnostics*. *Tools > Settings > Time on Event Log lines* puts the time of day each line was written in front of it, to the second -- *14:03:27 Connected ...* -- from the next line on, and it is kept when the log is copied. It is off to begin with, and kept with this computer rather than the workspace, like the theme.
|
|
31
31
|
|
|
32
32
|
Each pane can be dragged to another edge of the window, floated, or closed, and **View** brings one back. *View > Reset layout* puts every pane back where it starts and the window back to the size it opens at.
|
|
33
33
|
|
|
34
|
-
*Tools > Settings > Theme* chooses light, dark, or whichever the operating system is set to.
|
|
34
|
+
*Tools > Settings > Theme* chooses light, dark, or whichever the operating system is set to. Which look Qt draws it in depends on the system:
|
|
35
|
+
|
|
36
|
+
| System | Style |
|
|
37
|
+
|---|---|
|
|
38
|
+
| Windows 11 | Qt's Windows 11 style, light and dark |
|
|
39
|
+
| Windows 10 and earlier | Qt's style for these has no dark version, so Qt's Fusion style, light and dark alike |
|
|
40
|
+
| Linux | Usually Fusion, or the desktop's own Qt style where it sets one |
|
|
41
|
+
|
|
42
|
+
The style in use is shown greyed out at the bottom of the *Theme* menu, and in the menu's tooltip, and *Help > Diagnostics...* gives it with the theme.
|
|
35
43
|
|
|
36
44
|
## Workspaces
|
|
37
45
|
|
|
@@ -36,12 +36,12 @@ pyOCD has a few dozen targets built in. Every other Cortex-M device is described
|
|
|
36
36
|
|
|
37
37
|
*Tools > pyOCD device packs...* installs one, from the internet or from a file:
|
|
38
38
|
|
|
39
|
-
1. Press **Fetch the list**. It downloads the list of every device there is a pack for, which takes a little while and is needed once. Every device is listed.
|
|
39
|
+
1. Press **Fetch the list**. It downloads the list of every device there is a pack for, which takes a little while and is needed once. Once the count of files downloaded has reached its end, what they brought is processed into the list, which says so and takes about as long again. Every device is listed.
|
|
40
40
|
2. Type any part of the device's part number. The list narrows to the devices with that in their name as you type.
|
|
41
41
|
3. Select the device and press **Install pack**. The pack is downloaded from its maker; it holds the whole family the device belongs to, and can be over a hundred megabytes.
|
|
42
42
|
4. Press **Use as target**, or close the dialog and choose the device in the bar.
|
|
43
43
|
|
|
44
|
-
**A download cannot be stopped once it has started**, the list's or a pack's. The dialog can be closed while one runs: the [Event Log](manual.md) says when it has arrived, or why it did not. Both downloads need the internet; nothing else in pyflashgui does.
|
|
44
|
+
**A download cannot be stopped once it has started**, the list's or a pack's. The dialog can be closed while one runs: the [Event Log](manual.md) says when it has arrived, or why it did not. Both downloads need the internet; apart from *Help > Check for updates*, nothing else in pyflashgui does.
|
|
45
45
|
|
|
46
46
|
Under the list is where the selected device's pack is downloaded from. The *From* column of the packs installed says the same for each of them.
|
|
47
47
|
|
|
@@ -48,7 +48,7 @@ from pyflashgui.core.updates import is_newer as version_is_newer
|
|
|
48
48
|
from pyflashgui.core.worker import Worker
|
|
49
49
|
from pyflashgui.help import manual_text
|
|
50
50
|
from pyflashgui.ui import diagnostics as diagnostics_report
|
|
51
|
-
from pyflashgui.ui import fonts, messages
|
|
51
|
+
from pyflashgui.ui import fonts, messages, theme
|
|
52
52
|
from pyflashgui.ui.diagnostics import DiagnosticsDialog
|
|
53
53
|
|
|
54
54
|
|
|
@@ -340,7 +340,7 @@ class AboutDialog(QDialog):
|
|
|
340
340
|
|
|
341
341
|
|
|
342
342
|
def _label_width(names) -> int:
|
|
343
|
-
return max(len(name) for name in ("Python", "Platform", *names)) + 2
|
|
343
|
+
return max(len(name) for name in ("Python", "Platform", "Qt style", "Theme", *names)) + 2
|
|
344
344
|
|
|
345
345
|
|
|
346
346
|
def environment_report() -> str:
|
|
@@ -356,6 +356,10 @@ def environment_report() -> str:
|
|
|
356
356
|
found = "not installed" + (" (optional)" if requirement.extra else "")
|
|
357
357
|
lines.append(f"{requirement.name:{width}}{found}")
|
|
358
358
|
lines.append(f"{'Platform':{width}}{platform.platform()}")
|
|
359
|
+
# How it looks, for a report about how something looks.
|
|
360
|
+
if style := theme.style_name():
|
|
361
|
+
lines.append(f"{'Qt style':{width}}{style}")
|
|
362
|
+
lines.append(f"{'Theme':{width}}{theme.describe()}")
|
|
359
363
|
# Only a checkout has one, and only a checkout needs one: between two
|
|
360
364
|
# releases every build calls itself the same version, which is no help
|
|
361
365
|
# when the question is whether somebody had pulled.
|
|
@@ -34,7 +34,7 @@ from pyflashgui.backends import jlink_backend
|
|
|
34
34
|
from pyflashgui.backends.base import TargetInfo
|
|
35
35
|
from pyflashgui.core import shortcut, timing, workspaces
|
|
36
36
|
from pyflashgui.core.context import Context
|
|
37
|
-
from pyflashgui.core.events import PROBLEMS, EventLog
|
|
37
|
+
from pyflashgui.core.events import PROBLEMS, EventLog, with_time
|
|
38
38
|
from pyflashgui.core.excepthook import ExceptionLogger
|
|
39
39
|
from pyflashgui.core.flasher import Flasher, size_text
|
|
40
40
|
from pyflashgui.core.library_log import LibraryLog
|
|
@@ -101,6 +101,14 @@ DEFAULT_WIDTH = 1100
|
|
|
101
101
|
SHORTCUT_TOLD_KEY = "shortcut/told"
|
|
102
102
|
#: Where J-Link Commander is, when it is not where it is looked for.
|
|
103
103
|
COMMANDER_SETTING = "jlink/commander"
|
|
104
|
+
#: In QSettings, like the theme: how somebody likes to read the log, not
|
|
105
|
+
#: something about a workspace.
|
|
106
|
+
LOG_TIMES_KEY = "event_log/times"
|
|
107
|
+
LOG_TIMES_TIP = (
|
|
108
|
+
"The time of day each Event Log line was written, to the second, in front\n"
|
|
109
|
+
"of it: 14:03:27 Connected ... From the next line on; kept when lines are\n"
|
|
110
|
+
"copied."
|
|
111
|
+
)
|
|
104
112
|
DEFAULT_HEIGHT = 720
|
|
105
113
|
|
|
106
114
|
|
|
@@ -150,6 +158,8 @@ class MainWindow(QMainWindow):
|
|
|
150
158
|
#: the only thing that writes to the pane. Warnings and errors open it
|
|
151
159
|
#: if it has been closed; notes do not.
|
|
152
160
|
self.events = EventLog()
|
|
161
|
+
#: Whether each Event Log line has the time of day in front of it.
|
|
162
|
+
self._log_times = QSettings().value(LOG_TIMES_KEY, False, type=bool)
|
|
153
163
|
#: The last warnings and errors, for Help > Diagnostics: (time, level, text).
|
|
154
164
|
self.recent_problems: deque[tuple[str, str, str]] = deque(maxlen=RECENT_PROBLEMS)
|
|
155
165
|
self._surfacing = False
|
|
@@ -308,6 +318,11 @@ class MainWindow(QMainWindow):
|
|
|
308
318
|
"it by itself. It comes with SEGGER's J-Link software, and is only\n"
|
|
309
319
|
"needed to use a J-Link through it."
|
|
310
320
|
)
|
|
321
|
+
self.log_times_action = self.settings_menu.addAction("Time on Event Log lines")
|
|
322
|
+
self.log_times_action.setCheckable(True)
|
|
323
|
+
self.log_times_action.setChecked(self._log_times)
|
|
324
|
+
self.log_times_action.setToolTip(LOG_TIMES_TIP)
|
|
325
|
+
self.log_times_action.toggled.connect(self._set_log_times)
|
|
311
326
|
# Below the line: not how pyflashgui behaves, but how this copy of it
|
|
312
327
|
# is set up on this machine. For a source folder and for pip: the
|
|
313
328
|
# installer makes its own entries.
|
|
@@ -477,6 +492,10 @@ class MainWindow(QMainWindow):
|
|
|
477
492
|
under both, because its lines are long.
|
|
478
493
|
"""
|
|
479
494
|
files, log = (self.panes.docks[n] for n in DEFAULT_VISIBLE)
|
|
495
|
+
# splitDockWidget leaves a floating pane floating, so Reset layout
|
|
496
|
+
# left an undocked Event Log out of the layout it put back.
|
|
497
|
+
for dock in (files, log):
|
|
498
|
+
dock.setFloating(False)
|
|
480
499
|
self.addDockWidget(Qt.LeftDockWidgetArea, files)
|
|
481
500
|
self.splitDockWidget(files, log, Qt.Vertical)
|
|
482
501
|
self.resizeDocks([files, log], [7, 3], Qt.Vertical)
|
|
@@ -1020,7 +1039,8 @@ class MainWindow(QMainWindow):
|
|
|
1020
1039
|
shape.setForeground(colour)
|
|
1021
1040
|
if event_colours.bold_for(level):
|
|
1022
1041
|
shape.setFontWeight(QFont.Bold)
|
|
1023
|
-
|
|
1042
|
+
line = with_time(message, datetime.now()) if self._log_times else message
|
|
1043
|
+
cursor.insertText(line, shape)
|
|
1024
1044
|
# Painted again as a whole. Written through a cursor, a line could be
|
|
1025
1045
|
# drawn before its colour was laid out, and stayed in the plain text
|
|
1026
1046
|
# colour until scrolling redrew it -- an error that did not look like one.
|
|
@@ -1031,6 +1051,16 @@ class MainWindow(QMainWindow):
|
|
|
1031
1051
|
self.recent_problems.append((f"{datetime.now():%H:%M:%S}", level, message))
|
|
1032
1052
|
self._surface_log()
|
|
1033
1053
|
|
|
1054
|
+
@Slot(bool)
|
|
1055
|
+
def _set_log_times(self, on: bool) -> None:
|
|
1056
|
+
QSettings().setValue(LOG_TIMES_KEY, on)
|
|
1057
|
+
self._log_times = on
|
|
1058
|
+
self.events.information(
|
|
1059
|
+
"Event Log lines are written with the time of day."
|
|
1060
|
+
if on
|
|
1061
|
+
else "Event Log lines are written without the time."
|
|
1062
|
+
)
|
|
1063
|
+
|
|
1034
1064
|
def log_menu(self):
|
|
1035
1065
|
"""Copy and Select All as usual, and Clear. Built apart from being shown."""
|
|
1036
1066
|
menu = self.log.createStandardContextMenu()
|
|
@@ -48,9 +48,24 @@ TICK_BOX_OUTLINE = (
|
|
|
48
48
|
"QAbstractItemView::indicator:checked, QGroupBox::indicator:checked "
|
|
49
49
|
f'{{ image: url("{resources.path("tick.svg").as_posix()}"); }}'
|
|
50
50
|
)
|
|
51
|
+
#: Qt's names for its styles, as the menu and the diagnostics say them.
|
|
52
|
+
STYLE_NAMES = {
|
|
53
|
+
"windows11": "Windows 11",
|
|
54
|
+
"windowsvista": "Windows Vista", # Qt's native one up to Windows 10
|
|
55
|
+
"windows": "Windows",
|
|
56
|
+
"fusion": "Fusion",
|
|
57
|
+
"macos": "macOS",
|
|
58
|
+
}
|
|
59
|
+
STYLE_TIP = (
|
|
60
|
+
"The look Qt draws pyflashgui in. Windows 11 has its own, light and dark. "
|
|
61
|
+
"Qt's style for Windows 10 and earlier has no dark version, so Fusion is used there "
|
|
62
|
+
"for both. On Linux it is Fusion, or the desktop's own Qt style where it sets one."
|
|
63
|
+
)
|
|
51
64
|
#: Whether the style in use is Fusion, noted before a style sheet is set:
|
|
52
65
|
#: with one set, the application's style is no longer the one to ask.
|
|
53
66
|
_fusion = False
|
|
67
|
+
#: The style in use as Qt names it, noted with _fusion and for the same reason.
|
|
68
|
+
_style = ""
|
|
54
69
|
#: The applications whose colour scheme is already being followed.
|
|
55
70
|
_followed: set[int] = set()
|
|
56
71
|
#: The editor factory in use, kept here: Qt holds it, and Python must too.
|
|
@@ -71,11 +86,12 @@ def apply(choice: str, app: QApplication | None = None) -> None:
|
|
|
71
86
|
app = app or QApplication.instance()
|
|
72
87
|
if app is None:
|
|
73
88
|
return
|
|
74
|
-
global _fusion
|
|
89
|
+
global _fusion, _style
|
|
75
90
|
if not app.styleSheet(): # see _fusion
|
|
76
91
|
if app.style().name().lower() in LIGHT_ONLY:
|
|
77
92
|
app.setStyle(FUSION)
|
|
78
|
-
|
|
93
|
+
_style = app.style().name()
|
|
94
|
+
_fusion = _style.lower() == FUSION.lower()
|
|
79
95
|
hints = app.styleHints()
|
|
80
96
|
if choice == LIGHT:
|
|
81
97
|
hints.setColorScheme(Qt.ColorScheme.Light)
|
|
@@ -125,6 +141,27 @@ def opaque_cell_editors() -> None:
|
|
|
125
141
|
QItemEditorFactory.setDefaultFactory(_editors)
|
|
126
142
|
|
|
127
143
|
|
|
144
|
+
def style_name(app: QApplication | None = None) -> str:
|
|
145
|
+
"""The style pyflashgui is drawn in, as a person would say it."""
|
|
146
|
+
app = app or QApplication.instance()
|
|
147
|
+
if app is None:
|
|
148
|
+
return ""
|
|
149
|
+
name = _style if (app.styleSheet() and _style) else app.style().name() # see _fusion
|
|
150
|
+
return STYLE_NAMES.get(name.lower(), name)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def describe(settings: QSettings | None = None, app: QApplication | None = None) -> str:
|
|
154
|
+
"""The choice, and for *Match the system* what the system gave."""
|
|
155
|
+
choice = chosen(settings)
|
|
156
|
+
label = dict(CHOICES)[choice]
|
|
157
|
+
app = app or QApplication.instance()
|
|
158
|
+
if choice != SYSTEM or app is None:
|
|
159
|
+
return label
|
|
160
|
+
scheme = app.styleHints().colorScheme()
|
|
161
|
+
got = {Qt.ColorScheme.Dark: "dark", Qt.ColorScheme.Light: "light"}.get(scheme)
|
|
162
|
+
return f"{label} ({got})" if got else label
|
|
163
|
+
|
|
164
|
+
|
|
128
165
|
def choose(choice: str, settings: QSettings | None = None, app: QApplication | None = None) -> None:
|
|
129
166
|
"""Remember a choice and apply it straight away."""
|
|
130
167
|
(settings or QSettings()).setValue(SETTING, choice)
|
|
@@ -132,10 +169,14 @@ def choose(choice: str, settings: QSettings | None = None, app: QApplication | N
|
|
|
132
169
|
|
|
133
170
|
|
|
134
171
|
def menu(parent: QWidget, settings: QSettings | None = None) -> QMenu:
|
|
135
|
-
"""*Tools > Settings > Theme*: the three choices, the current one ticked
|
|
172
|
+
"""*Tools > Settings > Theme*: the three choices, the current one ticked,
|
|
173
|
+
and the style in use greyed out below them.
|
|
174
|
+
|
|
175
|
+
A greyed entry is skipped by the keyboard and by most screen readers, so
|
|
176
|
+
the menu's own tooltip says the style as well.
|
|
177
|
+
"""
|
|
136
178
|
out = QMenu(TITLE, parent)
|
|
137
179
|
out.setToolTipsVisible(True)
|
|
138
|
-
out.menuAction().setToolTip(TIP)
|
|
139
180
|
group = QActionGroup(out)
|
|
140
181
|
group.setExclusive(True)
|
|
141
182
|
current = chosen(settings)
|
|
@@ -146,4 +187,17 @@ def menu(parent: QWidget, settings: QSettings | None = None) -> QMenu:
|
|
|
146
187
|
action.triggered.connect(lambda _=False, v=value: choose(v, settings))
|
|
147
188
|
group.addAction(action)
|
|
148
189
|
out.addAction(action)
|
|
190
|
+
out.addSeparator()
|
|
191
|
+
style = QAction(out, enabled=False)
|
|
192
|
+
style.setToolTip(STYLE_TIP)
|
|
193
|
+
out.addAction(style)
|
|
194
|
+
out.style_action = style
|
|
195
|
+
|
|
196
|
+
def show_style() -> None:
|
|
197
|
+
name = style_name()
|
|
198
|
+
style.setText(f"Style: {name}")
|
|
199
|
+
out.menuAction().setToolTip(f"{TIP} Drawn in Qt's {name} style." if name else TIP)
|
|
200
|
+
|
|
201
|
+
show_style()
|
|
202
|
+
out.aboutToShow.connect(show_style)
|
|
149
203
|
return out
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyflashgui
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Graphical flash programming tool: Arm Cortex-M with any probe pyOCD supports, and more through J-Link Commander
|
|
5
5
|
Author: davhodg
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -34,7 +34,7 @@ Requires-Dist: bincopy>=20.0
|
|
|
34
34
|
Provides-Extra: dev
|
|
35
35
|
Requires-Dist: pytest; extra == "dev"
|
|
36
36
|
Requires-Dist: pytest-xdist; extra == "dev"
|
|
37
|
-
Requires-Dist: ruff==0.
|
|
37
|
+
Requires-Dist: ruff==0.17.*; extra == "dev"
|
|
38
38
|
Requires-Dist: trove-classifiers; extra == "dev"
|
|
39
39
|
Dynamic: license-file
|
|
40
40
|
|
|
@@ -90,7 +90,9 @@ tests/test_checkout.py
|
|
|
90
90
|
tests/test_compare.py
|
|
91
91
|
tests/test_console.py
|
|
92
92
|
tests/test_diagnostics.py
|
|
93
|
+
tests/test_docking.py
|
|
93
94
|
tests/test_erase.py
|
|
95
|
+
tests/test_event_log_times.py
|
|
94
96
|
tests/test_events.py
|
|
95
97
|
tests/test_excepthook.py
|
|
96
98
|
tests/test_flash_panes.py
|
|
@@ -104,6 +106,7 @@ tests/test_images.py
|
|
|
104
106
|
tests/test_installation.py
|
|
105
107
|
tests/test_jlink_backend.py
|
|
106
108
|
tests/test_launcher.py
|
|
109
|
+
tests/test_layout.py
|
|
107
110
|
tests/test_memory_map.py
|
|
108
111
|
tests/test_messages.py
|
|
109
112
|
tests/test_notice.py
|