feat(gui): add "Copy to sections" action for selected traces#103
feat(gui): add "Copy to sections" action for selected traces#103dustenhubbard wants to merge 2 commits into
Conversation
Copying a trace today drops it only into the current section, so placing the same trace at the same field location across several sections means navigating to each one and pasting by hand. Add a "Copy to sections..." action that copies the selected trace(s) onto multiple chosen sections in one step. Each trace's field coordinates are re-projected through every target section's own inverse transform (computed once per section), so the traces land at the identical field x-y regardless of how each section is aligned. Sections whose transform is not invertible are skipped and reported rather than written to. Trace attributes are preserved and the source (current) section is left unchanged. An alignment lock protects a section's transform, not its trace content, so traces are copied onto every chosen section regardless of its lock status, just as traces can be drawn on a locked section. Target sections are chosen by number or inclusive range in a small picker dialog. The parser validates tokens without materializing typed ranges, so a huge upper bound cannot hang the UI, and it reports requested sections that do not exist instead of silently dropping them. The action is available at the field context-menu top level (next to Copy) and in the trace list. Includes tests for the parser and the data-model copy operation. Closes #94. #91 is a duplicate of the same request.
Report the section numbers that actually received the trace(s) instead of
a bare count, so the message reflects what was done rather than what was
requested and surfaces any silently-skipped targets. Collapse long
contiguous runs to ranges (e.g. "2-5, 10") and use singular/plural grammar
("section 5" vs "sections 2, 3"). Preserve the existing non-invertible
"skipped" reporting. Move message building into a pure format_copy_result
helper.
quasiTriestino
left a comment
There was a problem hiding this comment.
When R-clicking in the field, "copy" appears gray when no objects are currently selected. Could you also make this the case for copy-to-sections? Also, copy is ctrl-C. If ctrl-shift-C isn't yet taken, let's make this the shortcut key for copy-to-sections.
| The traces' points must be given in FIELD coordinates (i.e. already | ||
| mapped through a section transform, the same form the clipboard/paste | ||
| path uses). Each target section stores the points through its own | ||
| inverse transform, so the traces land at the identical field x-y on | ||
| every section regardless of how each section is aligned. |
There was a problem hiding this comment.
| The traces' points must be given in FIELD coordinates (i.e. already | |
| mapped through a section transform, the same form the clipboard/paste | |
| path uses). Each target section stores the points through its own | |
| inverse transform, so the traces land at the identical field x-y on | |
| every section regardless of how each section is aligned. | |
| The traces' points must be given in FIELD (not SCREEN) coordinates. |
| info = QLabel(self, text=( | ||
| "Copy the selected trace(s) onto other sections at the same " | ||
| "location.\n" | ||
| f"The current section ({current}) is left unchanged.\n" | ||
| f"Enter section numbers or ranges from {smin} to {smax}, " | ||
| "e.g. \"10-20\" or \"5, 8, 11\"." | ||
| )) |
There was a problem hiding this comment.
| info = QLabel(self, text=( | |
| "Copy the selected trace(s) onto other sections at the same " | |
| "location.\n" | |
| f"The current section ({current}) is left unchanged.\n" | |
| f"Enter section numbers or ranges from {smin} to {smax}, " | |
| "e.g. \"10-20\" or \"5, 8, 11\"." | |
| )) | |
| info = QLabel(self, text=( | |
| "Copy selected trace(s) onto other sections.\n" | |
| f"Enter section numbers or ranges from {smin} to {smax}, " | |
| f"{section_placeholders}." | |
| )) |
|
|
||
| self.setLayout(vlayout) | ||
|
|
||
| def accept(self): |
There was a problem hiding this comment.
These checks on section range are good and likely should be pulled out of this file and made into a more general request-sections-verification tool that is run anytime a user requests an operation on multiple sections.
| def format_copy_result(copied_to, skipped, excluded_current=None) -> str: | ||
| """Build the user-facing summary for a "Copy to sections" run. | ||
|
|
||
| Reports the sections that ACTUALLY received the trace(s) rather than a bare |
There was a problem hiding this comment.
| Reports the sections that ACTUALLY received the trace(s) rather than a bare | |
| Reports the sections that actually received the trace(s) rather than a bare |
| self.setWindowTitle("Copy to sections") | ||
|
|
||
| vlayout = QVBoxLayout() | ||
|
|
There was a problem hiding this comment.
| random_sections = sorted(random.sample(range(smin, smax + 1), k=3) # get 3 random sections and sort | |
| random_sections = ", ".join(str(s) for s in random_sections) # join into string | |
| section_placeholders = f"e.g. \"{smin}-{smax}\" or \"{random_sections}\"" |
| vlayout.addWidget(info) | ||
|
|
||
| self.spec_input = QLineEdit(self) | ||
| self.spec_input.setPlaceholderText("e.g. 10-20 or 5, 8, 11") |
There was a problem hiding this comment.
| self.spec_input.setPlaceholderText("e.g. 10-20 or 5, 8, 11") | |
| self.spec_input.setPlaceholderText(section_placeholders) |
| @@ -0,0 +1,221 @@ | |||
| from PySide6.QtWidgets import ( | |||
There was a problem hiding this comment.
| from PySide6.QtWidgets import ( | |
| import random | |
| from PySide6.QtWidgets import ( |
| self.setWindowTitle("Copy to sections") | ||
|
|
||
| vlayout = QVBoxLayout() | ||
|
|
There was a problem hiding this comment.
Creating a realistic placeholder for a given series.
Adds a "Copy to sections..." action to the field and trace-list context menus. Selected traces are copied onto the chosen sections (numbers or ranges) at the same field x-y, re-projected through each section's own transform. Records per-section undo state so one undo reverts the whole copy.
Closes #94. #91 is a duplicate of the same request.